Skip to content

27 keys across 18 pairs have a zod mirror that ACCEPTS a spelling its TypeScript declaration refuses — each needs a narrow-the-mirror / widen-the-declaration ruling #7759

Description

@os-justin

Filed unassigned by the dev seat executing objectui#7069 (PR #7758), which added the fifth parity ledger WiderThanDeclared and SEEDED it at the debt this direction was already carrying. Seeding is what lets that PR be green on its own head; it is a floor, not a disposition. This card is the disposition lane, the shape objectui#6152 is for UnmirroredDeclared and objectui#6033 / objectui#5927 are for the NARROWER direction.

Recording and grouping only; ⛔ not proposing per-key remedies.

What was measured

packages/types/src/__tests__/zod-mirror-parity.test.ts, on origin/main e546222b merged at 8e037c81. Of the 157 registered pairs, the new operator reports 36 pairs / 54 keys. Those split into two classes that do NOT share a remedy, and only one of them is a defect:

  • SCHEMA-NODE — 27 keys / 21 pairs. An unknown survives into the mirror's input face from the recursion-breaking annotation on the schema-node mirrors. These record the ANNOTATION, not an accept-set gap: at runtime the lazy union does validate. ⛔ Not this card, and ⛔ not repairable per key. The instrument limitation behind them is its own card.
  • CONCRETE — 27 keys / 18 pairs. Both faces concrete, so the reading means what it says: an author can write the spelling, safeParse returns green, and tsc refuses it. That is this card.

The 27, grouped by why they diverge

A. INLINE-LOCALE widening — the mirror takes a locale map the declaration restates as a plain string

BaseSchema's mirror spells the label keys as a plain string OR an inline locale map; these declarations restate the key as a plain string, so the narrowing is on the DECLARED side and the forward comparison reads the pair as clean.

pair keys
app.zod.ts#AppComponentSchema label
objectql.zod.ts#ObjectGridSchema label, description
layout.zod.ts#PageNodeSchema aria (the nested aria label)
complex.zod.ts#DashboardComponentSchema header, globalFilters (both nested one level down)

B. SINGLE-OR-LIST where the declaration states the list alone — the objectui#6881 toolbar shape, alive

pair keys shape
form.zod.ts#SliderSchema defaultValue, value mirror z.union([z.number(), z.array(z.number())]), declaration number[]

⭐ This is the class objectui#7069 was filed to keep alive after PR #7066 retired its instance, and it turned out to have a live one on a pair nobody had looked at. A single number parses green and tsc refuses it.

C. An extra arm the declaration does not admit

pair key shape
form.zod.ts#FormSchema layout mirror z.enum(['vertical', 'horizontal', 'grid']), declaration states the first two
layout.zod.ts#ContainerSchema maxWidth mirror's arm is z.boolean(), declaration admits the FALSE literal alone, so true parses green

D. DISJOINT vocabularies — already in KnownDrift, now measured from the other side too

Each face refuses something the other admits, so the key is legitimately recorded in both ledgers. 12 keys across 8 pairs; the CONCRETE ones are:

pair keys
form.zod.ts#FormSchema fields, mode
form.zod.ts#CalendarSchema defaultValue, value
navigation.zod.ts#HeaderBarSchema variant
complex.zod.ts#FilterFieldSchema operators
complex.zod.ts#FilterBuilderSchema fields
complex.zod.ts#ChatbotSchema body
complex.zod.ts#DashboardComponentSchema header, globalFilters

⚠️ FilterFieldSchema.operators and FilterBuilderSchema.fields overlap objectui#7561, which measured the same operator-vocabulary divergence from the RENDERER side. Listed to prevent re-conflation, not re-filed.

E. FUNCTION-SLOT — 8 keys

pair keys
data-display.zod.ts#DataTableSchema renderCellEditor
data-display.zod.ts#TableColumnSchema cell
form.zod.ts#FieldConditionSchema custom
form.zod.ts#FieldConstraintsSchema validate
form.zod.ts#FormFieldSchema condition (nested)

zod 4 gives a function mirror an opaque input brand that no concrete signature equals, so the mirror accepts ANY callable where the declaration states one signature. ⚠️ These are mirrored runtime slots, so objectui#6152's RuntimeOnlyDeclared ruling does not reach them — that ledger is for keys the mirror never declares. Whether a runtime slot should carry a z.function() mirror at all is the question, and it may be one ruling for all of them rather than five. objectui#7351 records a related z.function() survivor of objectui#6124's sweep.

F. Structural — an inline element shape against a named declaration

pair keys
data-display.zod.ts#DataTableSchema columns
form.zod.ts#FormFieldSchema validation
views.zod.ts#DetailViewFieldSchema options
complex.zod.ts#DashboardComponentSchema dateRange — mirror takes a bare string where the declaration states a closed literal set

Why the default remedy is NARROW THE MIRROR, and why that is still a ruling

The mirror is the AUTHORING boundary — it validates AI-written metadata — so a wider key hands an author a green parse on a spelling the published types refuse, which is the contract-first direction to close at the producer. But each key needs the renderer read measured before the direction is chosen: group A may be a declaration that forgot the base's locale map rather than a mirror that over-accepts, and group D's disjoint pairs have a dead side to identify first. ⛔ Not decidable in bulk.

Dedup

One targeted semantic search over the parity / mirror / accept-set vocabulary, INCLUDING closed issues, returned 35 substantive results — a live instrument, not a silent zero. Reviewed: objectui#5927 / objectui#6033 are the NARROWER direction's per-key lane, objectui#6152 the unmirrored one, objectui#7069 this direction's class card, objectui#7561 / objectui#7465 / objectui#7735 per-key instances, objectui#6940 and objectui#5853 closed instance cards of this very direction. No card claims the wider direction's per-key dispositions. Channel note, declared: repo-scoped REST is refused for this seat (the session permission layer returns 403), so the dedup ran on the semantic-search channel.

One census card rather than 18 per-pair cards, stated as the dev seat's call: the groups above share remedies within themselves, and 18 cards would fragment a ruling that is really five or six.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions