Uh oh!
There was an error while loading. Please reload this page.
fix(devx): read the ledger row recognizer through typeDeclRegions too - #10900
Merged
Conversation
The recognizer read through codeOnly (#10683) but not through typeDeclRegions, which is the second of #10500's two exact discriminators for 'is this declaration in code position'. Type declarations are code, so a literal-union `route: 'GET /a' | 'GET /b'` TYPE member still minted a row — silently, for the same two reasons as the comment case: rows and routesDeclared moved together so the partial-read guard saw no gap, and outsideCode could not see it because the lead genuinely is in code position. The row loop and the first term of the denominator now skip the same region list declarationsIn has read since #10500, so the two scans answer the question the same way instead of drifting while both look right. Measured across all seven live ledgers, before and after: identical — 259 of 259 rows read, 221 of 221 client-bound, 176 unreachable, and the full --bridge-coverage --json byte-identical. 0 quoted route:/client: leads sit inside a type declaration today. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
The README's type-declaration paragraph described the exclusion as the COUNTER's. After #10683 and #10793 the recognizer reads through both masks too, and a paragraph that stops at the counter reads as if the recognizer still parses prose. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
os-zhuang
marked this pull request as ready for review
August 21, 2026 16:40
Uh oh!
There was an error while loading. Please reload this page.
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32504138883 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
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#10793
The defect
declarationsInhas read through both of #10500's exact discriminators since it waswritten:
The row recognizer read only the first.
codeOnlyblanks comments and string contents;it does not blank type declarations, and has no reason to — they are code. So a
single-quoted
route:inside aninterface/typebody was still assembled into arow.
It was silent in the same two ways as the comment case #10683 closed:
rowsandroutesDeclaredmoved together, so the partial-read guard saw no gap, andoutsideCodecould not see it either, because the lead genuinely is in code position.
The gap was self-documented in the file — the comment above
const code = codeOnly(text)read
ONE of #10500's two discriminators, not both. That comment is now updated ratherthan left standing.
Reproduced on the base (
9faa9bc51d), drivingparseLedgerSourcedirectly with thecard's fixture:
9faa9bc51drows 2 · routesDeclared 2 · clientsDeclared 1 · declined 0 · outsideCode 0rows 1 · routesDeclared 1 · clientsDeclared 1 · declined 0 · outsideCode 0Two rows from a file declaring one, exactly as the card measured — and one row after.
The change
parseLedgerSourcenow takestypeDeclRegions(code)beside itscodeOnly(text)call andskips a lead that falls inside a region, in two places that must move together:
if (inTypeDecl(m.index)) continue;), androutesDeclared.Both, or the partition
rows + declined === routesDeclaredbreaks: a member counted in thedenominator but skipped by the loop reads as a row this parse declined to read, and fires
a
PARTIAL readverdict on an accurate ledger. The ablation below runs that direction onpurpose.
This is the one-line direction the card named. It reuses the same region list
declarationsInreads rather than growing a second idea of what a type member is; the widerrefactor (the row loop filtering
declarationsIn(text)) was not needed and is not here.Why the skipped member is reported nowhere
#10683 paired its mask with
outsideCodeso the fix would not trade a phantom row for a newsilence. That argument does not carry over, and the PR says so in the code. A
prose-quoted lead is a would-be row sitting where the mask says code is not, so naming it
tells the reader something. A type member is a correct declaration of a type: the
route: string;member all seven entry interfaces declare has always produced nothing underthe same rule,
declarationsInhas excluded the literal-union spelling since #10500, and--self-testalready pinned that deleting the interface changes no count at all. This makesthe recognizer agree with the position the rest of the file already held rather than adding
a new one — and the new fixtures assert that reject side positively, on both
declinedandoutsideCode.Population move: measured, and it is zero
The standard the
--bridge-coverageheader attaches to. Both legs run against the realtree.
9faa9bc51dbfe43f064fbrokenScan/ exit00Counts alone can hide a swap, so the whole machine-readable report was compared, not the
header:
--bridge-coverage --json(31463 bytes, per-ledger rows and every unreachable row)is byte-identical across the two legs —
diffempty in both directions, and re-confirmedafter the ablations were restored.
Independently scanned, with the same
codeOnly+typeDeclRegionspair the fix uses: 0quoted
route:/client:leads inside a type declaration across all seven ledgers. Eachledger has exactly one
route:in a type declaration and it is the unquotedroute: string;member. So the card's premise held and the move is free today in a way it stops being the
moment one ledger unions its
route:member.⭐ No live figure moved.
Tests
--self-testgoes 323 → 339, in the idiomorphanSourceestablished for theclient:key — that fixture already pins this exact spelling on
client:, written there because aliteral-union type member opens with the very quote the counter reads. These are its
route:twins, which had nothing keeping them out of the row loop.
Pinned in both directions, because the expensive failure here is the opposite one: a
brace match that ran away would skip the member and the table after it, passing any test
that only asserted the phantom is gone while silently dropping all 259 live rows. So the new
cases assert what is dropped and what is kept, value included:
route:member is not a row, and the row in code still is, carryingGET /api/v1/meta → meta.getTypes;declined, notoutsideCode;client:too mints no client-bound phantom (the expensive shape: sucha phantom joins the UNREACHABLE population, since no registrar tail matches a route nobody
mounts) and the surviving row keeps its own binding;
type X = { … }, the other spellingtypeDeclRegionsrecognises;brokenScan, whole ratios, and no prose lead.Ablation
Predictions written down before either run. Every mutation proved on disk by marker
count, never by an editing tool's exit code, and each leg restored with
git checkout HEAD -- affected-docs.mjs+git diff --quiet(exit 0 = byte-identical) before the next.No rebuild leg applies: this is a plain
.mjsrun from source bynode scripts/..., sonothing resolves through a package
exports/distand there is no stale-artifact path fora mutation to hide in.
A — remove
if (inTypeDecl(m.index)) continue;from the row loop (markerinTypeDecl3 → 2, guard line 1 → 0). Predicted 9 reds, named case by case with theirexpected/got strings; observed 9, the same cases, the same strings — including
got "GET /api/v1/gone → null"for the phantom's value andgot "meta.getGone"for thestolen binding. The card's own reproduction went back to 2 rows under this leg. Both
directions, as the card required.
B — remove
.filter((d) => !inTypeDecl(d.index))from the denominator (marker 1 → 0).Predicted 3 certain reds plus a fourth flagged uncertain — whether
bridgeCoverageFromkeys its
PARTIAL readverdict on the shortfall or on the declined list. Observed 4: theuncertain one fired,
brokenScan → expected 0, got 1. That is the false red on an accurateledger, and it is why both terms had to move together.
Also recorded, because it is the card's thesis reproduced in the harness: under ablation A
none of the 323 pre-existing cases red. No existing fixture puts a quoted
route:inside a type declaration, which is exactly why this needed fixtures of its own.
Gates
Derived with
node scripts/pm/dispatch-gates.mjs(no hand-supplied paths — it takes its ownchange set from the merge base), run after the final commit. Union re-run at
bfe43f064fwith a clean tree, every exit code captured before any pipe, each gatequoted by its own verdict line:
pnpm check:docs-audit-scope✓ affected-docs self-test: 339 cases pass.·✓ check-audit-scope self-test: 24 cases pass.·✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).pnpm check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.node scripts/check-cross-package-test-inputs.mjspnpm check:entry-guard✓ check:entry-guard: 129 scripts/ file(s) — every entry guard goes through invoked-as.mjs; 87 export bindings, 77 of them inert on import (10 known-unsafe, ⛔ SHRINK-ONLY).pnpm check:parse-guard✓ check:parse-guard: 128 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.node scripts/check-ci-filter-parity.mjsOK: all 82 declared cross-package glob(s) (71 unique) are covered by core or crosspkg, every crosspkg entry still covers one, and the test job's if: still names both filters.node scripts/docs-audit/check-affected-docs.mjs259 of 259 declared·221 of 221 declared·prose-quoted leads (no row) . 0·UNREACHABLE .. 176pnpm check:nul-bytescheck-nul-bytes: OK (scanned 6298 text file(s) ... no raw ASCII control bytes).check:nul-byteswas run although the derivation did not name it — it is owed by any edit. Acontrol-byte self-scan over both changed files outside the gate came back empty. The
--filterzero-match trap (#10853) was exercised as a negative control:pnpm --filter @objectstack/definitely-not-a-package testprintedNo projects matched the filtersand exited 0; no step in this PR depends on a--filterrun.
No changeset: gate tooling publishes nothing, and "this PR edits a CI-internal script" is
the case
lint.ymlitself calls the textbook one.skip-changesetapplied additively, thenread back. Same disposition as #10795, the #10683 fix on this file.
Also in this PR
scripts/docs-audit/README.md— its type-declaration paragraph described the exclusion asthe counter's. After #10683 and #10793 the recognizer reads through both masks too, and a
paragraph that stops at the counter reads as if the recognizer still parses prose. It now
states both, and states why a skipped type member is reported nowhere.
Deliberately not in this PR
declinedInstill reads raw text, so aroute:quoted in a comment in ANY other spelling is billed as an unread row — a PARTIAL-read verdict on an accurate ledger #10794 —declinedInstill reads raw text. Closed not planned at triage; untouchedhere.
parseRegistrarSourceis untouched — docs-drift's route bridge counts identifiers found inside COMMENT text — an English sentence in a handler can mint a route anchor #9432 holds that shape one scan over.⛔ Draft on purpose: not marked ready, auto-merge not enabled.
Generated by Claude Code