Skip to content

Commit d6e7935

Browse files
os-zhuangclaude
andauthored
test(qa): record the ADR-0091 grant validity-window primitive in the authz conformance matrix (#8811) (#9376)
The ADR-0056 D10 matrix states one row per authorization primitive in exactly one honest state, but carried no row for the ADR-0091 grant validity windows — the only mentions were incidental. Adds the `grant-validity-window` row as `enforced` with no `covers`, mirroring the ADR-0049/#8613 `active`-flag disposition, and refreshes the header counts the new row moves. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 90c5285 commit d6e7935

1 file changed

Lines changed: 24 additions & 4 deletions

File tree

packages/qa/dogfood/test/authz-conformance.matrix.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,11 @@
1919
// [#8711] That completeness is over ROUTES, not over primitives: a primitive
2020
// enforced by a predicate inside an existing resolver adds no entry point, so
2121
// it can be neither UNCLASSIFIED nor STALE. Measured against the rows below:
22-
// 43 of 50 carry no `covers` key at all (7 rows, 9 keys, every one an
23-
// HTTP/transport pin), and 37 of the file's 43 `enforced` rows are exactly
24-
// that in-resolver shape — the ADR-0049/#8613 `active` rows among them (see
25-
// their own block further down) are the normal case, not an exception. Of the
22+
// 44 of 51 carry no `covers` key at all (7 rows, 9 keys, every one an
23+
// HTTP/transport pin), and 38 of the file's 44 `enforced` rows are exactly
24+
// that in-resolver shape — the ADR-0049/#8613 `active` rows and the ADR-0091
25+
// grant-validity-window row among them (see their own blocks further down)
26+
// are the normal case, not an exception. Of the
2627
// 9 `covers` keys that DO exist, 5 are GATE pins tied to the enforcement call
2728
// itself, not merely a function name — delete `shouldDenyAnonymous` from
2829
// `/actions`, `/automation` or `/packages`, or drop the MCP context-threading
@@ -285,6 +286,25 @@ export const AUTHZ_CONFORMANCE: AuthzPrimitive[] = [
285286
enforcement: 'core/security/resolve-authz-context.ts step 6a — isRowActive gates BOTH halves, and only both hold it: (i) only ACTIVE position ids collect their `sys_position_permission_set` linkage, so a deactivated position carries no bound set; (ii) the deactivated NAME is dropped from `grants.positions`, because resolvePermissionSetsForContext requests positions as permission-set NAMES and a name left standing resolves the same grant one layer down',
286287
note: 'Only a name whose `sys_position` row is EXPLICITLY deactivated is dropped — a name with no row at all (`org_owner`, a membership-derived role, the built-in `everyone` audience anchor) has no flag to read and is untouched. Deliberately NOT a blanket revocation of the sets themselves: a set held via BOTH a deactivated position AND a direct user grant still resolves, since the direct grant is a different grant (resolve-authz-context.test.ts pins exactly that case). Symmetrically, the WRITE gates and blast-radius reads in plugin-security (assertAudienceAnchorBindingGate, setsBoundToPosition, the delegated-admin surfaces) stay UNFILTERED on purpose — dropping a deactivated row there would make a refused binding permitted, narrow a delegate\'s boundary, and make a deactivated position unmanageable. Unit-proven in core/security/resolve-authz-context.test.ts (a deactivated position stops granting its sets; an active one still grants; an absent column grants; the 0/1 shape deactivates; deactivating ONE position leaves the others granting) + core/security/row-active.test.ts. Not HIGH_RISK for the same reason as `permission-set-active`.' },
287288

289+
// ── ADR-0091 D1/D2 — grant validity windows (#8811) ───────────────────
290+
//
291+
// The sibling of the `active` switch above, and enforced at the same seam
292+
// for the same stated reason: a grant that is supposed to lapse on a date,
293+
// but only lapses when a cleanup job runs, is an unenforced security
294+
// property (ADR-0049). `valid_from` / `valid_until` are therefore read at
295+
// RESOLUTION time by one shared predicate, `isGrantActive`
296+
// (@objectstack/core), so no reader can drift from another — the same
297+
// one-predicate discipline `isRowActive` gets above.
298+
//
299+
// The window is half-open `[from, until)` in UTC: absent/null bounds mean
300+
// unbounded (pre-ADR-0091 rows behave exactly as before), and a bound that
301+
// is PRESENT but unparseable DISABLES the grant — fail-closed, deliberately
302+
// unlike API-key `isExpired`, because a grant row is standing authority
303+
// rather than a single credential.
304+
{ id: 'grant-validity-window', summary: '`valid_from` / `valid_until` on a grant row — a grant outside its half-open window resolves to nothing, at resolution time and with no cleanup job (ADR-0091 D1/D2)', state: 'enforced',
305+
enforcement: 'core/security/grant-validity.ts isGrantActive — ONE half-open [valid_from, valid_until) predicate (UTC; absent bound = unbounded; a PRESENT but unparseable bound fails CLOSED), applied by every reader that turns a window-carrying row into authority: core/security/resolve-authz-context.ts step 4 (sys_user_position) and step 6 (sys_user_permission_set, dropped BEFORE any derivation so an expired admin_full_access cannot yield platform_admin either); plugin-security/explain-engine.ts buildContextForUser (plus isGrantExpired for the dedicated "expired" contributor state); plugin-sharing/position-graph.ts PositionGraphService.expandPositionUsers; plugin-security/delegated-admin-gate.ts held-scope resolution; plugin-auth/last-admin-guard.ts administrator-standing reads; plugin-approvals/approval-service.ts lookupActiveDelegation (sys_approval_delegation); runtime/domains/keys.ts membership check',
306+
note: '[#8811] NO `covers`, and — exactly like the two `active` rows above — that is a statement about the RATCHET rather than 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 this primitive could never have surfaced as UNCLASSIFIED or STALE during any period it was inert. Per the #8711 ruling (2026-08-15) the invariant\'s advertised SCOPE is narrowed to what the ratchet can check; this row is owed under the hand-maintained half the file header still states. WHAT IS AND IS NOT WINDOW-FILTERED — measured at the call sites, not taken from the filing: the columns are declared on exactly THREE objects (sys_user_position, sys_user_permission_set, sys_approval_delegation), and on all three EVERY authorization-resolution seam applies the predicate, which is what makes this row `enforced` rather than partial. Two reads deliberately do NOT filter and neither is a live hole: (i) `sys_member` carries no window columns at all, so the resolver\'s membership call (which builds accessible_org_ids) is FORWARD-LOOKING, and the org-administration role projection beside it is NOT window-filtered (#8802) — both are no-ops until the columns exist, and last-admin-guard.ts\'s MEMBER standing-key notes already record that the resolver is where the two halves must be reconciled FIRST if they ever land; (ii) plugin-approvals\' own expandPositionUsers projects user_id alone by the #8710 ruling, because approval ROUTING is not grant resolution — that note explicitly forbids "fixing" it with a filter. ⛔ #8802 is a defect IN the enforcement, not in this ledger: it does not change this row\'s state, and closing it leaves the row exactly as owed. Predicate unit-pinned in core/security/grant-validity.test.ts (half-open boundaries, seconds-vs-ms epoch, fail-closed on garbage, camelCase spellings); the resolver halves in core/security/resolve-authz-context.test.ts "grant validity windows (ADR-0091 D1/D2)". Deliberately NOT in HIGH_RISK for the same reason as the `active` rows — that list marks primitives guarding object DATA through a sibling HTTP entry point, and this one guards grant DERIVATION. Unlike those rows an end-to-end proof already EXISTS and is merely unclaimed by this ledger: delegation-of-duty.dogfood.test.ts boots a real stack and asserts the delegate STOPS resolving the delegated sys_user_position at valid_until through the real resolveAuthzContext. It is not cited here because the #7976 mutual-attribution contract requires that file to name this row back (a `// authz-row:` header line), an edit outside this card\'s declared file surface — filed as a follow-up.' },
307+
288308
// ── Experimental — declared, NOT enforced (ADR-0049/0056 D8) ───────────
289309
{ id: 'field-encryption', summary: 'at-rest field encryption', state: 'experimental',
290310
note: 'no crypto provider reads the config; marked [EXPERIMENTAL] (D8). Deliberately KEPT (2026-07 D8 disposition): at-rest encryption is a real enterprise roadmap item with a stable schema shape — removing and re-adding would cost more (ADR-0087) than carrying it marked.' },

0 commit comments

Comments
 (0)