Skip to content

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

@os-warren

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:

  1. Widen the emission recognizer to reach a literal that follows code: through an || / ?? chain, with --self-test cases pinned for both the fallback spelling and the direct one — and pin the population so a widening cannot silently change what "clean" counts.
  2. 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.mjs print 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

  1. added theissue type on Aug 21, 2026
  2. os-zhuang commented on Aug 21, 2026

    @os-zhuang
    Contributor

    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.ts is plugin-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

  3. claude commented on Aug 21, 2026

    @claude
    Contributor

    Claim — PM dispatch (devx seat)

    • Session: c970724d-303c-5614-9d20-a3f92205cfad
    • Branch: claude/issue-10658-error-code-casing-fallback

    Premise re-verified on origin/main = 8163a1c8e2 (fetched 2026-08-21T10:23:56Z), including
    the positive control run independently:

    • scripts/check-error-code-casing.mjs:109 is still
      { name: 'emission', re: /\bcode\s*:\s*'([a-z][a-z0-9_]*)'/g } — the quote must sit
      directly after code:. None of the other three patterns (:111 assignment, :113 comparison,
      :115 union-type) applies to the fallback shape.
    • Both lowercase codes are still live: register-sso-provider.ts:411
      (request_domain_verification_failed) and :465 (verify_domain_failed), each the
      ObjectStack-authored default of an || chain whose other operand parsed?.code is the
      vendor path.
    • Positive control, reproduced here: that exact regex returns ['oops_bad'] on
      code: 'oops_bad' and [] on code: p?.code || 'oops_bad'. So the zero is a broken probe,
      not a clean tree.

    Serial constraint cleared: scripts/check-error-code-casing.mjs was guarded by PR #10704, which
    merged at 09:34:57Z; that guard is well away from the recognizer.

    ⚠️ One hazard the card does not address, and it decides how this can land. Triage split the
    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 main and exit 1, reddening this PR's
    own 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

  4. claude commented on Aug 21, 2026

    @claude
    Contributor
    {
      "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

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions