Skip to content

gate(spec): register packages/spec/src/** doc blocks as the fourth symbol-anchor corpus, censused first - #17241

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-17065-spec-docblock-anchor-corpus
Sep 9, 2026
Merged

gate(spec): register packages/spec/src/** doc blocks as the fourth symbol-anchor corpus, censused first#17241
baozhoutao merged 2 commits into
mainfrom
claude/issue-17065-spec-docblock-anchor-corpus

Conversation

@claude

@claude claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Clause-②: no

Fixes #17065

Registers packages/spec/src/** doc blocks as the fourth corpus of the shared symbol-anchor resolver — a defineCorpus call in the deliberately thin shape check-adr-symbol-anchors.mjs and check-scripts-symbol-anchors.mjs already use. ⛔ No second resolver — the 2026-09-01 ruling on #13556 forbids one in terms, and scripts/symbol-anchors.mjs is untouched by this diff.

⭐ The census came first, and here is the number

The card wrote the order hard and it was followed literally: register, run the reporting arms, take the count, then choose the exit contract. Measured on 08e38c6377 over the 1,317 tracked .ts files under packages/spec/src, with the instrument named beside every number because three instruments give three answers on one tree:

instrument count
raw extractAnchors over the whole file (counts spec's own fixture data) 198
through the commentProse projection — doc prose only 189
…and the cited path names a tracked file — what this gate judges 6, across 5 files

Total hard findings: 7 across 6 of 1,317 files — the 6 line anchors plus 1 unresolved-path. Alongside them: 183 citations naming no tracked file (69 bare-filename, 62 directory-qualified, 52 continuation, across 34 files), and 3 soft cross-repo-skipped rows.

Controls, because a small number needs them more than a big one does. Every zero here is backed by a same-corpus positive control, using short fragments only:

  • counts.symbol is 0. Control: git grep for a backticked path-hash-identifier span across packages/spec/src returns exactly 1 file — api/websocket.zod.ts — and that one span is the unresolved-path finding. So the zero is "the convention has not reached this corpus", not "the extractor matched nothing".
  • The extractor is demonstrably live on this corpus: 2,639 anchors across 1,317 sources, 2,635 of them file-level.
  • The declined count is enumerated, not just counted: --list-unresolvable prints all 183 rows, and the self-test holds declined.length equal to the counter.

The bare-path axis, measured for the checkBarePaths: false call: 2,635 bare path spans; 346 name a tracked file at the repo root, 610 resolve only when prefixed with packages/spec/src/ (spec doc blocks habitually cite package-relative), and 1,679 (758 distinct) bind against neither base. Judging them would produce 2,290 findings — the same call docs/adr/** made at 1,056 and scripts/** at 1,617.

The exit contract that number justified, and why

A pinned day-one residual, with the gate ON. All 7 findings live in packages/spec/** text, which this card ⛔ forbids this lane from editing — repairing a spec doc block is domain:spec work with its own cards. That left two honest options: a gate that can never fail, or a gate that fails on everything except a dated, enumerated, shrink-only residual.

A gate that can never fail is the verifier AGENTS.md names as worse than no verifier, so CENSUS_RESIDUAL pins the 7 sites with their repairs and every finding outside it is a hard red from day one — which is exactly the property the card asked for: the fourth set does not have to be found by hand.

⛔ It is not an exemption list. It is exact in both directions: a row whose citation gets repaired goes stale and reds until it is deleted, so a repair forces the row out in the same PR. Rows are keyed by citation text, never by line — a line-keyed row would be a line anchor inside the line-anchor gate, rotting the first time anyone added a paragraph above it.

⚠️ Re-grade triggers: both measured, neither fires — with one caveat that belongs to the PM

Trigger 1 (rot comparable to 72.1%, or a count that makes this a migration): does NOT fire. 7 findings across 6 of 1,317 files is a gate, not a migration.

⚠️ But that answer is conditional on the scope call, and the conditionality is the reading rather than a footnote to it. With judgeUntrackedLineAnchors flipped to true, the same tree yields 189 findings on day one — which would be a migration. The small number is not a claim that spec doc blocks are clean; it is a statement about the citations a resolver living in this repo can bind. Both numbers are pinned in CENSUS_17065 and the self-test holds them consistent, so the reassuring one can never be read without the other.

Trigger 2 (a fifth hand-repair filed before this lands): does NOT fire. Searched with a positive control that the search reaches this very card (#17065 came back in the results); the open cards in this family are #16960 (the third repair) and #15809 (the scripts/** declined-citation worklist). No fifth.

⭐ An honest limit of this gate, stated because it is the card's own subject

The three objectql/engine.ts:NNNN sites that #16960 owns are written with an abbreviated path, so they name no tracked file and land in the declined set — this gate would not have caught them. Same for the record-validator.ts sites #16441 repaired. The fourth set this registration stops from being found by hand is the set written with a repo-root path.

That is not a defect in the registration — it is the same scope call scripts/** made, whose residual became #15809 — but it would be dishonest to let the header imply otherwise, so the header says it in those words and --list-unresolvable keeps the population a worklist rather than a number. Handed to the PM below rather than carded here.

Verification

Ablation — the gate really can fail, both directions, on the live tree. Each leg: on-disk mutation proven by occurrence counts and a changed blob hash, run, restore proven byte-identical against the HEAD blob. ⛔ No packages/spec/** text was touched by either leg — both mutate the gate's own CENSUS_RESIDUAL, which is what decides the exit contract.

leg mutation gate exit what it printed
1 — an unpinned finding must red delete the http-server.zod.ts residual row (occurrences 1 to 0; blob 955d9879 to ad18e70f) 1 [line-anchor] packages/spec/src/system/http-server.zod.ts:238
2 — a row matching nothing must red add a ghost row (occurrences 0 to 1; blob 955d9879 to 8dabf07e) 1 1 STALE CENSUS_RESIDUAL row(s) — and the self-test reds too
restore 0 on-disk blob 955d9879 identical to the HEAD blob; green line back

Derived gates. node scripts/pm/dispatch-gates.mjs --commands derived 63 families from the changeset; all 63 were run and reconciled with --ran: "63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED."

One derived gate went genuinely red and was repaired: check-scripts-symbol-anchorsscripts/** is itself a corpus, so the new gate's own header quoting a broken anchor verbatim made this file a finding. Both offending spellings are now described in words, with the reason stated inline. Both corpora are green on the final commit.

Five commands exit 3 = PREREQUISITE NOT MET and are NOT MEASURED, not red — they read dist/ and want a full pnpm build: check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content, check:type-check-debt. This diff touches no package source, so no package closure is affected; the full build belongs to CI.

Re-run on the final commit 0be9a49284: both symbol-anchor corpora plus their self-tests, the ADR corpus, the shared resolver's self-test, check-step-collectors, check-self-test-workflow-commands, check-self-test-wired, check:nul-bytes, check:parse-guard, check:declared-population-live, check:watch-hint-literal, check:type-check-coverage — all exit 0. check:pm-dispatch-gates (the tool whose ledger prose this diff edits) passes: "dispatch-gates self-test: 1624 cases pass."

Lint, as a declared narrowing rather than a repo-wide run. Targeted eslint --no-inline-config --format json over the 2 changed .mjs files: 2 files linted, 0 errors, 0 warnings. The three pieces of evidence the narrowing needs: ① the population is read from eslint.config.mjs itself, which lints only from the root; ② the file count is read from --format json output, not asserted; ③ the invariance claim is the config's own measured declaration — it "never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file" — so this diff cannot move the verdict on any file it did not touch. The repo-wide pnpm lint remains CI's run.

skip-changeset, measured with a positive control. Nothing published moves: 70 published packages inspected, 0 name scripts/ or lint.yml in files[]; the control is that all 70 name dist, proving the probe reads real entries. The root manifest is private: true. All four paths are fast-track — repo-root config, .github/workflows/, scripts/**, scripts/pm/**.

Acceptance notes — found, not fixed

Not addressed here

⛔ Out of scope: #16962 remains open — that card's @example caption convention is a different convention with no existing mechanism, deliberately not merged with this one. #16960 remains open and lands on its own; this PR ⛔ does not repair its three sites, and does not repair the 7 pinned residual sites either.


Generated by Claude Code

…hor corpus

The fourth corpus of the shared symbol-anchor resolver (scripts/symbol-anchors.mjs),
joined by a `defineCorpus` call in the deliberately thin shape
check-adr-symbol-anchors.mjs and check-scripts-symbol-anchors.mjs already use --
no second resolver.

The census came first and the exit contract second. Measured on 08e38c6 in
--list / --list-unresolvable reporting mode over 1,317 tracked .ts files:

  198  raw extractAnchors over the whole file (counts spec's own fixture data)
  189  through the commentProse projection (doc prose only)
    6  ...and the cited path names a tracked file -- what this gate judges

7 hard findings across 6 files (6 line-anchor + 1 unresolved-path), 183 declined
citations naming no tracked file, 3 soft cross-repo rows. That is a gate, not a
migration -- the opposite of docs/adr/**'s 243-of-337 (72.1%).

Repairing those 7 edits packages/spec/** text, which is domain:spec work with its
own cards, so the exit contract is a pinned day-one residual: CENSUS_RESIDUAL
enumerates the 7 sites with their repairs, every finding outside it is a hard red
from day one, and a row whose citation gets repaired goes STALE and reds until it
is deleted. Rows are keyed by citation TEXT, never by line -- a line-keyed row
would be a line anchor inside the line-anchor gate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
…erbatim

`scripts/**` is itself a registered corpus, so the new gate's own header is
swept by check-scripts-symbol-anchors. Quoting the websocket.zod.ts citation in
anchor form -- and spelling the bare-word-colon-path shape literally -- made
this file a finding against that corpus (1 unresolved-path, 1 soft cross-repo).

Both are now described in words, with the reason stated inline so the next
author does not reintroduce them. The citation itself is unchanged: it stays
pinned in CENSUS_RESIDUAL as a string literal, which the commentProse
projection blanks as code.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012GKcPZbMoGq7WPzKLfRBTU
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 9, 2026
@github-actions github-actions Bot added size/l ci/cd dependencies Pull requests that update a dependency file labels Sep 9, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 9, 2026 21:25
@baozhoutao
baozhoutao enabled auto-merge September 9, 2026 21:25
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 6aa1d09 Sep 9, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-17065-spec-docblock-anchor-corpus branch September 9, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd dependencies Pull requests that update a dependency file size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants