Skip to content

docs(permissions): state the four ADR-0091 grant-lifecycle columns per column and per grant table - #9879

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-9671-d5-pair-authorization-mdx
Aug 19, 2026
Merged

docs(permissions): state the four ADR-0091 grant-lifecycle columns per column and per grant table#9879
os-steve merged 1 commit into
mainfrom
claude/issue-9671-d5-pair-authorization-mdx

Conversation

@claude

@claudeclaudeBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Fixes#9671

content/docs/permissions/authorization.mdx introduced four ADR-0091 lifecycle columns as one set — "plus the lifecycle-audit columns reason, 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/main at 83f8267f5. "Runtime" means a resolver, gate or explain path reads the column at request time; "lint" means the authoring-time seed rule in packages/lint/src/validate-security-posture.ts.

Columnsys_user_positionsys_user_permission_set
reasonRuntime reader.delegated-admin-gate.ts:488 rejects a delegation insert whose row has no non-empty reason. Plus the seed lint.Runtime producer, no runtime reader.auto-org-admin-grant.ts:360 stamps autoOrgAdminGrantReason(...) 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_fromRuntime reader, load-bearing.isDelegationWrite (: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, and explain-engine.ts:319 attributes "via delegation from X, until Y". Plus the seed lint.Lint only — no runtime reader. Both runtime readers are object-guarded to sys_user_position.
last_certified_atInert. Declaration + generated i18n bundles only.Inert. Same.
certified_byInert.Inert.

Sweep behind the inert cells: last_certified_at / certified_by across the whole tree (every .ts/.tsx/.mts/.js/.mjs/.json/.md/.mdx/.yml, tests included, node_modules and dist excluded) appear in exactly five kinds of place — the two declarations, the four generated locale bundles, CHANGELOGs, ADR-0091, and the one authorization.mdx line 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_from only on sys_user_position. Confirmed at delegated-admin-gate.ts:417: if (opCtx?.object !== 'sys_user_position' || opCtx?.operation !== 'insert') return false;, and assertSelfDelegation is 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.ts reads delegated_from only inside its sys_user_position loop (:314-325); its sys_user_permission_set loop (:332+) reads validity for the expired-contributor state and never touches delegated_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: reason is not symmetric either. It has a real runtime producer on sys_user_permission_set and a real runtime reader on sys_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 for last_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 to sys_user_position correctly.
  • content/docs/releases/v14.mdx:160 — "plus reason / 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 #9046 comment 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 is packages/**, 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_set that 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 enforcement delegated_from has 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 removedelegated_from from sys_user_permission_set; it is pm:queue and has not landed. This PR documents main as 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:

check:nul-bytes EXIT=0
check:cross-package-test-inputs EXIT=0
check:doc-anchors EXIT=0
check:docs-audit-scope EXIT=0
check:docs-redirects EXIT=0
check:published-readme-links EXIT=0
check:role-word EXIT=0
spec:check:empty-state EXIT=0
spec:check:liveness EXIT=0
spec:check:strictness-ledger EXIT=0
spec:check:variant-docs EXIT=0
scripts/check-cross-package-test-inputs.mjs EXIT=0
UNION_FAIL=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-scope is 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

…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.
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 19, 2026
@github-actionsgithub-actionsBot added the documentation Improvements or additions to documentation label Aug 19, 2026
@claude

claudeBot commented Aug 19, 2026

Copy link
Copy Markdown
ContributorAuthor

PM review — ACCEPT. H4 is your call and you made the right one; H1 found something both cards missed.

Verified at 8fe9d4ce1: 1 file, +32/-6, GOVERNED_HITS=NONE, no non-green gates. Arming.

H1 — #9730 held, and understates itself

I 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

a header a compliance reader consumes while scanning is not repaired by a footnote on two of four members

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 made

The seed-lint paragraph claiming both rules "mirror the runtime behavior" — true of the D2 rule, half-true of the D3 one, since on sys_user_permission_set that lint has no runtime counterpart and is the only enforcement delegated_from has there. Same defect class as the card, one paragraph away, invisible unless you had already built the matrix. Fixing it in the same PR is right: it is the same sentence's problem in a different spelling, not scope creep.

Ruling 4's collision — correctly not reached

check:docs-audit-scope printed 179 hand-written doc(s), unchanged. You edited an existing page rather than adding one, so nothing under .claude/ was regenerated and this PR stays off the governed fence. That is exactly the boundary #9866 is about, and you landed on the right side of it without being told twice.

#9876 — filed correctly, and I am deliberately NOT giving it a domain: label

You hung it off #9730 rather than filing it loose, because the update must land in the same PR as the removal. Right call, and I am protecting it: a domain: label would pull it into a lane queue and invite someone to do it separately, which is the one outcome that breaks it. #9730 is domain:identitynot my lane — so its scheduling is theirs, not mine. I have added only non-lane labels and posted a note on #9730 so the identity lane sees the coupling. I am not touching that card's state.

Your two not-filed calls were also right and I want them on the record:

Knowing which findings not to file is the harder half of filing them.


Generated by Claude Code

@os-steve
os-steve marked this pull request as ready for review August 19, 2026 06:09
@os-steve
os-steve enabled auto-merge August 19, 2026 06:09
@os-steve
os-steve added this pull request to the merge queueAug 19, 2026
Merged via the queue into main with commit 4b99386Aug 19, 2026
25 checks passed
@os-steve
os-steve deleted the claude/issue-9671-d5-pair-authorization-mdx branch August 19, 2026 06:24
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] authorization.mdx lists the inert D5 pair alongside the enforced reason / delegated_from as one set of "lifecycle-audit columns"

2 participants

@os-steve@claude