fix(docs-audit): anchor the ledger route:/client: KEY in one place (#11542) - #11634
Merged
Conversation
#11542) Eight scans in `affected-docs.mjs` ask "is a `route:` / `client:` declaration written here?". `declLead` has spelled the colon and the run after it once since #11494, but the KEY stayed each call site's own argument: `declarationsIn` anchored it with `\b` and the other seven did not. So `subroute: 'GET /x'` was a declaration to SEVEN of the eight and not to the eighth, and it minted a silent phantom ROW — silent because the partial-read verdict keys on the gap between `rows` and `routesDeclared` and both terms read the unanchored spelling. The anchor now lives in `declLead`, once, and `declarationsIn` stops restating it. Priced against the `--bridge-coverage` before/after standard: 0 divergent leads across the seven live ledgers, and the census is byte-identical row for row (269 of 269 / 222 of 222 / 45 reachable / 177 UNREACHABLE / 0 prose-quoted leads / brokenScan 0). `--self-test` moves the pin #11584 left rather than deleting it, adds one fixture per previously-unanchored scan, and pins the `$route:` residue (#11630) where the next card will find it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
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-steve
marked this pull request as ready for review
August 24, 2026 10:16
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 free
to 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 #11542
scripts/docs-audit/affected-docs.mjshas eight scans that ask "is aroute:/client:declaration written here?". #11558 gave the run between the colon and the value one home (declLead) and deliberately left the key as each call site's own argument. OnlydeclarationsInanchored it with\b, sosubroute: 'GET /api/v1/gone'was a declaration to seven of the eight scans and not to the eighth, and it minted a silent phantom row.Silent for the reason #10683 and #10793 were: the partial-read verdict keys on the gap between
rowsandroutesDeclaredand both terms read the unanchored spelling, so both moved together and no verdict fired.outsideCodecould not see it either — the lead genuinely is in code position.declarationsIn, the one scan that got it right, sat on the side of the ledger where being right shows up only as a shortfall, which is exactly what that arithmetic hides.The anchor now lives in
declLead, once.declarationsInstops restating it.All eight scans read the same anchored spelling — 1 of 8 → 8 of 8
The #11558-equivalent measurement, taken by extracting the real
new RegExp(declLead(…))call sites out of the file and evaluating them through the file's owndeclLeadbody, on both trees:sourceorigin/main3637731e2\b(route|client)\s*:\s*×1 ·(route|client)\s*:\s*×2 ·route\s*:\s*×3 ·client\s*:\s*×1 ·(?:route|client)\s*:\s*×1505ecc609\b(route|client)\s*:\s*×3 ·\broute\s*:\s*×3 ·\bclient\s*:\s*×1 ·\b(?:route|client)\s*:\s*×1After the change the four remaining spellings differ only in the key alternation each scan's own question needs (capturing / bare / non-capturing); the anchor and the colon run are byte-identical across all eight. Pinned three ways in
--self-test, so a ninth scan or a call site that restates the\bfails there rather than drifting green:String.raw\b${keys}\s*:\s*``, exactly 1 occurrence);\b(exactly 0 occurrences);Priced against the
--bridge-coveragebefore/after standardAnchoring removes rows from the measured population, which is why #11542 was filed rather than folded into #11558. Priced on the tree where the move is provably free, and proven at row granularity rather than counter granularity:
3637731e2(unanchored)505ecc609(anchored)rowsParsed/routesDeclaredclientRows/clientsDeclaredreachableunreachableleadsOutsideCodebrokenScantails/ ledger files / registrar filesWhich rows move: none.
--bridge-coverage --json— which carries all 177unreachableRowsby{file, route, client}, not just the counts — is byte-identical across the change:git hash-objectgivesd04a5cedfb613370e5b46ac4725db1d941e5dc88for the base tree, for the ablated (unanchored) branch tree, and for the fixed tree. The human-rendered output diffs empty too. So no row — phantom or genuine — enters or leaves any population on today's seven ledgers, and the reach ratio is untouched.The direct reason: across the seven live ledgers there are 0 divergent leads — every match of
(route|client)\s*:\s*over raw text is also a match of\b(route|client)\s*:\s*at the same index. Same "provably free tree" argument #10683 and #10793 each made explicitly for their own population moves, made explicitly here for the same reason.The pin #11584 left is moved, not deleted
--self-testpinned today's phantom behaviour as deliberately unmoved so that whoever closed this card would flip an existing pin. It is flipped, and the boundary comment above it is rewritten to record what moved.Seven previously-unanchored scans, one fixture each — because "all eight agree now" is a claim about seven behaviours, and anchoring one more while leaving six is this defect with a smaller denominator. Every one of the seven is observed failing with the anchor reverted (see the ablation below); the eighth is kept as a control that was green both ways.
Not all seven are counting errors. The worst is the window delimiter: the row window is delimited by the same lead, so a
subroute:written between a realroute:and itsclient:closed the real row's window and handed the binding to the phantom —— a wrong binding, on a path no registrar mounts, joining the UNREACHABLE population while the real row loses the binding it plainly declares. A count comparison is blind to it by construction (the same shape #10636 measured for the quote spellings). The in-window
client:match had its own version:window.match()takes the first hit, so amyclient:ahead of the realclient:became the binding (wrong.binding) and the real one was then named by #10636's unclaimed sweep as a value no row read. AnddeclinedIngave the loud direction — a double-quotedsubroute:was billed as aroute:the parse failed to read, firing a PARTIAL-read verdict with exit 1 on a wholly accurate ledger.Reverse verification (ablation), mutation proven on disk both ways
Both lines the fix moved were reverted to their exact pre-fix spelling, under
trap … EXIT INT TERM. The mutation was confirmed on disk by counting the injected and the removed text separately (not by a barediff --stat, which a same-round edit can turn green), and the restore was confirmed byte-identical by hash:Direction predicted before running: turn red, on the new pins only. Observed: 12 checks fail, and each
gotvalue is the before state.The
$route:pins did not fail in the ablation, which is correct and deliberate — see below. Non-vacuity in the other direction is carried by the same fixtures: the genuineroute: 'GET /api/v1/meta'is still read, still carriesmeta.getTypes, and the 269 live rows are unchanged.The boundary this card does not cross — filed, and pinned
\bfails only against a preceding word character, so$route:— a legal JS identifier — is still read as a declaration. It is no longer a divergence, which is what this card was about: all eight scans agree on it now, and they agree by reading the spellingdeclarationsInalready had. It is still a phantom row. Closing it means widening the anchor from\bto a negative lookbehind that also excludes$— the idiomrulePatternFor,symbolReanddottedRealready use in this same file. (Spelled verbatim indeclLead's docblock and in the--self-testboundary comment, not here: GitHub's body sanitizer strips the<+!digraph out of an issue or PR body — inside a code span and inside a fenced block alike — and silently leaves a different, valid-looking regex behind. Measured twice on this PR.) That widening movesdeclarationsIntoo — the one scan this card's before/after was priced to leave byte-identical — so it is a second population move with its own before/after to price.Filed as #11630 and pinned in
--self-testas deliberately unmoved, so that card flips a pin rather than finding none.Verification
Base
origin/main3637731e2; measured and re-measured on final commit505ecc609.Derived gate union —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths; change set taken from the merge base by the script itself), 9 families, pluscheck:nul-bytesand the self-test directly. Exit codes captured before any pipe; each verdict below is the line the gate itself printed.pnpm check:cross-package-test-inputsOK: 16 package(s) read outside themselves, all declared…pnpm check:docs-audit-scope✓ docs-accuracy-audit scope is in sync with content/docs/: 189 hand-written doc(s).pnpm check:entry-guard✓ check:entry-guard: 142 scripts/ file(s) — every entry guard goes through invoked-as.mjspnpm check:parse-guard✓ check:parse-guard: 141 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.pnpm check:pm-governed-merges✓ check-governed-merges --self-test: 129 assertionspnpm check:pnpm-filter-targets✓ check:pnpm-filter-targets: 120/148 --filter occurrence(s)… resolvenode scripts/check-ci-filter-parity.mjsOK: all 95 declared cross-package glob(s) (80 unique) are covered…node scripts/check-cross-package-test-inputs.mjsOK: 16 package(s) read outside themselves, all declared…node scripts/docs-audit/check-affected-docs.mjs✓ affected-docs self-test: 395 cases pass.pnpm check:nul-bytes✓ check-nul-bytes --self-test: 75 assertions over a temp git repoaffected-docs.mjs --self-test✓ affected-docs self-test: 395 cases pass.(base: 381 — +14)Lint — a declared, measured narrowing, not a skip. Repo-wide
eslint .is CI's run; the narrowing is proven rather than assumed, with all three pieces of evidence:ESLint#isPathIgnoredsaysscripts/docs-audit/affected-docs.mjsis in the population (2 resolved rules) andscripts/docs-audit/README.mdis not (ignoredByEslintConfig=true).--format json: 2 entries,errors=0 warnings=1; the one warning isFile ignored because no matching configuration was supplied.on the README — ESLint's notice about an out-of-population path, not a finding. Exit 0.eslint.config.mjsand it never enables type-aware linting for any file — noparserOptions.project, noprojectService, no typed@typescript-eslintrules (0 occurrences; the config's own header documents the measurement with a positive control). A two-file diff therefore cannot move any untouched file's verdict.No changeset:
scripts/**publishes nothing (the root package is private and ships nofiles), matching every prior PR in this family (#11558, #11500, #10900, #10795).skip-changesetapplied.Generated by Claude Code
Generated by Claude Code