Skip to content

fix(tooling): check-error-code-casing — reach our default through an ||/?? fallback chain, and stop printing an unqualified clean sweep - #10760

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10658-error-code-casing-fallback
Aug 21, 2026
Merged

fix(tooling): check-error-code-casing — reach our default through an ||/?? fallback chain, and stop printing an unqualified clean sweep#10760
os-zhuang merged 1 commit into
mainfrom
claude/issue-10658-error-code-casing-fallback

Conversation

@claude

@claudeclaudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Closes#10658. Verified at 613fc4363b; base origin/main = 8163a1c8e2.

One file: scripts/check-error-code-casing.mjs. No changeset — gate tooling, publishes nothing (skip-changeset).

The defect, reproduced before anything changed

All four CODE_POSITION_PATTERNS anchor the string literal immediately after the position token, so an intervening expression makes the literal invisible to the whole set — silently, and then the run prints a total that reads as complete.

$ node scripts/check-error-code-casing.mjs # on the base
EXIT=0
no lowercase error codes in 4356 scanned file(s) (ADR-0112).

That tree has two, both inside the scanned set. Positive control, so the zero is read as a broken probe rather than a clean tree — the gate's own emission regex on both spellings:

code: 'oops_bad' -> ["oops_bad"]
code: p?.code || 'oops_bad' -> []
code: p?.code ?? 'oops_bad' -> []

Re-derived extent: 2, and it is still 2

Swept packages/**.ts/.tsx (comments masked, the gate's own walk) with a deliberately broad probe — code: then any non-quote gap, then ||/??, then a lower_snake literal:

filelinecode
packages/plugins/plugin-auth/src/register-sso-provider.ts411request_domain_verification_failed
packages/plugins/plugin-auth/src/register-sso-provider.ts465verify_domain_failed

2 hits in 4356 files — matches the card, so the known-list below is exactly two rows. The sibling shape .code = <expr> || '<lower>' measures 0 occurrences; it is covered anyway (below), so the class is closed rather than the instance. origin/main advanced to 78ac958552 while this was in flight and touched 0 paths under packages/, so the scanned population — and the extent — are unchanged.

The landing hazard, and how it is resolved

Triage split the two wire-visible renames to #10716 (services lane), so this PR must not rename them — but a widened recognizer finds them on main and exits 1. Option 2 was checked, not assumed: #10716 is open and both codes are still live at 411/465 on the base, so it has not landed.

Resolved by option 1, the KNOWN_IMPORT_UNSAFE shape from #10665: a ⛔ SHRINK-ONLYKNOWN_LOWERCASE_CODES carrying exactly those two rows, each naming the owning card. Keyed <file>::<literal> rather than by line, so a line shift cannot quietly invalidate an entry. Nothing new can join: a fresh finding is never absorbed by the list, and the author-facing remedy says so and refuses that route rather than offering it. When #10716 lands, each entry goes stale and the gate fails until the line is deleted — the list only ever shrinks. Both directions are pinned in --self-test.

Where the line is drawn on vendor codes

The widened pattern still only ever captures a string literal, and a literal in our source is by construction ours. A vendor code passing through is a runtime value (parsed?.code, err.code, a variable) — it has no literal for any pattern here to capture, before or after this change. So widening cannot start flagging a pass-through; it reaches only the default we author, which is exactly the operand ADR-0112 D1 governs (the code our failing request answers with). A vendor's spelling hard-coded as our default is a real violation, not a false positive.

The second half of the line is the gap class, and it is what keeps a match inside one property's value. It admits an operand chain (identifiers, member/optional access, calls, indexes, further ||/??) and refuses , ; : { } = and every quote. The real false positive here is a neighbour's fallback —

{code: a.code,message: m||'lower_thing'}

— and the comma is what stops it. Pinned as a reject case. The length bound is a runaway guard, also pinned.

Reject side, at tree scale

Differential sweep of the four base patterns vs. the five, per pattern, over the same 4356 files. The new pattern is additive — the other four are byte-identical to the base and unchanged in what they match:

patternbasethis branchaddedremoved
emission50850800
assignment0000
comparison17617600
union-type2200
fallback (new)02the two rows above

Zero false positives repo-wide: the new pattern's only two raw matches are the two known rows. check:dispatcher-error-vocabulary reports neither code on the base (a chain with one runtime limb reduces to nothing under #9568's all-or-nothing rule), so this shape was owned by no gate at all — which is the card.

Both spellings pinned, as a pair

--self-test grows 12 recognizer cases and 4 registry cases (17 -> 29, plus 4). The pair matters: the direct spelling of the same code is pinned beside the fallback spelling, because a recognizer that reached the new shape by breaking the old one would pass a self-test that only pinned the new one.

Ablation

Prediction stated before running: reverting the widening reddens exactly 4 recognizer cases — the four expecting a hit through the fallback pattern — while the reject-side cases (expecting 0) and the registry cases stay green.

Mutation confirmed on disk before measuring: the two anchors name: 'fallback', and its re: line both went 1 -> 0, 151 bytes removed, git diff --stat showing 4 deletions. Observed, on the mutated tree:

 self-test "our default behind an || fallback": expected 1 hit(s), got 0
self-test "our default behind a ?? fallback": expected 1 hit(s), got 0
self-test "fallback in the assignment position": expected 1 hit(s), got 0
self-test "fallback at the end of a chain": expected 1 hit(s), got 0
check-error-code-casing self-test failed (4 case(s)). EXIT=1

4 predicted, 4 observed, and the direct-spelling half stayed green — the pair did its job. The full scan independently flipped 0 -> 1 with 2 staleKNOWN_LOWERCASE_CODES entries, which exercises the shrink-only limb from the other side. Restore leg proven too: anchors back to 1, tree clean against HEAD, both runs green again. No build/dist is involved — package.json runs this script from source — so there is no stale-artifact risk in either leg.

The verdict shape: yes, it now says what it could not read

The card asks this to be weighed either way. Decided yes, on the #10501 precedent, because the reporting defect is the point of the card: an unqualified total renders a bounded read exactly like a complete one, and that is what shipped over a tree carrying two.

no unlisted lowercase error codes in 4356 scanned file(s) (ADR-0112).
what this run did NOT read - the line above is a bounded claim, not a clean bill:
- 23 file(s) skipped whole (EXEMPT_FILES: D6/D6b/D6c and foreign vocabularies)
- 12 literal(s) suppressed by an adr0112-ok: reason
- 2 known lowercase code(s) deferred to their owning card (KNOWN_LOWERCASE_CODES)
- a code value with NO literal at the position - a constant, a template, a ternary,
a helper parameter - is out of reach for every pattern here by construction; that
half belongs to check:dispatcher-error-vocabulary, which reports its own scope.

Two deliberate choices about what it counts:

The qualifier prints on the failure path too: a partial finding list reads as complete for the same reason a partial total does.

Gates

node scripts/pm/dispatch-gates.mjs with no paths, at 613fc4363b, clean tree — it derived the change set from the merge base as 1 path (a two-dot origin/main diff shows 5 files here, four of them sibling PRs that landed after this branch was cut). It named 3 families; the other three below were run because they were reasoned, not named:

gateverdict lineexit
check:error-code-casingno unlisted lowercase error codes in 4356 scanned file(s)0
check:cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared0
check:ratchet-remedy-authority111 scripts swept; 7 mark ... 3 turn it down outright, 101 hand out no ratchet-expanding remedy0
check:entry-guard125 scripts/ file(s) ... 83 export bindings, 73 of them inert on import (10 known-unsafe, SHRINK-ONLY)0
check:parse-guardself-test + sweep clean0
check:nul-bytesOK (scanned 6234 text file(s) ... no raw ASCII control bytes)0

Two of those were run because a new shrink-only registry is exactly the shape they police, and both were verified directly rather than inferred from a green exit:

  • check:ratchet-remedy-authority hand-classifies this very file as excluded. A shrink-only registry whose remedy offers expansion would flip it to unmarked — a violation. Its own classify() run against this branch's source returns excluded, 0 live offers, 0 refused, 0 anchors, so the control entry stays correct and untouched.
  • check:entry-guard — this file gains a second export (partitionKnown), and fix(scripts): burn 25 of the 35 KNOWN_IMPORT_UNSAFE entry-guard debts down #10704's isEntrypoint guard on it is untouched. It stays off the KNOWN_IMPORT_UNSAFE SHRINK-ONLY ledger (0 occurrences), and importing it for its exports alone runs nothing, which is how the recognizer was proven against the real file.

Out of scope, filed separately

The two renames are not addressed here#10716 remains open and owns them. One follow-up filed for a documentation drift this change creates in the sibling gate's delegation map; see the report comment on #10658.

Generated by Claude Code


Generated by Claude Code

…, and qualify its verdict
The four CODE_POSITION_PATTERNS all anchor the string literal immediately after
the position token, so an intervening expression makes the literal invisible to
the whole set. Two live wire-visible codes shipped through that gap while the
gate printed an unqualified 'no lowercase error codes'.
- fifth pattern reaching our default through an ||/?? chain, bounded so a match
cannot leap into a neighbouring property's fallback;
- KNOWN_LOWERCASE_CODES, shrink-only, carrying the two codes whose rename is
owned by #10716 (services lane) — a stale entry fails;
- the verdict now states what the run could not read (#10501 precedent).
Part of #10658
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 10:57
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit f06d8edAug 21, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10658-error-code-casing-fallback branch August 21, 2026 11:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

1 participant

@os-zhuang