Found while implementing #8536 (PR #8749). That card scoped the DERIVED half's skip branch — the #5876 guard that continues on a row it does not own. This is the neighbouring update branch, which reaches the same "platform bucket unseeded" end state by a different route and is not covered by #8749's new counter or warning. Filing rather than fixing: out of #8536's scope, and the severity call belongs to triage.
The mechanism
In packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts, the derived half's lookup is { name } under SYSTEM_CTX (no tenantId), so it reads across organizations. The ownership guard is a test on managed_by alone:
if(isDerived&&row.managed_by!=='platform'){/* skip */}// …otherwise fall through and UPDATE the row that was foundmanaged_by: 'platform' was a sufficient ownership test when name was unique installation-wide. Since #8461 made it unique per ORGANIZATION (ADR-0120 D1), a platform-marked row can sit inside an organization's bucket — the file's own header already contemplates this shape, "a platform-marked row sitting inside an organization — from seed data or a legacy import". Such a row passes the guard, so the seeder updates it and never inserts into the NULL-organization bucket.
This is the same asymmetry #8470 fixed for the CURATED half, whose predicate is the conjunction managed_by: 'platform' AND organization_id: null precisely because managed_by alone "would not carry that guarantee". The derived half kept the single-condition test.
Measured
In-memory double mirroring sys-capability.object.ts's unique key, one pre-existing row { organization_id: 'org_x', name: 'showcase.export_data', managed_by: 'platform', label: 'Stale Org Label' }, one grant naming that capability:
orgLabel: "Showcase Export Data" ← rewritten with the humanized placeholder
platformBucketRow: ABSENT
skippedAuthored: 0
unseededDerived: 0 (the counter #8749 adds — this branch never reaches it)
warns: 0
Two facts in one run: the platform bucket is never seeded, and an organization's row has its label/description overwritten by a generated placeholder. Every counter reads zero and nothing is logged, so this state is invisible in the boot summary as well as in the new diagnostic.
Why it is distinct from #8536
#8536 and #8749 are about the branch where the guard declines. Here the guard admits the row, so no skip is counted and the new unseededDerived counter is never incremented. A fix would be a change to the lookup or the guard predicate, not to observability — which is why it had no place in a card explicitly ruled "parity, not policy; no behaviour change beyond observability".
Note also that the overwrite half is arguably the more interesting one: #5876's whole point was that the derived half must never rewrite an author's copy with humanize(name), and managed_by: 'platform' was the test chosen to enforce it. Post-#8461 that test admits a row an organization holds.
Consequence, and what is NOT claimed
Unmeasured beyond the above, and probably the same as #8536's: no runtime path reads a sys_capability row (grants and requiredPermissions resolve by name; validateCapabilityReferences resolves against PLATFORM_CAPABILITY_NAMES plus stack declarations), so the effect should be confined to registry contents and Setup listings. I did not re-derive that claim here — I am carrying it across from #8536's body, which established it for the sibling branch.
Reachability of a platform-marked row inside an organization is likewise not measured. The file header names seed data and legacy imports as the sources; whether either actually produces one on a shipped install is the open question that decides whether this is a defect or a dormant asymmetry.
Filed unassigned, observation-class. Related to #8536 / PR #8749; not blocked by either.
Generated by Claude Code
Found while implementing #8536 (PR #8749). That card scoped the DERIVED half's skip branch — the #5876 guard that
continues on a row it does not own. This is the neighbouring update branch, which reaches the same "platform bucket unseeded" end state by a different route and is not covered by #8749's new counter or warning. Filing rather than fixing: out of #8536's scope, and the severity call belongs to triage.The mechanism
In
packages/plugins/plugin-security/src/bootstrap-system-capabilities.ts, the derived half's lookup is{ name }underSYSTEM_CTX(notenantId), so it reads across organizations. The ownership guard is a test onmanaged_byalone:managed_by: 'platform'was a sufficient ownership test whennamewas unique installation-wide. Since #8461 made it unique per ORGANIZATION (ADR-0120 D1), a platform-marked row can sit inside an organization's bucket — the file's own header already contemplates this shape, "a platform-marked row sitting inside an organization — from seed data or a legacy import". Such a row passes the guard, so the seeder updates it and never inserts into the NULL-organization bucket.This is the same asymmetry #8470 fixed for the CURATED half, whose predicate is the conjunction
managed_by: 'platform'ANDorganization_id: nullprecisely becausemanaged_byalone "would not carry that guarantee". The derived half kept the single-condition test.Measured
In-memory double mirroring
sys-capability.object.ts's unique key, one pre-existing row{ organization_id: 'org_x', name: 'showcase.export_data', managed_by: 'platform', label: 'Stale Org Label' }, one grant naming that capability:Two facts in one run: the platform bucket is never seeded, and an organization's row has its
label/descriptionoverwritten by a generated placeholder. Every counter reads zero and nothing is logged, so this state is invisible in the boot summary as well as in the new diagnostic.Why it is distinct from #8536
#8536 and #8749 are about the branch where the guard declines. Here the guard admits the row, so no skip is counted and the new
unseededDerivedcounter is never incremented. A fix would be a change to the lookup or the guard predicate, not to observability — which is why it had no place in a card explicitly ruled "parity, not policy; no behaviour change beyond observability".Note also that the overwrite half is arguably the more interesting one: #5876's whole point was that the derived half must never rewrite an author's copy with
humanize(name), andmanaged_by: 'platform'was the test chosen to enforce it. Post-#8461 that test admits a row an organization holds.Consequence, and what is NOT claimed
Unmeasured beyond the above, and probably the same as #8536's: no runtime path reads a
sys_capabilityrow (grants andrequiredPermissionsresolve by name;validateCapabilityReferencesresolves againstPLATFORM_CAPABILITY_NAMESplus stack declarations), so the effect should be confined to registry contents and Setup listings. I did not re-derive that claim here — I am carrying it across from #8536's body, which established it for the sibling branch.Reachability of a platform-marked row inside an organization is likewise not measured. The file header names seed data and legacy imports as the sources; whether either actually produces one on a shipped install is the open question that decides whether this is a defect or a dormant asymmetry.
Filed unassigned, observation-class. Related to #8536 / PR #8749; not blocked by either.
Generated by Claude Code