Skip to content

finding(types): zod-mirror-parity pairs navigation.zod's two Breadcrumb mirrors with the data-display declaration, so their green says nothing about the mirror it names #6720

Description

@os-sales

Observed while wiring objectui#6646 / #6645 (branch claude/issue-6646-breadcrumb-family-declared-keys). Filed unassigned, not fixed there — see "Why it was not fixed in that PR" below.

Sub-issue of #6349: this is the BreadcrumbSchema / BreadcrumbItem row of that card's table, measured at the one place the duplication is already doing damage.

Measured

packages/types/src/__tests__/zod-mirror-parity.test.ts states its own pairing rule in its header:

MIRRORS pairs a mirror with the declaration it restates. The PAIRING is the only hand-maintained part … Pairing by name alone is not safe

Two of its pairings do not follow it:

line 124  import { BreadcrumbItemSchema, BreadcrumbSchema, ... } from '../zod/navigation.zod.js';
line 136  import type { ..., BreadcrumbItem as Ts_BreadcrumbItem,
                          BreadcrumbSchema as Ts_BreadcrumbSchema } from '../data-display';
line 444  'navigation.zod.ts#BreadcrumbItemSchema': BreadcrumbItemSchema,
line 445  'navigation.zod.ts#BreadcrumbSchema':     BreadcrumbSchema,
line 606  'navigation.zod.ts#BreadcrumbItemSchema': Ts_BreadcrumbItem;      <- from ../data-display
line 607  'navigation.zod.ts#BreadcrumbSchema':     Ts_BreadcrumbSchema;   <- from ../data-display

The mirror half comes from navigation.zod.ts; the declaration half comes from data-display.ts. They are not the same contract, because BreadcrumbSchema and BreadcrumbItem are each declared twice in this package (#6349's table):

packages/types/src/navigation.ts packages/types/src/data-display.ts
BreadcrumbSchema type · items · separator · maxItems type · items · separator
BreadcrumbItem label · href · icon · onClick · siblings label · href
re-exported by index.ts ✅ line 272, from ./navigation.js ⛔ only through the DataDisplaySchema union (data-display.ts:1623)
mirrored in zod/ navigation.zod.ts:40 / :97 — the only zod breadcrumb in the package; data-display.zod.ts has none

So the mirror restates the navigation declaration (it is the one that carries maxItems, and its BreadcrumbItemSchema carries icon), while the file compares it against the data-display one.

Why the mis-pairing is currently silent, and why that is the bad half

The comparison runs over the union of the mirror's keys and the declaration's, and reports two classes: a key the mirror REFUSES (KnownDrift) and a key the mirror has never heard of (UnmirroredDeclared). A key the MIRROR has and the DECLARATION does not is neither. maxItems, icon, onClick and siblings are exactly that shape against the narrow declaration, so:

  • neither Breadcrumb pair appears in KnownDrift, UnmirroredDeclared or RuntimeOnlyDeclared;
  • both read as fully clean pairs;
  • and that clean reading is about a declaration no consumer of this package resolves.

This is #6349's hazard realised inside a gate, in the wording that card's own triage used: anyone who thinks the mirror is checked against BreadcrumbSchema is reading a check against the one nobody uses, and TypeScript reports nothing because the two declarations are different files with the same name.

Which declaration each consumer actually resolves (verified on 98188c284)

consumer resolves
renderers/data-display/breadcrumb.tsx (import type ... from '@object-ui/types') navigation.ts:205
packages/types/src/registry.ts:87,176 ('breadcrumb': BreadcrumbSchema) navigation.ts (imported from './navigation.js')
packages/types/src/index.ts:272 public export navigation.ts
zod/navigation.zod.ts:97 restates navigation.ts
data-display.ts:1623 DataDisplaySchema union data-display.ts:1578 — publicly reachable through DataDisplaySchema / ComponentSchema, so narrowing either union on type: 'breadcrumb' yields a member with no maxItems and items with no icon
__tests__/zod-mirror-parity.test.ts data-display.ts ⚠️

Why it is worth a card now rather than before

objectui#6646 makes ui:breadcrumb read maxItems, and objectui#5931 / PR #6644 made it read BreadcrumbItem.icon. Both keys live only on the navigation declaration, so the two published keys most recently given behaviour are precisely the ones this pairing cannot see. The parity file exists to catch a mirror drifting narrow against a declaration that widened; on this pair it would not.

Not decided here

Two routes, and the second is not this card's to take:

  1. Re-point the pairing to ../navigation. Mechanically two import specifiers. But the comparison then runs against a declaration with three more keys (icon, onClick, siblings) plus maxItems, and whatever that reveals may need new KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared entries — writing new debt into governed ratchet ledgers, which is a ruling, not an edit. (onClick is the callback-shaped class objectui#6152 split into its own ledger.)
  2. Wait for 46 exported type names carry a second authority — 42 of them are named by no family card #6349's de-duplication of this row, which makes the question moot: with one authority the pairing cannot point at the wrong one. 46 exported type names carry a second authority — 42 of them are named by no family card #6349's own header prescribes the remedy — pick the one authority, delete or re-point the others, drop the KNOWN_COLLISIONS line in the same PR.

⛔ Nothing was stripped and no ledger was touched. Note also the standing fence from #6646's triage: the un-exported-from-index BreadcrumbSchema in data-display.ts must not simply be deleted — data-display.ts:1623's union references it, so any route 2 work re-points that union rather than removing the reference.

Adjacent, not this

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repopm:queuepriority:p2

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions