Uh oh!
There was an error while loading. Please reload this page.
tooling(check-adr-anchors): verify a cited Dk names a real decision of the cited ADR - #12785
Merged
Merged
Conversation
… of the cited ADR Extends `scripts/check-adr-anchors.mjs` one layer below the number: for every `ADR-NNNN Dk` citation on the anchored surface (each anchor's `invariant` and each anchored file's text), `Dk` must appear in the cited record's decision index. The number layer already proved 0057 names one record; nothing read the `D10` half, which is the #8386 / #9255 failure family. The heading grammar was measured across `docs/adr/**` before the parser was written, and the check recognises exactly the four shapes that exist: `### Dk` headings, `**Dk —**` bold leads, `| Dk |` decision-table rows, and a `Dk` heading's own top-level list declaring `Dk.1`/`Dka` sub-decisions. That last rule is load-bearing: 20 live citations into ADR-0120 D5's lettered gates and ADR-0020 D3's numbered steps resolve only through it. "Cannot verify" is a third verdict and is printed, never folded into the OK line: 67 of 125 ADR numbers declare no machine-readable decision letters, and an unrecognised shape must not read the same as a clean one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69
This was referenced Aug 27, 2026
This was referenced Aug 27, 2026
os-zhuang
marked this pull request as ready for review
August 27, 2026 19:35
os-zhuang
enabled auto-merge
August 27, 2026 19:35
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 27, 2026
Merged
This was referenced Aug 28, 2026
Merged
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9592
Remedy 3 of the maintainer ruling on #9255 (2026-08-18). The number layer already proved that
ADR-0057names exactly one record; nothing ever read theD10half — the part that says which of that record's decisions the code is standing on. That is the #8386 / #9255 failure family, and every gate called it green.This adds the layer below the number: for every
ADR-NNNN Dkcitation on the anchored surface — each anchor'sinvariantinscripts/adr-anchors/, and the full text of each anchored file —Dkmust appear in the cited record's decision index.The grammar was measured before the parser was written
The judgement in this card is entirely in the heading grammar: too strict reddens correct citations, too loose passes anything. So
docs/adr/**was counted first (131 files, 125 numbers), and the check recognises exactly the four shapes that exist:### Dk — Titleheading (h3 dominant, h4/h5 for sub-decisions)**Dk — Title.**bold-lead paragraph| Dk | Title |first cell of a decision-table rowDkheading's own top-level list declares its sub-decisions: an ordered list givesDk.1,Dk.2, …; a lettered list givesDka,Dkb, …Rule 4 is load-bearing, not a convenience. Without it,
ADR-0120 D5a/D5b/D5c/D5eandADR-0020 D3.3— 20 live citations — read as bad letters. Both were red under the first, stricter parser draft, and both turned out to be correct citations: ADR-0120 spells its gates asa./b./c./d./e.under### D5, and ADR-0020 spells the introspection endpoint as item 3 under### D3. That is the "too strict" half of this failure, caught by measuring instead of by shipping.The rule is bounded in both directions: sub-items stop at the next heading of any level (so a list under
## Consequencesdeclares nothing), and a sub-number the list does not reach is still refused."Cannot verify" is a third verdict, and it is printed
67 of the 125 ADR numbers declare no machine-readable decision letters at all — the older records number their decisions topically (
### Schema) or ordinally (### 2. Commits are atomic), never asDk. 37 live citations point into six of them.Those are neither resolvable nor clean, and the run says the difference out loud rather than letting an unrecognised shape read like a pass:
Reported, never failed — for the same reason the ambiguous-number note is: the defect is the record's spelling, and
docs/adr/**is governed surface (#6741) that a script gate must not force. Those 37 are filed as a finding rather than baselined away.An ordinal→letter inference (
D2= the 2nd### N.heading, which would have made ADR-0067 verifiable) was weighed and rejected: the record never writesD2, so the mapping would be this gate's own invention, and a gate that manufactures the index it checks against cannot fail honestly.First-run reds: there are none, and that is a measurement
The card warned that first-run reds are their own findings, never a reason to weaken the check. Two candidates appeared under the first parser draft (
ADR-0020 D3.3,ADR-0120 D5b); both were parser artifacts, dissolved by reading the sub-decision grammar the records actually use. After that, 0 bad letters remain on the anchored surface. Nothing was baselined, no allowlist was added, and the predicate was not softened — the grammar was corrected to match the corpus.One citation is skipped by the pre-existing cross-repo rule and correctly so:
// [cloud ADR-0024 D5.2]inpackages/plugins/plugin-auth/src/auth-plugin.tsnames a sibling repo's registry.Two silent-failure modes this closes on the way
… (ADR-0106newline* D3) — …. A line-oriented scan misses every one of them silently. The citation regex follows the wrap.--self-testpins measured floors on the index (≥ 40 of 125 numbers), on the scan (> 500 citations read) and on the resolution count (> 500 actually resolved) — so green cannot mean "nothing was checked".Ablation — proved it can fail, end to end
Mutation and restore both proved on disk, against
HEAD(the implementation was committed first, so the restore leg has a real reference):The restore leg is proved by observed state (hash equality plus an empty
git diff HEAD), not by an exit code — acheckoutthat does nothing also exits 0.--self-testcarries the same ablation as an assertion (ablation-a-fabricated-letter-is-red) so it cannot rot.Scope
scripts/check-adr-anchors.mjs. No ADR was edited — the gate readsdocs/adr/**, which stays governed surface.ADR-NNNN Dkoccurrences across 1609 files and stays unchecked; that boundary is filed as a finding rather than widened here.skip-changeset: the diff is one rootscripts/gate. Nothing published changes.Verification
Gate union run on the final commit
8071dd26b, clean tree, exit codes captured before any pipe:Each gate's own printed verdict line, not a bare
$?— for example:✓ check-adr-anchors --self-test: 106 assertions over the real auditAdrDirectory() / auditCitedNumbers() / assembleAnchors() paths.(100 before this change)OK self-test: 46 live row(s), 39 unreachable as spelled, 39 recorded verdict(s) — none stale, none missing, none contradicted.(bare-root-worklist)✓ dispatch-gates self-test: 723 cases pass.(check:pm-dispatch-gates— a convention-triggered obligation the dispatched gate list did not name;node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstacksurfaced it for this diff)check-nul-bytes: OK (scanned 7118 text file(s) … no raw ASCII control bytes).Whole-repo
pnpm lint(eslint . --no-inline-config) also run on8071dd26b:LINT_EXIT=0, no findings. This is the full farm scan, not a narrowed one.Session: https://claude.ai/code/session_01PfaSTikked61BkcsB5Rn69
Generated by Claude Code