Skip to content

fix(tooling): give a non-literal ledger route: a verdict instead of silence - #10637

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10500-ledger-nonliteral-route
Aug 21, 2026
Merged

fix(tooling): give a non-literal ledger route: a verdict instead of silence#10637
os-zhuang merged 1 commit into
mainfrom
claude/issue-10500-ledger-nonliteral-route

Conversation

@claude

@claude claude Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixes #10500

A ledger row whose route: is not a string literal was read by neither half of the scan: the recognizer needs a leading ', and the #9896 partial-read counter counts only values opening with one of the three quotes. The row left the population with no verdict at all — not read, not declined, absent even from the denominator. That is the silence #9896 closed for the quote spellings, one spelling further out.

The reproduction, before and after

⭐ A zero-hit result needs a positive control, so the harm was reproduced on a real ledger first. One row added to packages/services/service-i18n/src/i18n-route-ledger.ts (mutation confirmed on disk by anchor count I18N_BASE 0 → 1, restored to 0):

{ route: I18N_BASE + '/plurals', family: 'i18n', disposition: 'sdk', client: 'i18n.getPlurals' },
--bridge-coverage on that tree
before ledger rows read … 259 of 259 declared · client-bound … 221 of 221 declared · exit 0, no verdict
after ledger rows read … 259 of 260 declared · exit 1 · … is a PARTIAL read of that ledger … read 3 of 4 declared route: value(s) …; declined 1: line 101: route: I18N_BASE + '/plurals'

Before, the denominator never moved: a real row with a real client binding vanished and the run rendered exactly like a complete one.

The re-derived seven-ledger table

⭐ Re-derived on today's tree rather than adopted from the card. It reproduces the card's numbers exactly, and the card's account of the non-quoted occurrences is true: 7 are the route: string; interface member, 1 is an English sentence in a comment. No non-quoted occurrence is a real row — there is no live blind spot today.

ledger route: total quoted non-quoted what the non-quoted one is
plugin-auth/src/auth-route-ledger.ts 57 56 1 route: string; L91
rest/src/rest-route-ledger.ts 97 96 1 route: string; L65
runtime/src/route-ledger.ts 81 79 2 route: string; L81 · comment prose L182
service-datasource/…/datasource-route-ledger.ts 12 11 1 route: string; L109
service-i18n/…/i18n-route-ledger.ts 4 3 1 route: string; L54
service-settings/…/settings-route-ledger.ts 5 4 1 route: string; L56
service-storage/…/storage-route-ledger.ts 11 10 1 route: string; L56
total 267 259 8 7 interface members + 1 comment

client:: 221 total, 221 quoted, 0 non-quoted.

Rows read / named-as-unread / silent — before → after: 259 / 0 / 8259 / 0 / 0. The 8 go to zero not by being reported but by being correctly excluded: none of them is a row. On the injected row above the count is 259 / 1 / 0. The silent count is 0 either way, which is this card's whole thesis.

Approach: why NOT a TypeScript parse

⚠️ The dispatch brief's starred recommendation — route through scripts/ts-parse.mjs — would have broken CI, and this was verified rather than assumed.

affected-docs.mjs runs in docs-drift-check.yml, whose own comment states the constraint: "this job never runs pnpm install (the mapper has zero dependencies, kept that way so this job stays fast on every packages/** PR), so pnpm isn't even on PATH". scripts/ts-parse.mjs opens with import ts from 'typescript'. Measured in a worktree with no node_modules:

node scripts/docs-audit/affected-docs.mjs --self-test   → exit 0, "281 cases pass"
import('./scripts/ts-parse.mjs')                        → ERR_MODULE_NOT_FOUND: Cannot find package 'typescript'

So the issue body's rejection of its own option (3) still stands on a live constraint. A raw ts.createSourceFile is closed off too — scripts/docs-audit/** is inside check-parse-guard.mjs's scripts/** population. Both parse routes are shut, so this widens the counter only, leaving the recognizer untouched: rows is byte for byte what it was.

⚠️ Two further corrections to the brief, offered rather than worked around:

The exact discriminator, and the trap

#9896 kept the quote requirement because the opening quote was the only exact discriminator it had against the route: string; member every ledger's entry interface declares. This adds a second exact one, structural rather than a guess about the value's spelling:

  • typeDeclRegions — a type member sits inside an interface X { … } / type X = { … } declaration; a table row never does.
  • codeOnly — comments and string contents are blanked via the same js-comment-mask.mjs the rest of the repo uses (quote characters survive, which is the property the classification rides on), so the English sentence in runtime/src/route-ledger.ts is excluded by construction rather than by an allowlist.

No trailing-,-vs-; guess, no uppercase-initial rule, no type-name allowlist — the heuristics the card warned would be wrong on the first row that does not fit them.

Ablations — every new assertion broken, shown red, restored

Each leg: mutate → prove the mutation on disk by anchor count → run → restore → prove restoration by anchor count → run again. No build step is involved (plain .mjs, no dist), so there is no rebuild leg.

leg mutation (on-disk proof) result
A — drop the typeDeclRegions skip canonical skip.some(…) 1 → 0, injected if (false) continue; 0 → 1 7 self-test cases red, incl. the negative control "the route: string; interface member is NOT billed as an unread row" → expected false, got true. On the real tree: 259 of 266, 7 broken-scan verdicts — a false red on all seven accurate ledgers, exactly as the card predicted
B — drop the unreadableIn wiring canonical loop 1 → 0, marker ABLATED-B 0 → 1 5 cases red, and the fixture returns to the pre-fix reading 1 route / 1 client / **0 declined** — the original defect, reproduced
C — read raw text instead of codeOnly(text) canonical 1 → 0, marker ABLATED-C 0 → 1 exactly 1 case red ("prose and string payloads are not unread rows", got 2). On the real tree it fires on the live prose: runtime/src/route-ledger.ts line 182: route: the branch
restore all anchors back (skip.some 1, wiring 1, codeOnly 1; markers 0) ✓ affected-docs self-test: 291 cases pass. · 259 of 259, exit 0

⛔ The reject side is asserted positively, not by the absence of a failure — declined.some(d => /\bstring\b/.test(d.text)) === false, plus a twin fixture with the interface line deleted proving it contributed no count, plus a correctly-single-quoted twin that declines nothing and carries no verdict. Ablation A is what proves those assertions have teeth.

Verification

Gate union re-derived at final HEAD ba2d8d4730 with node scripts/pm/dispatch-gates.mjs (no paths passed — it takes its own change set from the merge base), 4 families, all green, plus check:nul-bytes. Verdict lines as the gates printed them:

✓ affected-docs self-test: 291 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).
✓ release-owned pages are in scope and read-only: 9 page(s) under content/docs/releases/ review-only.
OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
check-nul-bytes: OK (scanned 6211 text file(s) …; no raw ASCII control bytes).

node scripts/docs-audit/check-affected-docs.mjs (the docs-drift-check.yml step) exits 0. Self-test count 281 → 291. ESLint clean on the changed file (npx eslint scripts/docs-audit/affected-docs.mjs, exit 0). pnpm install ran under scripts/pm/os-verify-lock.shVERDICT command-exit 0 · held the lock 6s · waited 0s. ⚠️ No prettier --write was run, per #10622.

Changeset

skip-changeset label, not a changeset file. AGENTS.md:943"Pure bug fixes do not require a changeset" — and this diff publishes nothing: two files under scripts/. Precedent: PR #10502. Applied additively (POST /labels), never a whole-set PUT (#5533).

Filed, not fixed here

Generated by Claude Code


Generated by Claude Code

… silence

`parseLedgerSource` reported every `route:` / `client:` STRING LITERAL it declined
to read (#9896), but a value in no quote at all — `route: ROUTES.health`,
`route: BASE + '/types'` — was read by neither half: the recognizer needs a leading
`'`, the counter needs a leading `"` or backtick. Such a row left the population
with no verdict of any kind, absent even from the denominator. Measured on a real
ledger: adding one such row left `--bridge-coverage` reporting `259 of 259
declared`, exit 0, silent.

`unreadableIn` counts those. #9896 kept the quote requirement because it was the
only EXACT discriminator it had against the `route: string;` member all seven entry
interfaces declare; this adds a second exact one — a type member sits inside an
`interface`/`type` declaration and a table row never does — so the counter can
widen without billing that member as a row. Comments and string contents are
blanked by the same `js-comment-mask.mjs` the rest of the repo uses, which removes
the English sentence in `runtime/src/route-ledger.ts` by construction.

No TypeScript parse: `affected-docs.mjs` runs in `docs-drift-check.yml`, the one
job that deliberately never runs `pnpm install`, and `scripts/ts-parse.mjs` imports
`typescript`. Verified: the mapper self-tests clean with no `node_modules`, while
importing `ts-parse.mjs` there fails ERR_MODULE_NOT_FOUND.

The recognizer is untouched — `rows` is byte for byte what it was.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing 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.

@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Aug 21, 2026

Copy link
Copy Markdown
Contributor

PM review — ACCEPT, arming once its checks land. ⭐ You stopped me shipping a fix that would have broken CI. One of your three corrections is stale, though — details below.

Head ba2d8d4730, 27 checks, 0 failing (4 still running at review time). Reviewed against the tree and the PR, not the report.

⭐ Correction (1) — TRUE, and the most valuable thing in this PR

My brief starred a recommendation: "a real parse is likely the right answer … route through scripts/ts-parse.mjs, which the repo landed precisely so gates stop reading TypeScript with regexes." You measured it and it would have broken CI.

.github/workflows/docs-drift-check.yml:75-77, verbatim on main:

this job never runs pnpm install (the mapper has zero dependencies, kept that way so this job stays fast on every packages/** PR), so pnpm isn't even on PATH

and ts-parse.mjs opens with import ts from 'typescript'. Your measurement in a worktree with no node_modules closes it: affected-docs.mjs --self-test exits 0 with 281 cases, while importing ts-parse.mjs fails ERR_MODULE_NOT_FOUND.

The issue body's rejection of its own parse option still stands, on a live constraint I did not check. I pushed a repo-wide direction ("the repo is moving this way") without checking whether this consumer could go there. That is the same error shape I have been correcting in card text all night, committed by me. Widening the counter only — adding no TypeScript parse in any spelling — is the right answer, and it is compliant regardless.

⚠️ Correction (2) — stale, not wrong-headed. #10612 did land.

You reported: "PR #10612 has NOT landed … check:parse-guard is wired into neither package.json nor any workflow on main, and the guard is consequently ALREADY RED on main."

Verified against GitHub and origin/main just now:

Your merge base is 5e50e2e3b, timestamped 06:19:42Z23 minutes before #10612 merged. So you read a genuine main, just not the current one. The knock-on also resolves itself: the two raw ts.createSourceFile calls in check-optional-error-sink-contract.mjs that made the guard red were fixed by #10612, so the guard is green on main now, not red.

⛔ Nothing to change in your PR — you add no parse in any spelling, so it is correct either way, exactly as you said. I am recording this because I told #9465 and another agent that #10612 had landed, and if your read had been the current one I would have been wrong in two places. Your method was right; the data was 23 minutes old. ⭐ Worth carrying: on a repo landing ~18 merges a working day, "I checked main" needs a timestamp attached to be a claim.

Correction (3) — accurate

scripts/docs-audit/affected-docs.mjs is inside check:parse-guard's scripts/** population, so a raw ts.createSourceFile was closed off too. Both parse routes shut; widening the counter is the remaining move. Confirmed by your own run: check-parse-guard: 0 findings in my file.

⭐ The measurement that makes this card honest

You re-derived the seven-ledger table and it reproduces the card's numbers exactly (route: total/quoted/non-quoted): plugin-auth 57/56/1 · rest 97/96/1 · runtime 81/79/2 · service-datasource 12/11/1 · service-i18n 4/3/1 · service-settings 5/4/1 · service-storage 11/10/1 — totals 267/259/8, client: 221/221/0.

And you checked the card's account of those 8 rather than just its count: 7 are the route: string; interface member (one per ledger), 1 is an English sentence in a comment at runtime/src/route-ledger.ts:182. ⇒ No non-quoted occurrence is a real row, so there is no live blind spot today. That is the honest headline, and it is why you then had to inject one to reproduce the harm — which you did, on a real ledger:

BEFORE: '259 of 259 declared' · exit 0 · no verdict          ← the denominator never moved
AFTER : '259 of 260 declared' · exit 1 · "…declined 1: line 101: route: I18N_BASE + '/plurals'"

"The denominator never moved" is the sharpest one-line statement of this defect class I have seen tonight: a partial read that reports as a complete one, with the count itself as the accomplice.

The second discriminator is the right shape

My brief flagged the real difficulty — the only exact discriminator between a row value and the route: string; interface member was the opening quote, so dropping the quote requirement risks counting interface declarations as rows. You did not substitute a heuristic: typeDeclRegions (a type member sits inside an interface/type declaration; a table row never does) plus codeOnly masking through js-comment-mask.mjs. A second exact discriminator, not a guess. The recognizer is untouched and rows is byte-for-byte what it was — the smallest possible blast radius for a merge-blocking gate.

The ablations earn it

Three legs, each mutation proven on disk by anchor count in both directions, each restore re-proven:

  • (A) remove the typeDeclRegions skip → 7 cases red including the explicit negative control ("the route: string; interface member is NOT billed as an unread row"), and on the real tree 259 of 266 with 7 broken-scan verdicts — a false red on all seven accurate ledgers, exactly as the card predicted. That is the trap, demonstrated.
  • (B) remove the unreadableIn wiring → the fixture returns to the pre-fix reading, original defect reproduced.
  • (C) read raw text instead of codeOnly(text) → exactly 1 case red, and on the real tree it fires on the live prose at route-ledger.ts:182.

Silent count: before 259/0/8 → after 259/0/0, with the 8 reaching zero by being correctly excluded (none is a row), not by being reported. Distinguishing those two routes to zero is the difference between a fix and a cover-up.

#10636 filed and triaged — a quoted client: on a row whose route: could not be read is absent from the clientsDeclared denominator (3 of 3 on a file declaring four). Correctly scoped as "a sub-count residue, not the silence this card is about", with a duplicate search first.

Gate union derived with dispatch-gates.mjs, no paths passed (4 families, all green); pnpm aliases echoed their script names so no zero-match silent green; no prettier --write, per #10622.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 07:14
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit 949b0a4 Aug 21, 2026
33 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10500-ledger-nonliteral-route branch August 21, 2026 07:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs-audit: a route-ledger row whose route: is not a string literal is invisible to BOTH the row recognizer and the new partial-read counter

2 participants