Uh oh!
There was an error while loading. Please reload this page.
fix(tooling): declare kebab-case diagnostic codes a separate vocabulary, in one place - #13130
Merged
Merged
Conversation
…ry, in one place Two gates guard code-shaped values and hand work to each other, and a kebab-case code falls in the seam: it HAS a quoted literal at the stamp position (so check-dispatcher-error-vocabulary defers it as lowercase), and it is lowercase (so it is check-error-code-casing's), but that gate's grammar is [a-z][a-z0-9_]* and no grammar in either gate admits a hyphen. So a kebab literal matches no pattern anywhere, both gates count the file as scanned, and neither has anything to say about it. Coverage of that family was already zero; this changes no verdict. What changes is that the silence is now DECLARED rather than accidental -- KEBAB_DIAGNOSTIC_VOCABULARY states it once, the printed bounds are derived from it so a run says it, and --self-test pins it against BOTH gates' recognizers by importing the other gate's detector instead of paraphrasing it. If anyone later widens a grammar to admit a hyphen, the pin fails and names the decision as a gate-population change for the maintainer. Deliberately NOT done here: widening any grammar (unmeasured blast radius across every constant in the repo), and renaming the diagnostic family to SCREAMING_SNAKE to make a regex see it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
… not the finding list Found by ablating the pin rather than by reading it. Widening `objlit` to admit a hyphen makes a kebab literal MATCH and still produces no finding: `objlit` carries `lowercase: 'casing-gate'`, so the delegation hands the value to a gate whose grammar also excludes the hyphen -- back into the same seam, one layer deeper. The first version of this pin watched findings and therefore sat GREEN through exactly the widening it exists to catch. The population question is whether any published recognizer MATCHES the value, so that is what is counted now, on both gates' recognizers. The reading is recorded in the declaration too, because it sharpens the card's claim: matching is not the same as seeing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrUz21stTFhJRUirdc4yw
…bab-code-vocabulary
os-elon
marked this pull request as ready for review
August 29, 2026 06:14
os-elon
enabled auto-merge
August 29, 2026 06:15
Contributor
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 33239036507 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 29, 2026
Merged
This was referenced Aug 29, 2026
Merged
This was referenced Aug 29, 2026
Merged
This was referenced Aug 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#12925
Turns an accidental silence into a declared one, in one place. No grammar is widened, no code is renamed, and no gate verdict changes today.
The seam
The two gates over code-shaped values are designed as a pair that hands work to each other, and a kebab-case code falls in the seam between them:
check-dispatcher-error-vocabulary'sobjlit/assignshapes delegate a lowercase value to the other gate (lowercase: 'casing-gate');check-error-code-casing's — but that gate's grammar is[a-z][a-z0-9_]*.Neither grammar admits a hyphen, so the literal matches no pattern in either gate. Both count the file as scanned; neither has anything to say about it. The bound this gate declares for itself — a value it cannot reduce is REPORTED, never dropped — is honoured for the constant form and quietly violated for the literal form, because a value outside every grammar fires no pattern and so cannot report itself.
What landed
KEBAB_DIAGNOSTIC_VOCABULARYinscripts/check-dispatcher-error-vocabulary.mjs— one exported constant with a docblock, following the two shapes this repo already uses for a declared population boundary (SANDBOX_AUTHORED_LIMBin the declaration file, cited in the printed bounds;isDeclarationFile, a named exclusion pinned by--self-test).Three properties, deliberately:
check-error-code-casing.mjsis untouched — two gates each carrying half of a boundary is what produced this hole.boundsline is derived from the constant, so it cannot drift from it.--self-testpins it, and imports the other gate's real detector (findViolations) rather than paraphrasing its patterns — so widening either gate trips it. The failure text names the decision: a gate-population change, to be measured and routed to the maintainer on its own card.Measurements
The five grammars, re-verified on this tree (not relayed):
:272assign,:274classfield,:280objlit,:427literalCodeValues,:1088template shape — none contains a hyphen. Plus all six patterns incheck-error-code-casing, grammar[a-z][a-z0-9_]*.Census, with an independent control. The triage comment flagged its own control as same-population; this is the independent one. Both production instruments were run over three populations:
packages/sdui-parser/src/validate.ts(subject)packages/metadata-protocol/src/build-probes.ts(snake_case diagnostics)packages/runtime/src/dispatcher-error-vocabulary.ts(SCREAMING_SNAKE)Both instruments return non-zero on independent populations, so the zeros on the subject are readings and not dead instruments; the kebab detector returns zero on both independent populations, so it discriminates. The subject file is confirmed inside the scanned set (2078 files) — it is read and matches nothing, which is the whole defect.
The family is larger than the card's census, and fully homogeneous. Triage corrected the card's six to seven; decomposed across the whole package it is 24 distinct codes, 24 of 24 kebab, 0 non-kebab:
code: '...'— 7 invalidate.ts(triage's seven) plusunconsumed-widget-optionindashboard-widget-options.ts;Parser#error(code, ...)inparse.ts.Decomposing the 10 raw
codepositions in the package: 8 value stamps (all kebab) and 2 type positions carrying no value (Diagnostic.code: string, and the helper's parameter annotation).The inverted incentive is confirmed, and is worse than the card states.
deriveSiteson a kebab constant yields 0 sites and exactly 1unresolved-constant, pushed unconditionally inreconcile(). Trying to discharge it with a declaration row does not remove it — the row itself becomes a second finding (stale-row), so the attempt takes the author from 1 finding to 2. Pinned in the self-test.The ablation, including the leg that falsified my prediction
Predicted before running; the control run must stay GREEN (both gates are already green over the kebab family, so "still green" would prove nothing on its own), each mutation leg must go RED and name the decision. Every mutation is proved on disk by an exact-literal anchor with a match count, and every restore by
git diff HEADempty and blob hash equal to the HEAD blob. No build leg: each subject is executed bynode PATHdirectly — a root.mjs, never through a packageexportsintodist— and the witness is read as source by the pin.objlitto admit a hyphenliteralCodeValuesto admit a hyphenemissionpatternLeg A came back GREEN on the first run, against my prediction, and that changed the fix. Widening
objlitalone makes a kebab literal match and still report nothing:objlitcarrieslowercase: 'casing-gate', so the delegation hands the value to the gate whose grammar also excludes the hyphen — back into the same seam, one layer deeper. The first version of the pin watched findings and therefore sat green through exactly the widening it exists to catch. The pin now counts recognizer matches on both gates, which is the actual population question, and the reading is recorded in the declaration because it sharpens the card's claim: matching is not the same as seeing. Leg A is red in the re-run above.Local verification
Union run after the final commit, at
e5481a759d. 18 commands, all exit 0, each verdict read from the gate's own printed line:check:agent-test-spelling,check:bash32-floor,check:cli-command-ids,check:cross-package-test-inputs,check:dispatcher-error-vocabulary,check:entry-guard,check:parse-guard,check:pnpm-filter-targets,check:watch-hint-literal,check:nul-bytes,check:pm-dispatch-gates,check-ci-filter-parity,check-shard-attestation,bare-root-worklist --self-test,check-self-test-wired, and the--self-testof both vocabulary gates plus the casing gate's ordinary run. Family derived withdispatch-gates --repo objectstack-ai/objectstackon the synced tree (change set: 1 path, no stale-tree warning); the convention pair for a gate-script edit is included.check-test-completeness.mjsis named by the derivation but answers a standalone run with exit 1 and a usage line — an invocation error rather than a red gate, already on record as #13110. Not run, not fixed here.ESLint, narrowed and declared (the repo-wide sweep is CI's run). Targeted:
eslint --no-inline-config --format jsonon the changed file, 1 file linted, 0 errors, 0 warnings. The three pieces of evidence the narrowing needs: the population comes from eslint's own config resolution rather than my guess (it resolved a config and linted the path); the file count is read from the JSON output; and this repo's singleeslint.config.mjsnever enables type-aware linting for any file — noparserOptions.project, no typed rules — so a one-file diff cannot move any untouched file's verdict.No changeset, derived rather than asserted: root
scripts/appears in no package'sfileswhitelist, so this releases nothing.check-empty-changeset.mjsnames this case itself — "a PR that edits a CI-internal script is the textbook case for that label". Theskip-changesetlabel is applied.Deliberately not done
Out of scope, filed separately
A second hole surfaced while measuring the helper half of the census, and it is a different defect class — casing-independent, so not a rider on this card. A code-carrying helper that stamps its code through an object-literal shorthand (
{ severity, code, message }) rather than an assignment (obj.code = code) is invisible to both gates for any casing. Measured against a control:.code = codeinside a helper is detected ascodehelper, while the same helper stamping{ code }returns 0 from both gates even for an unregistered SCREAMING_SNAKE value;{ code: code }longhand is 0 too. That is the #9223/#10918 genre one stamp position later.Parser#errorinsdui-parseris a live instance, carrying 16 codes. Filed as a finding; no fix here.Context, not scope: #12924 reads the same file's diagnostics for an unrelated reason and independently arrives at the same seven in
validate.ts. That card is not addressed here and remains open.Generated by Claude Code