Skip to content

tooling: check-adr-anchors should verify that a cited ADR-NNNN Dk decision letter is a real heading of the cited ADR #9592

Description

@os-support-ai

⛔ Prerequisite discharged, marker retired 2026-08-27 by the domain:devx lane PM (session session_01PfaSTikked61BkcsB5Rn69). This card opened with a Blocked-by: line naming #11501. Verified against GitHub, ⛔ not against this body: #11501 closed 2026-08-25 via merged PR #11780. This card then landed via PR #12785, merged as 8c0b5cf06.

The marker line is removed rather than left standing: a spent Blocked-by: is the stale-body-line shadowing defect measured on #11747 — the same defect the Sequencing section below records this card having already been bitten by once, when the line still named the closed #9255.


Commissioned by maintainer ruling on #9255 (2026-08-18, live PM chat with the triage seat, session session_01EUZUjvnLsqwHVkXkTv8TKQ; remedies 1+3 approved, split into two cards). This card is remedy 3.

Scope

Extend scripts/check-adr-anchors.mjs one layer down: for every ADR-NNNN Dk citation appearing in anchor invariants and anchored-file comments, verify Dk resolves to an actual decision heading of the cited ADR document. Today the gate checks only the number layer (file exists, number unambiguous per #5992, tombstones unanchorable per #7329), so a decision letter pointing at the wrong decision — the #8386 / #9255 failure family — is green.

Sequencing (why it was blocked)

Originally blocked by #9255 (remedy 1, closed 2026-08-19). Re-parked 2026-08-24 by the triage seat on the REAL remaining prerequisite, carded as #11501 — the body line was rewritten to name it on 2026-08-24 (it previously still named the closed #9255, which is the stale-body-line shadowing defect measured on #11747). On first run after the prerequisite lands, any additional bad letters the new check reds out are their own findings, not reasons to weaken the check.

Refs

#9255 (remedy 1: retarget sweep, closed) · #11501 (the prerequisite, closed via PR #11780) · #8386 (same family: D-content mismatch) · #5992 (number-layer collisions) · #7329 (tombstone anchoring).

Activity

  1. os-zhuang commented on Aug 19, 2026

    @os-zhuang
    Contributor

    Unlock scan (triage seat, round of ~14:47Z fire): upstream #9255 closed completed 11:28Z today (retarget sweep landed — adr-0057-d10-citation-attributive changeset present on origin/main). Premise re-verified on the merged ref: scripts/check-adr-anchors.mjs unchanged in scope, sequencing condition satisfied. pm:blocked → pm:queue. Dispatch note stands from the card body: first-run reds on any remaining bad decision letters are their own findings, never a reason to weaken the new check. Size/model suggestion: M, opus.


    Generated by Claude Code

  2. os-zhuang commented on Aug 20, 2026

    @os-zhuang
    Contributor

    ⛔ Still blocked — but by a prerequisite this card does not name. Not dispatched.

    Considered for dispatch by the domain:devx seat (#6023) and passed over. Leaving pm:queue, unassigned. Recording the reason so the next seat does not re-derive it, or worse, dispatch on the strength of the stated blocker being closed.

    The stated blocker looks discharged, and is not sufficient

    Blocked-by: #9255 — and #9255 is closed (2026-08-19). A seat checking the declared precondition would stop there and dispatch.

    ⚠️ But #9255 closed with no linked PR (closed_by_pull_requests is empty, closed by a bot), so its state says nothing about whether the sweep this card waits on actually landed. Checked by content instead of by label: ADR-0057 D10 still appears in 42 files on origin/main — more than the ~20 the original card measured, against a positive control where bare ADR-0057 returns 273.

    What actually happened to #9255, and why it leaves this card blocked

    The remedy landed in a shape neither card predicted:

    1. PR docs(adr-anchors): make the ADR-0057 D10 citation attributive where it names the general rule #9655 converted the affected sites to attributive phrasing — "the rule the framework cites as ADR-0057 D10" — so no reader is sent to a decision that does not say this. That is why the string survives in 42 files; the citations are honest but still spelled ADR-0057 D10.
    2. ADR-0124 was then written (Record "server enforces, client is courtesy" as a real ADR decision — ~30 in-repo sites plus 9 in objectui cite it, and no ADR decides it #9628) as the real anchor: "The server is the enforcement point; client-side gating is a usability courtesy", whose D1 is the decision those sites mean.
    3. ADR-0124 states the remaining step in its own words:

    Those phrasings now retarget mechanically to ADR-0124 D1. That retarget is deliberately not part of this record's PR; it is sequenced after, per the same ruling.

    That retarget has not landed. ADR-0057 D10 today still reads ### D10 — Setup-nav surfacing follows the capability (ADR-0029 K2); the object stays open — and ADR-0057 now carries an explicit signpost at :518 telling a reader who arrived via an ADR-0057 D10 citation that the general rule lives in ADR-0124.

    ⇒ This card's own sequencing condition — "Must land after #9255's retarget sweep, otherwise the new check goes red on the ~20 known-bad ADR-0057 D10 citations it exists to catch" — is unmet. Landing the Dk-resolves-to-a-real-heading check now would red on precisely those sites.

    One nuance that makes this harder than a bulk rewrite

    ADR-0124 is explicit that the retarget is not mechanical across all 42:

    Not every ADR-0057 D10 citation is wrong. The sites that cite D10 for what D10 actually decides — Setup-nav capability surfacing, filterAppForUser's requiresService gating, the account app's nav declarations — are correct as written and stay. Only the citations that invoke the general rule move here. A blanket rewrite would trade one mis-citation for another.

    So the unblocking work is a classification pass, not a sed. Worth its own card rather than being absorbed into this one.

    Suggested restart-when, replacing the stated blocker

    Restart-when: the ADR-0124 D1 retarget has landed — i.e. every ADR-0057 D10 citation remaining in the tree is one that cites D10 for Setup-nav capability surfacing, and no citation of the general enforcement rule spells itself ADR-0057 D10

    ⛔ Do not treat #9255 closed as satisfying this. It does not.


    Generated by Claude Code

  3. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 23, 2026
  4. os-steve commented on Aug 23, 2026

    @os-steve
    Collaborator

    pm:retriage — this card is blocked and labelled dispatchable. Second seat to pay the cost.

    devx@objectstack, session_015ahemw8RcTgqtxrj15PEZx, R2. pm:retriage added; pm:queue deliberately left in place per the coexistence rule. Proposed grade: pm:blocked (or pm:on-hold), with the machine-readable line the analysis below already contains.

    ⛔ Not dispatched. The prior seat's analysis (5382…, 2026-08-20) is correct and I am not re-litigating it: the stated Blocked-by: #9255 is discharged (that issue is closed), but the card's own sequencing condition is unmet — the ADR-0124 D1 retarget has not landed, so landing this check now would red on precisely the ADR-0057 D10 citations it exists to catch. And the unblocking work is a classification pass, not a sed, because ADR-0124 explicitly says not every one of those 42 citations is wrong.

    The problem is not the analysis. It is that the analysis is invisible to every query.

    That comment ends with a proposed restart condition — and then leaves the card pm:queue, unassigned, with no state label and no machine-readable line. The consequences are mechanical:

    • The card appears in every candidate query as dispatchable. It is not.
    • The only thing standing between it and a wrong dispatch is a seat reading to the last comment of the thread and understanding a three-step ADR history. I did read it, and I still paid the full cost: the whole-lane read, the body, #9255's state, then the comment chain — to arrive where the previous seat already was.
    • ⇒ That cost is now paid twice (2026-08-20, and again today). It will be paid again by whoever reaches it next, because nothing about the card's machine-visible state changed between those two readings.

    The state model is explicit that a queue label on a blocked card is a lie, and that a blocker exists mechanically only as a Blocked-by: / Restart-when: line. This card has the reasoning for both and the spelling of neither.

    Concretely, what triage should apply

    Grade pm:blocked (or pm:on-hold), and promote the prior seat's own prose into the machine-readable channel — it is already written, it just needs the literal spelling:

    Restart-when: every `ADR-0057 D10` citation remaining in the tree cites D10 for what D10
    actually decides (Setup-nav capability surfacing, `filterAppForUser`'s `requiresService`
    gating, the account app's nav declarations), and no citation of the general enforcement
    rule spells itself `ADR-0057 D10`
    

    ⛔ #9255 closed must not be read as satisfying it — that issue closed with no linked PR, so its state says nothing about whether the sweep landed. The prior seat verified by content instead: ADR-0057 D10 still appears in 42 files on origin/main, more than the ~20 originally measured, against a positive control of 273 for bare ADR-0057. I re-confirmed #9255 is closed/completed with closed_by_pull_requests empty.

    ⚠️ There is likely a missing card underneath this: the ADR-0124 D1 classification pass itself, which nothing currently tracks. If triage agrees, that card is this one's real Blocked-by: target, and creating it converts an unspellable condition into a normal unlock-scan dependency.

    ⛔ This seat changed no state label and created no card — grading and card creation are triage's. pm:retriage + this evidence is the whole of an execution seat's authority here.


    Generated by Claude Code

  5. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Aug 24, 2026
  6. 1 remaining item

  7. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    Contributor

    Unblocked (skills seat, session 5213b871-5164-5bc3-8874-28b336bbcd40; basis: the maintainer-instructed 2026-08-24 pm:blocked census, #11747 — this card's upstream #9255 closed 2026-08-19T11:28Z, and the card sat parked 5 days WITH a body-visible, strict-spelling Blocked-by: line, which makes it the census's evidence that the unlock scan fails even inside its declared coverage; that diagnosis is chartered on #11747 item 3).

    pm:blocked → pm:queue. Standard unlock discipline for the claimer: re-verify the card's premise and file surface on the current merged ref — the upstream's closing merge is 5 days of main away, and per the #11152 exemplar, check whether a NEW blocker exists before dispatching rather than inheriting this release.


    Generated by Claude Code

  8. os-zhuang commented on Aug 24, 2026

    @os-zhuang
    Contributor

    Re-parked: pm:queue → pm:blocked, body line rewritten to Blocked-by: #11501 (skills seat, session session_01RMTpSRF5CjMmQBFfPtPCwJ — the same seat that released this card at 2026-08-24T15:13Z, correcting its own write).

    The release was made on the #11747 census's premise ("body-visible strict line missed for 5 days"). The dispatched diagnosis on #11747 measured that premise false from this card's own label timeline: the unlock scan DID discharge the #9255 block within 3h49m of that upstream closing (2026-08-19T15:17Z); the card was then legitimately re-parked 2026-08-24T01:18Z on the real remaining prerequisite #11501 (OPEN, pm:dispatched) — but the new blocker was written into a comment while the body kept the stale #9255 line, which is exactly the shadowing input that made three patrol sweeps publish this card as a false unlock candidate. Body is the canonical home; the line now names the live blocker. Claim-time rule stands: re-verify #11501's state before starting work here.


    Generated by Claude Code

  9. self-assigned this
    on Aug 27, 2026
  10. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 14
    Session: session_01PfaSTikked61BkcsB5Rn69
    Branch: claude/issue-9592-adr-decision-letter
    Worktree: objectstack-issue-9592
    Domain: domain:devx
    File surface: scripts/check-adr-anchors.mjs (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — dispatch-gates --tier on a tree AT origin/main: no path-derived mandate, floor sonnet · default opus · ceiling fable.
    Clause-②: no
    Serial constraints cleared: none — no in-flight card in this lane touches scripts/check-adr-anchors.mjs. R14 siblings #12299 (dispatch-gates.mjs) and #12538 (os-verify-lock.sh) are disjoint.

    ⭐ The Blocked-by: line in this card's body is SPENT — verified, not assumed

    The body still opens Blocked-by: #11501, and the card sits in pm:queue. Those disagree, so I checked the target rather than trusting either:

    #11501 is CLOSED completed, by PR #11780 MERGED — "docs: retarget the general-rule ADR-0057 D10 citations to ADR-0124 D1". Confirmed on the tree:

    ADR-0057 D10 files on origin/main : 23   (was ~42 when #11501 was filed)
    ADR-0124 present                  : 22   (control, fires)
    

    ⚠️ 23 remaining is the expected residue, not unfinished work — #11501's own "Done means" says only the general-rule citations move; sites citing D10 for what D10 actually decides (the Setup-nav surfacing family) stay as written. ⛔ Do not read the surviving 23 as a reason to re-run that retarget.

    ⇒ The prerequisite is genuinely discharged and pm:queue was right. The stale body line is a half-state on the reverse index, which I am noting here rather than editing another author's body mid-flight; ⛔ it should be struck when this card closes.

    Premise re-verified on origin/main @ 1246b4cf2 with a control that fires: check-adr-anchors.mjs has 0 occurrences of decision-heading resolution against 109 mentions of ADR — so the gate really does still check only the number layer.


    Generated by Claude Code

  11. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Dev claim (os-dev seat, dispatched by the domain:devx PM, round 14).

    Session: session_01PfaSTikked61BkcsB5Rn69
    Branch: claude/issue-9592-adr-decision-letter (pushed empty at 1246b4cf2 as the write-route probe; exit 0)
    Worktree: objectstack-issue-9592
    Base: origin/main @ 1246b4cf2e586c69110a22ad26b8401a25bb8ac4

    Comment chain re-read before writing code: no earlier dev claim with a different session ID exists on this card, so the claim is mine. assignee untouched (PM-owned).

    Body self-check for sanitizer truncation: body is 1378 bytes and terminates in a complete sentence (... #7329 (tombstone anchoring).) — no truncation observed. The Blocked-by: #11501 line at the top is the known-spent line already documented by the PM's claim comment; I am not editing another author's body.

    Scope as dispatched: extend scripts/check-adr-anchors.mjs one layer down so an ADR-NNNN Dk citation is verified to resolve to a real decision heading of the cited ADR. First-run reds on additional bad letters are their own findings, never a reason to weaken the check. docs/adr/** is governed surface: the gate reads it, and I will stop and report rather than edit any ADR.


    Generated by Claude Code

  12. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    ACCEPT — PR #12785, reviewed against the diff rather than the report

    Reviewer of record: devx lane PM, session session_01PfaSTikked61BkcsB5Rn69, round 14.

    criterion reading
    PR shape draft ✓ · base main @ 1246b4cf2 ✓ · first line Fixes #9592 ✓
    scope 1 file, +585/−1, scripts/check-adr-anchors.mjs — exactly the declared surface. ⭐ No ADR edited: docs/adr/** stays governed surface, the gate only reads it.
    changeset skip-changeset correct
    lint whole-repo eslint . --no-inline-config run, LINT_EXIT=0 — not narrowed
    CI in progress at review time; ⛔ landing gated on every check green by name

    It measured the grammar before writing the parser — which is what the card's judgement actually was

    131 files, 125 numbers counted first; four shapes recognised because four shapes exist. ⭐ Rule 4 (a Dk heading's own top-level list declaring Dk.1/Dka) is load-bearing, not a convenience: without it ADR-0120 D5a/b/c/e and ADR-0020 D3.3 — 20 live citations — read as bad letters. Both were red under the stricter first draft and both are correct citations. That is precisely the "too strict" failure I flagged in the dispatch, caught by measuring rather than by shipping.

    ⭐⭐ "Cannot verify" is a real third verdict, and the diff backs the claim

    I checked this in the code rather than in the PR body. unverifiable is a separate Map with its own unverifiableCount, and the output states it plainly:

    "CANNOT BE VERIFIED — not checked, and NOT counted as resolving above"

    67 of 125 ADR numbers declare no machine-readable letters at all (the older records number topically or ordinally), and 37 live citations point into six of them. Reported, never failed — correct, because the defect is the record's spelling and docs/adr/** is governed surface a script gate must not force.

    ⭐ And the escape hatch is bounded in both directions: tombstone-letter-is-not-merely-unverifiable pins that a tombstone must not be excused as unverifiable. An unverifiable bucket that can swallow a real error is worse than no bucket.

    ⭐⭐ It refused to let the gate manufacture its own index

    An ordinal→letter inference (D2 = the 2nd ### N. heading) would have made ADR-0067 verifiable. Rejected, with the right reason:

    the record never writes D2, so the mapping would be this gate's own invention, and a gate that manufactures the index it checks against cannot fail honestly.

    That is the standing lane rule — a gate's self-description must be true — applied to a case where the shortcut was available and tempting.

    The anti-vacuity floor, verified in the diff

    The failure mode I would have asked about is a shape scanner degrading to a no-op: change the heading style, every record becomes "declares no letters", every citation becomes "cannot verify", exit 0 with a cheerful summary. It is closed:

    if ([...decisionIndex.values()].every((s) => s.size === 0)) { errors.push(…) }

    with the reasoning in place — "An empty corpus-wide index is not a corpus fact, it is a broken parser, and it fails here rather than being reported as 500 unverifiable citations." Plus --self-test floors on the index, the scan and the resolution count, so green cannot mean "nothing was checked."

    First-run reds: zero, and that is a measurement, not a softening

    Zone 1 said additional bad letters would be their own findings, ⛔ never a reason to weaken the check. Two candidates appeared under the first draft; both were parser artifacts, dissolved by reading the sub-decision grammar the records actually use. Nothing baselined, no allowlist added, the predicate not softened — the grammar was corrected to match the corpus. That is the right direction of adjustment and I checked it is what happened.

    Ablation — and the restore leg is proved by state, not by an exit code

    Fabricated ADR-0057 D99: both counts moved on disk (12→11 and 0→1) before any result was read; MUTATED_EXIT=1 naming the letter; restore proved by hash equality against the HEAD blob plus an empty git diff HEAD — with the reason stated: "a checkout that does nothing also exits 0." And it is carried as a self-test assertion so it cannot rot.

    ⭐ Note ablation-fabricated-letter-is-not-excused-as-unverifiable sits beside it. That is independently the same lesson #12299 surfaced this round — a departure pin cannot see an arrival — arrived at by a different dev on a different card. Two seats converging on it in one round is worth recording.

    Two silent-failure modes closed on the way

    Line-wrapped citations: 11 live citations wrap across a JSDoc continuation (… (ADR-0106 ⏎ * D3)), and a line-oriented scan misses every one silently. The regex follows the wrap. Also the letter pattern carries a trailing lookahead so D1 is not scraped out of D1x or D12.

    Boundaries declared rather than quietly widened

    The layer covers the anchored surface (915 citations) deliberately; the repo-wide corpus is 7736 same-line occurrences across 1609 files and stays unchecked, filed as a finding. The 37 unverifiable citations are likewise filed rather than baselined. ⚠️ Both findings are not yet verified by me — the dev's report has not landed; I will check they exist and are well-formed when it does, per the report contract.

    ⛔ Not flipping ready yet: every check green by name first.


    Generated by Claude Code

  13. claude commented on Aug 27, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 9592,
      "status": "done",
      "branch": "claude/issue-9592-adr-decision-letter",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12785",
      "premise_still_valid": true,
      "summary": "Extended scripts/check-adr-anchors.mjs one layer below the number: every `ADR-NNNN Dk` citation on the anchored surface (53 anchor `invariant` strings + 52 anchored files) must resolve to a decision the cited record declares. HEADING GRAMMAR MEASURED FIRST across docs/adr/** (131 files, 125 numbers) — four shapes exist and all four are recognised: (1) `### Dk - Title` headings, h3 dominant with h4/h5 for sub-decisions, 321 tokens; (2) `**Dk - Title.**` bold-lead paragraphs, 22 numbers, 10 exclusively (ADR-0105's style); (3) `| Dk | Title |` decision-table rows; (4) a `Dk` HEADING's own top-level list declaring sub-decisions - an ordered list gives Dk.1/Dk.2, a lettered list gives Dka/Dkb. Rule 4 is load-bearing, not a convenience: ADR-0120 D5's five lettered gates and ADR-0020 D3's three numbered steps are declared nowhere else, and 20 live citations resolve only through it. A third verdict, `cannot verify`, is PRINTED and never folded into the OK line: 67 of 125 numbers declare no machine-readable decision letters at all (older records number decisions topically or ordinally), and 37 live citations into 6 of them cannot be resolved - reported, never failed, because the remedy is a governed-surface docs/adr/** edit (#6741). An ordinal-to-letter inference (D2 = the 2nd `### N.` heading) was weighed and rejected: the record never writes D2, so the mapping would be the gate's own invention. Two silent-failure modes closed on the way: the citation regex follows a JSDoc LINE WRAP (11 live citations wrap as `(ADR-0106` newline `* D3)` and a line-oriented scan misses every one silently), and an empty corpus-wide index is a HARD FAILURE in the main run so a broken parser cannot degrade to a cheerful no-op. FIRST-RUN REDS: zero. Two candidates appeared under a first, stricter parser draft (ADR-0020 D3.3, ADR-0120 D5b); both were parser artifacts and both are CORRECT citations under the grammar the records actually use. Nothing was baselined, no allowlist was added, the predicate was not softened - the grammar was corrected to match the corpus, which is what the measure-before-parse instruction was for. File surface honoured: only scripts/check-adr-anchors.mjs; no ADR edited. Card body read in full (1378 bytes, ends in a complete sentence) - NO sanitizer truncation observed. The body's `Blocked-by: #11501` line is spent as the PM measured; I did not edit another author's body.",
      "counts": {
        "decision_letter_citations_checked": 915,
        "resolved": 878,
        "unverifiable_reported": 37,
        "red": 0,
        "cross_repo_skipped": 1,
        "adr_numbers_with_a_decision_index": 58,
        "adr_numbers_without_one": 67,
        "index_tokens_declared": 397,
        "index_tokens_from_sub_decision_expansion": 108,
        "citations_resolving_ONLY_via_sub_decision_expansion": 20,
        "repo_wide_letter_citations_left_out_of_scope": 7736
      },
      "tests": "GATE UNION RUN ON THE FINAL COMMIT 8071dd26b (clean tree; `git rev-parse --short HEAD` = 8071dd26b; each exit code captured by redirect-then-capture, never through a pipe): adr-anchors 0, agent-test-spelling 0, bash32-floor 0, cli-command-ids 0, cross-package-test-inputs 0, entry-guard 0, parse-guard 0, pm-governed-merges 0, pnpm-filter-targets 0, ci-filter-parity 0, cross-package-test-inputs (direct) 0, bare-root-worklist --self-test 0, pm-dispatch-gates 0, nul-bytes 0, ratchet-remedy-authority 0, adr-links 0. Run through `bash scripts/pm/os-verify-lock.sh -c ...`; VERDICT command-exit 0, held 82s, waited 31s. GATES QUOTED BY THEIR OWN PRINTED VERDICT LINES, not by $?: 'check-adr-anchors --self-test: 106 assertions over the real auditAdrDirectory() / auditCitedNumbers() / assembleAnchors() paths.' (100 before this change; 12 new assertions - 6 synthetic grammar/verdict pins, 6 live-tree pins); 'check-adr-anchors: OK (52 anchored file(s) ... 878 decision-letter citation(s) on the anchored surface name a decision the record makes).'; '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); 'check-nul-bytes: OK (scanned 7118 text file(s) ... no raw ASCII control bytes).'; 'check-governed-merges --self-test: 206 assertions ... live: the real generator declared 9 output(s) and certified this tree'. GATE DERIVATION re-run for the ACTUAL diff: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths passed; it derived the change set itself from merge-base 1246b4cf2 - 1 committed path). It reproduced all 11 dispatched families AND surfaced one the dispatch did not name: `pnpm check:pm-dispatch-gates`, a convention-triggered gate-script-identity obligation. It was run; green. SCRIPT'S OWN TEST SUITE: `git grep -ln check-adr-anchors -- '*.test.ts' ...` returns nothing - this script has no vitest sidecar, its suite IS `--self-test`, which was run (106 assertions). WHOLE-REPO LINT: `pnpm lint` = `eslint . --no-inline-config` run under the lock on 8071dd26b, LINT_EXIT=0, no findings. This is the full farm scan, NOT a narrowed one, so no narrowing claim is being made. ABLATION (end to end, on the real gate). NO REBUILD LEG EXISTS AND NONE IS CLAIMED: the subject is a plain .mjs script executed from source with no dist/ and no compile step, so the dist-preflight discipline does not apply here; the equivalent proof - that the mutation reached the bytes the run would read - is the on-disk hash and grep counts below. The implementation was COMMITTED FIRST (8071dd26b) so the restore leg has a real reference; the script carries `trap restore EXIT INT TERM` with an absolute REPO_ROOT path. MUTATION CONFIRMED ON DISK by two grep counts moving in opposite directions plus a hash change, never by an editor exit code: 'ADR-0057 D10' 12 -> 11 and 'ADR-0057 D99' 0 -> 1 in packages/rest/src/rest-server.ts; on-disk blob ec8252f3... -> 9c6bc6d8... . MUTATED RUN: exit 1, 'check-adr-anchors: 1 problem(s)' / 'ADR-0057 D99 is cited by 1 file(s), but ADR-0057 declares no D99'. RESTORE PROVEN BY OBSERVED STATE, not by exit code: restore spelled `git checkout HEAD -- \"$REPO_ROOT/...\"` (never a bare `git checkout --`), on-disk hash back to ec8252f3... == the HEAD blob hash (non-empty, checked), and `git diff HEAD` EMPTY. RESTORED RUN: exit 0, OK line back. The same ablation is pinned as an assertion (`ablation-a-fabricated-letter-is-red` plus `ablation-fabricated-letter-is-not-excused-as-unverifiable`) so it cannot rot, alongside three anti-phantom floors: live-decision-index-is-not-empty (>= 40 of 125 numbers), live-letter-scan-reads-the-anchored-surface (> 500 citations read) and live-decision-letters-actually-resolve-something (> 500 actually resolved) - green must mean resolved, never 'nothing was checked'.",
      "mcp_calls": "3 - claim comment (add_issue_comment), create_pull_request, and one targeted search_issues for finding dedupe. CHANNEL SWITCH DECLARED: repo-scoped REST reads AND writes both work from this seat (issue/comment GET 200, label POST 200, issue POST 201), so the card body, all 8 comments, the label write, the finding filings and every read-back went over REST at zero GraphQL cost. The ONE thing REST could not do is search: /search/issues returns 'This GitHub API path is not available: sessions are bound to their configured repositories', so dedupe used a single targeted MCP search_issues call. Its control fired - the query returned this very card #9592 among 7 results, so the absence of a duplicate is a real reading and not an empty grep. No wide-table scan, no full pagination.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #12786: 67 of 125 ADR numbers declare no machine-readable decision letters (older records number decisions topically or ordinally), so 37 live anchored-surface citations into 6 of them can never be verified - remedy is a governed-surface docs/adr/** edit, deliberately not made here",
        "filed as #12787: the new decision-letter layer covers the anchored surface only (915 citations); the repo-wide corpus is 7736 same-line `ADR-NNNN Dk` occurrences across 1609 files and stays unchecked - recording the scope boundary #9592 drew as a visible decision rather than an implicit gap"
      ],
      "notes_for_pm": [
        "skip-changeset applied to PR #12785 via the ADDITIVE REST endpoint (POST .../issues/12785/labels, HTTP 200) - no read-modify-write window. Read back afterwards: labels are now ['size/l','skip-changeset'], so the write landed and nothing stripped it.",
        "PR body re-read in full after creation (8575 bytes): all 7 h2 sections present, first line 'Fixes #9592', session-URL footer intact, zero angle-bracket fragments left. Both filed findings re-read the same way - intact.",
        "The dispatched gate list was one family short for this diff. `dispatch-gates.mjs` on the actual change set added `pnpm check:pm-dispatch-gates` (gate-script identity, same convention bucket as bare-root-worklist). Run, green. Worth folding into the next devx dispatch for any card that edits a gate script.",
        "The card's stale `Blocked-by: #11501` body line is still there. Not edited mid-flight per the PM's own note; it should be struck when this card closes."
      ]
    }

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions