test(types): watch the mirror-wider-than-declared direction with a fifth parity ledger - #7758
Merged
Merged
Conversation
…fth parity ledger The parity file's four ledgers are blind by construction to a mirrored, data-shaped key whose zod face accepts a spelling the TypeScript face refuses: the forward comparison finds the declared type fits, and the unmirrored half finds the key present in `.shape`. Adds the reverse-inequality operator, a fifth ledger seeded at the measured debt, its invariant, recognition pins for both blindnesses, and a runtime leg bounding the region the type level cannot judge. No mirror and no declaration moves: every seeded entry is a disposition still to be made. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
…ity-mirror-wider-ledger
… by pair The pair-level assignment locates the failure; the symmetric difference names the key that moved. Neither message alone is the diagnosis. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
The type level cannot report which recursion-breaking mirrors produce the unconstrained input faces it excludes; at runtime the lazy nodes are reachable, so the sources are enumerated and pinned in both directions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s
…ity-mirror-wider-ledger # Conflicts: # packages/types/src/__tests__/zod-mirror-parity.test.ts
…ity-mirror-wider-ledger
This was referenced Sep 5, 2026
Contributor
✅ 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
|
This was referenced Sep 5, 2026
os-justin
marked this pull request as ready for review
September 5, 2026 16:02
This was referenced Sep 5, 2026
This was referenced Sep 5, 2026
This was referenced Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7069
The parity file watched two directions and, by construction, not a third: a mirrored,
data-shaped key whose zod face accepts a spelling the TypeScript face refuses. This adds
the reverse-inequality operator, a fifth ledger seeded at the measured debt, its
invariant, and a runtime leg bounding the part the type level cannot judge.
⛔ No mirror and no declaration moves. Every seeded entry is a disposition still to be
made, which is the shape #6058 landed
UnmirroredDeclaredwith. The dispositions arefiled as #7759; the instrument limitation the audit found is filed as #7760.
Deliverable 1 — the audit, before the ledger
Triage's hard order. Every number below was re-derived on this branch's merged tree
(
origin/maine546222bmerged at8e037c81), after PR #7726, PR #7731 and the latersiblings landed in this file; nothing is carried from before.
The population I scanned for
"Mirror wider than declared" has several producers. The instrument is one type-level
comparison —
[z.input of the mirror slot] extends [the declared type], non-distributive,over keys BOTH faces declare — so what it covers is a property of that comparison, not a
choice:
z.string()/ a wider enum against a narrower TS literal unionz.inputof an optional carriesundefined, so it enters the comparisonz.union([SchemaNodeSchema, z.array(SchemaNodeSchema)])against a TS twin spelling only the arrayunknown, not the unionz.any()against a narrower TS typeanyis assignable to everything, so the comparison is green by construction and can never report itz.unknown()against a narrower TS type⭐ The producer the card called systematic is the one the type level cannot judge, and
that is the audit's main finding.
base.zod.ts#SchemaNodeSchemais annotated with az.ZodTypewhose type argument isany, to break the recursion inside its ownz.lazy,and zod 4 defaults such a schema's INPUT parameter to
unknown. So every slot spelledthrough it reads
unknown(orunknown[]) on the input face — wider than everydeclaration BY DEFINITION, and silent about what the mirror accepts at runtime, where the
lazy union does validate. Comparing there reports the annotation, not the accept-set.
Filed as #7760.
Re-derived on the merged tree, two ways: the literal
z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)])spelling occurs at 61 sitesacross 12 files under
packages/types/src/zod/— confirming the dispatch's count andshowing triage's "12" counted FILES. Structurally, after
.extend()inheritance, 304registered-mirror slots reach a recursion-breaking lazy node, because every component
mirror inherits the base's two schema-node slots.
The distribution
Denominator: 157 registered pairs (
EXPECTED_MIRROR_PAIRS, the pin PR #7731 landed).unknownnested deeper)The raw-signal figure was measured by mutating
Unconstrainedto a constantfalseon atrap-guarded scratch tree (anchor counts before/after; restore proven by blob hash and an
empty
git diff HEAD), not estimated. Of the 54 seeded keys, 12 across 8 pairs arealso in
KnownDrift: those are DISJOINT vocabularies, where each face refuses somethingthe other admits, now measured from both sides.
Ledger sizes re-derived on the same tree, for the record:
KnownDrift42 entries / 63keys,
UnmirroredDeclared14 / 96,RuntimeOnlyDeclared7 / 24, the newWiderThanDeclared36 / 54.Positive control per producer
Each is a synthetic pair the instrument must catch or must ignore, pinned in the file, not
run once by hand:
assertionWiderOperatorReportsAWidening— producer (b): mirrorz.string(), declarationtwo literals. Reported.
assertionNarrowerOperatorIsBlindToAWideningandassertionUnmirroredOperatorIsBlindToAWidening— the SAME pair is silent under bothoperators that stood before. ⭐ This is the card's whole claim reduced to one pair, pinned
rather than argued.
assertionUnconstrainedFaceIsExcludedButNotAWaiver— anunknownslot and anunknown[]slot are excluded while a concrete widening on the same pair is stillreported, so the exclusion is a boundary and not a silencer.
assertionSchemaNodeFaceIsUnconstrained— the premise the exclusion rests on: theannotation really does erase that const's input face.
What the audit found that the card did not predict
⭐ The class the card said would outlive its instance has a live instance.
form.zod.ts#SliderSchema.defaultValueand.valuearez.union([z.number(), z.array(z.number())])on the mirror againstnumber[]on thedeclaration — the exact
DataTableSchema.toolbarshape #6881 measured and PR #7066 retired,on a pair nothing had looked at.
form.zod.ts#FormSchema.layoutis the plainest instance ofproducer (b): the mirror is
z.enum(['vertical', 'horizontal', 'grid'])and the declarationstates the first two, so the third spelling parses green and
tscrefuses it.layout.zod.ts#ContainerSchema.maxWidthadmitstruewhere the declaration admits the falseliteral alone.
TableColumn.typeis NOT in the ledger — the #5853 repair landed, and its absence isthe ratchet reading correctly rather than a miss.
TableColumnSchema.cellis in, on the samepair, as a different key in a different class.
DataTableSchema.rowActions(#6940), is also nothere. That card is CLOSED and the key was repaired — the parity header records the repair
on the
KnownDriftside — so the card's third example is stale, not missed.Deliverable 2 — the ledger
WiderThanDeclaredKeys— the operator, dual toNarrowerThanDeclared, at the typelevel, in the same non-distributive tuple-wrapped shape.
DeclaredKeysand notkeyof Dsupplies the declared half, because
BaseSchema's index signature resolves the declaredtype to
anyfor a key the declaration does not really state, andanyabsorbs thisdirection as silently as the other.
WiderThanDeclared— the ledger, keyed byMirrorKey, seeded with all 36 pairs andtheir exact key sets, each entry naming its class and its wider shape, and the instance card
where one exists.
assertionWiderMatchesLedger— the invariant, routed through the existingReconcileAgainstLedger(its shape fits unchanged), written as an assignment toneversothe compiler prints the offending PAIR.
assertionWiderLedgerRecordsEveryKey— the same reconciliation reported by KEY. Not aduplicate: the pair-level message locates the failure and says nothing about which key
moved; the two together are the diagnosis, which the ablation below shows.
assertionNoVacuousWiderMeasurement— see the withdrawn design note.assertionWiderLedgerKeysAreRegistered— every ledger key names a registered pair.measurement's hole is.
Runtime, where the type level cannot see
The dispatch asked for the type level first and runtime only where the type level is blind,
and to say which. It is blind to WHICH mirrors produce the unconstrained faces it excludes:
at the type level every unconstrained face looks alike, so a new recursive mirror widens the
blind region without changing one character the compiler reads. At runtime the mirrors are
values, so the region is enumerable. Three cases walk
_zod.deffrom every registeredmirror and pin the SOURCES in both directions — not a count of slots, which moves with every
.extend().⭐ The first spelling of that leg asserted the region had a single source,
SchemaNodeSchema. The walk refuted it on its first run: the navigation, menu, tree,action and filter-condition mirrors each carry the same
z.ZodTypeANY annotation — 10such consts across 7 files — plus three lazy nodes with no exported const to name. The
claim was replaced by the measurement, and the file records that it was wrong, because the
wrong version is the intuitive one: the card, the triage and the dispatch all describe the
producer as if it were one const.
One design withdrawn, and why it is in the file
A recursive
Unconstrainedthat descended into object properties would have excluded theSCHEMA-NODE class properly instead of only its top two levels. It was written, measured and
withdrawn: on the deepest pairs it drove the whole
WiderOfinstantiation at a pair toany, andanyis assignable tonever, so the invariant went silently green on exactlythe pairs with the most structure — a guard that cannot fail, wearing the shape of a
stricter one.
assertionNoVacuousWiderMeasurementexists because of that attempt and pinsthe failure mode, so a future deeper predicate cannot land unnoticed. This is why the
SCHEMA-NODE entries are seeded and labelled rather than excluded.
Red-first proof
Re-run on the FINAL head
8e037c81. Trap-guarded, absolute paths, mutation proven on disk byanchor counts before and after (⛔ never by an editor's exit code), restore proven by blob
hash against
HEADand an emptygit diff HEAD. Baseline at HEAD:tscexit 0.AlertSchema.variantgains a third enum armtscexit 1;Type '"data-display.zod.ts#AlertSchema"' is not assignable to type 'never'andType '"variant"' is not assignable to type 'never''layout'dropped from theFormSchemaentrytscexit 1; names the pair andType '"layout"'Both restores: blob matches
HEAD,git diff HEADempty. Predicted direction was RED inboth legs, in opposite directions, and that is what was observed. No
distis involved —tsc -p tsconfig.test.jsonreads the mirrors as sources in one program, so there is no buildstep between the mutation and the measurement.
Changeset
check-changeset-presence.mjscounts the test file as source of a released package, so itdemanded a declaration and got the empty-frontmatter form, which that gate documents as
the explicit exemption. ⛔ No
skip-changesetlabel — a phantom in this repo.Nothing ships:
tsc -p tsconfig.json --listFilesOnlylists 0 files under__tests__,and
tsc -p tsconfig.test.json --listFilesOnlydoes list the parity file, so the type-levelinvariants are inside a program that runs.
Gates — exit codes captured before any pipe, heavy runs through the shared verify lock
All re-run on the final head
8e037c81:pnpm exec vitest run --maxWorkers=2 packages/types/pnpm --filter @object-ui/types type-checknode scripts/check-changeset-presence.mjsnode scripts/check-changeset-fixed.mjsnode scripts/check-changeset-no-major.mjsnode scripts/check-changeset-overwrite.mjspnpm run check:control-bytespnpm run check:spec-symbolspnpm run check:published-tsconfig-excludepnpm --filter @object-ui/types lintThe three new runtime cases were confirmed to EXECUTE under
--reporter=verbose, not merelyto be collected. Control bytes were also self-scanned on the changed files outside the gate.
Governed-surface predicate on the final file list: 0 of 2 paths hit the register —
ordinary queue landing applies.
🤖 Generated with Claude Code
https://claude.ai/code/session_01BAZFhALsQsGqxui8sNqM8s