Repository navigation
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 #10500
Description
Activity
Triage:
finding+domain:devx+tooling, type Task — observation class: measured 0 occurrences today, the boundary is declared in--self-testand README, and #9896 (in flight) already makes the string-literal half loud. Awaiting first-touch grading. Direction worth carrying to that grading: the card's option (2) — an ESLint rule over**/*route-ledger.tsrequiring single-quoted literalroute:/client:values — converts the scanner's assumption into an authoring constraint, which is the enforceable end; option (3) is rejected by the scanner's own dependency-free premise.
Generated by Claude Code
Claim:
/pm-dispatchdomain:devx execution seat (bug-first round).
Session:session_01DdCnBGcHeufjrq7drTD3wt
Branch:claude/issue-10500-ledger-nonliteral-route
Worktree:objectstack-issue-10500
Domain:domain:devx
File surface:scripts/docs-audit/affected-docs.mjs(parseLedgerSource/bridgeCoverageFrom) + its--self-testfixtures⛔ Governed surface out of scope for the diff (
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md,content/docs/releases/**) and the #9465 epic fence (.changeset/tooling,cut-rc.yml,release.yml, rootpackage.json).
⭐ Changeset:AGENTS.md:943— "Pure bug fixes do not require a changeset"; ascripts/**-only diff that publishes nothing takes theskip-changesetlabel via the additivePOST /labels(⛔ never a whole-set PUT — see #5533). Precedent: PR #10502.
Generated by Claude Code
{ "issue": 10500, "status": "done", "branch": "claude/issue-10500-ledger-nonliteral-route", "pr": "https://github.com/objectstack-ai/objectstack/pull/10637", "premise_still_valid": true, "summary": "The card's premise holds and was reproduced on a real ledger before any fix: a row spelled `route: I18N_BASE + '/plurals'` in i18n-route-ledger.ts left --bridge-coverage reporting `259 of 259 declared`, exit 0, no verdict -- the denominator never moved. parseLedgerSource now counts every `route:`/`client:` value that is a literal in NO quote (unreadableIn), so such a row is named as unread with its file, line and expression. The recognizer is untouched; `rows` is byte for byte what it was. The trap the quote heuristic guarded -- the `route: string;` member all seven entry interfaces declare -- is excluded on a SECOND exact discriminator rather than a guess: typeDeclRegions (a type member sits inside an interface/type declaration; a table row never does), plus codeOnly comment/string masking via js-comment-mask.mjs. THREE BRIEF CLAIMS WERE FALSE and are reported rather than worked around. (1) The brief's starred recommendation -- 'a real parse is likely the right answer' via scripts/ts-parse.mjs -- would have BROKEN CI: affected-docs.mjs runs in docs-drift-check.yml, the one job that deliberately never runs `pnpm install` (its own comment says so), and ts-parse.mjs opens with `import ts from 'typescript'`. Measured in a worktree with no node_modules: 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 option (3) still stands on a live constraint. (2) PR #10612 has NOT landed -- it is open, not in the merge queue; check:parse-guard is wired into neither package.json nor any workflow on main, and the guard is consequently ALREADY RED on main (2 raw parses in check-optional-error-sink-contract.mjs). Only the guard FILE landed, via #10573. (3) affected-docs.mjs IS in the guard's scripts/** population, so a raw ts.createSourceFile was closed off too -- both parse routes shut, which is why this widens the counter only. This PR adds no TypeScript parse in any spelling and is compliant whichever way #10612 lands.", "tests": "All at final HEAD ba2d8d4730. Gate union re-derived with `node scripts/pm/dispatch-gates.mjs` NO PATHS PASSED (it took its own change set from merge base 5e50e2e3b; committed 2, working tree 0) -- 4 families, all green, plus check:nul-bytes which my standing rules require on any edit. Exit codes captured before any pipe (redirect-then-capture), and each verdict quoted from the gate's own printed line: `check:docs-audit-scope` EXIT=0 -> '/ 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).'; `check:cross-package-test-inputs` EXIT=0 -> 'OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; `node scripts/docs-audit/check-affected-docs.mjs` (the docs-drift-check.yml step) EXIT=0; `check:nul-bytes` EXIT=0 -> 'check-nul-bytes: OK (scanned 6211 text file(s) ...; no raw ASCII control bytes).'. pnpm aliases echoed their script names, so no zero-match silent-green. ESLint clean on the changed file (npx eslint, exit 0); no prettier --write, per #10622. pnpm install ran under scripts/pm/os-verify-lock.sh -> 'VERDICT command-exit 0 . held the lock 6s . waited 0s'. check-parse-guard: 0 findings in my file. Self-test 281 -> 291 cases. ABLATIONS -- three legs, each mutated, proven ON DISK BY ANCHOR COUNT (never an editor exit code), run, restored, restoration proven by anchor count again. NO REBUILD LEG EXISTS AND NONE IS CLAIMED: these are plain .mjs files with no dist/ and no build step, so nothing can be measured against a stale artifact. (A) removed the typeDeclRegions skip -- canonical `skip.some(...)` 1->0, injected `if (false) continue;` 0->1 -- 7 self-test cases red including the explicit negative control 'the `route: string;` interface member is NOT billed as an unread row' (expected false, got true), and on the REAL tree 259 of 266 with 7 broken-scan verdicts, i.e. a false red on all seven accurate ledgers exactly as the card predicted. (B) removed the unreadableIn wiring -- canonical loop 1->0, marker ABLATED-B 0->1 -- 5 cases red and the fixture returned 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), and on the REAL tree it fired on the live prose at runtime/src/route-ledger.ts line 182 'route: the branch'. Restore leg for all three: anchors back to 1/1/1, markers to 0, `/ affected-docs self-test: 291 cases pass.` and 259 of 259 exit 0. END-TO-END REPRODUCTION on a real ledger (mutation proven on disk, anchor I18N_BASE 0->1, restored to 0): BEFORE = '259 of 259 declared', '221 of 221 declared', exit 0, no verdict (silent); AFTER = '259 of 260 declared', exit 1, 'i18n-route-ledger.ts is a PARTIAL read ... read 3 of 4 declared `route:` value(s) ...; declined 1: line 101: route: I18N_BASE + '/plurals''. SEVEN-LEDGER TABLE re-derived on today's tree, reproducing 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. The card's account of the 8 is TRUE: 7 are the `route: string;` interface member (one per ledger) and 1 is the 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. Read / named-as-unread / SILENT: before 259/0/8, after 259/0/0 -- the 8 reach zero by being correctly EXCLUDED (none is a row), not by being reported; on the injected row it is 259/1/0. The silent count is 0 either way.", "open_questions": [], "out_of_scope_findings": [ "filed as #10636: a QUOTED `client:` on a row whose `route:` could not be read is absent from the clientsDeclared denominator -- measured `3 of 3` on a file declaring four. The row itself still lands in a verdict, so this is a sub-count residue, not the silence this card is about. Searched open issues first; no duplicate. Filed unassigned, labels tooling/domain:devx/finding, no pm:queue." ] }
Generated by Claude Code
Filed unassigned by the
os-devseat implementing #9896 (sessionsession_01DdCnBGcHeufjrq7drTD3wt, PR for #9896). Recording, not fixing — deliberately out of that card's scope, and the boundary is pinned in--self-testrather than left to be discovered.What
#9896 makes a PARTIAL ledger read loud:
parseLedgerSourceinscripts/docs-audit/affected-docs.mjsnow reports everyroute:/client:string literal it declined to read (double-quoted or backtick-quoted), andbridgeCoverageFromturns that into abrokenScanverdict naming the file, the line and the literal.That counter is exact for string literals and blind to everything else. A row written as
is read by neither half: the recognizer needs an opening
', and the counter counts only values that open with one of the three quotes. The row leaves the population with no verdict — the same silence #9896 closed for the quote spellings, one spelling further out.Why it was not closed in #9896
The only exact discriminator between a row value and the
route: string;member that every ledger's own entry interface declares is the opening quote. Measured ona718ee3ddacross all seven ledgers:route:totalplugin-auth/src/auth-route-ledger.tsrest/src/rest-route-ledger.tsruntime/src/route-ledger.tsservice-datasource/.../datasource-route-ledger.tsservice-i18n/.../i18n-route-ledger.tsservice-settings/.../settings-route-ledger.tsservice-storage/.../storage-route-ledger.tsEvery non-quoted occurrence is accounted for: 7 are the
route: string;interface member (one per ledger) and 1 is an English sentence in a comment inruntime/src/route-ledger.ts:182("It never named a mounted route: the branch"). So a naive "count everyroute:" check would red on all seven ledgers today — a false red on accurate files — and every heuristic that separates the two (trailing,vs;, an uppercase-initial value, a type-name allowlist) is a guess that will be wrong on the first row that does not fit it. #9896 chose to state the boundary rather than paper over it with one.How likely is this spelling?
Lower than the template literal #9896 addressed, but not zero. These modules are deliberately import-free (see the header of
i18n-route-ledger.ts: "It must stay import-free — the client-side guard imports it as a relative SOURCE file"), so a cross-module constant cannot land. A file-localconst BASE = '/api/v1/i18n'above the table can, and is the natural first move the moment a ledger's rows get long. Today: 0 occurrences.Direction, not a prescription
affected-docs.mjs --self-test("a non-literalroute:is out of scope for the counter, and so isroute: string;") and stated inscripts/docs-audit/README.md, so the next reader meets it rather than discovering it. Cost: the gap stays real.**/*route-ledger.tsrequiring everyroute:/client:value inside the exported table to be a single-quoted literal. This turns the scanner's narrowness into an authoring constraint, which is where it can be enforced exactly, and would subsume the docs-audit: a PARTIAL ledger parse is silent — one backtick-quotedroute:drops a row from the bridge population with no verdict and exit 0 #9896 verdict as a second line of defence rather than duplicate it.affected-docs.mjsis a dependency-free source scan precisely so it cannot fail to resolve in CI.(2) looks like the shape worth costing: it is enforcement at the point of writing, and it makes the scanner's assumption a declared rule instead of an implicit one.
Refs: #9896, #9572, #9433, #4690.
Generated by Claude Code