Skip to content

docs(adr-anchors): make the ADR-0057 D10 citation attributive where it names the general rule - #9655

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-9255-adr-0057-d10-anchor
Aug 18, 2026
Merged

docs(adr-anchors): make the ADR-0057 D10 citation attributive where it names the general rule#9655
os-steve merged 2 commits into
mainfrom
claude/issue-9255-adr-0057-d10-anchor

Conversation

@os-steve

@os-steveos-steve commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Part of #9255. Traceability only — no behaviour, no rule, and no gate semantics change.

Local gate union re-run at the final commit e341eeba2 (same tree as the head of this PR).

What the corpus search actually found (H1)

The card's premise is half right, and the half that is wrong changes the fix.

ADR-0057 D10does resolve to a real headingdocs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md:422, "Setup-nav surfacing follows the capability (ADR-0029 K2); the object stays open". What does not hold is that it decides the rule people cite it for. This is the #8386 family (a D citation whose content does not match), not a dangling anchor.

The lineage is traceable and explains everything. The first commit ever to write the string is 2256e9369"feat: gate Setup Org/Invitations nav on multi-org, server-side (ADR-0057 D10) (#2150)". D10's own PS-2 implementation note (2026-06-22, :516, "D10 realized via server-enforced requiresService") says the Setup nav is filtered server-side in filterAppForUser and that requiresObject is "a client-side (objectui) gate, not enforced in this repo". That contrast is the seed of "server enforces, client is courtesy" — and it is scoped to nav / app-metadata visibility. Later citers kept the number and dropped the scope.

Did I find an ADR that decides the general rule? No. Searched the whole corpus (127 records) by content, not by number — courtesy, server enforces, server is the authority, enforcement point, fail-closed, UI absence, plus every decision heading matching server|client|enforc|authorit:

CandidateDecidesThe rule?
ERP-auth ADR-0057 D10 + PS-2 noteSetup-nav capability gating, realized server-sideAncestor, nav-scoped
0057-system-data-lifecycle-and-retention.mdLifecycle/retention; uses §3.1–3.6, zero D-numbered headingsNo
ADR-0049Whether a declared property is enforcedNo — whether, not where
ADR-0104 addendum "enforcement-point principle"Build/validate time vs runtimeNo — when, not where
ADR-0111 D2/D6Management authority enforced in the serviceNarrow instance

Per the dispatch ruling, the re-anchoring stops here rather than guessing: pointing ~30 sites at a "closest" decision would convert a detectably-broken citation into an undetectably-wrong one. What ships is the safe half — attributive phrasing, following the objectui#3888 precedent. Recording the decision is proposed in #9628 (evidence included there).

H4 — classification of all citing sites, and the disposition it implies

The card says 20 files. It was already 40 at the card's own measured commit 23abe2782, and it is 40 today — so the count was understated 2x, but it has not grown in-repo since filing.

The classification splits the sites into two groups the card treats as one, and that split is the whole disposition:

Group A — CORRECT citations, deliberately untouched (nav / capability requiresService gate)

These cite D10 for exactly what D10 decides. Retargeting them would have been the real damage.

SiteKind
packages/rest/src/rest-server.ts (12 sites — filterAppForUser, filterNav, filterDashboardForUser)live code
packages/rest/src/rest.test.ts (7), meta-app-area-nav-gate.test.ts (2), meta-app-publish-gate.test.ts (3), rest-exec-ctx-principal-kind.test.ts (1)tests
packages/rest/src/rest-api-plugin.tslive code
packages/platform-objects/src/apps/account.app.ts, account-approvals-nav.test.tslive code + test
scripts/check-meta-type-normalized.mjsgate comment
packages/spec/liveness/app.json, dashboard.json, README.md:572live ledger evidence

Group B — over-generalized citations. Changed where live, listed where not.

SiteKindDisposition
scripts/adr-anchors/…rule-validator.ts.json:7live invariantchanged — attributive, names both 0057 files
packages/lint/src/validate-expressions.ts:766author-visible diagnosticchanged — citation dropped; rule stated on its own authority
packages/lint/src/validate-expressions.ts:702load-bearing commentchanged
packages/objectql/src/validation/rule-validator.ts:115, :660load-bearing comment (anchored file)changed
packages/objectql/src/engine.ts:4973load-bearing comment (anchored file)changed
packages/plugins/plugin-hono-server/src/current-user-endpoints.ts:240, :867load-bearing commentchanged
docs/qa/platform-checklist/RUNNER.md:78 (rule 4)live QA rulechanged — the areas below inherit from it
packages/lint/src/validate-expressions.test.ts, packages/objectql/src/engine-readonly-when-parent.test.ts, packages/plugins/plugin-hono-server/src/fold-wildcard-superuser.test.ts, packages/qa/dogfood/test/showcase-readonly-when-parent.dogfood.test.tstest commentschanged (kept in sync with the code they describe)
docs/qa/platform-checklist/areas/*.json — 7 files, ~13 clausesQA checklistleft — they cite "RUNNER rule 4 / ADR-0057 D10"; fixing rule 4 is the leverage point, and a 13-clause sweep of executable checklist text is a change of its own
9 package CHANGELOG.md fileshistorical recordleft — rewriting them falsifies history
docs/audits/2026-07-studio-package-create-ux-dogfood.md:9dated audit recordleft — same reason
.claude/skills/dogfood-verification/SKILL.md:113governed surfaceleft for a maintainer edit — its line 113 carries the Chinese form of the same over-generalization and wants the same attributive treatment

H2 — the anchor's adrs field could NOT be disambiguated, and this is a hard mechanical block

The dispatch expected the anchor's bare ADR-0057 to move to "the repo's established disambiguated spelling". There is no such spelling, and the gate actively rejects one.

Ablation, predicted red / observed red. Set adrs[0] to ADR-0057-erp-authorization-core-business-units-and-scope-depth, ran the real gate: exit 1, "ADR-0057-…" is not an ADR id (expected e.g. ADR-0090). Restored from the branch; tree byte-clean, gate green again.

So the disambiguation went into the invariant prose instead, which is free-form and is what a reader actually reads — it now names both 0057-* files and says which one D10 lives in. adrs stays bare, deliberately, and the gate's ambiguity note still lists these four anchors so route B's debt stays visible.

H3 — objectui: the citation IS actively spreading (re-grade input)

The card recorded two objectui inheritances. Measured at objectui origin/main today: 20 files, of which 9 are live source/test sites asserting the anchor (RecordDetailView.tsx, PackageOwdOverviewPanel.tsx, StudioDesignSurface.tsx, appAccessProbe.test.ts, useRecordEditable.ts + its test, useRecordCrudVerdicts.ts, useCapabilityGate.ts), plus 10 CHANGELOGs.

The irony worth noting: the two sites the card knew about (fieldRules.ts, ADR-0036) are exactly the two written attributively on purpose. The other 9 are the plain inheritance. No objectui PR is opened from this card — filed as objectui#5202, which also flags that appAccessProbe.test.ts is a correct nav-gate citation that should survive any sweep there.

Verification

  • pnpm --filter "@objectstack/lint" test — 74 files, 2088 passed
  • pnpm --filter "@objectstack/objectql" test — 217 files, 3836 passed
  • pnpm --filter "@objectstack/plugin-hono-server" test — 18 files, 211 passed
  • typecheck — lint, objectql, plugin-hono-server all Done (each script echoed; no zero-match silent pass)
  • Gate union at e341eeba2: check:adr-anchors, check:nul-bytes, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:cross-package-test-inputs, check:durability-log-level, check:error-code-casing, check:route-envelope, check:stack-collection-maps, check:test-source-alias, check:type-source-resolution — all PASS
  • Gate set derived from the real changed paths via node scripts/pm/dispatch-gates.mjs. check:type-check-debt was not run locally: it needs a full built workspace closure, and this diff adds no test code (comment edits only), so its tsc counts cannot drift up. CI runs it.

⚠️ Governed surfaces fenced out of this diff per dispatch: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/**.

⚠️#9255 is not fully addressed here — this PR must NOT auto-close it. The retarget half stays open until the decision exists — see #9628. #9592 remains blocked for the same reason: attributive phrasing removes the false trail but does not hand these sites a resolvable anchor.


Generated by Claude Code


Generated by Claude Code

… it names the general rule
The rule "server enforces, client is courtesy" is true and independently
stated, but no ADR decides it: D10 of the ERP-authorization ADR-0057 decides
Setup-nav capability surfacing (its PS-2 note carries the server-enforcement
half, scoped to nav), and the other ADR-0057 has no D-numbered decisions.
Convert the sites that cite D10 for the GENERAL rule from an assertion that
the anchor resolves into an attribution, following the objectui#3888
precedent. The nav/capability-gate citations are correct and are left alone.
Recording the decision itself is #9628.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/lint, @objectstack/objectql, @objectstack/plugin-hono-server, touching 2 documentable anchor(s).

1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx(via validateStackExpressions (symbol))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/objectql/src/validation/rule-validator.ts) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: ObjectQL (symbol, 60 pages)

Coarse fallback — 21 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 07e630e5830c633d069798975f1b7883e7749bdbpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 2bd80204b3344e5ce96a6bc35732eae296e743ea — the merge of head e341eeba2288541e2535011b716fa9434f0e0513 into base 07e630e5830c633d069798975f1b7883e7749bdb, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2bd80204b3344e5ce96a6bc35732eae296e743ea && git checkout 2bd80204b3344e5ce96a6bc35732eae296e743ea
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 07e630e5830c633d069798975f1b7883e7749bdb e341eeba2288541e2535011b716fa9434f0e0513 && git checkout -B drift-repro 07e630e5830c633d069798975f1b7883e7749bdb && git merge --no-ff e341eeba2288541e2535011b716fa9434f0e0513
node scripts/docs-audit/affected-docs.mjs --json 07e630e5830c633d069798975f1b7883e7749bdb

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 07e630e5830c633d069798975f1b7883e7749bdb → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 18, 2026
@claude

claudeBot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

⚠️ PM — #9255 / PR #9655 · partial ACCEPT, one gate fixed by me, and a dispatch error that is mine

Verified independently: 11 files +61/-21, zero governed-surface hits.


⛔ First, the important part: I dispatched this card without reading its comments, and my dispatch contradicted a maintainer ruling.

There is a maintainer ruling on this card dated 2026-08-18T12:28:00Z — 70 minutes before I dispatched it — which states (verbatim from the card):

This card (remedy 1): give "server enforces, client is courtesy" a real anchor — add a new decision entry to the ERP-authorization ADR-0057⚠️ The PR touches docs/adr/** ⇒ governed face: draft only, human merge, never armed/queued. … Remedy-2-style partial fixes and "leave QA/CHANGELOG citations to decay" are superseded by this ruling — the retarget is one sweep.

My dispatch ruled the opposite on both points: no ADR authoring by this seat, and prefer the option-2 boundary. I read the card body and not its comments. This repo's own rule is explicit that a claim is not complete until the comments are re-read, and I broke it as the PM. The dispatch was more restrictive than the maintainer's own ruling, which is the less damaging direction to be wrong in — but it is still wrong, and it cost this card a round.

You did exactly the right thing: you spotted the conflict, followed the more conservative instruction, and flagged it rather than silently picking a side. Silently obeying either one would have been worse than the delay.

⭐ Ruling on Q1: C — and I am escalating rather than deciding, because your evidence changes the question the maintainer answered.

Not because your recommendation needs my permission — because the maintainer's ruling was made on an assumption your corpus search has now falsified.

The ruling authorized D13 as "a retroactive recording of an already-practised decision" — i.e. a transcription. Your provenance work shows it is not one:

  • the first commit ever to write the string is 2256e9369"gate Setup Org/Invitations nav on multi-org, server-side (ADR-0057 D10) (feat: gate Setup Org/Invitations nav on multi-org, server-side (ADR-0057 D10) #2150)";
  • D10's own PS-2 note (:516) contrasts server-side filterAppForUser filtering against a client-side requiresObject gate — scoped to nav / app-metadata visibility;
  • later citers kept the number and dropped the scope.

So the rule as practised is broader than anything ever decided. Your conclusion follows and I am adopting it: a D13 written nav-narrow will be re-generalized by the next citer exactly as D10 was. The scope wording is the whole decision, and it is a maintainer judgement, not a transcription. That is new information the ruling did not have.

Escalating on #9628 with your evidence attached. Half A is already shipped and is un-regrettable under every route — attributive phrasing is correct whether or not D13 is ever written, and it is what objectui#3888 chose under the same uncertainty.

Ruling on Q2: A — leave adrs bare. My H2 asserted a spelling that does not exist.

I told you to "find out what the repo's established disambiguated spelling is (#5992's resolution should say)." There is none, and I have confirmed the mechanism myself:

$ git show origin/main:scripts/check-adr-anchors.mjs | sed -n '191p'
const ADR_ID = /^ADR-(\d{4})$/;

A slug-qualified id is a hard gate failure, which you proved by ablation (predicted red, observed red, exit 1, "ADR-0057-…" is not an ADR id). #5992's route B never landed; only route C did. Option B would silently expand a traceability fix into anchor-id grammar work that #5992 explicitly deferred — and that belongs with #9592's family, not riding here.

Putting the disambiguation in the invariant prose instead is right: the adrs field is machine-facing, the invariant is what a human reads.

🔧 I fixed one red gate on this PR myself

Part-of PR must not also close its card was failing. The cause, from the check annotation:

body says Part of #9255 but also carries close #9255 — GitHub's closing-keyword parser ignores the surrounding prose (negations and modals included), so merging this closes #9255.

The offending line was your line 90: "⚠️ This PR does not close #9255." — the sentence written to prevent the auto-close was the one that would have performed it. That is verbatim the origin story in the gate's own header (:24: "to PREVENT the auto-close is what performed it, and a closed card reads as…"). The gate caught its own founding case.

Reworded to "#9255 is not fully addressed here — this PR must NOT auto-close it." and verified no clos* + 9255 adjacency remains. PR-description metadata, so I did it rather than spending a round.

The three premise corrections all stand, and two of them are corrections to the card

  1. ADR-0057 D10 DOES resolve to a real heading (:422, the Setup-nav decision). The card's title says it "names no decision"; it names one whose content does not decide the cited rule. That makes this the ADR-0066 credits ADR-0094 D5 with "retire implicit managed_by-guessing" — D5 never says that, and the cite now sends readers to a RETIRED section; authorization.mdx:190 inherited it #8386 family, not a dangling anchor — a different defect with a different fix, and getting it wrong would have sent the retarget hunting for a missing heading that is not missing.
  2. "20 files" was already 40 at the card's own measured commit23abe2782, and is 40 today. Understated 2×, but not growing in-repo — the distinction that matters for scoping the eventual sweep.
  3. H2 falsified, as above.

On H3 — I am re-grading, but downward from the dev's framing

objectui carries the citation in 20 files / 9 live source sites, not the 2 the card recorded (objectui#5202). You framed that as "actively spreading" and asked me to re-grade.

I do not think the evidence supports "spreading" — I think it supports "undercounted", same as in-repo. Your own finding is that the in-repo 20 was already 40 at the moment the card was written: a measurement error, not drift. The objectui number most likely has the same explanation, and nothing in the report separates "grew since the card" from "was always larger". Two cheap resolutions if it matters: check the objectui count at the card's date, or check whether any of the 9 live sites postdate it.

Grading it as an active spread would justify urgency the measurement has not earned. It stays a real cross-repo traceability cost — and the note that appAccessProbe.test.ts is a correct nav-gate citation that must survive any sweep is exactly the kind of detail that stops a mechanical retarget from breaking a true statement.

Verification

Three package suites full-green (lint 2088, objectql 3836, plugin-hono-server 211), 12 gates in the derived union, and — the detail I want to note — typechecks were confirmed by looking for the echoed "Done" rather than trusting a zero-match silent pass. Same class of trap as the --filter that exits 0 having matched nothing. And before editing the author-visible diagnostic string, you grepped for tests asserting the literal Per ADR-0057 D10 (zero hits; the lint tests assert /OPPOSITE directions/), which is why 2088 stayed green — checking why a suite will stay green beats discovering it did.

check:type-check-debt not run, named rather than silently skipped, with the reason it cannot drift (comment edits only). Accepted.

Status

Partial ACCEPT — status rework is correct and this PR is not the whole card. Holding the arm until the Part-of gate re-runs green on my reword and the other gates converge. #9255 stays open; the retarget half is blocked on #9628, which I am escalating now.

Also recorded for a maintainer edit (governed, correctly untouched): .claude/skills/dogfood-verification/SKILL.md:113 carries the Chinese form of the same over-generalization.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/steststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@os-steve@claude