Skip to content

Commit 6156951

Browse files
os-zhuangclaude
andauthored
fix(docs-audit): a PARTIAL ledger read is a verdict, not a smaller number (#10501)
`parseLedgerSource` recognizes a route-ledger row through a single-quote-only regex. The pre-existing broken-scan guard is `rowsParsed === 0`, so it catches only the all-or-nothing case; a ledger where SOME rows are spelled in a quote the recognizer declines parsed fine and reported a smaller population with no verdict and exit 0. Measured on a718ee3, respelling one row of `i18n-route-ledger.ts`: - backtick-quoted `route:` -> 221 -> 220 client-bound rows, no verdict, exit 0 - backtick-quoted `client:` -> 221 -> 220, `rowsParsed` still 3, exit 0 - backtick-quoted `route:` in `rest-route-ledger.ts` -> clientRows still 221, but `meta.getTypes` moved onto the server-only `GET /api/v1/docs`: the row window is delimited by the same single-quote-only lead, so a declined row does not close the previous row's window The last case is why the verdict keys on the DECLINED SPELLING rather than on a count shortfall - the count is blind to it. `parseLedgerSource` now returns what it declined beside what it read, and `bridgeCoverageFrom` turns any declined declaration (or any declared row value it did not read) into a `brokenScan` verdict naming the file, the line and the literal. Both halves of the fraction print on every run. The recognizer is untouched: today's seven ledgers are wholly single-quoted (259 route, 221 client, delta 0), so no verdict can fire on this tree, and `--self-test` pins the population at its old value so a silent widening fails there. Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 359f595 commit 6156951

3 files changed

Lines changed: 297 additions & 23 deletions

File tree

‎.github/workflows/docs-drift-check.yml‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,16 @@ jobs:
207207
if (bridge && bridge.measured && bridge.unreachable > 0) limits.push(`the SDK route bridge reached **${bridge.reachable}** of **${bridge.clientRows}** client-bound route-ledger rows — the other **${bridge.unreachable}** have no registrar \`path:\` tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: \`node scripts/docs-audit/affected-docs.mjs --bridge-coverage\``);
208208
// A broken scan is not a coverage number and must not read like one — see
209209
// `bridgeCoverageFrom`. Rendered outside the collapsed section below, because
210-
// "the bridge found no ledger at all" is not a limit of this run, it is a
210+
// "the bridge could not read the ledgers" is not a limit of this run, it is a
211211
// defect in the tool that every subsequent run will keep reporting as clean.
212+
//
213+
// ⛔ NOT "structurally empty" any more, which is what this line used to say: the
214+
// verdict list also carries PARTIAL reads, where the scan produced a perfectly
215+
// ordinary-looking population that is simply smaller — or mis-bound — than what
216+
// the ledgers declare. Wording that promises emptiness would let a reader skip
217+
// exactly the case that renders like a clean one.
212218
const bridgeBroken = (bridge && bridge.measured && (bridge.brokenScan || []).length)
213-
? ['', `> ⛔ The SDK route bridge's own scan came back structurally empty: ${bridge.brokenScan.join('; ')}. Treat every \`sdk\` row above as unavailable rather than absent.`]
219+
? ['', `> ⛔ The SDK route bridge's own scan is broken, not clean: ${bridge.brokenScan.join('; ')}. Treat every \`sdk\` row above as unavailable rather than absent.`]
214220
: [];
215221
// Rendered whenever there is anything to say, INCLUDING when the only thing to
216222
// say is "the wide net exists and holds N pages". A short list is the right

‎scripts/docs-audit/README.md‎

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,11 @@ Two things it deliberately is **not**:
100100

101101
- **Not a verdict.** The ratio is reported. Failing CI on it would widen the recognizer
102102
under CI pressure, which the #9747 family declines explicitly. What *does* exit non-zero
103-
is `brokenScan` — no ledger found, no tail produced, or a ledger file matching the
104-
convention that the row recognizer parses to zero rows. Those cannot fire on a tree where
105-
the scan works at all, and each of them otherwise reports `0 of 0 unreachable`, which is
106-
arithmetically true and reads exactly like a healthy bridge.
103+
is `brokenScan` — no ledger found, no tail produced, a ledger file matching the
104+
convention that the row recognizer parses to zero rows, or a **partial read** (next
105+
section). Those cannot fire on a tree where the scan works at all, and each of them
106+
otherwise reports `0 of 0 unreachable`, which is arithmetically true and reads exactly
107+
like a healthy bridge.
107108
- **Not a fix for the 176.** The card measured the causes and they are unrelated: ~97 rows
108109
have no static registration of any shape in `packages/**` (better-auth mounts the auth
109110
table; the runtime dispatcher matches `cleanPath` with `===`), ~50 have a `path:` literal
@@ -116,6 +117,42 @@ Half the blind spot is load-bearing: **88 of the 176** unreachable rows name a c
116117
method that at least one hand-written page carries (31 distinct pages, `api/client-sdk.mdx`
117118
among them), so the silence is not an empty region.
118119

120+
### A PARTIAL ledger read is a verdict too (#9896)
121+
122+
The row recognizer reads **single-quoted** values only, and the `rowsParsed === 0` guard
123+
above is the all-or-nothing case. The likelier shape is a ledger that parses *almost*
124+
completely — and it used to render exactly like a complete one. Measured on `a718ee3dd` by
125+
respelling **one** row of `i18n-route-ledger.ts`:
126+
127+
| ledger written as | client-bound rows | verdict | exit |
128+
| --- | --- | --- | --- |
129+
| all single-quoted (today, all 7 ledgers) | 221 | none | 0 |
130+
| one row backtick-quoted `route:` | **220** | none *(now: `PARTIAL read … 2 of 3`)* | 0 → **1** |
131+
| one row backtick-quoted `client:` | **220** | none *(now: `PARTIAL read … 2 of 3 client`)* | 0 → **1** |
132+
| one `route:` backtick-quoted in `rest-route-ledger.ts` | **221** ⚠️ | none *(now: `PARTIAL read … 95 of 96`)* | 0 → **1** |
133+
134+
The last row is why the verdict keys on the **declined spelling** and not on a shortfall:
135+
the row window is delimited by the same single-quote-only lead, so a declined row does not
136+
close the *previous* row's window and the row before it **inherits the declined row's
137+
client**. Backtick-quoting `GET /api/v1/meta` moved `meta.getTypes` onto the server-only
138+
`GET /api/v1/docs` while `clientRows` stayed 221 — a count comparison is blind to it.
139+
140+
A template literal is the realistic spelling: the formatter rewrites double quotes back to
141+
single, but leaves `` route: `${base}/locales` `` alone, and that is the natural shape the
142+
moment anyone interpolates a base path into a row.
143+
144+
So both halves of the fraction now print on every run — `ledger rows read … 259 of 259
145+
declared` — and a declined declaration is a `brokenScan` verdict that **names the entry**
146+
(`line 96: route: \`GET /api/v1/i18n/locales\``). The recognizer itself is untouched: this
147+
reports its narrowness, it does not widen it, and `--self-test` pins the population at its
148+
old value so a silent widening fails there.
149+
150+
⚠️ **Boundary, declared not discovered:** a `route:` whose value is not a string literal at
151+
all (`route: ROUTES.health`) is invisible to the recognizer *and* to this counter — the only
152+
exact discriminator against the `route: string;` member each ledger's own entry interface
153+
declares is the opening quote. Pinned in `--self-test`; filed separately rather than papered
154+
over with a heuristic.
155+
119156
### What it cannot see is reported, never implied
120157

121158
`anchorlessChanges` lists changed files that yielded no anchor at all; a non-empty value

0 commit comments

Comments
 (0)