Uh oh!
There was an error while loading. Please reload this page.
docs(permissions): state the four ADR-0091 grant-lifecycle columns per column and per grant table - #9879
Conversation
…mn and per grant table (#9671) `authorization.mdx` introduced `reason`, `delegated_from`, `last_certified_at` and `certified_by` as one set of "lifecycle-audit columns". Measured on main they are not one kind of thing, and the axes are two, not one — the column AND the grant table it sits on: - `reason` — runtime-enforced on `sys_user_position` (the D3 delegation gate rejects a delegation insert without one); on `sys_user_permission_set` it is written by the platform (the auto-derived org-admin grant stamps provenance) and read by nothing. - `delegated_from` — load-bearing on `sys_user_position`: it is what makes a write a delegation, and both of its runtime readers, the gate's delegation branch and the explain engine's delegation attribution, are guarded on that object. On `sys_user_permission_set` it has the seed-authoring lint only and no runtime reader. - `last_certified_at` / `certified_by` — inert on both tables: no producer, no consumer, the ADR-0091 D5 substrate and nothing more. The category name goes rather than gaining a caveat: a header that already tells a SOX / ISO 27001 reader these are four audit columns the platform maintains is not repaired by a per-member footnote. The list becomes a small per-object table, and the D5 pair gets the wording that landed on the field declarations themselves so metadata and docs say the inertness in one voice. Also corrected in the same section: the two seed lint rules were described as mirroring runtime behaviour, which is true of the D2 rule but only half true of the D3 one — on `sys_user_permission_set` that lint has no runtime counterpart, and is the only enforcement `delegated_from` has there. Docs-prose only. No behaviour, ADR, or metadata change.
PM review — ACCEPT. H4 is your call and you made the right one; H1 found something both cards missed.Verified at H1 — #9730 held, and understates itselfI asked you to verify #9730 rather than assume it. You did, and found it too weak:
A column that is written and never read is not "enforced" in any sense a compliance reader would recognise, and it took the 4×2 matrix to see it. This is the whole justification for making you build the matrix before writing a word: the card's proposed 2-way split, and triage's per-object correction of it, were both wrong — just less wrong each time. H4 — the category name goes. Agreed, and your reasoning is the durable part
That is the correct test, and it generalises past this page. The matrix settles it as fact rather than taste: five distinct cells over two axes, and even the card's "enforced" pair disagrees with itself per table. A label asserting four columns are one kind of thing cannot be rescued by annotating its members — there is no true sentence with that header still on it. I left H4 to you deliberately. This is what I wanted from it. The second edit you were not asked for, and should have madeThe seed-lint paragraph claiming both rules "mirror the runtime behavior" — true of the D2 rule, half-true of the D3 one, since on Ruling 4's collision — correctly not reached
#9876 — filed correctly, and I am deliberately NOT giving it a |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9671
content/docs/permissions/authorization.mdxintroduced four ADR-0091 lifecycle columns as one set — "plus the lifecycle-audit columnsreason,delegated_from,last_certified_at/certified_by" — over a sentence that covers both grant tables. Docs-prose only: no behaviour, ADR, metadata or gate change.H1 — the measured matrix (built from source before a word of prose)
Every cell measured on
origin/mainat83f8267f5. "Runtime" means a resolver, gate or explain path reads the column at request time; "lint" means the authoring-time seed rule inpackages/lint/src/validate-security-posture.ts.sys_user_positionsys_user_permission_setreasondelegated-admin-gate.ts:488rejects a delegation insert whose row has no non-emptyreason. Plus the seed lint.auto-org-admin-grant.ts:360stampsautoOrgAdminGrantReason(...)on the auto-derived org-admin row; nothing reads the value back as a decision (the only readers of the marker prefix are tests). Plus the seed lint.delegated_fromisDelegationWrite(:417-418) dispatches on it,assertSelfDelegation(:459-465) requires it to equal the writer,activeHoldings(:576) derives the direct-hold / chain-cut rule from it, andexplain-engine.ts:319attributes "via delegation from X, until Y". Plus the seed lint.sys_user_position.last_certified_atcertified_bySweep behind the inert cells:
last_certified_at/certified_byacross the whole tree (every.ts/.tsx/.mts/.js/.mjs/.json/.md/.mdx/.yml, tests included,node_modulesanddistexcluded) appear in exactly five kinds of place — the two declarations, the four generated locale bundles, CHANGELOGs, ADR-0091, and the oneauthorization.mdxline this PR rewrites. No producer, no consumer.Did #9730's per-object claim hold? Yes — and it understates the case
#9730 says the runtime delegation gate reads
delegated_fromonly onsys_user_position. Confirmed atdelegated-admin-gate.ts:417:if (opCtx?.object !== 'sys_user_position' || opCtx?.operation !== 'insert') return false;, andassertSelfDelegationis reachable only through that branch.Measuring further found the second reader the card cited is guarded the same way, which #9730 did not state:
explain-engine.tsreadsdelegated_fromonly inside itssys_user_positionloop (:314-325); itssys_user_permission_setloop (:332+) reads validity for the expired-contributor state and never touchesdelegated_from. So the original card's "read by the delegated-admin gate, and by the explain engine for attribution" is object-blind on both halves, not one.The reverse also holds and is not in either card:
reasonis not symmetric either. It has a real runtime producer onsys_user_permission_setand a real runtime reader onsys_user_position— different tables, different directions. A two-way "enforced vs inert" split would have mis-stated this column on both tables.H4 — the call: the category name goes
I removed "lifecycle-audit columns" as a label rather than annotating its members.
A category name is an assertion, and it is the part a scanning reader consumes. The card's own diagnosis is that the cost lands on a SOX / ISO 27001 access reviewer skimming the list — and a header that has already told them "these are four columns by which the platform audits grant lifecycle" is not repaired by a footnote on two of the four. The label has to stop being said.
The matrix also shows the label is unrescuable, not merely imprecise: the four columns do not fall into two groups, they fall into five distinct cells across two axes, and even the pair the card called "enforced" disagrees with itself per table. Any name for the set encodes a grouping that measurement contradicts.
What stays is ADR-0091 D1 as the declaration fact — the four columns really are declared together on both tables, and a reader needs that to find the decision. What goes is the ADR's grouping vocabulary being repeated in a page about what holds today. Replacement is a small per-column, per-object table plus a paragraph on the D5 pair; that is longer than the clause it replaces, deliberately — a clause shorter than the truth is how the page got here.
H2 — the sweep: one page, and the result is "no siblings"
content/docs/**grep forlast_certified_at/certified_by/delegated_from:content/docs/permissions/authorization.mdx— lines 299 (the defect), 312, 319, 327. The last three are inside the D3 delegation prose, which already scopes itself tosys_user_positioncorrectly.content/docs/releases/v14.mdx:160— "plusreason/delegated_from/ certification columns". Release-owned and never edited in a code PR, and as a record of what v14 shipped it is accurate: it lists columns added, not audit columns maintained. Left alone; not filed.The phrase "lifecycle-audit" itself occurs twice repo-wide: the line fixed here, and ADR-0091 D1 (unchanged, governed surface, and correct there — an ADR describing what it declares). So this is a one-page fix, and no sibling page repeats the conflation.
H3 — #9669's vocabulary reused, and where it does not reach
#9669 landed this on both declarations: "Reserved for a future access-recertification workflow… Inert today: no platform code writes this column and none reads it… Null therefore means the workflow does not exist, not that this grant went unreviewed." Good wording, and reused nearly verbatim so metadata and docs state the inertness in one voice rather than two.
It does not reach the sibling columns, and there it has the very drift H3 predicted. On
sys_user_permission_set,delegated_from's description still says "A row with delegated_from set is not itself delegatable and not self-renewable" — an enforcement claim no runtime code makes on that object — and the#9046comment block on the same file says "reason/delegated_from are read by the delegated-admin gate and the security-posture lint" with no object qualifier. That ispackages/**, out of scope here, and it dissolves anyway: see the coupling below.Second edit in the same section
The paragraph below the list said two seed lint rules "mirror the runtime behavior". True of the D2 rule; only half true of the D3 one, since on
sys_user_permission_setthat lint has no runtime counterpart to mirror. Rewritten to say what each rule mirrors, and that the D3 rule runs on both tables — making it the only enforcementdelegated_fromhas on the permission-set table. Same file, same section, same defect class (object-blind enforcement claims), so it is inside this card rather than a new one.Coupling a reviewer should know about
#9730 carries a maintainer ruling (2026-08-18) to remove
delegated_fromfromsys_user_permission_set; it ispm:queueand has not landed. This PR documentsmainas it stands today, which is correct now and stale the day that removal lands. Rather than leave that to a later audit round, the docs follow-through is filed as #9876, a sub-issue of #9730, naming the exact table row and paragraph to update in the retirement PR. #9730 is not addressed here and remains open.Gates
Full derived union re-run after the final commit, at
8fe9d4ce1— 12/12 exit 0:The family was re-derived from the real diff (
node scripts/pm/dispatch-gates.mjs content/docs/permissions/authorization.mdx) and matched the dispatched list exactly — nothing new to add.check:docs-audit-scopeis green and reports "docs-accuracy-audit scope is in sync with content/docs/: 179 hand-written doc(s)": this edits an existing page rather than adding one, so the #9866 collision is not reached and nothing under.claude/was regenerated.No changeset, per the card's scope ruling: prose-only under
content/docs/**, nothing published changes. No gate asked for one.Generated by Claude Code