Skip to content

fix(tooling): the error-vocabulary resolver reduces a code held in a ternary or a literal chain (#9568) - #9622

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-9568-resolve-ternary-literal-codes
Aug 18, 2026
Merged

fix(tooling): the error-vocabulary resolver reduces a code held in a ternary or a literal chain (#9568)#9622
os-zhuang merged 2 commits into
mainfrom
claude/issue-9568-resolve-ternary-literal-codes

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#9568

Verified at 3c91ed447 (the branch head this description reports on — every count below was re-taken on that tree after the origin/main merge).

What changed

check:dispatcher-error-vocabulary could not reduce a code held in a local ternary of string literals. resolveConstant reduced exactly one spelling, const NAME = 'LITERAL', so the live 403 in packages/metadata-protocol/src/sys-metadata-repository.ts:1265 contributed nothing:

constcode=intent==='runtime-only' ? 'NOT_CREATABLE' : 'NOT_OVERRIDABLE';err.code=code;

The resolver is now value-SET valued end to end. resolveConstant returns an array, and a new literalCodeValues reducer handles a quoted literal, a ternary of reducible branches (nested; the condition is never evaluated), a || / ?? chain whose every operand reduces, and either wearing parentheses or as const. An identifier limb is handed back to the resolver, so a ternary of two named constants reduces too. Every constant-resolving shape routes through it, so classconst, objlitconst, assignconst and a helper's argument all gained the same reduction from one change.

Two properties are deliberate and pinned by the self-test:

  • All-or-nothing.asSemanticCode(a) ?? asSemanticCode(b) (packages/client) reduces to nothing rather than to its literal half. Harvesting the literal limbs of a partly-runtime expression reports a code the program may never stamp while staying silent about the limb it cannot see, which is wrong in both directions at once.
  • No guessing between two declarations. A file-wide textual lookup has no scopes, so two declarations of one name that reduce differently answer null and are reported, the same rule the workspace half already applied across a package.

The one wiring change outside the resolver: codehelper (the .code = ident position) used to drop the identifier when it was not a parameter of the enclosing declaration. It now offers it to resolveConstant first and reports what reduces, under assignconst — the same shape name that position already uses for its constant indirection. An identifier that does not reduce keeps its old treatment exactly: no site, no unresolved finding. That is the header's runtime-value bound, not a silencing.

The widening matches: predicted, then observed

A resolver that matches nothing looks exactly like a resolver that passes, so this is measured by name against origin/main's script, not by an exit code.

Resolver level, on the real file (packages/metadata-protocol/src/sys-metadata-repository.ts:1265):

PRE-CHANGE (origin/main) resolveConstant('code') -> null
sites naming the specimen's codes: NONE
POST-CHANGE (this branch) resolveConstant('code') -> ["NOT_CREATABLE","NOT_OVERRIDABLE"]
sites: [{"code":"NOT_CREATABLE",...,"shape":"assignconst"},
{"code":"NOT_OVERRIDABLE",...,"shape":"assignconst"}]

Gate level, repo-wide, registry neutralized. Both trees scan the identical 1840 sources (this diff touches no scanned file); deriveSites was run with an empty registered set so the measurement shows what the RESOLVER reaches rather than what the ledger filters:

base (origin/main resolver) files=1840 sites=333 unresolved=0
new (this branch) files=1840 sites=335 unresolved=0
diff:
> SITE NOT_CREATABLE assignconst packages/metadata-protocol/src/sys-metadata-repository.ts
> SITE NOT_OVERRIDABLE assignconst packages/metadata-protocol/src/sys-metadata-repository.ts

Exactly +2, both by name, nothing lost and no new unresolved finding. The newly-resolvable set repo-wide is the named specimen and nothing else.

The card's registration premise is falsified, and it changes the outcome

The card states the two codes are unregistered and "both codes stay invisible". They are registered: NOT_CREATABLE and NOT_OVERRIDABLE sit in ERROR_CODE_LEDGER (packages/spec/src/api/error-code-ledger.zod.ts:387-388), added by 445ae4deb on 2026-08-16, two days before the card was filed. The card's control ("the same probe finds PERMISSION_DENIED in the union") proved the probe reads the union; it did not test these two.

So the gate as CI runs it reports the same 18 sites, all classified, 2 awaiting a ledger entry before and after this change. That is the gate's declared bound working, not blindness: it reports only codes the registered vocabulary does not contain, and these two are in it. The reduction is real and proven above; what it resolves is already registered.

Consequences, stated rather than left to be inferred:

  • Zero newly-surfaced sites, so zero classification rows.packages/runtime/src/dispatcher-error-vocabulary.ts is untouched. Nothing was classified silently and nothing was filed, because the widened scan surfaced nothing to classify or file.
  • The separate spec-lane step this card deferred is already done. No ledger edit here, and none needed.
  • The value that remains is the class, not this specimen: the resolver no longer goes blind on a shape whose every branch is written in the source, and the header's published bound now says what the code does.

Self-test

--self-test goes from 81 to 102 assertions (8 shapes unchanged). The new block pins the specimen shape, the reduction in the other constant positions, chains, nesting, a ternary of named constants, a ternary handed to a helper, both splitters' silent hazards (a comparison operator read as a bracket; ?. / ?? read as the conditional), and the all-or-nothing bound in both directions.

Ablation, predicted before running:

ablationpredictedobserved
splitTernary returns null (reduction reverted)6 failures: specimen reduce · objlitconst ternary · nested ternary · ternary of named constants · helper-argument ternary · the comparison-operator pin6, exactly those
the codehelper fallback wiring removed1 failure: the specimen only — every other ternary case reaches assignconst's own regex through a SCREAMING_SNAKE constant1, exactly that

Restored from the commit after each leg; git status clean, self-test back to 102.

Gates

Local, at 3c91ed447:

check-dispatcher-error-vocabulary --self-test: 8 shapes + 102 assertions OK
check-dispatcher-error-vocabulary: OK - 18 unregistered code-stamping site(s), all classified; 2 awaiting a ledger entry (#8846).
scope: 1840 non-test source files under packages/; 292 registered codes (239 ledger + 53 standard); 18 found; 18 classified.
check-nul-bytes: OK (scanned 6173 text file(s); no raw ASCII control bytes)
eslint scripts/check-dispatcher-error-vocabulary.mjs: 1 file linted, 0 errors 0 warnings

scripts/pm/dispatch-gates.mjs re-derived on the merged ref matches one family for this surface, check:dispatcher-error-vocabulary. The new check:single-claim-paths declares only .objectui-sha, which this diff does not touch. CI job names are the authority; the PM reads them.

No changeset: the diff is scripts/ only. It publishes nothing, so skip-changeset carries the reason.

Not weakened: no ignore or allowlist entry, no narrowed pattern, no ratchet number moved. The resolver only gets stricter (it now refuses to guess between two declarations) and only gains reach.

Generated by Claude Code


Generated by Claude Code

…ternary or a literal chain (#9568)
Co-Authored-By: Claude <noreply@anthropic.com>
@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 18, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review August 18, 2026 13:32
@os-zhuang
os-zhuang enabled auto-merge August 18, 2026 13:32
@os-zhuang
os-zhuang added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 461de63Aug 18, 2026
24 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-9568-resolve-ternary-literal-codes branch August 18, 2026 13:55
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