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
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 21, 2026
  2. added
    toolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface
    pm:queueReady for the PM dispatch loop
    domain:devxDomain lane — seat card indexed by label:pm:seat
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 21, 2026
  3. added theissue type on Aug 21, 2026
  4. 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

  5. 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

  6. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 21, 2026
  7. 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

  8. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Aug 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seattoolingFix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions