Repository navigation
fix(plugin-security): an environment kernel's security catalog serves metadata-door permission sets and positions again - #23334
Conversation
…s into an environment kernel's security catalog On an environment kernel the metadata door hydrates no stored row into the engine registry, so the security catalog can find a metadata-door permission set or position only in the kernel's metadata service. The permission-set projector's echo was the only thing that put a set there, and C3 stage 8 unwired the projector: since then such a set resolved to nothing, granted nothing and recorded no everyone suggestion. Positions never had an echo. A new module, security-catalog-echo.ts, keeps only the echo: SecurityPlugin registers it as the awaited mutation projector for permission and position, and runs one pass over the stored env-wide overlays at kernel:ready. It writes no row. The projection module (unwired) now imports the shared helpers from it. The echo-owner pins move onto the new module with the #21789 provenance cases, and a boot-level CLI test drives an environment kernel and a host kernel through the production wiring, across a restart. Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS Co-authored-by: Claude <noreply@anthropic.com>
…curity-catalog-echo
…e's rename; the boot-pass find double holds its bound Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS Co-authored-by: Claude <noreply@anthropic.com>
…ne registry already serves A host kernel hydrates every stored row into the engine registry, so an echo there is never read by the catalog, and a delete's registry heal re-registered it as a plain registry item: a deleted set, or an uninstalled package's set, went on being served. The echo now registers only a name the registry does not serve (the environment-kernel shape), and a delete also removes a copy of our echo that leaked into the registry. The boot test gains a delete and an uninstall leg on both kernels. Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS Co-authored-by: Claude <noreply@anthropic.com>
…curity-catalog-echo
📓 Docs Drift CheckThis PR changes 1 package(s): 15 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 8 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 15 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 1f309e9c934e4af38d4713dc03a49a7f9d7fc3e7 && git checkout 1f309e9c934e4af38d4713dc03a49a7f9d7fc3e7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 688a39d7700f3b3195e3bd782f42f25622cb4327 a966a51bc1013f3c738ce3b50140dfa414ddbe9c && git checkout -B drift-repro 688a39d7700f3b3195e3bd782f42f25622cb4327 && git merge --no-ff a966a51bc1013f3c738ce3b50140dfa414ddbe9c
node scripts/docs-audit/affected-docs.mjs --json 688a39d7700f3b3195e3bd782f42f25622cb4327
|
Contract reviewServed-tier: Reviewed read-only against the brief's inputs: card #23308 (body, claim 6151102268, dispatch 6151126677, report 6154629520), the 7a blocker 6150594560 and measurement 6151042307 on #22975, PR #23258's body ( ① Derived judgmentsParity with S8's parent on an environment kernel.
The dev's deviation.
Positions.
The boot pass.
Prohibitions from the dispatch.
The pins.
Published text.
② Semver level
③ Boundary flags
Adopted by
Implemented-by: VERDICT: PASS |
…claration the metadata service holds, and removes nothing while the stored row exists A name the kernel's metadata service already holds as something other than our echo (a stack-declared position, which a composition can declare in the metadata service only) is not echoed: the echo would overwrite the declaration, and the overlay's delete would then remove it until restart. The boot pass takes the same path. On a name the engine registry serves, an earlier echo is now left in place instead of being dropped: the metadata service's one removal, unregister, also deletes through its writable datasource loaders and announces a deleted event, which is wrong while the stored row still exists. The overlay's delete drops it. The module doc no longer claims every echoed position is tenant-authored because no artifact declares it. The boot test gains a stack position whose name a stored row took before the stack declared it. Claude-Session: https://claude.ai/code/session_01287UbjvrigpWwNfGDmdwpS Co-authored-by: Claude <noreply@anthropic.com>
…curity-catalog-echo
…curity-catalog-echo
Contract reviewServed-tier: Delta review of PR #23334 from the previous reviewed head The delta is 18 commits: one of the branch's own, ① Derived judgments③-1, the stack-declared position.
③-2, the drop.
The JSDoc is corrected. The header no longer says a position is tenant-authored because no artifact declares it; it states both skips (the registry, the metadata service) and why The merged
Everything else the previous record judged still holds on this head: parity with S8's parent for ② Semver level
③ Boundary flags
Adopted by Implemented-by: VERDICT: PASS |
Fixes #23308
Clause-②: no
On an environment kernel, a permission set or position written through the metadata door is in the security catalog again: it resolves, it grants, and an
isDefault: trueset in a runtime package records itseveryonesuggestion. This is the regression C3 stage 8 (#23222) introduced, fixed as A2: a SecurityPlugin hook that keeps only the echo.Why it broke
The security catalog (ADR-0131 D3) reads the engine registry first, then the kernel's metadata service.
applyRegistryWriteThroughreturns before the hydrator when the kernel has anenvironmentId. The catalog can find a stored set only in the metadata service.Only one thing put stored sets there: the permission-set projector's in-memory echo (
syncEvaluatorRegistry). Stage 8 unwired the projector, and the echo went with it. Since then, on an environment kernel, every metadata-door set:permission-set-echo-package-owner.test.tsstayed green because it wired the projector itself. Positions never had an echo, so on an environment kernel a metadata-door position was unresolved before stage 8 too.Premise, re-checked on this base. Nothing else on
mainfeeds an environment kernel's metadata service for a metadata-doorpermissionorposition:registerInMemorycallers are boot-time stack and code items inapp-plugin.ts, the datasource plugin and the metadata manager itself.main2808069121without the fix, fails all six environment-kernel legs. The host control passes.What this changes
security-catalog-echo.ts. It holds the echo alone:stripDecorations,isProjectionEchoandreadServedPackageBinding(generalized to the type) moved out of the projector.permissionorposition, it reads the environment-wide overlay throughgetMetaItemLayered._envProjection. When the item is tenant-authored it also gets_provenance: 'org'(plugin-security: permission sets an org owns (or that live in a writable runtime package) are reported and enforced as "locked by the code package" — the lock reads the package id without the row's provenance #21789) and the owning_packageId([finding] The permission-set projection drops _packageId, so an isDefault set in a runtime package records no everyone suggestion — and two framework reads disagree on its owner #23233).deleteMetaItem's registry heal re-registered the echo as a plain registry item, so a deleted set and an uninstalled package's set went on being served (permission-metadata-door-reaches-catalog,package-uninstall-revokes-named-grants).unregisteralso deletes through writabledatasource:loaders and publishes adeletedevent. A package uninstall deletes each row throughdeleteMetaItem, so the same projector covers that case.sys_permission_setwrite comes back. It decides nothing about authorization.security-plugin.tsregisters it as the protocol's awaited mutation projector for both types. Onkernel:ready(runBootstrap) it runs one pass over every stored, active, environment-wide overlay of both types, before the suggestion reconcile reads the catalog.permission-set-projection.tsstays in the tree, unwired. It imports the moved helpers from the new module, so 7a's deletion of it is a pure removal.packages/metadata-protocol/src/protocol.ts.Tests
packages/cli/test/security-catalog-echo-env-kernel.integration.test.ts(new). This is the boot-level pin, and it wires nothing itself. It boots cloud's per-project composition: an environment-scopedObjectQLPluginpluscreateMetadataProtocolPlugin, the real metadata service,AuthPluginandSecurityPlugin, over a SQLite file. It asserts on the environment kernel:metadataunder its owning package;everyonesuggestion is recorded;deleteMetaItemof the no-package set and adeletePackageof the runtime package, neither set is served or grants, while the position still resolves;The host kernel runs the same scenario as a control.
Ablation, measured on the final code:
mainfails the six authoring and restart legs the same way.kernel:readypass removed, only the restart leg and the leg after it fail.security-catalog-echo.test.ts(renamed frompermission-set-echo-package-owner.test.ts):sys_permission_setrow is written; it used to pin the row'smanaged_by. The failed-binding case drivesechoSecurityCatalogMutation._provenance: 'org'only on anorgverdict) are re-homed beside them.Acceptance notes
protocol.ts) was not built, per the dispatch.Verification
On head
a966a51bc1(main688a39d770merged in):plugin-securitytypecheck: exit 0.plugin-securityunit suite: 189/189 files, 3,878 passed (45 skipped).--project integration): 122/122 files, 1,068 tests passed, including the boot test, 9/9.permission-metadata-door-reaches-catalogandpackage-uninstall-revokes-named-grants.dispatch-gates --ranre-reconciled on this tree: 76/76, with 0 NOT-MEASURED and 0 UNRUN.On
2f3ea34fc5, the patch round's head before the last merge ofmain:dispatch-gates --commands): 76 derived and 76 run, all exit 0. That includes the three symbol-anchor sweeps, both censuses andcheck:dual-build-cjs-loads. eslint on the changed files: exit 0.check:engine-double-contract, because the ledger still named the renamed suite. It was regenerated with--write.check:objectql-double-limit, because the boot-passfinddouble ignoredlimit. The double now applies it.maintouchedprotocol.tsandsuggested-audience-bindings.ts, so the tiers above were run again on the pushed head.Also:
@objectstack/plugin-security's entry changes, so nothing the console imports moves.4051c1349a, with ③-1 (a declaration held only by the metadata service) and ③-2 (no removal while the stored row exists) fixed in22813f7459. Then the delta review PASS 6156782288 ata966a51bc1.main.Generated by Claude Code