Repository navigation
check-error-code-casing reports a clean sweep it cannot support: a lowercase code in an || fallback matches none of its four recognizers, and two live ones ship today #10658
Description
Found while documenting the SSO domain-verification routes for #10534. Not fixed there — different defect class, so it is filed rather than absorbed.
The claim, and why it is not supported
pnpm check:error-code-casing prints, on origin/main at 47aff0938:
✓ check-error-code-casing self-test: 17 cases pass.
✓ no lowercase error codes in 4346 scanned file(s) (ADR-0112).
That second line is unqualified — it reads as "this tree has none". It has two, in a file that is inside the scanned set:
| file | line | code |
|---|---|---|
packages/plugins/plugin-auth/src/register-sso-provider.ts |
411 | request_domain_verification_failed |
packages/plugins/plugin-auth/src/register-sso-provider.ts |
465 | verify_domain_failed |
Both are emitted as the ObjectStack-authored default of an || chain, e.g. line 465:
return { status: resp.status, body: { success: false, error: { code: parsed?.code || 'verify_domain_failed', message } } };These are not vendor codes passing through — parsed?.code is that path. The literal is ours, and ADR-0112 / Prime Directive #3 make error codes SCREAMING_SNAKE.
Cause: the recognizer requires the quote to sit directly after code:
All four patterns in scripts/check-error-code-casing.mjs (CODE_POSITION_PATTERNS) anchor the string literal immediately after the position token. The emission one:
{ name: 'emission', re: /\bcode\s*:\s*'([a-z][a-z0-9_]*)'/g }
An intervening expression (parsed?.code || ) breaks that adjacency, and the other three do not apply — it is not .code =, not code ===, not a 'a' | 'b' union type. So the shape is invisible to the whole set.
Positive control, so a zero is a real zero rather than a broken probe — the gate's own emission regex, run on both spellings:
direct code: 'oops_bad' -> ["oops_bad"]
fallback code: p?.code || 'oops_bad' -> []
The recognizer demonstrably fires on one and not the other.
Extent, measured. Repo-wide, excluding tests and node_modules, the code: <expr> || '<lower_snake>' shape occurs 2 times — both rows above. So the correction is small; the reporting defect is the point.
Why this is a verdict question, not just two renames
This is the shape PR #10501 just closed for a different scanner: a partial read is a verdict, not a smaller number. A scan that cannot see a spelling produces no finding, silently, and then prints a total that reads as complete. Two prior cards found the same class in the sibling gate — #9223 (non-literal code:) and #9460 (a lowercase thrown code) — so this is the third instance of one pattern across the error-code gates, and the first in this one.
Note the two are not independent: #9460's fix taught check:dispatcher-error-vocabulary about a spelling, and this gate was not revisited.
Suggested direction (not prescriptive)
Two separable pieces, and the second matters more than the first:
- Widen the emission recognizer to reach a literal that follows
code:through an||/??chain, with--self-testcases pinned for both the fallback spelling and the direct one — and pin the population so a widening cannot silently change what "clean" counts. - Consider whether the summary line should be able to say how much it could not read, the way fix(docs-audit): a PARTIAL ledger read is a verdict, not a smaller number #10501 made
affected-docs.mjsprint both halves of its fraction. A recognizer will always have a boundary; the defect is a boundary that reports as zero.
Then fix the two codes themselves — but note they are also wire-visible, so renaming them is a behaviour change for any client matching on them, and belongs with whoever owns that call. packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts:252 currently pins verify_domain_failed by name.
Refs
#10534 (where this was found) · #10501 (the same "partial read reported as complete" class, other scanner) · #9223 / #9460 (the same class in check:dispatcher-error-vocabulary) · ADR-0112
Activity
os-zhuang commentedon Aug 21, 2026
First-touch grading: promoted to
pm:queue·domain:devx· type Task, scoped to the gate: widen the emission recognizer through||/??chains, pin both spellings in--self-test, and weigh the "print how much you could not read" summary shape (#10501 precedent) — the reporting defect is the point, per the card.The two wire-visible code renames are split out to a separate services card (see cross-reference below):
register-sso-provider.tsisplugin-auth(services lane), the rename changes what clients can match on, and a dogfood test pins one code by name — different lane, different risk, must not ride a tooling PR. The gate fix does NOT wait for the renames: land the recognizer widening with the two known hits recorded as expected findings (or an allowlist entry pointing at the services card), whichever the gate's conventions already support — do not let this card's PR turn the farm red waiting on the other lane.Size/model suggestion: S-to-M, opus floor (recognizer semantics + population pinning).
Generated by Claude Code
claude commentedon Aug 21, 2026
Claim — PM dispatch (devx seat)
c970724d-303c-5614-9d20-a3f92205cfadclaude/issue-10658-error-code-casing-fallbackPremise re-verified on
origin/main=8163a1c8e2(fetched 2026-08-21T10:23:56Z), includingthe positive control run independently:
scripts/check-error-code-casing.mjs:109is still{ name: 'emission', re: /\bcode\s*:\s*'([a-z][a-z0-9_]*)'/g }— the quote must sitdirectly after
code:. None of the other three patterns (:111assignment,:113comparison,:115union-type) applies to the fallback shape.register-sso-provider.ts:411(
request_domain_verification_failed) and:465(verify_domain_failed), each theObjectStack-authored default of an
||chain whose other operandparsed?.codeis thevendor path.
['oops_bad']oncode: 'oops_bad'and[]oncode: p?.code || 'oops_bad'. So the zero is a broken probe,not a clean tree.
Serial constraint cleared:
scripts/check-error-code-casing.mjswas guarded by PR #10704, whichmerged at 09:34:57Z; that guard is well away from the recognizer.
two wire-visible renames out to a separate services card — so this card must not rename them.
But a widened recognizer will then find those two on
mainand exit 1, reddening this PR'sown CI. The dev must resolve that explicitly rather than discover it: either land the widening
behind a ⛔ shrink-only known-list carrying both rows and referencing the services card, or
establish that the services card has already landed. ⛔ Silently renaming them here to get green
is not an option — it takes another lane's work and makes this PR unreviewable against its card.
Scope per triage: the gate only — widen the emission recognizer through
||/??chains,pin both spellings in
--self-test, and weigh the "print how much you could not read"summary shape (#10501 precedent), since the reporting defect is the point of the card.
Generated by Claude Code
fix(tooling): teach check-error-code-casing the || / ?? fallback slot…
claude commentedon Aug 21, 2026
{ "issue": 10658, "status": "done", "branch": "claude/issue-10658-error-code-casing-fallback", "pr": "https://github.com/objectstack-ai/objectstack/pull/10760", "premise_still_valid": true, "summary": "Added a fifth CODE_POSITION_PATTERN to scripts/check-error-code-casing.mjs reaching our authored default through an ||/?? fallback chain (both the `code:` and `.code =` heads), bounded by a gap class that refuses `, ; : { } =` and every quote so a match cannot leap into a neighbouring property's fallback. The landing hazard was resolved with option 1: a SHRINK-ONLY KNOWN_LOWERCASE_CODES keyed <file>::<literal> carrying exactly the two rows, each naming #10716 (open, services lane — option 2 was checked and falsified, both codes still live at 411/465 on the base). A stale entry FAILS the gate, so the list can only shrink and the renames' landing is what deletes it; a fresh finding is never absorbed. Third scope item decided YES: the verdict now states what the run could not read (exempt files, applied opt-outs, deferred known codes) and names the delegated boundary's owner rather than printing a count for a population another gate covers. One file changed; no changeset (gate tooling, publishes nothing) — skip-changeset label applied and read back.", "tests": "All at HEAD 613fc4363b, clean tree, single-file change set (three-dot vs merge base 8163a1c8e2; a two-dot origin/main diff misleadingly shows 5 files because main advanced to 78ac958552 under the branch). REPRODUCTION (base): `node scripts/check-error-code-casing.mjs` -> EXIT=0, 'no lowercase error codes in 4356 scanned file(s)' while both codes live at register-sso-provider.ts:411/465; positive control reproduced — the gate's own emission regex returns ['oops_bad'] on `code: 'oops_bad'` and [] on `code: p?.code || 'oops_bad'` and on the ?? form. AFTER: importing the real findViolations (safe because #10704's isEntrypoint guard is intact) against the real file returns exactly 2 hits — :411 'request_domain_verification_failed' (fallback), :465 'verify_domain_failed' (fallback). EXTENT re-derived independently with a deliberately broad probe over the gate's own walk of packages/** (comments masked): 2 hits in 4356 files, matching the card; the sibling shape `.code = <expr> || '<lower>'` measures 0. main gained 0 paths under packages/ since the base, so the population is unchanged. REJECT SIDE, tree-scale differential of the 4 base patterns vs the 5, per pattern over the same corpus: emission 508->508, assignment 0->0, comparison 176->176, union-type 2->2, all +0/-0 (the base four were taken verbatim from `git show origin/main:` and the new regex was byte-compared between probe and source, so the differential is about the real regexes); the new pattern's only 2 raw matches tree-wide are the two known rows, i.e. zero false positives. check:dispatcher-error-vocabulary reports NEITHER code on the base (EXIT=0, 21 classified sites), confirming the shape was owned by no gate. SELF-TEST: 17 -> 29 recognizer cases + 4 new registry cases, all pass; the direct spelling of the same code is pinned beside the fallback spelling as a pair. ABLATION: predicted 4 red before running; mutation proven on disk first — anchors `name: 'fallback',` and its `re:` line both 1 -> 0, 151 bytes removed, 4 deletions in git diff --stat; observed exactly 4, all 'expected 1 hit(s), got 0' (|| , ?? , assignment-fallback, chain), with the direct-spelling half and all 8 reject cases staying green, and the full scan independently flipping 0 -> 1 with 2 stale KNOWN_LOWERCASE_CODES entries. Restore leg proven: anchors back to 1, tree clean vs HEAD, self-test and scan green again. No build/dist in either leg — package.json runs the script from source. One self-test expectation was wrong on first write and the harness caught it: `.code = e?.code || 'lookup_failed'` scored 0 because the pre-existing error-shaped-neighbour filter uses \\berror|\\bfailed and 'lookup_failed' offers no word boundary before 'failed'; the sample was given a real neighbour rather than the expectation being bent. GATES: `node scripts/pm/dispatch-gates.mjs` with no paths, EXIT=0, named 3 families — check:error-code-casing ('no unlisted lowercase error codes in 4356 scanned file(s)', 0), check:cross-package-test-inputs ('OK: 13 package(s) read outside themselves, all declared', 0), and its ci.yml twin. Three more run because reasoned, not named: check:ratchet-remedy-authority (0) — it hand-classifies THIS file as 'excluded' and a shrink-only registry whose remedy offers expansion would flip it to a violation, so its own classify() was run against this branch and returns excluded / 0 live / 0 refused / 0 anchors; check:entry-guard (0) — the file gains a second export, #10704's guard is untouched, and it stays OFF the KNOWN_IMPORT_UNSAFE SHRINK-ONLY ledger (0 occurrences); check:nul-bytes (0, 'no raw ASCII control bytes'). check:parse-guard also run (0) — dispatch-gates reports it unreachable-by-construction, so no path derivation can name it, but CI runs it on every PR.", "open_questions": [], "out_of_scope_findings": [ "filed as #10762: check-dispatcher-error-vocabulary's delegation map enumerates four check:error-code-casing recognizers and is now short the fifth, and does not record that a runtime-limbed ||/?? chain is delegated rather than dropped under #9568's all-or-nothing reduction (finding + domain:devx + tooling, unassigned, no pm:queue; drift understates coverage, so no live hole)", "not filed, recorded here: the two wire-visible renames remain owned by #10716 (open, services lane) and are deliberately untouched — KNOWN_LOWERCASE_CODES goes stale and fails the gate when that card lands, which is the handshake", "not filed, observed while measuring: the tree carries 20 adr0112-ok: comments but only 12 suppress an actual literal; the new verdict line reports the 12 applied, not the 20 present, so the gap is visible rather than overstated" ] }Generated by Claude Code
fix(plugin-auth): answer a registered ADR-0112 code from the SSO doma…
fix(tooling): teach check-error-code-casing the || / ?? fallback slot…
fix(plugin-auth): answer a registered ADR-0112 code from the SSO doma…