Filed unassigned by the os-dev seat while implementing objectui#8499 (session session_01Jmxdo7bmeqCQHLSfmLVX9w). Grading and domain:* are the triage seat's. Surfaced by #8499's InputShorthandSchema arm; deliberately NOT repaired there, because the repair is an accept-set NARROWING and #8499's PR moves the accept set in one direction only (wider), which is what makes it reviewable.
The claim
packages/components/src/renderers/form/input.tsx registers the two shorthands by wrapping the input renderer and pinning inputType:
ComponentRegistry.register('password',
(props: any) => (props.schema is spread first, then inputType: 'password' last),
{ namespace: 'ui', label: 'Password Input', icon: 'lock', ... });
The wrapper's own value is spread LAST, so an authored inputType is overwritten before the renderer sees it. { "type": "password", "inputType": "text" } therefore renders a MASKED field, not a text one — the author's key had no effect and no diagnostic said so.
The validator does not catch it: BaseSchema passes unknown keys through, so the document parses green on both faces. Measured on claude/issue-8499-node-slot-unresolved-arms after that branch added the email / password arm:
ACCEPT { type: 'password', inputType: 'text' } <- the trap
ACCEPT { type: 'input', inputType: 'text' } <- correct, and the control:
on `input` the key IS read
Both accepted; only the second is honoured. That is the "runtime silently discards authored metadata" shape — an author (or an AI writing metadata) gets a green check and a component that ignored what they wrote.
Why the #8499 arm did not close it
zod/form.zod.ts#InputShorthandSchema is spelled InputSchema.omit({ type: true, inputType: true }), so inputType is absent from the DECLARED face and from the TypeScript twin. That states the contract, and its docblock says so — but omission is not refusal while BaseSchema is passthrough, and the docblock records that measurement rather than overclaiming.
The repair, and why it needs a ruling rather than a patch
This repository already owns the mechanism: zod/tombstone.zod.ts declares a key that is unwritable and refuses it BY NAME with guidance, feeding one string into both the parse-time message and .describe() (retirementTombstone, and its siblings handlerKeyRefusal / aliasKeyRefusal). Applying it here would refuse inputType on the two shorthand literals and point the author at { "type": "input", "inputType": "email" }, which IS the honoured spelling.
That is a narrowing of the published accept set — a document that parses today would stop parsing — so it wants the same treatment ADR-0049 removals get, not a rider on a widening card. Two things a ruling would need:
- a census of authored
inputType on the shorthand literals across the corpora (this repo's catalog: 23 files carry inputType, none of them on a shorthand root — but the hotcrm / objectstack corpora are not measured here);
- whether the sibling shorthands registered the same way (
email) and any later ones are covered by one rule or enumerated.
⚠️ Adjacent but NOT the same defect: the wrapper's precedence could instead be flipped so an authored inputType wins. That would make { type: 'password', inputType: 'text' } render an unmasked field under a password key, which is worse than refusing it. Recorded so the cheaper-looking route is not taken by accident.
Filed unassigned by the
os-devseat while implementing objectui#8499 (sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w). Grading anddomain:*are the triage seat's. Surfaced by #8499'sInputShorthandSchemaarm; deliberately NOT repaired there, because the repair is an accept-set NARROWING and #8499's PR moves the accept set in one direction only (wider), which is what makes it reviewable.The claim
packages/components/src/renderers/form/input.tsxregisters the two shorthands by wrapping theinputrenderer and pinninginputType:The wrapper's own value is spread LAST, so an authored
inputTypeis overwritten before the renderer sees it.{ "type": "password", "inputType": "text" }therefore renders a MASKED field, not a text one — the author's key had no effect and no diagnostic said so.The validator does not catch it:
BaseSchemapasses unknown keys through, so the document parses green on both faces. Measured onclaude/issue-8499-node-slot-unresolved-armsafter that branch added theemail/passwordarm:Both accepted; only the second is honoured. That is the "runtime silently discards authored metadata" shape — an author (or an AI writing metadata) gets a green check and a component that ignored what they wrote.
Why the #8499 arm did not close it
zod/form.zod.ts#InputShorthandSchemais spelledInputSchema.omit({ type: true, inputType: true }), soinputTypeis absent from the DECLARED face and from the TypeScript twin. That states the contract, and its docblock says so — but omission is not refusal whileBaseSchemais passthrough, and the docblock records that measurement rather than overclaiming.The repair, and why it needs a ruling rather than a patch
This repository already owns the mechanism:
zod/tombstone.zod.tsdeclares a key that is unwritable and refuses it BY NAME with guidance, feeding one string into both the parse-time message and.describe()(retirementTombstone, and its siblingshandlerKeyRefusal/aliasKeyRefusal). Applying it here would refuseinputTypeon the two shorthand literals and point the author at{ "type": "input", "inputType": "email" }, which IS the honoured spelling.That is a narrowing of the published accept set — a document that parses today would stop parsing — so it wants the same treatment ADR-0049 removals get, not a rider on a widening card. Two things a ruling would need:
inputTypeon the shorthand literals across the corpora (this repo's catalog: 23 files carryinputType, none of them on a shorthand root — but the hotcrm / objectstack corpora are not measured here);email) and any later ones are covered by one rule or enumerated.inputTypewins. That would make{ type: 'password', inputType: 'text' }render an unmasked field under apasswordkey, which is worse than refusing it. Recorded so the cheaper-looking route is not taken by accident.