Skip to content

finding(types): 57 ZodDefault nodes still reachable from the published @object-ui/types/zod barrel after #7735 — batch #69's principle stops at the files it named, and the rest are imported by reference from @objectstack/spec #8317

Description

@os-zhuang

Observation-class finding, measured by the objectui#7735 developer session and escalated by that PR's contract review. Filed unassigned, no labels — grading is the triage seat's. ⛔ This is a ruling question, not a defect with an obvious repair.

The claim

Decision batch #69 (2026-09-07, maintainer 「其他同意」) ruled, on objectui#7735:

A validator validates; it does not write values into an author's document.

PR #8299 delivers that for the 41 .default() call sites written in this repo's own mirrors. It does not — and under its ruling's named scope could not — reach the defaults this repo's published barrel re-exports from elsewhere.

After #8299, 57 ZodDefault nodes remain reachable from @object-ui/types/zod, every one inside a subschema imported by reference from @objectstack/spec. So safeValidateSchema still substitutes values into a parsed document, on those keys, exactly as the ruling says a validator should not.

Reproducer

safeValidateSchema({ type: 'object-view', objectName: 'account', navigation: {} })
⇒ navigation: { mode: 'page', preventNavigation: false, openNewTab: false, size: 'auto' }

Four keys the author did not write, present in result.data. The affected families named by the measuring session: app.active / isDefault · object-view.navigation.{mode, preventNavigation, openNewTab, size} · list-view.sharing.type · kanban.grouping.fields[].{order, collapsed} · page.interfaceConfig.* · dashboard chartConfig.*.

⇒ The "one authored document, two shapes" defect objectui#7735 was opened about survives on these keys. The graph delta is the same instrument that priced #8299: 98 ZodDefault nodes on its base, 57 on its head — the 41 it removed are exactly the difference.

⭐ Why this is a ruling and not a port — two measurements that set the price

1. The upstream population is two orders of magnitude larger. Measured on objectstack-ai/objectstack origin/main = f2f6684, real .default() call sites (docblock mentions excluded):

scope real call sites
packages/spec/src/** (whole package) 1546
packages/spec/src/ui/** only 126 — view.zod.ts 41, component.zod.ts 38, chart.zod.ts 11, app.zod.ts 10, page.zod.ts 7, dashboard.zod.ts 6, action.zod.ts 5, report.zod.ts 4, sharing.zod.ts 2, widget.zod.ts 2

⇒ Extending batch #69 to the spec face is a 1546-site question with its own consumers, its own release train and its own authoring story — ⛔ not a 57-site follow-up, and ⛔ not something to infer from a ruling written about this repo's mirrors.

2. The cheap route already exists here and has been used once. packages/types/src/zod/objectql.zod.ts:452 on origin/main = 8f9d87a:

const ViewKindEnum = SpecListViewSchema.shape.type.removeDefault();

⇒ Stripping an imported default at this repo's boundary is an established local pattern, not an invention. Whether it should be applied to 57 more sites — and whether doing so silently diverges this repo's parse output from the upstream contract it mirrors — is the question.

The fork, stated so nobody has to reconstruct it

  • (a) Strip at this repo's import boundary (.removeDefault(), ×57). Cheap, local, keeps batch Redesign examples based on new JSON project specification #69's principle whole for objectui consumers. ⚠️ Cost: this repo's parse output then differs from @objectstack/spec's own, on keys it claims to mirror — a new divergence in a package whose whole job is not to diverge.
  • (b) Raise it upstream as a spec-side ruling. Principled and one answer for everyone. ⚠️ Cost: 1546 sites, another repo's release train, and a decision that is not this seat's to make.
  • (c) Rule the boundary explicitly: the mirror stops authoring defaults, imported subschemas keep theirs, and that asymmetry is written down rather than left to be rediscovered. ⚠️ Cost: safeValidateSchema keeps two behaviours and an author cannot tell which key is which without reading the import graph.

⛔ This card picks none. ⚠️ What it argues is that (c) by default and unstated — which is what lands if nobody rules — is the one outcome with no defender, because it leaves batch #69's principle true of some keys and false of others with nothing saying where the line is.

Refs

Filed by the domain:spec @ objectui PM seat, session session_01QtGhnU3WnnWyiWeYQhw2aX, 2026-09-07T11:15Z.


Generated with Claude Code

https://claude.ai/code/session_01QtGhnU3WnnWyiWeYQhw2aX

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

    domain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lanefindingpackage: typespriority:p2

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions