test(dogfood): add the ADR-0056 D10 matrix rows for the grant-catalogue active flag (#8711) - #8812
Conversation
…ue `active` flag (#8711) `sys_permission_set.active` and `sys_position.active` became authorization primitives when #8613 landed: a deactivated row no longer resolves, and a deactivated `admin_full_access` no longer confers PLATFORM_ADMIN. The matrix advertises "one row per authorization primitive, each in EXACTLY ONE honest state" and had a row for neither. Both classified `enforced`, read off what #8613 actually enforces rather than assumed: - `permission-set-active` names three readers — resolve-authz-context step 6b (dropped before any derivation, so `hasPlatformAdminGrant` cannot be read off a deactivated set), the plugin-security dbLoader (the only place a set reached by NAME is judged), and the last-admin guard's standing keys; - `position-active` names the two halves that only together hold it — the bound sets are not collected AND the name is dropped from `grants.positions`, since positions are requested as permission-set names one layer down. Neither row carries `covers`, and the block comment says why that is a statement about the ratchet rather than an omission: `discover()` enumerates HTTP entry points from a curated probe table, so a predicate inside an existing resolver can be neither UNCLASSIFIED nor STALE — which is why these flags sat outside the ledger for their whole inert period without breaking CI. The conformance test reports 15 passed both before and after this change: the gate cannot see either row. That measurement is the evidence for #8711's second question, which is deliberately left open here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NaS1PAHJcPfAA2acnV53Tn
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
ACCEPT — PM review,
|
Dogfood Regression Gate — local full-suite runRun against CI shards this three ways; the run above is unsharded, so it covers all three shards' slices in one pass. The suite log carries Targeted runs, same head
The before/after being identical is the point rather than a formality — it is the measurement the card's second question turns on. The gate cannot see either new row. Ablation, for the other direction: dropping Generated by Claude Code Generated by Claude Code |
Part of #8711 — this PR lands the card's first question only. The second is left open deliberately; see "What is NOT here".
What landed
Two rows in
packages/qa/dogfood/test/authz-conformance.matrix.ts, bothenforced:permission-set-activeresolve-authz-context.tsstep 6b (dropped BEFORE any derivation, sohasPlatformAdminGrantcannot be read off a deactivatedadmin_full_access); the plugin-securitydbLoader(the only place a set reached by NAME is judged);last-admin-guard.tsPERMISSION_SET_STANDING_KEYSposition-activeresolve-authz-context.tsstep 6a, both halves — the bound sets are not collected AND the deactivated name is dropped fromgrants.positions, since positions are requested as permission-set names one layer downsys_permission_set.activeandsys_position.activebecame authorization primitives when theactiveenforcement landed: a deactivated row no longer resolves. The matrix advertises "one row per authorization primitive, each in EXACTLY ONE honest state" and had a row for neither.The state was read, not assumed
The card notes that before the enforcement landed an
experimental/ declared-but-unenforced row was arguably owed instead, so "which state is honest" is a question about today's code. Both areenforcedtoday: one shared predicate,isRowActive, backs all three readers — explicitly deactivated, never "explicitly active", so an absent column keeps granting and the 0/1 storage shape is judged as well as a literalfalse.Neither row is added to
HIGH_RISK. That list marks primitives guarding object data through a sibling HTTP entry point; these guard grant derivation, which is the same disposition the ADR-0105 block already carries. Each row's note names the unit proofs that exist and states an e2e dogfood proof as the honest upgrade path, rather than citing a neighbouring file.The measurement — why neither row carries
coversThis is the part worth reading. Both rows are ratchet-invisible by construction, and that is a statement about the ratchet rather than an omission:
discover()enumerates HTTP entry points from a curated per-file probe table — 15 probes over 10 named source files. A predicate inside an existing resolver adds no entry point, so it can be neither UNCLASSIFIED nor STALE.coverskey at all (7 rows, 9 keys total, all of them HTTP/transport pins). Of the 41enforcedrows, 35 are in-resolver predicates outside the ratchet's reach.So the answer to "is this one primitive, or many?" is many — the
activeflag was the normal case, not an exceptional blind spot. The completeness the file provides is over a curated route set; the completeness it claims is over primitives.Evidence the gate cannot see this change:
authz-conformance.test.tsreports15 passed (15)both before and after. The ledger gained two rows encoding a real enforcement fact and the checker's output did not move by one character.The rows are still genuinely checked for the shape invariants — ablating
permission-set-active'senforcementfield turns the suite red at3 failed | 12 passedwithpermission-set-active: enforced but names no enforcement site. So they are live ledger entries, not inert text; it is specifically the discovery half that cannot reach them.What is NOT here
The card's second question — whether the matrix's advertised scope should be narrowed in writing to what the ratchet can check, or the ratchet widened — is a scope decision about the ledger and about gate design. It is not implemented here, and the header comment is untouched. The block comment added with the rows records the mechanism and points at #8711 for the open half. A recommendation with the measurement above goes back to the PM rather than into this diff.
Verification
All of the below measured at
1e6af0bd3, which is this branch's head — no commit follows the run.packages/qa/dogfoodonly;authz-conformance.matrix.tsis imported byauthz-conformance.test.tsalone (other references are comments;plugin-security/authz-matrix-gate.test.tsmentions it in prose but does not read it).pnpm --filter @objectstack/dogfood exec vitest run test/authz-conformance.test.ts— 15 passed before, 15 passed afterpnpm --filter @objectstack/dogfood typecheck— cleanpnpm check:test-source-alias,pnpm check:type-source-resolution— both OK (re-derived against the changed path viascripts/pm/dispatch-gates.mjs, which named these two; neither was in the dispatch list)pnpm check:nul-bytes— OK (5817 files, no raw control bytes)turbo run test --filter=@objectstack/dogfood) — run locally because it is a required check; result recorded in a PR commentNo changeset: tests-only, nothing user-visible ships. Labelled
skip-changeset.Adjacent finding filed while measuring, not addressed here: #8811 (the ADR-0091 grant validity windows have no matrix row either — a second instance of the same gap, and part of why the measurement above matters).
Generated by Claude Code