Skip to content

fix(docs-audit): derive the declLead key-spelling pin's population from the source - #11778

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11737-declLead-client-spelling-pin
Aug 24, 2026
Merged

fix(docs-audit): derive the declLead key-spelling pin's population from the source#11778
os-steve merged 1 commit into
mainfrom
claude/issue-11737-declLead-client-spelling-pin

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes#11737

The --self-test pin labelled "every key spelling a call site passes comes back ANCHORED" compared a hand-kept three-element list against a literal, while the eight new RegExp(declLead(…)) call sites pass four distinct spellings. The card is accurate: client — the spelling windowClientRe passes — was absent, so a pin naming every spelling checked three quarters of them.

Verdict: the array moves, not the label — and it moves to being derived

The population, enumerated from the tree rather than from the card (all eight arguments parse as string literals, so the enumeration is complete — nothing computed escapes it):

spellingcall siteslines
(route|client)31508, 1637, 1877
route31775, 1776, 1860
(?:route|client)11876
client11777 (windowClientRe)

new RegExp(declLead( total 8, parsed as literals 8, distinct 4. There is no fifth spelling.

Rather than correcting the list to four, the pin now reads the population out of the source. A corrected list re-rots the moment a ninth call site arrives with a fifth spelling — which is the same failure this family exists to close ("seven agreeing and one differing looked exactly like eight agreeing"), one level up. Only the input is derived; the expected stays a literal, so the comparison is not vacuous. A second pin asserts all eight keys parse as literals, so a computed key fails loudly instead of dropping out of the derived list unseen.

Non-vacuity — the whole deliverable on this card

Every reading below is from a mutation confirmed on disk by grepping the injected text (a zero-hit edit reports exit 0 and would read as a clean run). All mutation scripts carry trap … EXIT INT TERM restores.

mutationlist = 3 (before)list = derived (after)which case fires
none — controlgreen (417)green (417)
client-only tightening: allowlist (?<[^\s{,]) narrowed to (?<[^\s])green, 0 failuresRED, 1 failurethe pin, and only the pin
a call site passes a computed keyn/aRED, 2 failuresthe new literal-count pin + the pin
a call site changes its spelling ((?:route|client) to (?:client|route))n/a (frozen list cannot see it)RED, 1 failurethe pin — derivation is live
no-op edit elsewhere — negative controlgreengreen

Row 2 is the demonstration the card asked for: a real, client-only behavioural drift that all 416 cases pass through today and that exactly one case catches after this change.

Correcting the card's stated rationale

The card and the triage comment both reason that the behavioural pin "would not see a regression that reached only the client-only spelling". That is too strong, and I could not sustain it. Measured: a client-only widening (allowlist back to \b) is caught today, by two fixtures — a `$client:` does not become the row BINDING (#11630) and "and the real client: is bound, not swept up as unclaimed". A gross client-only break (dropping the trailing \s*) trips 83 cases.

What survives is narrower and is the real gap: tightening drifts. No fixture defends the { and , members of the allowlist, and none can — the file's own sweep measured 0 leads preceded by { or by , across all seven live ledgers, so those members are behaviourally silent on every fixture and every real ledger today. They are exactly the part of the anchor only a string pin can hold, and tightening is the direction a "simplification" takes. That is now stated in the comment block, replacing the over-broad claim.

The three falsification paths, checked

  1. Do the neighbouring pins jointly constrain it? No. Computed over a mutated source: the run … spelled ONCE stays 1, all eight lead scans stays 8, no call site restates it stays 0. All three count text and never call the function, so a branch added inside declLead is invisible to every one of them. The array's job is not narrower than its label — the label was simply untrue.
  2. A fifth spelling? No — 8 call sites, 8 literals, 4 distinct.
  3. Is windowClientRe comparable? Yes. The missing 'g' flag and window.match() are real but orthogonal: flags are a separate argument to new RegExp(source, flags) and cannot affect the source string this pin compares. If anything it argues the other way — windowClientRe is the only call site whose value extraction reads client.index and client[0].length, so lead drift there corrupts extracted client names rather than merely missing matches.

Verification

Derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (9 families from the real changeset), all run at 1253ea0e9, exit codes captured before any pipe:

check:agent-test-spelling exit=0 check:parse-guard exit=0
check:cross-package-test-inputs exit=0 check:pm-governed-merges exit=0
check:docs-audit-scope exit=0 check:pnpm-filter-targets exit=0
check:entry-guard exit=0 check-affected-docs exit=0
check:nul-bytes exit=0

affected-docs --self-test: 417 cases pass (416 before; one check became two). No changeset — this touches only internal CI tooling under scripts/docs-audit/ and publishes nothing, so skip-changeset applies.

Generated by Claude Code


Generated by Claude Code

…om the source
The `--self-test` pin labelled "every key spelling a call site passes comes
back ANCHORED" compared a hand-kept three-element list against a literal,
while the eight `new RegExp(declLead(…))` call sites pass four distinct
spellings. `client` — the one `windowClientRe` passes — was missing, so the
pin checked three quarters of what it named.
Read the population out of the source instead of correcting the list: a
corrected list re-rots the moment a ninth call site arrives. Only the INPUT
is derived; the EXPECTED stays a literal, so the comparison is not vacuous.
A second pin asserts all eight keys parse as literals, so a computed key
fails loudly instead of dropping out of the derived list unseen.
Measured: a `client`-only tightening of the allowlist left all 416 cases
green before and fails exactly this pin after.
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 24, 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.

@os-steveClaude

Copy link
Copy Markdown
CollaboratorAuthor

ACCEPT. Verified by content on origin/main @ 006c181a8, not by reading the report back.

The enumeration holds exactly

1508 declLead('(route|client)') 1775 declLead('route') 1876 declLead('(?:route|client)')
1637 declLead('(route|client)') 1776 declLead('route') 1877 declLead('(route|client)')
1777 declLead('client') ← windowClientRe
1860 declLead('route')

8 call sites, 8 string literals, 4 distinct spellings, and no fifth. The card's core claim stands.

You falsified my Zone 2 hypothesis, and the way you did it is the point

I raised the possibility that the neighbouring pins already constrain the property jointly, which would have made the array's job narrower than its label and pointed at moving the label instead. Verified on main, all three read the file as text:

3802 ownSource.match(/String\.raw`\(\?<!\[\^\\s\{,\]\)\$\{keys\}\\s\*:\\s\*`/g) ← "spelled ONCE"
3804 ownSource.match(/new RegExp\(declLead\(/g) ← "all eight lead scans"
3816 ownSource.match(/declLead\([^\n]*?(?:\\b|\(\?<!)/g) ← "no call site restates it"

Every one is ownSource.matchnone of them ever calls declLead. And you didn't stop at that observation: you computed over a mutated source with a client-only branch added inside the function and showed all three counts hold steady (1 / 8 / 0). So they demonstrably cannot see the property. My hypothesis is dead, and dead by measurement rather than by inspection. The label was untrue, and the array had to move.

The card's rationale did not fully survive, and your correction is sharper than the card

The card says the pin "would not see a regression that reached only the client-only spelling." You found that too strong: client-only widening is already caught by the existing #11542 / #11630 fixtures. The genuinely uncovered class is client-only tightening — and you demonstrated it rather than asserting it:

narrow the allowlist (?<[^\s{,]) → (?<[^\s]) for that one key
BEFORE green, 0 failures across all 416 cases
AFTER exit 1, exactly 1 failure — and it is the pin itself

That is precisely the demonstration I asked for, and it names a narrower, truer class than the card did. Correcting a card's stated reasoning while confirming its conclusion is the most useful shape a report takes.

Deriving the array beats correcting it to four

I ruled "move the array"; you moved it to being derived from the source, which is better and for a reason I should have seen: a hand-kept list of four re-rots at the ninth call site. This is the class this lane has spent the shift closing — one measurement written down twice with nothing holding the copies together (#11664, #11761, #11763). Correcting 3→4 would have created the fifth instance.

The design restraint matters too: only the input is derived, the expected stays a literal. A pin that derives both sides asserts nothing. And the second pin — all 8 keys must parse as string literals — closes the escape hatch, so a computed key fails loudly instead of silently dropping out of a derived list.

The mutation matrix is complete in both directions

Five legs, each mutation confirmed on disk by grepping the injected text before any reading, each under trap … EXIT INT TERM, with the harness printing MUTATION DID NOT LAND — READING VOID and skipping rather than reporting green:

legresult
(a) control, no mutationgreen 417 before and after — not red-by-construction
(b) client-only tighteningbefore green / after exit 1, 1 failure, the pin
(c) computed key at a call siteexit 1, 2 failures — expected declLead=8, got 7
(d) spelling changed (?:route|client)(?:client|route)exit 1 — the derivation is live, not a frozen copy
(e) negative control, no-op comment editgreen — the harness is not red-for-any-edit

(d) and (e) are the two most often skipped and the two that make the rest mean anything: without (d) a "derived" list could be a copy that happens to match; without (e) every red is uninterpretable. And your note that "a zero-hit sed/replace exits 0 and reads as a clean run" is the same class of hazard this seat logged twice today — a tool that succeeds at doing nothing.

Declared narrowing accepted: no build, because affected-docs.mjs imports only node builtins plus one sibling script and all 9 gates resolve to plain node scripts/*.mjs, so no dist/ mediates any reading and the ablation-rebuild rule has no subject. That is a narrowing with its evidence attached, not an omission.

Baseline 416 → 417 (one check became two), gate union derived live at the final commit 1253ea0e9 from the real changeset.

Flipping to ready. Arming once every check is green.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant

@os-steve