Repository navigation
fix(security)!: under a wall a catalog-carrying package is the platform operator's — install-local and the /packages delete refuse tenants (ADR-0131 D3) - #22935
Conversation
… door (ADR-0131 D3) Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
… with single and assignment controls (ADR-0131 D3) Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…g refusal Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…131 D3) Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…ll-catalog-refusal
…087 semantic entry; census row 55b Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…ll-catalog-refusal # Conflicts: # content/docs/permissions/system-context.mdx
…efusal (ADR-0131 D3) Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
… row 55b names the clone door Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…y name the clone door Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…ace: exact doors, capability's unchanged accept set, the D12 delegate Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…he clone door and the Setup env-door write (ADR-0131 D3) Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
…nvironmentSets describe Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP Co-authored-by: Claude <noreply@anthropic.com>
Resolve content/docs/permissions/system-context.mdx: keep row 55b (this branch) and main's row 58 rewording (#22875); counts re-derived with pnpm gen:system-context-census. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
…refusal' into claude/issue-15204-wall-package-refusal
…ADR-0131 D3) Under group/isolated, below the platform operator rung, install-local refuses an install whose incoming version declares a position, permission set or capability, an upgrade where either version declares one, and an uninstall of a package whose installed version declares one; DELETE /packages/:id refuses a package holding stored catalog rows. Both ask walledCatalogWriteVerdict once per declared type. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
… own packages Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
…talog-package-writes-retired Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
…log items; alias objectql in cloud-connection tests Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
…package-refusal Resolve content/docs/permissions/system-context.mdx: keep this branch's row 55b (it names the /packages delete refusal); counts re-derived with pnpm gen:system-context-census. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
📓 Docs Drift CheckThis PR changes 4 package(s): 47 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 145 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f1694e51a053e436e3b209aaaac3da6e28a34def && git checkout f1694e51a053e436e3b209aaaac3da6e28a34def
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a2240c4dcfc3a19f058cb0b3a4f15a351908acde 944ebdb98d6e53c7882c614705141d7ac776edf6 && git checkout -B drift-repro a2240c4dcfc3a19f058cb0b3a4f15a351908acde && git merge --no-ff 944ebdb98d6e53c7882c614705141d7ac776edf6
node scripts/docs-audit/affected-docs.mjs --json a2240c4dcfc3a19f058cb0b3a4f15a351908acde
|
|
Seat note on the red
Generated by Claude Code |
|
Re-run record · epic PM At the maintainer's request in the seat's session, the failed jobs of CI run 38202524320 (this PR's current head Generated by Claude Code |
…ll-package-refusal
|
Seat note: the second drift red has a root cause, and the fix is already on The one re-run of run 38202524320 (6118076783) reds
Root cause. This head merged Next:
Generated by Claude Code |
|
Correction to seat note 6118313233: its root cause was wrong · epic PM 6118313233 said this branch lacked the #22923 shard-timings refresh. That does not hold:
The merge of The actual cause is main-wide and already tracked:
This PR changes nothing for either issue. Its next result on Generated by Claude Code |
|
Seat note: What reds on Why it is not this PR's: the diff does not touch No fix exists yet, and this PR's one re-run is already spent (6118076783). So the seat makes no change here. A sighting goes on #22949. The PR stays draft, and the seat lands it when the check clears, by ruling or by fix, with the contract review and the ACCEPT draft unchanged. Generated by Claude Code |
Resolve content/docs/permissions/system-context.mdx: main's count-free prose (#22950), this branch's row 55b; no count lines. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP
`--check-drift` takes the run's event (`--event`), required. On `schedule`, the profile scripts/test-shard-timings.json is measured on, a DRIFT exits 1 as before; on every other event it prints the same text, raises one `::warning::` annotation and exits 0. A missing or empty event is refused. Claude-Session: https://claude.ai/code/session_012yK1ddAdAqfzwigRHZq3r2 Co-authored-by: Claude <noreply@anthropic.com>
…one, with a 20s floor The ceiling check takes the run's event (`--event`, required). On `schedule`, the profile the ceilings were measured on, a package over its ceiling reds the step as before; on every other event the same table prints, each package over its ceiling is a `::warning::` annotation, and the step exits 0. Refusals stay red on every event. A package whose effective ceiling is under CEILING_FLOOR_SECONDS (20s) is never red and prints "below floor"; the header records the floor's reading. ci.yml passes `--event "$GITHUB_EVENT_NAME"` to both timing steps, which keep no `if:` and no `continue-on-error`. Claude-Session: https://claude.ai/code/session_012yK1ddAdAqfzwigRHZq3r2 Co-authored-by: Claude <noreply@anthropic.com>
Seat ACCEPT: #22935 (stage W2, the walled catalog refusal at the package doors, ADR-0131 D3) at
|
Part of #15204
Clause-②: yes (narrowing)
Stage W2 of #15204 (seat note 6114823168): the walled catalog refusal at the package doors. Stage W (#22895) refused catalog writes at
/meta, the clone door and the data door. This PR refuses them where a package carries them. It asks W's predicate,walledCatalogWriteVerdict, frommain.The ruling this executes
ADR-0131 D3: "tenant admins cannot create or edit catalog items; the operator defines the catalog for every tenant (managed packages, Studio)". A package that declares positions, permission sets or capabilities is the operator's instrument. Seat note 6114823168: under
group/isolated, below the operator rung (and notisSystem),install-localrefusesThe whole package is refused, since a partial install is not well-formed. The note also asked for a measurement of
DELETE /api/v1/packages/:id, and for that door to be refused the same way if the measurement showed it reachable.Measured first
The harness was a booted
examples/app-crmstack (bootStack,multiTenant: 'posture-only', plusMarketplaceInstallLocalPlugin), with two organizations. Organization A's owner holdsmanage_metadatathrough a grant scoped to organization A (rungTENANT_ADMIN). Organization B's member holds the operator's permission sets.isolatedgroupinstall-localupgrade of the operator's package, addingmanage_usersto its setmanage_usersinstall-localuninstall of that packageDELETE /packages/:idof a runtime package the operator saved a set into (PUT /meta/permission/:name?package=)PATCH /packages/:id/disableof such a packageThose catalog items are stored as
sys_metadatarows bound to the package (package_id). The running registry does not hold them under the package, so the/packagesrefusal reads the stored rows, the same rowsdeletePackagedeletes.disablechanges nothing any organization resolves, so it is not refused.What changed
install-local(@objectstack/cloud-connection): the admission carries the caller's posture rung and the effective tenancy posture, both from its one authorization read.refuseWalledCatalogPackageaskswalledCatalogWriteVerdictonce per catalog type that the incoming version, the installed version or the running registry under that id declares. It answers403 FORBIDDEN, the envelope of the door's sibling refusals, and runs before any registration or ledger write (install) or before the ledger is touched (uninstall).error.details.catalogItemslists the items.DELETE /packages/:id(@objectstack/runtime):refuseWalledCatalogPackageDeletereads the package's stored rows, and asks the predicate per stored catalog type, after the capability and writable-package gates and before anything is deleted. It answers403 PERMISSION_DENIED, this domain's 403. Thepackages.tscomment that calledrequireManageMetadata"the operator gate" now says what that gate checks: the capability orisSystem, and no rung.@objectstack/objectqlexportsdeclaredSecurityCatalogNames, the engine's own reading of what a manifest declares. The door judges the declarations the engine registers, not a second reading of them.cloud-connectionnow depends onobjectqldirectly (it was already in its closure throughruntime).walled-catalog-package-writes-retired; the changeset carries FROM → TO per door./packagesdelete and anchorsrefuseWalledCatalogPackageDelete, with the counts regenerated.capabilities.mdxgains the clause from W's contract review (6115069587 ③ 3 ii): under a wall a sub-operator caller reads the walled refusal first,FORBIDDENon REST andPERMISSION_DENIEDon the dispatcher, before the code-onlyNOT_CREATABLE.Tests
wall-package-refusal.dogfood.test.ts, 19 tests. Under each ofisolatedandgroup:/packagesdelete refused.B's resolution does not move. Controls under both postures: A installs, upgrades and uninstalls a catalog-free package and deletes a catalog-free runtime package; the operator installs, upgrades, uninstalls and deletes catalog-carrying packages.
singlecontrol: the samemanage_metadataholder does all of it as before.Ablation, install-local:
refuseWalledCatalogPackagewas forced to admit,cloud-connectionrebuilt, and the marker confirmed indist/byablation-dist-preflight. The 10 install-local refusal pins went red (5 per posture), and the other 9 stayed green. Restored to theHEADblob and rebuilt; the marker is absent fromdist/and the tree is clean.Ablation,
/packages: the same forrefuseWalledCatalogPackageDelete(runtime). The 2/packagesrefusal pins went red, and the other 17 stayed green. Restored and rebuilt; the marker is absent.Full suites (pre-merge code head
f4d6a8f104, the same W2 code as this head):objectql401 files / 7,817 passed;runtime355 files / 5,126 passed, 19 skipped;cloud-connection43 files / 517 passed. The full dogfood suite was stopped when W landed, before it finished, so it is not counted. The pin files are counted on the merged head below.dispatch-gates --ran(pre-merge, 141 derived): 140 exit 0.check-engine-split-ratio --days 90refused on this shallow clone (exit 2, nothing measured).Round-1 findings, fixed:
check:test-source-alias:cloud-connection's tests now reach@objectstack/objectql, so that package'svitest.config.tsgained an anchored source alias.cloud-connection's own suite: fake registries lack the private catalog reader. Both doors now ask the predicate before reading anything, so undersingle, with notenancyservice, or for the operator, neither door reads anything new.On the merged head
0d0a42082d:wall-package-refusal19/19 andwall-catalog-refusal17/17;check:system-context-census,check-adr-0087-registration --base origin/main,check-changeset-no-majorandcheck:test-source-aliaseach exit 0;gen:schema,gen:docs,gen:system-context-census) leaves 0 changed bytes.The 124 gates
dispatch-gatesderives for this head are running; their verdict goes in the os-dev-report addendum on refactor(plugin-security,platform-objects,spec): retire the catalog seeders, the per-organization catalog machinery and the four catalog objects; Setup creation is an environment write undersingleand refused under a wall (ADR-0131 D2/D3/D5/D13) #15204.Acceptance notes
manage_metadatamay install packages at all, or author the environment's objects, views and flows. This PR refuses only catalog-carrying packages, per 6114823168.refuseSecurityCatalogNameConflicts(the shadow refusal) is unchanged; this refusal runs before it.🤖 Generated with Claude Code
https://claude.ai/code/session_015JgoXpZUGtNxnMDvRZsKzP