Uh oh!
There was an error while loading. Please reload this page.
test(console): gate every declared coarse arm against what spec accepts on that key - #6220
Conversation
…ts on that key `registry-inputs-spec-parity.test.ts` compared top-level key NAMES in both directions and nothing else. objectui#3832 gave `ComponentInput.type` the array form, which created a second way for a declaration to disagree with the contract — and the two are not symmetric. Declaring FEWER arms than the spec accepts is NOISE (`checkType` warns on a legal write — audible). Declaring an arm the spec REJECTS is SILENT: the gate clears a value the contract refuses, the manifest and the generated `.d.ts` publish it as legal, and `declared = enforced` inverts with nothing to announce it. This adds the one-directional gate for the silent half: every declared arm must be a kind the spec accepts on that key. An arm names a value's KIND, never its domain (`ComponentInput.type`, maintainer ruling 2026-08-17), so the judge reads the parse ISSUES rather than a boolean — scoped to the key so a required sibling cannot speak for it, recursing through a union's branches, and refuting only a KIND refusal. A refusal of the VALUE leaves the arm standing, which is what stops a correct `'string'` arm on a spec-enum key reading as invented; the same rule still refutes a `'number'` arm there. Both halves are pinned by name on `record:quick_actions.variant`. An `enum` arm is judged exactly instead: every declared member must be a value the spec accepts. Red on arrival on two single-arm declarations no gate had ever compared to the contract — reported, not declared away: `element:number.filter` (objectui#6206) and `object-grid.data` (objectui#6207), carried as reasoned, issue-backed exemptions that a stale-exemption test deletes the moment either side moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
yinlianghui-tw
commented
Aug 25, 2026
PM review — ACCEPT. ⛔ Not armed (6 checks running). The cross-PR interaction I went looking for does not exist.Reviewed by the The interaction check, because two console PRs are in flight tonightThis PR adds two console imports ( All three aliased already, and ⭐ The union-path trap is the best thing here, and the card itself fell into it
A judge built the obvious way would have been vacuously green on every union key while looking like it was working — and the card's own analysis was produced by exactly that mistake. Finding it, fixing it, and then naming the card's reading as an instance of it is the difference between a gate that passes and a gate that measures. Reading the parse issues rather than the boolean, scoped to the key so a required sibling cannot speak for it, is the same discipline one level down. The enum trap as a rule, pinned in both directions
The two listed exemptions ( Red on arrival, reported not fixed — correctTwo single-arm declarations predating #3832's array form had never been compared to the contract, and both are contract questions, not implementation details:
Carrying each as a reasoned, issue-backed exemption that the stale-exemption test deletes the moment either side moves is exactly right: the gate lands, the debt is registered with its reasoning attached, and the exemption cannot outlive the condition. Fixing either here would have silently picked a winner in a contract dispute inside a tooling PR. Non-vacuityMutation 3b — the card's definition of done — reds with the expected message on a file that was entirely green (856 tests) before the change. Mutation 3a shows the per-block pin still reds unchanged, so no per-block coverage was traded for the new gate; that is the trade a new gate most often makes without anyone noticing. Census measured by a declared, trapped, anchored flip of the floor and then restored: 27 blocks · 215 keys · 229 arms · 5 slot-exempt · 2 refused · 2 registered exemptions. Full-repo lint with no narrowing to declare (one flat config, 3697 files, 0 errors). The The two non-zero gates are correctly classified as environmental
⛔ Not arming yet
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#4971
registry-inputs-spec-parity.test.tscompared top-level key NAMES in both directions and nothing else — its own header said so. This adds the third direction, one-way: every coarse arm a block declares on a key must be a kind@objectstack/specaccepts there.Why one direction
objectui#3832 gave
ComponentInput.typethe array form so a union key can declare its real arms, and in doing so created a second way for a declaration to disagree with the contract. The two are not symmetric:checkTypewarns on a legal write. Annoying, occasionally harmful, but audible. Left to per-block discipline.sdui.manifest.jsonand the generated.d.tspublish it as legal, anddeclared = enforcedinverts with nothing to announce it. That is the half gated here.The measurement that defines done — #4971's mutation pair, re-run
Both mutations were applied to the working tree, proven on disk by anchored counts (never an editor's exit code), and restored via
git checkout HEAD -- <path>with an emptygit diff HEAD. Each script carried atrap … EXIT INT TERMrestore. No rebuild was needed and none is claimed:apps/console/vite.config.ts:424aliases@object-ui/componentstopackages/components/src, so a source mutation reaches the run without passing throughdist/.③b —
page:card.title+ a'number'arm (spec rejects a numeric title). Green on all 856 tests before this change; the card is that single reading.③a —
element:text_input.defaultValue+ an'object'arm. It already reddened the per-block test; it must still — no per-block coverage traded for the new gate.How an arm is compared — and the enum trap, stated
An arm names a value's KIND, never its DOMAIN (
ComponentInput.type, maintainer ruling 2026-08-17: "the coarse arm plusdescriptionIS the publication face's expression ceiling today, and SPEC IS THE SOLE JUDGE OF VALUES").page:header.maxVisibleis the worked example — contract1..n, arm'number', and0/-1/1.5pass this layer by design.So the judge reads the parse ISSUES, not the boolean, and refutes an arm only on a kind refusal:
{ filter: … }alone failselement:numberfor the missingobject/aggregate; a whole-parse boolean would report every arm on such a block as invented;page:card.titlereading;invalid_valuedecided by the listed values — a probe of a kind no listed value has is refused for its KIND; one sharing a kind with a listed value is refused for its VALUE.That last rule is the enum treatment, and it is a rule rather than an exemption list. It is asserted in both directions on one key, so it cannot be widened into a hole:
record:quick_actions.variant(a spec enum of strings, declared with a'string'arm) must readrefuses-contenton'Account'— or a correct arm reads as invented — andrefuses-kindon42, or a'number'arm on an enum contract would be the exact silent widening this gate exists for.page:card.title+42is pinned asrefuses-kindby name, which is what proves the enum handling is not why ③b could slip through.An
enumarm is judged exactly rather than coarsely —armAcceptsconsults the input's own member list, so every declared member must be a value spec accepts.Two arm kinds are exempt, listed rather than silent:
'slot'(it names a child position, not a value —armAcceptsadmits everything for it) and an'enum'arm declaring no members (it admits nothing, so it can widen nothing).Non-vacuity, with the census in the verdict line
Zero blocks, zero keys or zero arms fails; so does any covered block contributing no judgement at all (a global count cannot see one block dropping out). The probe vocabulary is compared against
MANIFEST_INPUT_TYPESitself, so an eleventh arm kind arriving upstream fails here naming itself rather than being silently unjudged; and at least oneenumarm with real members must exist, or the exact branch would rot into dead code while the coarse rule kept the file green.No second classifier: it reuses the file's own
coveredset,specTopLevelKeys(tombstone-narrowed), and the same explicit / reasoned / issue-backed / stale-checked exemption discipline as both key-name directions.declaredInputsnow projects a singledeclaredInputEntriesreader rather than a second read ofconfig.inputs.RED ON ARRIVAL — two findings, reported, not fixed
#4971 was filed believing there were zero fake arms. True of what it had measured — #3832's five multi-arm specimens, each checked against spec and its renderer. Both of these are single-arm declarations, the form that predates the array
typeentirely, and nothing had ever compared one to the contract. Accept-set unchanged; no declaration was edited to make this gate green.element:number.filterdeclares the arm'array', butElementNumberProps.filteraccepts only a record/object — the manifest gate clears a value spec refuses #6206 —element:number.filterdeclares'array'(as does every otherfilterinput in the repo), whileComponentPropsMap['element:number'].filteris a record/object and refuses an array outright. The renderer is an opaque passthrough, so nothing in-tree settles which side moves.object-grid.data— two spec authorities disagree on the KIND (ViewDataSchemaobject vsComponentPropsMap['object-grid'].dataarray), so whichever the declaration matches, the other refuses it #6207 —object-grid.data— two spec authorities disagree about the KIND:ObjectGridSchema.dataresolves toViewDataSchema(object, discriminated onprovider),ComponentPropsMap['object-grid'].dataisz.array(z.unknown()). The'object'arm is the deliberate one (objectui#5090 / PR objectui#5108 put it there againstViewDataSchema, pinned bygridDataInputContract.test.ts); flipping it back re-opens object-grid 声明的data输入类型是array(内联行),而契约类型是ViewData(对象)—— 照设计器写出的元数据 spec 解析不过 #5090. Convergence is upstream.Both are carried as reasoned exemptions that
carries no stale arm exemptiondeletes the moment either side moves.Also in this diff — two comments this change makes false
Comment-only, in the same claim-family as the gate itself, both named here rather than left as quiet drift:
apps/console/src/__tests__/component-input-union-specimens.test.tssaid "whether a declared arm matches the contract is nobody's gate (objectui#4971)". Corrected, with why the two files are still not redundant (this one judges declarations against the contract; that one drivesmanifestFromConfigs+validateTreeand judges the gate against the declarations).packages/plugin-grid/src/__tests__/gridDataInputContract.test.tssaid it was "the only gate on this fact". Corrected, and it records the finding:object-grid.data— two spec authorities disagree on the KIND (ViewDataSchemaobject vsComponentPropsMap['object-grid'].dataarray), so whichever the declaration matches, the other refuses it #6207 disagreement — it stays the authority for the contractObjectGridSchemaactually resolves to.Verification — all at
1f205b1f4, the pushed HEADvitest run× 14 affected files (the three touched + every in-tree arm/parity pin +input-type-union)Test Files 14 passed · Tests 265 passedpnpm exec eslint .— the whole repo, one flat config, no narrowingfiles linted: 3697 · errors: 0 · warnings: 10814(pre-existing; CI sets no--max-warnings)pnpm --filter @object-ui/console --filter @object-ui/plugin-grid type-check(after--filter '@object-ui/console^...' build— the closure a fresh worktree needs)Done, exit 0check:control-bytes·check:phantom-deps·check:self-import·check:esm-specifiers·check:vi-mock-specifiers·check:spec-symbols·check:doc-fences·check-changeset-presenceExit codes captured by redirect before any pipe throughout; the lock wrapper's
VERDICTline is what each reading is taken from, never a bare$?.Two gates returned non-zero for reasons this diff cannot reach, reported rather than hidden:
check:eager-closureneedsapps/console/dist/eager-closure.jsonfrom avite buildof the console ("a broken gauge, not a passing budget" — CI's performance-budget job produces it), andscripts/pm/check-half-states.mjsexits 3 on a missing GitHub credential in this container ("no reading at all"). Neither is triggered by a test-file change.Generated by Claude Code