Split out of #12810 by the triage seat (session session_01Aujz2zykf5LXt3T98gRsGe), on that card's own recommendation — "They may deserve splitting." ⛔ All measurement below is #12810's, not re-derived here; read it there for the evidence and the controls.
Why split.#12810's two items need different treatment, and folding them drags the cheap one behind the expensive one:
| item | shape | treatment |
|---|
1 — dashboard-widget-options (absent here entirely) | additive: a new warning code reporting an already-inert state | same shape as #12719 — stays on #12810, off the manual floor |
2 — union-arm type-mismatch (this card) | ⚠️changes what this copy accepts and rejects | Clause-② content leg; contract-review tier |
What is missing here
objectui's checkType is built on inputTypeArms(input.type) (src/input-type.ts, absent in this repo) and clears a prop when any declared arm accepts the value. This repo's checkType is the older single-arm switch (input.type). Two measured consequences:
- A manifest input declaring a union type falls through this repo's
default: return null and draws no diagnostic at all, while objectui checks every arm. - Where no arm accepts, objectui emits one
type-mismatch naming every arm, at error severity when an enum arm is present. This repo can only emit the single-arm form ⇒ the same value yields a different code/severity pair across the copies.
Why this is Clause-② and not a mechanical port
⛔ This is not#12719's "reports an already-inert state" shape. Today a union-typed input draws nothing; after the port it can draw an error. That is a change to what the save gate accepts and rejects, which is the content leg — needs:contract-review is attached at filing and the dispatch must declare Clause-②: yes.
⚠️But it is not a new contract decision either, and that is why this is pm:queue rather than the decision inbox. The underlying ruling is objectui#3832, already made on the other copy. #12719 establishes the invariant both copies owe each other — "both copies byte-agree on the accepted grammar and on diagnostic codes" — so not porting is the drift, and porting applies an existing ruling to the lagging copy. ⇒ Contract-review tier, ⛔ not a maintainer question. If the implementer finds the port would require deviating from objectui#3832's behaviour rather than matching it, stop and report — that would be a new decision.
⚠️ Sequencing — settle this before or with the manifest wiring, ⛔ not after
The blast radius is currently bounded by a separate unfixed gap: this repo resolves no sdui.manifest.json (none in the tree; @objectstack/console/dist/sdui.manifest.json absent), so resolveSduiManifest() returns undefined and validateJsxPages runs parse-only — validateTree is not reached from the production gate at all today. ⇒ The drift is latent. Wiring the manifest is what turns it author-visible, so this card should land before or with that wiring. That wiring is the second gap recorded on #12719 and is still unowned.
⚠️ Interaction with an open maintainer decision
objectui#6614 (needs-user-decision) asks whether interpretBrace should materialise a JS literal subset. If it rules to widen the grammar, the set of values reaching checkType changes — which may reshape what this port should assert. ⛔ Check #6614's state at dispatch: if it is still open, say so in the report and consider whether the port is stable under either outcome. It probably is (arm checking is orthogonal to what parses), but that is a reading to take, not to assume.
Not drifted, measured — ⛔ do not re-derive
interpretBrace itself is byte-identical across the copies (sha256 of the extracted function body agrees, and the comparator was proved able to detect a disagreement by mutating one side first). The accepted grammar is genuinely in lockstep; the gap is entirely on the diagnostic-code side.
Refs: #12810 (parent, item 1 and the full measurement) · #12719 (the inert-expression port and the invariant) · #11148 (the same two-copy port pattern) · objectui#3832 (the ruling being ported) · objectui#6614 · ADR-0078 · ADR-0080.
Split out of #12810 by the triage seat (session
session_01Aujz2zykf5LXt3T98gRsGe), on that card's own recommendation — "They may deserve splitting." ⛔ All measurement below is #12810's, not re-derived here; read it there for the evidence and the controls.Why split.#12810's two items need different treatment, and folding them drags the cheap one behind the expensive one:
dashboard-widget-options(absent here entirely)type-mismatch(this card)What is missing here
objectui's
checkTypeis built oninputTypeArms(input.type)(src/input-type.ts, absent in this repo) and clears a prop when any declared arm accepts the value. This repo'scheckTypeis the older single-armswitch (input.type). Two measured consequences:default: return nulland draws no diagnostic at all, while objectui checks every arm.type-mismatchnaming every arm, aterrorseverity when anenumarm is present. This repo can only emit the single-arm form ⇒ the same value yields a differentcode/severitypair across the copies.Why this is Clause-② and not a mechanical port
⛔ This is not#12719's "reports an already-inert state" shape. Today a union-typed input draws nothing; after the port it can draw an
error. That is a change to what the save gate accepts and rejects, which is the content leg —needs:contract-reviewis attached at filing and the dispatch must declareClause-②: yes.pm:queuerather than the decision inbox. The underlying ruling is objectui#3832, already made on the other copy. #12719 establishes the invariant both copies owe each other — "both copies byte-agree on the accepted grammar and on diagnostic codes" — so not porting is the drift, and porting applies an existing ruling to the lagging copy. ⇒ Contract-review tier, ⛔ not a maintainer question. If the implementer finds the port would require deviating from objectui#3832's behaviour rather than matching it, stop and report — that would be a new decision.The blast radius is currently bounded by a separate unfixed gap: this repo resolves no
sdui.manifest.json(none in the tree;@objectstack/console/dist/sdui.manifest.jsonabsent), soresolveSduiManifest()returns undefined andvalidateJsxPagesruns parse-only —validateTreeis not reached from the production gate at all today. ⇒ The drift is latent. Wiring the manifest is what turns it author-visible, so this card should land before or with that wiring. That wiring is the second gap recorded on #12719 and is still unowned.objectui#6614 (
needs-user-decision) asks whetherinterpretBraceshould materialise a JS literal subset. If it rules to widen the grammar, the set of values reachingcheckTypechanges — which may reshape what this port should assert. ⛔ Check #6614's state at dispatch: if it is still open, say so in the report and consider whether the port is stable under either outcome. It probably is (arm checking is orthogonal to what parses), but that is a reading to take, not to assume.Not drifted, measured — ⛔ do not re-derive
interpretBraceitself is byte-identical across the copies (sha256 of the extracted function body agrees, and the comparator was proved able to detect a disagreement by mutating one side first). The accepted grammar is genuinely in lockstep; the gap is entirely on the diagnostic-code side.Refs: #12810 (parent, item 1 and the full measurement) · #12719 (the
inert-expressionport and the invariant) · #11148 (the same two-copy port pattern) · objectui#3832 (the ruling being ported) · objectui#6614 · ADR-0078 · ADR-0080.