packages/types/src/zod/node-derivation.ts declares the shared walker def type:
export interface WalkableDef {
type: string;
items?: z.ZodType[];
rest?: z.ZodType;
...
}
rest?: z.ZodType means z.ZodType | undefined. Zod 4.4.3 does not put undefined there — it puts null.
The measurement
zod 4.4.3, zod/v4/classic/schemas.cjs, the tuple factory:
const rest = hasRest ? _paramsOrRest : null;
Probed directly:
z.tuple([z.number(), z.number()])._zod.def
def keys : ["type","items","rest"]
hasOwn(rest) : true
def.rest === null : true
def.rest === undefined: false
A census of every null literal in zod v4's classic/schemas.cjs + core/schemas.cjs returns 30 occurrences, of which exactly one is minted into a def member a walker reads — that rest. Every other one is an instance-level accessor default (inst.format, inst.minValue, …), a parse-time value, or unrelated. Swept per member across the twelve the walkers read (items, rest, options, element, shape, valueType, left, right, in, out, innerType, getter), rest is the only one with a non-zero count.
Why it is worth a card rather than a note
This inaccurate declaration is the root cause of objectui#9088, not a cosmetic drift. The tuple arm was written
const rest = def.rest ? walk(def.rest) : undefined;
and undefined is exactly what the declared type says the absent case is. The author read the type, the type was wrong, and unchanged compares by === — so null === undefined was false and every rest-less tuple was rebuilt. objectui#9088 fixes that one arm by copying def.rest instead of normalising it; it does not correct the declaration, because node-derivation.ts is outside that card's declared file surface.
⇒ the next arm written against this type is licensed to make the same mistake, and tsc will agree with it.
Candidate repair
rest?: z.ZodType | null, and then check whether any read of def.rest in either walker needs adjusting. Both current readers (imported-defaults.ts's tuple arm and strict-authoring-face.ts) already guard with a truthiness test, so the widening is expected to be declaration-only — but that is a claim to measure, not to assert. def.out was measured clean in objectui#9088 and needs no change.
Scope note
Found while implementing objectui#9088. Not fixed there: packages/types/src/zod/node-derivation.ts is not on that card's file surface, and the widening touches a type shared by two walkers, so it deserves its own review rather than riding in on a one-arm fix.
Filed by the objectui domain:spec developer seat while implementing objectui#9088. Generated by Claude Code, session session_01L5xpA5q533BgTTNADibEFt.
Generated by Claude Code
packages/types/src/zod/node-derivation.tsdeclares the shared walker def type:rest?: z.ZodTypemeansz.ZodType | undefined. Zod 4.4.3 does not putundefinedthere — it putsnull.The measurement
zod 4.4.3,
zod/v4/classic/schemas.cjs, thetuplefactory:Probed directly:
A census of every
nullliteral in zod v4'sclassic/schemas.cjs+core/schemas.cjsreturns 30 occurrences, of which exactly one is minted into a def member a walker reads — thatrest. Every other one is an instance-level accessor default (inst.format,inst.minValue, …), a parse-time value, or unrelated. Swept per member across the twelve the walkers read (items,rest,options,element,shape,valueType,left,right,in,out,innerType,getter),restis the only one with a non-zero count.Why it is worth a card rather than a note
This inaccurate declaration is the root cause of objectui#9088, not a cosmetic drift. The
tuplearm was writtenand
undefinedis exactly what the declared type says the absent case is. The author read the type, the type was wrong, andunchangedcompares by===— sonull === undefinedwas false and every rest-less tuple was rebuilt. objectui#9088 fixes that one arm by copyingdef.restinstead of normalising it; it does not correct the declaration, becausenode-derivation.tsis outside that card's declared file surface.⇒ the next arm written against this type is licensed to make the same mistake, and
tscwill agree with it.Candidate repair
rest?: z.ZodType | null, and then check whether any read ofdef.restin either walker needs adjusting. Both current readers (imported-defaults.ts'stuplearm andstrict-authoring-face.ts) already guard with a truthiness test, so the widening is expected to be declaration-only — but that is a claim to measure, not to assert.def.outwas measured clean in objectui#9088 and needs no change.Scope note
Found while implementing objectui#9088. Not fixed there:
packages/types/src/zod/node-derivation.tsis not on that card's file surface, and the widening touches a type shared by two walkers, so it deserves its own review rather than riding in on a one-arm fix.Filed by the objectui
domain:specdeveloper seat while implementing objectui#9088. Generated by Claude Code, sessionsession_01L5xpA5q533BgTTNADibEFt.Generated by Claude Code