Uh oh!
There was an error while loading. Please reload this page.
fix(docs-audit): widen the ledger key anchor past the word boundary (#11630) - #11710
Conversation
…okbehind `declLead` anchored the `route:`/`client:` key with `\b`, which fails only against a preceding WORD character. `$` is not one, and `$route` is a legal JS identifier, so `$route:` was read as a declaration by all eight lead scans — agreed, and agreed by being wrong together. The anchor is now `symbolRe`'s set rather than `dottedRe`'s, because `declLead`'s key is a BARE token: a preceding `$` continues an identifier, and a preceding `.` makes the token a member access whose colon belongs to a ternary. Both minted phantom rows; the character class closes the class instead of enumerating escapees. `-` is deliberately left out — `a-route` is two tokens, so that `route` is the whole token and is not a declaration for a different reason, one it shares with the bare `cond ? route : x` no lookbehind can reach. Unlike the previous move, this one also moves `declarationsIn` — the eighth scan, left byte-identical on purpose last time — so it is priced with its own before/after, at row identity rather than counter equality. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
📓 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
commented
Aug 24, 2026
ACCEPT. Verified by content, and both of my Zone 2 falsifications fired — which is the point of putting them there. The card named the wrong character class, and you caught itI warned that copying the precedent idiom blindly would give "a fix that is right about the mechanism and wrong about the class." That is exactly what would have happened: The card, and the docblock #11634 left behind, both pointed at And it found a second escapee nobody namedA preceding
The second population move — measured, not arguedThis is the part I flagged as harder than what it inherits, and it was done properly. #11634's before/after was priced to leave
Population 1 held to #11634's standard too: ⭐ "Free on today's tree" re-derived rather than inherited — 499 live leads, every one preceded by a space, 0 by ⭐ The subset invariant swept, not asserted: over code points Pins went UPThe one #11634 left is flipped, three source pins updated, two new residue pins added. Nothing deleted — verified by count, not by assertion. ⭐ The self-test caught you, and you did the right thing with it
Bumping 8 to 9 would have been green in one minute and would have destroyed the only thing that catches a genuine ninth scan. Reporting that you were caught, rather than quietly fixing it, is what makes the rest of the report credible. Ruling on your open question — B, and it is my call, not the maintainer'sYou recommended B as the end state, A as the next step, and flagged B as a maintainer decision. I agree with the first two and take the third back. This is not a maintainer question. It is an implementation-approach choice inside one file on this lane's own surface — no product tradeoff, no fleet shape, no permission I lack — and you have already measured the thing that decides it: all 499 live leads are preceded by a space, so an allowlist is provably free on today's tree by the same argument each lookbehind card has used. Escalating a decision I have the evidence to make would be padding a queue whose four remaining items all genuinely need a human. So: B is the end state. I am filing it as its own card now, with your measurement as its affordability argument, and ⛔ #11711 is blocked on it rather than dispatched — B subsumes the Unicode residue, and shrinking one more character class first is work B discards. A was the right next step and it is this PR. The chain stops here. Flipping ready; arming once every check is green. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Resolved scripts/docs-audit/affected-docs.mjs in favour of the key-position allowlist, taking main's version as the resolution base so #11710 (#11630) is carried forward rather than reverted: all of its scan-by-scan `$route:` and member-access fixtures are kept, and its two boundary pins (`-` and Unicode, both left deliberately unmoved there) are FLIPPED rather than duplicated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
Fixes#11630
declLeadinscripts/docs-audit/affected-docs.mjsanchored the ledgerroute:/client:key with a word boundary. A word boundary fails only against a preceding word character ([A-Za-z0-9_]).$is not one, and$routeis a legal JS identifier, so$route:was read as a declaration by all eight lead scans — they agreed, and they agreed by being wrong together. That is exactly the "agreed-and-wrong" end state #11542's card rejected on sight when it was proposed as the other direction for the key.The anchor is now a negative lookbehind, still spelled once, in
declLead. It is spelled verbatim indeclLead's docblock and in the--self-testboundary comment, and deliberately not here: GitHub's body sanitizer strips the less-than + bang digraph out of an issue or PR body — inside code spans and fenced blocks alike — and silently leaves a different, valid-looking regex behind. Measured twice on #11634.The character SET is the load-bearing half — and it is not the one the card named
The card and the old docblock both named the excluded set as word characters plus
$. That isdottedRe's set, and it is the wrong analogue. Three lookbehind idioms already live in this file and they exclude three different classes:symbolRe$,.dottedRe$rulePatternFor/commandPatternFor$,.,--glues tokens in EnglishdeclLead's key is a bare token, sosymbolRe's set is the analogue and the shipped set is word characters,$and.. Copying the nearest idiom blindly is how a fix comes out right about the mechanism and wrong about the class..was found by measurement, not assumed from the card: a.before the key makes the token a member access, and the colon then belongs to a ternary and never to a key.cond ? defaults.route : 'GET /api/v1/gone'minted a phantom row on a path nobody declares. Pinned as its own fixture.-is deliberately left out, which is the one place this departs fromrulePatternFor.a-routeis two tokens (a - route), so thatrouteis the whole tokenroute— it is not a declaration for a different reason (expression position), and that reason is shared with the barecond ? route : xthat no lookbehind can reach. Excluding-would close one spelling of that class while leaving its plainest spelling open: enumerating escapees wearing a character class. Pinned as still-admitted, deliberately.It can only ever REMOVE — swept, not argued
The key alternation always opens with a word character, so the old word boundary failed exactly when the previous character was a word character, which makes the new anchor a strict subset. Swept over code points
0..0x2FFF:Exactly 2 characters move:
$and.. Both numbers are pinned in--self-test, so a future widening that admits something the old anchor did not fails there.TWO populations priced, not one — this is the harder move
declarationsInbyte-identical. Widening past the word boundary movesdeclarationsIntoo. That is a second population with its own before/after, and it is priced separately below. This is not the same measurement as #11634's and is not reported as such.Both are priced at row identity, not counter equality — counters agreeing is consistent with two rows swapping places.
Population 1 — the ledger rows (
--bridge-coverage)ffbb7a1006de837d30rowsParsed/routesDeclaredclientRows/clientsDeclaredreachable/unreachableleadsOutsideCodebrokenScanWhich rows move: none.
--bridge-coverage --jsoncarries all 177unreachableRowsby{file, route, client}, andgit hash-objectgivesd04a5cedfb613370e5b46ac4725db1d941e5dc88on all three trees — base, ablated and fixed. Set difference over{file, route, client}is 0 in each direction. The human-rendered output diffs empty. That is the same hash #11634 reported, so this ledger population has not moved since it merged.Population 2 —
declarationsIn, the eighth scan (NEW to this card)Measured by slicing
declarationsInand its dependency closure (codeOnly,typeDeclRegions,declLead— walked, not hand-listed) out of the file and running the file's own bodies over the seven live ledgers:git hash-objectffbb7a1007f89c810a6060a3a2755f16a00295f42774749e37f89c810a6060a3a2755f16a00295f42774749e36de837d307f89c810a6060a3a2755f16a00295f42774749e3Positive control — the instrument is sensitive, and proven so before the zero was believed. Run over a synthetic ledger carrying both escapees, the same harness reports 5 declarations on the base tree and 3 on this branch, and the diff names exactly the two that disappear:
Why both are free on today's tree — re-derived, not inherited from #11634
Across the seven live ledgers there are 499
route:/client:lead occurrences, and every one of them is preceded by a space: 0 preceded by$, 0 by., 0 by any non-word character at all. Positive control: the same tally reports$and.correctly (and a word character as blocked) the moment a fixture carries them, so the zero is a reading and not a blind scan.The pin is FLIPPED, not deleted
--self-testpinned$route:'s behaviour as deliberately unmoved so this card would flip an existing pin. Both pinned assertions are moved, and the boundary comment above them is rewritten to record what moved — the same treatment #11542 gave the pin #11584 left it.a `$route:` still mints a phantom row — deliberately unmoveda `$route:` mints NO row, 1 rowand it is still SILENT — all eight scans agree, so both terms move together2 route / 0 declinedand the DENOMINATOR drops it too,1 route / 1 client / 0 declinedthe run between a colon and its value is spelled ONCEand the KEY anchor is spelled once too — no call site restates itevery key spelling a call site passes comes back ANCHOREDTwo new boundary pins are added in the same shape, so the next card of this family moves a pin rather than finding none:
-still admits, and a Unicode identifier character still admits (\wis ASCII-only, soéroute:passes exactly as it did before — closing it means a\p{L}class under theuflag, which changes escape semantics for every source these leads are composed with at the eight call sites). 0 occurrences of either across the seven live ledgers.Reverse verification (ablation) — mutation proven on disk, restore under a trap
The one line the fix moved was reverted to its exact pre-fix spelling, under
trap … EXIT INT TERMarmed before the mutation.affected-docs.mjsis run directly from source bynode— there is nodist/in its path, so no rebuild leg is involved and none is claimed. Mutation confirmed on disk by counting the injected and the removed text separately, anchored on the exact text meant to move (a barediff --statgoes green on any same-round edit), plus hashes:Direction predicted before running: turn red on the new pins, and a named set of controls stay green. Observed exactly that —
ABLATED_SELFTEST_EXIT=1, 16 checks fail, and eachgotvalue is the before state:The controls that stayed green are the point, not an omission — each is a fixture that must be green both ways, and a red one would mean the fixture, not the fix, was doing the work:
a `-`-prefixed lead still mints a rowanda UNICODE-prefixed lead still mints a phantom row— the two boundaries this card deliberately does not cross.and the widened read carries no verdict on an accurate ledger(brokenScan 0) andand a `$route:` in CODE position is not reported as a prose-quoted lead— this is the silence the card is about: both terms move together, so no verdict ever fired.and the KEY anchor is spelled once too— 0 restatements on both trees.the anchor only ever REMOVES— 0 on both trees, since the ablated anchor is the one being compared against.and the real row keeps its binding across it(the.fixture) — the real row is ahead of the phantom there, so it keeps its binding either way.Non-vacuity in the other direction is carried by the same fixtures: the genuine
route: 'GET /api/v1/meta'is still read, still carriesmeta.getTypes, and the 269 live rows are unchanged.Ablated row identity is also reported: both populations hash identically to base and fixed under ablation (
d04a5ced…and7f89c810…), which is what makes the "free on today's tree" claim a three-way measurement rather than a two-way one.Verification
Base
origin/mainffbb7a100; measured and re-measured on final commit6de837d30.Gate union derived, not recalled —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no paths; the script takes the change set from the merge base itself), 9 families, pluscheck:nul-bytesand the self-test directly. The derivation is fresh for this card: it namescheck:agent-test-spelling, which #11634's run did not carry. Exit codes captured before any pipe; each verdict below is the line the gate itself printed.pnpm check:agent-test-spelling✓ check-agent-test-spelling: 0 violations — 351 file(s) · 3319 bare -- token(s)…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: 143 scripts/ file(s) — every entry guard goes through invoked-as.mjspnpm check:parse-guard✓ check:parse-guard: 142 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: 134/167 --filter occurrence(s)… resolvenode 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: 413 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: 413 cases pass.(base: 395 — +18)Lint — the full repo-wide run, not a narrowing.
pnpm lint(eslint . --no-inline-config) was run in full on6de837d30: exit 0, no findings, 54s under the shared verify lock. No narrowing is claimed and none is needed.No changeset — checked against the actual rule, not inherited.
changeset-checkin.github/workflows/pr-automation.ymlhas no path exemption; theskip-changesetlabel (or the changesets release PR) is the only route. This diff is one file underscripts/, which publishes nothing: the root package isprivate: truewith nofilesfield,scripts/is not inside anypnpm-workspace.yamlglob, and it has nopackage.jsonof its own.skip-changesetapplied and read back.Generated by Claude Code