From 882055342323cd91dc8b06e83d2d4c19b930bae5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 16 Aug 2026 06:56:21 +0000 Subject: [PATCH] docs(qa): narrow the authz conformance matrix's completeness claim to what its ratchet checks (#8711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Half (2), the ruled remainder of #8711 (half (1) landed in #8812). Maintainer ruling: narrow the matrix's written claim to what the mechanism delivers (Option A) — no primitive-discovery ratchet (Option B, measured unachievable in general form), option C (a declaration-side ratchet) deliberately left unruled and preserved as a separate finding. The matrix header and its companion test's header claimed a new declared-but-unenforced primitive "breaks CI." That is true only for the curated set of HTTP/transport entry points `discover()` enumerates (15 probes over 11 named source files) — a primitive enforced by a predicate inside an existing resolver adds no entry point and can be neither UNCLASSIFIED nor STALE, which is exactly why the #8613 `active` flag sat outside the ledger for its whole inert period without breaking CI. Both headers now state the narrowed, routes-completeness claim with the measured numbers so it is load-bearing: 43 of 50 rows carry no `covers` key at all (7 rows, 9 keys, every one an HTTP/transport pin), 37 of the 43 `enforced` rows are in-resolver predicates outside the ratchet entirely, and 5 of the 9 `covers` keys that do exist are gate-pins that vanish (failing CI) when the guard call they name is deleted — the real anti-regression value, preserved. Outside the curated table, "one row per primitive" is stated as the hand-maintained invariant it actually is. Prose and comments only. `discover()`, `PROBES`, `HIGH_RISK`, `checkLedger`, and every row's `state`/`enforcement`/`covers`/`proof` are untouched — the conformance test still reports 15 passed (15), unchanged, which is the evidence this boundary was respected. Claude-Session: https://claude.ai/code/session_01NaS1PAHJcPfAA2acnV53Tn Co-authored-by: Claude --- .changeset/authz-matrix-scope-narrowing.md | 24 +++++++++++ .../dogfood/test/authz-conformance.matrix.ts | 43 ++++++++++++++----- .../qa/dogfood/test/authz-conformance.test.ts | 13 +++--- 3 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 .changeset/authz-matrix-scope-narrowing.md diff --git a/.changeset/authz-matrix-scope-narrowing.md b/.changeset/authz-matrix-scope-narrowing.md new file mode 100644 index 0000000000..4f248d9ea2 --- /dev/null +++ b/.changeset/authz-matrix-scope-narrowing.md @@ -0,0 +1,24 @@ +--- +"@objectstack/dogfood": patch +--- + +docs(qa): narrow the ADR-0056 D10 authz conformance matrix's advertised completeness claim to what its ratchet actually checks (#8711) + +The matrix header and its companion test's header previously read as though +a new declared-but-unenforced authorization primitive would "break CI." It +would not, for most of the ledger: the completeness `discover()` ratchets is +over a **curated table of HTTP/transport entry points** (15 probes over 11 +named source files), not over primitives. A primitive enforced by a predicate +inside an existing resolver — the `sys_permission_set.active` / +`sys_position.active` rows added in #8812 are the normal case, not an +exception — adds no entry point, so it can be neither UNCLASSIFIED nor STALE. + +Both headers now say so explicitly, carrying the measured numbers so the +narrowed claim is load-bearing rather than vague: 43 of the matrix's 50 rows +carry no `covers` key at all, 37 of the 43 `enforced` rows are exactly that +in-resolver shape, and — preserved, because it is real — 5 of the file's 9 +`covers` keys are gate-pins that vanish (and fail CI) when the guard call +they name is deleted. Prose and comments only; nothing about the ratchet's +checking behaviour, the `discover()` table, or any row changes. Maintainer +ruling on #8711 (Option A): narrow the claim, do not build a +primitive-discovery ratchet (measured unachievable in general form). diff --git a/packages/qa/dogfood/test/authz-conformance.matrix.ts b/packages/qa/dogfood/test/authz-conformance.matrix.ts index d35b5bc1e2..5d6d08c00e 100644 --- a/packages/qa/dogfood/test/authz-conformance.matrix.ts +++ b/packages/qa/dogfood/test/authz-conformance.matrix.ts @@ -6,11 +6,31 @@ // primitive, each in EXACTLY ONE honest state (enforced / experimental / // removed). `enforced` rows name their runtime enforcement site; high-risk // enforced rows additionally reference an end-to-end dogfood proof. The -// companion test (`authz-conformance.test.ts`) asserts the matrix is complete, -// that every referenced proof file exists, AND that the row ↔ proof pairing is -// MUTUAL — so "the permission model is landed" is a CHECKED artifact, not a -// one-time scan. A new fail-open (a declared-but-unenforced primitive) or a -// deleted proof breaks CI. +// companion test (`authz-conformance.test.ts`) asserts every referenced proof +// file exists and that the row ↔ proof pairing is MUTUAL (#7976 below), AND +// ratchets completeness over a CURATED table of HTTP/transport entry points +// (`discover()`: 15 probes over 11 named source files) — a new ungated route +// there is UNCLASSIFIED, a deleted guard is STALE, and either breaks CI. +// +// [#8711] That completeness is over ROUTES, not over primitives: a primitive +// enforced by a predicate inside an existing resolver adds no entry point, so +// it can be neither UNCLASSIFIED nor STALE. Measured against the rows below: +// 43 of 50 carry no `covers` key at all (7 rows, 9 keys, every one an +// HTTP/transport pin), and 37 of the file's 43 `enforced` rows are exactly +// that in-resolver shape — the ADR-0049/#8613 `active` rows among them (see +// their own block further down) are the normal case, not an exception. Of the +// 9 `covers` keys that DO exist, 5 are GATE pins tied to the enforcement call +// itself, not merely a function name — delete `shouldDenyAnonymous` from +// `/actions`, `/automation` or `/packages`, or drop the MCP context-threading +// / stdio principal binding, and the pinned key vanishes from source, its row +// goes STALE, and CI catches the regression. That anti-regression property is +// real and is what this file mechanically delivers. Outside the curated +// table, "one row per primitive, each in EXACTLY ONE honest state" is a +// HAND-MAINTAINED invariant, not a checked one: a primitive added without its +// row is not something this ratchet can see (a primitive-discovery ratchet +// was measured unachievable in general form — there is no syntactic signature +// for "a predicate that decides a grant"; `isRowActive` looks exactly like any +// other `.filter()`). // // [#7976] Existence used to be the whole `proof` contract, which meant a row // could cite a file exercising a NEIGHBOURING primitive and stay green forever: @@ -236,12 +256,13 @@ export const AUTHZ_CONFORMANCE: AuthzPrimitive[] = [ // RATCHET, not an omission: `discover()` enumerates HTTP entry points from a // curated per-file probe table, and a predicate inside an existing resolver // adds no entry point — so neither flag could ever have surfaced as - // UNCLASSIFIED during the whole period it was inert, despite this file's - // header claiming a declared-but-unenforced primitive breaks CI. These two - // rows restore the ledger's stated invariant; whether the invariant's - // advertised SCOPE should be narrowed to what the ratchet can check, or the - // ratchet widened to reach in-resolver predicates, is the open half of #8711 - // and is deliberately NOT settled here. + // UNCLASSIFIED during the whole period it was inert. These two rows restore + // the ledger's stated invariant. [Resolved — maintainer ruling on #8711, + // 2026-08-15] The invariant's advertised SCOPE is narrowed to what the + // ratchet can check, not the ratchet widened to reach in-resolver + // predicates like this one — widening was measured unachievable in general + // form. See this file's header for the narrowed claim and the measured + // numbers. { id: 'permission-set-active', summary: '`sys_permission_set.active` — a deactivated permission set grants nothing (ADR-0049 / #8613)', state: 'enforced', enforcement: 'core/security/resolve-authz-context.ts step 6b — isRowActive drops the row BEFORE any derivation, so a deactivated set contributes no name to `grants.permissions`, no systemPermissions and no tabPermissions, AND `hasPlatformAdminGrant` cannot be read off a deactivated `admin_full_access`; plugin-security/security-plugin.ts dbLoader applies the SAME predicate, which is the only place a set reached by NAME is judged (position names are commonly reused as set names, so an ACTIVE position carries a DEACTIVATED set\'s name that far); plugin-auth/last-admin-guard.ts carries `active` in PERMISSION_SET_STANDING_KEYS so deactivating the last admin set is judged as an emptying rather than read as a bootstrap window', note: 'Unit-proven; an e2e dogfood proof is a follow-on, the same disposition as the ADR-0105 block above and for the same reason — the flag is a predicate inside the grant resolver, not an HTTP surface, so there is no route for a dogfood boot to drive at it directly. core/security/resolve-authz-context.test.ts "[#8613] the `active` flag on the grant catalogues (ADR-0049)" covers the derivation half, including THE HIGH-BLAST-RADIUS CASE (a deactivated admin_full_access confers no PLATFORM_ADMIN) and that deactivating ONE set leaves the others granting; plugin-security/permission-set-active.test.ts covers the loader half, including THE REACHABILITY CASE (a position name reaching a deactivated set of the same name) plus its non-vacuous twin (the same request with the set ACTIVE does resolve); core/security/row-active.test.ts pins the predicate itself (absent grants, junk does not revoke, 0/1 deactivates). Deliberately NOT in HIGH_RISK: that list marks primitives guarding object data through a sibling HTTP entry point, and this one guards grant DERIVATION. The honest upgrade path is a real proof (seed a deactivated set, drive REST as its holder, observe the refusal), not re-citing a neighbouring file.' }, diff --git a/packages/qa/dogfood/test/authz-conformance.test.ts b/packages/qa/dogfood/test/authz-conformance.test.ts index 3a8339c208..0e859f8cd6 100644 --- a/packages/qa/dogfood/test/authz-conformance.test.ts +++ b/packages/qa/dogfood/test/authz-conformance.test.ts @@ -1,10 +1,13 @@ // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// ADR-0056 D10 — the authorization conformance matrix is a CHECKED artifact. -// Refactored onto the reusable ADR-0060 `checkLedger` helper: one call asserts -// every shared invariant (valid state, enforced-has-site, experimental/removed- -// has-note, proof-file-exists, high-risk-has-proof). A new fail-open or a deleted -// proof breaks the build. +// ADR-0056 D10 — the authorization conformance matrix is a CHECKED artifact, +// within the scope the mechanism can see: routes are ratcheted, primitives are +// hand-maintained (see the matrix's own header for the narrowed claim and the +// measured numbers — #8711). Refactored onto the reusable ADR-0060 +// `checkLedger` helper: one call asserts every shared invariant (valid state, +// enforced-has-site, experimental/removed-has-note, proof-file-exists, +// high-risk-has-proof). A row that regresses one of THOSE invariants, or a +// deleted proof, breaks the build. // // #2567 Phase 2 — the anonymous-deny SURFACES are additionally pinned by the // `discover()` ratchet: this test STATICALLY enumerates the data/meta/graphql