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

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):

spelling call sites lines
(route|client) 3 1508, 1637, 1877
route 3 1775, 1776, 1860
(?:route|client) 1 1876
client 1 1777 (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.

mutation list = 3 (before) list = derived (after) which case fires
none — control green (417) green (417) —
client-only tightening: allowlist (?<[^\s{,]) narrowed to (?<[^\s]) green, 0 failures RED, 1 failure the pin, and only the pin
a call site passes a computed key n/a RED, 2 failures the 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 failure the pin — derivation is live
no-op edit elsewhere — negative control green green —

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.
@claude claude Bot 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.

Copy link
Copy Markdown
Collaborator Author

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.match — none 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:

leg result
(a) control, no mutation green 417 before and after — not red-by-construction
(b) client-only tightening before green / after exit 1, 1 failure, the pin
(c) computed key at a call site exit 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 edit green — 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

@os-steve
os-steve marked this pull request as ready for review August 24, 2026 16:14
@os-steve
os-steve added this pull request to the merge queue Aug 24, 2026
Merged via the queue into main with commit 56630b7 Aug 24, 2026
33 checks passed
@os-steve
os-steve deleted the claude/issue-11737-declLead-client-spelling-pin branch August 24, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant