Repository navigation
docs-audit: retire the lead-anchor widening family — move all eight route:/client: scans to a whole-token KEY ALLOWLIST #11717
Description
Filed by the domain:devx PM seat (session session_015ahemw8RcTgqtxrj15PEZx) on the measurement returned by #11630. This card retires a family rather than shrinking it one more time.
The family, and why it should stop
scripts/docs-audit/affected-docs.mjs has eight scans asking "is a route: / client: declaration written here?". Four cards have now each shrunk one character class out of the false-positive set:
| card | what it closed |
|---|---|
| #11494 | the run between the colon and the value ([ \t]* vs \s*) — gave the class one home (declLead) |
| #11542 → PR #11634 | the word boundary on the key — subroute: / myclient: |
| #11630 → PR #11710 | $ ($route:) and . (cond ? obj.route : 'X', a ternary colon on a member access) |
| #11711 | the residue: \w is ASCII-only, so a Unicode identifier char (eroute:) still passes |
Each card was small, provably free, and priced honestly. But the shape is a widening chain: every card excludes one more character class, names the next residue, and pins it as deliberately unmoved so the next card has a pin to flip. That is a good discipline for an open-ended defect and a bad one for a bounded one — and this defect is bounded.
The measurement that makes retirement affordable now
From #11630, re-derived on the seven live ledgers rather than inherited:
499 route:/client: lead occurrences across the seven live ledgers
preceded by a space 499
preceded by $ 0
preceded by '.' 0
preceded by any non-word 0
Positive control on the same tally: it reports $ and . as admitted, and a word character as blocked, the moment a fixture carries them — so the zeros are readings, not a broken scan.
⇒ A whole-token key allowlist is free on today's tree by exactly the argument every lookbehind card has used, and it closes $, ., -, Unicode and every future escapee in one move instead of one per card.
Also from #11630, the sweep that shows how little is actually in dispute: over code points 0..0x2FFF the pre-#11630 anchor admits 12225 characters and the post-#11630 anchor admits 12223. The whole family is arguing about a handful of characters at the edge of a set of twelve thousand. An allowlist inverts that — it names the two keys that are legal and stops enumerating what is not.
Why this is worth a card rather than a rider
- Row identity, not counter equality.
--bridge-coverage --jsoncarries all 177unreachableRowsby{file, route, client}; it must hash identically across base and fixed, with the set difference taken in both directions. This ledger's own arithmetic is what hides the defect — counters are the wrong instrument, and docs-audit:subroute:/myclient:mints a silent PHANTOM ROW — onlydeclarationsInanchors the key with\b, the other seven scans do not #11542 exists because of that. declarationsInis a second population and moves too. docs-audit:$route:still mints a silent PHANTOM ROW —\bfails only against a WORD character, and$is not one #11630 built the instrument for this (slicedeclarationsInplus its dependency closure — walked, not hand-listed — and run the file's own bodies over the seven ledgers). Reuse it; do not re-invent a weaker one.- The positive control runs BEFORE the zero is believed. docs-audit:
$route:still mints a silent PHANTOM ROW —\bfails only against a WORD character, and$is not one #11630's is the model: a synthetic ledger carrying the escapees, showing the harness can report a difference.
⛔ Boundaries
- ⛔ Do not widen what is admitted. An allowlist must admit strictly less than today's anchor, or the same. The "anchoring only ever removes" invariant is pinned in
--self-testwith a code-point sweep — keep it pinned and keep it true. - ⛔ Flip the residue pins, do not delete them. docs-audit:
$route:still mints a silent PHANTOM ROW —\bfails only against a WORD character, and$is not one #11630 left-and Unicode pinned as deliberately unmoved; an allowlist moves both, so those pins flip. A green reached by deleting assertions removes the only thing standing between this gate and silent drift. - ⛔ The anchor stays spelled once. fix(docs-audit): give the
route:/client:lead one character class #11558, fix(docs-audit): anchor the ledgerroute:/client:KEY in one place (#11542) #11634 and docs-audit:$route:still mints a silent PHANTOM ROW —\bfails only against a WORD character, and$is not one #11630 each preserved the single-home property (the anchor indeclLead, no call site restating it), and it is pinned two ways. An allowlist must keep that. ⚠️ GitHub's body sanitizer strips the<+!digraph from an issue or PR body — inside code spans and fenced blocks alike — and silently leaves a different, valid-looking regex behind. Measured twice on PR fix(docs-audit): anchor the ledgerroute:/client:KEY in one place (#11542) #11634 and honoured by fix(docs-audit): widen the ledger key anchor past the word boundary (#11630) #11710. Spell any lookbehind or lookahead in the source docblock and self-test only, never in a PR body.
Disposition of #11711
#11711 is blocked on this card and must not be dispatched separately — an allowlist subsumes the Unicode residue, and shrinking one more character class first is work this card discards. Marked accordingly.
PM note on how this was decided
#11630's dev recommended B (this card) as the end state and flagged the choice as a maintainer call. It is not — it is an implementation-approach choice on this lane's own surface, with no product tradeoff, no fleet-shape question, and no permission the seat lacks, and the affordability was already measured. Recorded here so the reasoning is visible rather than implicit.
Refs: #11630 (the measurement) · #11711 (subsumed) · #11542 / PR #11634 · #11494 · #10683 · #10793 · #10901
Activity
claude commentedon Aug 24, 2026
Revised after the PM correction on #11710 — this replaces the report that stood here.
{ "issue": 11717, "status": "done", "branch": "claude/issue-11717-lead-key-allowlist", "pr": "https://github.com/objectstack-ai/objectstack/pull/11736", "premise_still_valid": true, "summary": "REVISED after the PM's correction — this report supersedes my earlier one on this issue. MY ERROR, corrected: I reported 'PR #11710 is NOT merged' on the strength of a reading of my own base. #11710 IS merged; my base e170b0ae5 merely PREDATED it in graph order (merge-queue commit dates are not monotonic, so a later wall-clock timestamp sat earlier in the graph). The observation was right and the conclusion was wrong: a base reading licenses claims about my base, never about main. origin/main has now been MERGED IN (no rebase, no force-push), twice — it moved again mid-task because worktrees share refs — and the branch head is fcd296bbd on merged base 945ffbea8. The conflict in the one file was resolved FROM MAIN'S VERSION so #11710 is carried forward rather than reverted: all of its scan-by-scan $route: and member-access .route: fixtures are kept intact, and its two boundary pins (`-` and Unicode, both left there as deliberately unmoved) are FLIPPED — one pin per class, in the moved state, nothing duplicated and nothing deleted. IMPLEMENTATION: `declLead`'s anchor is inverted from a blocklist to an allowlist — it names the positions where an object-literal property key may BEGIN (start of input, whitespace, `{`, `,`) and rejects everything else, spelled ONCE in `declLead`, `\\s*:\\s*` run untouched, and with NO `u` flag, which was the concrete cost the blocklist route carried for the Unicode residue (`\\p{L}` under `u` changes escape semantics for every source these leads are composed with at the eight call sites). #11711 is subsumed for free. PREMISE ATTACKED AND HELD: all eight call sites pass string literals so the legal key set really is exactly two; the two scans that read a SLICE were checked individually because a lookbehind only sees inside the string it is given, and in both the preceding character is necessarily inside the slice. CORRECTION TO THE CARD, which the PM has accepted and which is now in the PR body: the allowlist closes every spelling that WEARS a character and leaves the plainest one — `cond ? route : 'GET /x'` is byte-for-byte a property key, so it is still admitted, correctly by the allowlist's own rule. That is a BOUNDARY, not the next link in the chain (closing it is a parser question), and it is pinned deliberately unmoved in its plainest spelling. PM RULINGS RECORDED: merge not rebase (done); #11711 to be closed by hand as subsumed on merge, and I deliberately wrote no closing keyword for it because a closing keyword ignores any negation around it.", "tests": "ALL NUMBERS RE-DERIVED ON THE MERGED BASE — none carried over from the pre-#11710 measurement. Final head fcd296bbd, merged base 945ffbea8 (origin/main advanced from dd4113ec0 to 945ffbea8 mid-task; affected-docs.mjs is byte-identical across those two, verified by `git diff --stat`, and the newer main was merged in as well). Tree clean at fcd296bbd. Before-state taken in a SEPARATE COMPARISON WORKTREE at origin/main — never a stash. NO BUILD LEG EXISTS AND NONE IS CLAIMED: affected-docs.mjs runs directly from source, no dist/ in its path. || POPULATION 1 (ledger rows) at ROW IDENTITY: `--bridge-coverage --json` hashes d04a5cedfb613370e5b46ac4725db1d941e5dc88 on BOTH merged base and fcd296bbd; set difference over {file,route,client} across all 177 unreachableRows = 0 in EACH direction; human-rendered diff empty; 269/269, 222/222, 45/177, leadsOutsideCode 0, brokenScan 0 exit 0 on both. || POPULATION 2 (`declarationsIn`): closure WALKED from declarationsIn and following IMPORTED edges — local [codeOnly, typeDeclRegions, declLead, unreadableIn, declinedIn, declarationsIn] + imported [scanSource, blank]. The walk mattered twice: blank/scanSource live in ../js-comment-mask.mjs, and on the merged base it picked up `unreadableIn`, a function that did not exist at my earlier base — a hand-listed closure would have silently measured the wrong thing. 7 ledgers / 491 declarations / hash 766641aeefd8fff085a39a087abc24e4fe2ecfff IDENTICAL on merged base and head, set difference 0 each direction. The hash is this harness's own row shape and is compared only against itself; it is NOT offered as matching any figure from #11710. || POSITIVE CONTROL RUN BEFORE EITHER ZERO WAS BELIEVED: synthetic ledger — merged base reports 5 declarations, branch reports 3, diff naming exactly the two this card adds over #11710 (dash, unicode). $ and . are absent from BOTH sides because #11710 already closed them, which is itself the control working. The 3 survivors are the genuine route:, its genuine client:, and the bare-expression residue. The preceding-character tally carries its own control and FAILED FIRST on a wrong expectation spelling, fixed in the expectation not the instrument. || TALLY re-derived at the merged base: 499 lead occurrences across the seven live ledgers, ALL 499 preceded by a space — 0 by $, 0 by ., 0 by -, 0 by {, 0 by ,, 0 outside the allowlist at all. || SWEEP 0..0x2FFF, now THREE-WAY and compared against the anchor actually replaced: word boundary admits 12225, #11630's lookbehind (the merged base) admits 12223, allowlist admits 25; admitted by allowlist but NOT by the merged base = 0 (THE invariant); admitted by allowlist but NOT by the word boundary = 0. Start-of-input checked separately, admitted by all three. All pinned in --self-test; the second leg is new precisely because a sweep that only compares with the oldest spelling stops being evidence when two cards land in a row. || ABLATION against the MERGED BASE spelling (#11630's lookbehind, not the word boundary two cards back), trap armed BEFORE the mutation, mutation proven ON DISK with injected and removed counted SEPARATELY: CLEAN_HASH=60ab693ca688db1fc29b5937bd7a9b396166f806, injected=1 (want 1) removed=0 (want 0), MUTATED_HASH=3c7ec562ca54b3437ded2783e05dfeca845133c5, RESTORED_HASH=60ab693ca688db1fc29b5937bd7a9b396166f806 byte-identical, tree clean after. Direction predicted before running: red on exactly this card's delta over #11710 and nothing else. Observed: ABLATED_SELFTEST_EXIT=1, 5 checks fail (the two flipped residue pins, both spelling pins, and the sweep margin 12225/12223/25 -> 12225/12223/12223), each `got` the before state. GREEN CONTROLS, named in the PR body: every one of #11710's $route: and .route: fixtures stayed green — a red one there would have meant the merge reverted #11710; both sweep 'admits nothing X did not' legs are 0 both ways because each compares the reverted anchor against itself; the bare-expression residue pin and the 'no call site restates' (0) and 'all eight lead scans' (8) pins are green both ways. || SELF-TEST 413 (merged base) -> 416, +3 net on a diff that flips two assertions and adds three. || GATES: union DERIVED on the merged head — `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`, no paths; stderr confirms the answer is about objectstack-ai/objectstack at fcd296bbd and that the change set correctly reduces to this branch's ONE path against merge base 945ffbea8. 9 families, all exit 0, exit codes captured BEFORE any pipe, each quoted by its own printed verdict line in the PR body; plus check:nul-bytes (75 assertions) and the self-test (416 cases). Control-byte scan on the changed file: clean. || LINT: FULL repo-wide `pnpm lint` on fcd296bbd under the shared verify lock — exit 0, no findings, `VERDICT command-exit 0 - held the lock 56s - waited 51s`. No narrowing claimed or needed. || CHANGESET: changeset-check has NO path exemption (only skip-changeset and the Changesets release PR); this branch's own diff publishes nothing (root private:true, no `files`, scripts/ in no workspace glob, no scripts/package.json). skip-changeset applied and read back, still present. The .changeset/*.md files in the merge commits are main's, arriving through the merge.", "open_questions": [], "out_of_scope_findings": [ "filed as #11737: the `every key spelling a call site passes comes back ANCHORED` self-test pin checks 3 of the 4 spellings the call sites actually pass — `client` (windowClientRe) is omitted. Not a live defect (the anchor is spelled once, so all four get it by construction) and possibly deliberate, so filed for triage rather than fixed inside this card. Still true on the merged base." ] }Generated by Claude Code