Skip to content

fix(devx): read the our-default slot in a code local's initializer too - #10914

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10897-casing-local-initializer
Aug 22, 2026
Merged

fix(devx): read the our-default slot in a code local's initializer too#10914
os-zhuang merged 1 commit into
mainfrom
claude/issue-10897-casing-local-initializer

Conversation

@claude

@claudeclaudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#10897

check-error-code-casing's fifth recognizer (#10760) anchors on the position
token
code: / code?: / .code = — so it reaches an ||/?? fallback
chain only where the chain sits at the stamp site. The identical chain one
indirection earlier matched nothing:

constcode=parsed?.code||'lower_thing';// our authored defaultconsterr=newError(msg);err.code=code;throwerr;

A type annotation does not rescue it either: const code: string = ...does
match code:, but the gap then has to cross an =, which that character class
refuses on purpose (refusing = is part of what stops a match leaping out of
one property's value into a neighbour's). Verified both, below.

The table, re-derived on today's origin/main (58563be069)

Driving both gates' own exported entry points (findViolations, deriveSites).
Every cell reproduces the card's measurement at 9faa9bc51d — nothing moved.

shapecheck:dispatcher-error-vocabularycasing, beforecasing, after
stamp-site { code: p?.code || 'stamp_lower_failed' }no site, no unresolved1 (fallback)1 (fallback)
stamp-site err.code = p?.code || 'stamp_lower_failed'no site, no unresolved1 (fallback)1 (fallback)
local-init const code = p?.code || 'local_lower_failed'no site, no unresolved01 (local-fallback)
local-init const code: string = p?.code || 'typed_lower_failed'no site, no unresolved01 (local-fallback)
local-init const code = p?.code || 'LOCAL_UPPER_FAILED'no site, no unresolved00 (out of the value space)
control: const code = flag ? 'TERN_A' : 'TERN_B'2 sites (assignconst)n/an/a
deleg: const code = 'local_direct_failed'1 site (assignconst)00
deleg: const code = flag ? 'tern_lower_a' : 'tern_lower_b'2 sites (assignconst)00
deleg: const code = 'chain_lower_a' || 'chain_lower_b'2 sites (assignconst)00

The control row is what makes the zeros readable rather than a broken probe.
The last three rows are new here and they set the boundary in the other
direction: an all-literal initializer — a bare literal, a ternary, a chain —
IS reducible under #9568, so it stays the dispatcher gate's site under
assignconst, lowercase included. Only the runtime-limbed chain fell
through both gates. The dispatcher column is identical in every row before and
after this PR.

The fix

A sixth recognizer, local-fallback. The annotation gap is spelled [^=;\n]
— the spelling check-dispatcher-error-vocabulary's own classfield already
uses for this job — and everything after the = is the fifth pattern's gap
class and tail verbatim: same operand alphabet, same 80-char runaway bound, same
lowercase value space. The quote refusal (the lookahead plus a gap class that
admits no quote) keeps it off the head of an all-literal chain, so one literal
never gets two reporters.

The reasoning is #10760's own, applied one indirection earlier: the capture is
still only ever a string literal, and a literal in our source is by
construction the default we author — the operand ADR-0112 D1 governs. A vendor
code passing through is a runtime value with no literal to capture. Where we
write the chain does not change whose default it is.

The boundary that was NOT touched

check:dispatcher-error-vocabulary's silence on a runtime-limbed chain is
correct and is untouched. Its ALL-OR-NOTHING reduction (#9568) is deliberate:
harvesting the literal half would report a code the program may never stamp
while staying silent about the limb it cannot see. Its verdict line is
byte-identical before and after:

before: check-dispatcher-error-vocabulary: OK - 21 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (#8846).
after: check-dispatcher-error-vocabulary: OK - 21 unregistered code-stamping site(s), all classified; 1 awaiting a ledger entry (#8846).
(diff before/after: byte-identical, and self-test 8 shapes + 102 assertions unchanged)

The only edit in that file is comment-only: the three delegation
enumerations #10762 had just corrected recorded this position as owned by
NOBODY and named #10897 as the open half. Leaving them would have recreated the
exact drift #10762 closed. Named here rather than slipped in.

Blast radius across packages/** — measured, with two positive controls

Swept 4393 non-.d.ts.ts/.tsx files, comments masked with the gates' own
maskComments:

probematches
the new recognizer, as shipped (lowercase literal default)0
control: the same anchor, any-case literal default5
control: any code-named local declaration at all47

So the widened recognizer newly matches nothing in the tree — and that zero
comes from an instrument shown to produce 5 and 47. The 5 any-case matches, each
judged:

  • packages/formula/src/template-engine.ts:75|| 'USD' — a currency code, not
    an error code, and out of the value space regardless.
  • packages/metadata-fs/src/repository.ts:655?? 'UNKNOWN' — a Node errno
    fallback; SCREAMING, compliant.
  • packages/plugins/plugin-auth/src/admin-user-endpoints.ts:405 and
    set-initial-password.ts:87?? 'INTERNAL_ERROR' — SCREAMING, registered.
  • packages/rest/src/import-runner.ts:293?? 'IMPORT_ROW_FAILED' — SCREAMING,
    and that file is already in EXEMPT_FILES (D6).

Every one of them is a genuine authored default, and every one is compliant
today. The gate's real-tree verdict is byte-identical before and after:
no unlisted lowercase error codes in 4393 scanned file(s) (ADR-0112).

Self-test: 29 recognizer cases to 46, both directions

New positives: untyped local, typed local, let, fallback at the end of a
chain — plus both stamp-site spellings re-pinned as the pair halves that must
not regress. New negatives: a SCREAMING default; a vendor code with no literal;
codeName (a local whose name merely starts with code); a neighbour's local
fallback; the three all-literal shapes that stay the dispatcher gate's; a case
proving the annotation gap cannot leap into the next statement; and NOT_CODES,
D6 and adr0112-ok: still winning through the new shape.

Every case carries the error-shaped neighbour the filters require, negatives
included — a zero produced by a missing neighbour would test nothing about the
recognizer. Each negative was then checked as a live probe: flipping the single
property under test takes it 0 to 1, all ten of them.

Ablation — prediction written before the run

Both legs mutated on disk and proven by marker counts, never by an editing
tool's exit code; each restore leg proven by git diff --quiet.

Leg 1 — delete the local-fallback recognizer. Marker
name: 'local-fallback' 1 to 0. Predicted: self-test red, exit 1, exactly 4
failures, the four positive local cases; stamp-site halves and all negatives
green. Observed: exactly that. The real tree stayed green under the
mutation — which is precisely why the real tree cannot be the control here and
the fixture self-test has to be.

Leg 2 — admit a quote to the local-fallback gap class only
(&\s]{0,80}? to &\s']{0,80}?, marker 1/0 to 0/1). Predicted: exit 1,
exactly 1 failure — "an all-literal chain reduces too, and stays the dispatcher
gate's". Observed: exactly that, proving the delegation boundary is enforced
by the pattern and pinned by that case rather than merely asserted in a comment.

Restore after each: git diff --quiet clean, markers back to 1 and 1/0,
self-test 46+4 green.

Gates run locally at 23e86a219b

Derived with node scripts/pm/dispatch-gates.mjs from the real merge-base
changeset (2 paths), not from a hand-written list — all 7 matched families:

pnpm check:error-code-casing EXIT=0 46 recognizer case(s) + 4 registry case(s) pass / no unlisted lowercase error codes in 4393 scanned file(s)
pnpm check:dispatcher-error-vocabulary EXIT=0 8 shapes + 102 assertions OK / OK - 21 sites, all classified
pnpm check:cross-package-test-inputs EXIT=0 OK: 13 package(s) read outside themselves, all declared
pnpm check:entry-guard EXIT=0 130 scripts/ file(s); 88 export bindings, 78 inert on import
pnpm check:parse-guard EXIT=0 129 scripts/ file(s) - every TypeScript parse goes through ts-parse.mjs
node scripts/check-ci-filter-parity.mjs EXIT=0 OK: all 82 declared cross-package glob(s) covered
node scripts/check-cross-package-test-inputs.mjs EXIT=0
node scripts/check-nul-bytes.mjs EXIT=0 no raw ASCII control bytes

Exit codes captured before any pipe; each verdict quoted is the gate's own
printed line.

Coordination

Checked immediately before pushing, against all 11 open PRs:

Changeset

skip-changeset: this PR touches only two CI-internal gate scripts and
publishes nothing, which pr-automation.yml calls the textbook case for the
label in its own words.

Refs #10762 (the delegation map) - #10658 / #10760 (the widening) - #9568 (the
ALL-OR-NOTHING reduction) - ADR-0112 D1

Generated by Claude Code


Generated by Claude Code

`check-error-code-casing`'s fifth recognizer (#10760) anchors on the POSITION
token — `code:` / `code?:` / `.code =` — so it reaches an `||`/`??` fallback
chain only where the chain sits AT the stamp site. The identical chain one
indirection earlier, in a local's initializer, matched nothing:
const code = parsed?.code || 'lower_thing'; // our authored default
err.code = code;
`const code =` is neither spelling, and a type annotation does not rescue it:
`const code: string = …` does match `code:`, but then the gap has to cross an
`=`, which that character class refuses on purpose.
Nothing else saw it either. `check:dispatcher-error-vocabulary` reaches the
local (`err.code = code` is its `codehelper`/`assignconst` shape) but its
`resolveConstant` reduction is ALL-OR-NOTHING by design (#9568): one runtime
limb reduces the whole chain to nothing, because half an expression's values
is a finding wrong in both directions at once. That bound is deliberate and is
untouched here — its verdict line is byte-identical before and after.
So the literal half is this gate's, on exactly the reasoning #10760 published
for the stamp site: the capture is still only ever a STRING LITERAL, and a
literal in our source is by construction the default WE author. Where we write
the chain does not change whose default it is; the asymmetry was an artifact of
where the recognizer anchored, not a decision anyone took.
Adds a sixth recognizer, `local-fallback`, with the annotation gap spelled
`[^=;\n]` (the same spelling `classfield` uses in the sibling gate) and the
fifth pattern's own gap class and tail verbatim. The delegation runs the OTHER
way for an ALL-literal initializer — a bare literal, a ternary, a chain — which
IS reducible and stays the dispatcher gate's site under `assignconst`,
lowercase included; the quote refusal keeps this pattern off those, so one
literal never gets two reporters.
Also updates the three delegation enumerations in
`check-dispatcher-error-vocabulary.mjs` that #10762 had just corrected: they
recorded this position as owned by NOBODY and named #10897 as the open half.
Comment-only there; 8 shapes + 102 assertions and the full-run verdict all
unchanged.
Fixes#10897
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_c970724d-303c-5614-9d20-a3f92205cfad
@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 22, 2026 03:56
@os-zhuang
os-zhuang added this pull request to the merge queueAug 22, 2026
Merged via the queue into main with commit 3f0ed1cAug 22, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10897-casing-local-initializer branch August 22, 2026 04:24
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32550887524 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.07s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 76 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

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

2 participants

@os-zhuang@claude