Repository navigation
feat(plugin-security): the curated platform capabilities are declared capability metadata (ADR-0131 D3, #15204 stage 6b-1a) - #22669
Conversation
…apability metadata (ADR-0131 D3) Claude-Session: https://claude.ai/code/session_014DUFKvmT2Vyzx42PyAAXV3 Co-authored-by: Claude <noreply@anthropic.com>
…eal boot Claude-Session: https://claude.ai/code/session_014DUFKvmT2Vyzx42PyAAXV3 Co-authored-by: Claude <noreply@anthropic.com>
… beside the curated declarations Claude-Session: https://claude.ai/code/session_014DUFKvmT2Vyzx42PyAAXV3 Co-authored-by: Claude <noreply@anthropic.com>
…ies beside the stack's; changeset Claude-Session: https://claude.ai/code/session_014DUFKvmT2Vyzx42PyAAXV3 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014DUFKvmT2Vyzx42PyAAXV3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 16 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 a93b2cd2ff4d31e651af2b135f6fc93b12bca441 && git checkout a93b2cd2ff4d31e651af2b135f6fc93b12bca441
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 243dd3c6256673dc1a56f9419be03c397129f825 1c502d391a910d83d876d0e5cfbfe235bed3691f && git checkout -B drift-repro 243dd3c6256673dc1a56f9419be03c397129f825 && git merge --no-ff 1c502d391a910d83d876d0e5cfbfe235bed3691f
node scripts/docs-audit/affected-docs.mjs --json 243dd3c6256673dc1a56f9419be03c397129f825
|
Merge-queue kick-out: signature and first reading · epic PM
|
|
Re-queued once · epic PM This follows the kick-out record 6098007229. Head This is the single re-queue. If shard 2 stalls the same way again, I will treat it as real and open a patch round to reproduce it on the merged tree. |
Part of #15204
Clause-②: no
Stage 6b-1a of the cutover plan only (claim amendment 6095767104): the platform's nine curated capabilities (
PLATFORM_CAPABILITIES,packages/spec/src/security/capabilities.ts) are declaredcapabilitymetadata ofplugin-security, so the security catalog's one home (ADR-0131 D3; #15196: "Positions, permission sets and capabilities have exactly one home — the registry") holds them. The rest of #15204 stays open: no seeder is edited or deleted (that is 6b-1b, after C9), nosys_capabilityrow is written or deleted, and who may author a capability is unchanged (#22621 → A).Premise, re-verified on
origin/maind85615ddThe curated capabilities had no registry home. On a booted showcase, in all three postures, the engine registry, the metadata service, the catalog read and
GET /api/v1/meta/capabilityeach held exactly the stack's two capabilities (showcase.export_data,showcase.restricted_ops), andGET /api/v1/meta/capability/manage_usersanswered404 RESOURCE_NOT_FOUND. The nine curated names existed only assys_capabilityrows. No open PR and noclaude/*branch declared them.Before / after (booted showcase, same worktree; before =
d85615dd, after = this branch)createSecurityCatalogReader().list('capability')GET /api/v1/meta/capabilitycapabilityitemscapabilityitemsGET /meta/capability/manage_usersPUT /meta/capability/manage_users(platform admin)PUT /meta/capability/zz_s6b1a_control(control)sys_capabilityrows (name, managed_by, package_id, label, scope)capability_platform_name_refusedlines in the boot logThe nine new entries answer from the registry with
packageId: 'com.objectstack.plugin-security'. ThePUTstays refused; only its message changes, from "the type is code-only" to "provided by a managed package and its type is code-only".What changed
builtin-capabilities.ts(new).registerBuiltinCapabilitiesregisters eachPLATFORM_CAPABILITIESentry (name,label,description,scope) throughregistry.registerItem('capability', item, 'name', SECURITY_PLUGIN_ID). The list is the spec's own (securityBuiltinCapabilities === PLATFORM_CAPABILITIES), so there is one list, not a copy.CapabilityDeclarationSchemaalready declares every curated field, so the spec is not widened.capabilitiescollection, but the package door in front of it (SchemaRegistry.refuseSecurityCatalogNameConflicts, the Q4 A one-holder rule) refuses a package that declares a built-in name, and the curated names are exactlyBUILT_IN_SECURITY_CATALOG_NAMES.capability. The item seam with a package id asks only whether another package holds the name.SecurityPlugin.startcalls it besideregisterBuiltinPositions, and says so once atwarnif the engine has no registry to register into.bootstrapDeclaredCapabilitiesreads the registry's capabilities as package declarations. Handed the curated ones, it would refuse each as a package claiming a curated name (nine falsecapability_platform_name_refusedlines every boot), and it would stop falling back to the metadata service when the registry holds no package declaration. The seeder may not be edited (batch Release version 0.4.0 #310 item 4), so the call site insecurity-plugin.tshands it an engine view whose registry lists every capability except this plugin's own curated declarations (withoutPlatformCapabilityDeclarations).readDeclaredCapabilityContext(declared-capability-context.ts, not a seeder) drops the same items. Both readers see exactly what they saw before. The view goes with the seeder in 6b-1b.The one-holder rule, measured
builtin-capabilities.boot.test.tsboots the plugin twice on one SQLite file. The second boot registers cleanly, writes zerosys_capabilityrows, and leaves the same census. Existing rows are rows, not registry holders.SecurityCatalogNameConflictErrorin any posture.The derived half (not declared here)
The seeder's derived half humanizes a
systemPermissionsstring that no curated entry and no declaration names. Measured: everysystemPermissionsentry of the platform's default sets is a curated name. The showcase's only non-curated one,showcase.export_data, is the stack's own declared capability and already resolves through the catalog read. Nohumanized placeholder is declared.Pins
builtin-capabilities.test.ts:CapabilityDeclarationSchema;readDeclaredCapabilityContextstill falls back to the metadata service.builtin-capabilities.boot.test.ts: a real boot (init,start, everykernel:readyhandler) over ObjectQL on SQLite, insingleand walled, with the stack's capability in the registry or in the metadata service only, each on a fresh and on a re-opened database. It asserts:@registry, owned by this plugin, with the spec's fields;sys_capabilitycensus equals the one derived from the producers (the curated pass's rows plus the stack's package row), and the second boot writes nothing;packages/qa/dogfood/test/security-catalog-showcase.dogfood.test.ts: its expected capability set addsPLATFORM_CAPABILITY_NAMES, read off@objectstack/spec. Its door-parity rows stay as they were (the read and the door agree on 11).Ablations. Each went through
scripts/ablation-replace.mjs(anchor hit 1 → 0, blob changed). After each run the blob equalledHEADandgit diff HEADwas empty.security-plugin.tsif (0 === 0))security-plugin.tswarnfires) in all four scenariosdeclared-capability-context.tsThe third ablation's first attempt was a no-op: its replacement text was a substring of the anchor, so
ablation-replacerefused it and restored the file. The second attempt used a distinct replacement.Verification (at
cca8fe4648)plugin-security: build;typecheckexit 0 (tsc, scripts,check:test-typecheck);vitest run197 files, 4124 passed, 45 skipped.security-catalog-showcase,audit-log-audit-capability,me-apps-and-everyone-baselineandorg-admin-affordance-reach(4 files, 64 passed). Runtime:standalone-stack-seeder-declaration-copy(13 passed).dispatch-gates --commandsderives 70 commands for this diff. All 70 exit 0, and--ranreconciles them: 70 derived, 70 run, 0 NOT-MEASURED (a derived zero, with exit codes recorded).check:plugin-teardown-shape --self-testandcheck:dual-build-cjs-loadsanswered PREREQUISITE NOT MET: a shallow clone, and eight packages outside this diff's closure were unbuilt. Both passed once the clone was unshallowed and those packages were built.check:query-options-erasurecaught twoas anyfind options in the new boot test (test surface 236 → 238). They are typed incca8fe4648, and the surface is back at 236.Deviations
packages/qa/dogfood/test/security-catalog-showcase.dogfood.test.ts(adomain:clipath). It pinned the capability set to the stack's names only, so it reds with the nine registered. S2 made the same edit for positions.Acceptance notes
core/src/security/security-catalog.ts's module doc records a measured table (capability: 2 per reader) taken at3d9188502e. It is a dated measurement, not a false claim; after this stage the engine registry holds 11 on the showcase. Noted for whoever next edits that seam (owner: none).skip-changesetdoes not apply:@objectstack/plugin-securitypublishesdist, and the changeset isminor.Generated by Claude Code