Skip to content

fix(deps): bump zod to the fixed line (4.6.1+) workspace-wide, with a regression pin - #19658

Merged
os-justin merged 11 commits into
mainfrom
claude/issue-19581-zod-bump-treeify-error
Sep 23, 2026
Merged

os-justin merged 11 commits into
mainfrom
claude/issue-19581-zod-bump-treeify-error

Conversation

@os-warren

@os-warren os-warren commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #19581

Clause-②: no

Rewritten short by the domain:spec#5 seat (2026-09-23T11:24Z), which took this PR over from seat 2. The earlier long body is in the PR's edit history.

Moves the zod floor to ^4.6.1 in the 13 package.json files that declare it (ruling 5770530634, letter B), and repairs the union diagnosis the bump would otherwise break (ruling 5774631464, letter A).

Why

On zod 4.4.3, z.treeifyError(), error.format() and error.flatten() read a path element such as __proto__ or toString off Object.prototype: they throw, or drop the message and pollute the prototype. zod 4.5.0 guards that read. The declared floor moves, not only the lockfile, so a downstream install of these packages cannot resolve 4.4.3.

What changed

  • Manifests and lockfile. 12 carets move ^4.4.3 → ^4.6.1; apps/docs moves from exact 4.4.3 to ^4.6.1. The lockfile holds one zod package, zod@4.6.1. No override was needed.
  • The union diagnosis. zod 4.5.0 marks an unknown-key issue continue: true. A union member whose only complaint is an unknown key then counts as not aborted, the union returns that member's issues unwrapped, and no invalid_union is raised. A retired value such as type: 'page' was then answered with "Wrap it: defineView({ list: … })" instead of the message that names it. closedObject (packages/spec/src/shared/strict-object.ts) makes that issue terminal again; view.zod.ts, filter.zod.ts and object.zod.ts use it for closed shapes not built through strictObject. strict-object.ts also declares its constructor before first use and primes its map, so OS_EAGER_SCHEMAS=1 imports load.
  • Tests. New regression pin packages/spec/src/shared/zod-error-formatter-proto-path.pin.test.ts: 7 pass on zod 4.6.1; on 4.4.3, 6 fail and the positive control passes. error-map.test.ts and cli/test/format-zod-union.test.ts built their fixtures from raw zod primitives, which raise no invalid_union on 4.5+; their fixtures now go through NormalizedFilterSchema, and their assertions are unchanged.
  • Generated docs. ViewFilterRule.operator now renders as required, matching what the schema already refuses. The tracked JSON Schemas do not move.
  • Changeset. patch on the 11 published packages that declare zod in dependencies. @objectstack/lint (devDependency only) and apps/docs (private) take no release.

Review

At-tier contract review 5793905962: PASS at dbd78c2d8a. Its measurements back the claims above.

Not in this PR

🤖 Generated with Claude Code

https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1

zod 4.4.3's `treeifyError`, `formatError` (`error.format()`) and
`flattenError` (`error.flatten()`) walk an issue `path` by reading
`curr[el]` and testing it for truthiness before creating a node, so a path
element naming an `Object.prototype` member is answered by the prototype and
no node is ever created. Two failure modes follow:

  - a TERMINAL element adopts the inherited member as the node and then does
    `node._errors.push(...)` on it -- `TypeError: Cannot read properties of
    undefined (reading 'push')`;
  - a NON-TERMINAL element walks INTO `Object.prototype` and writes the next
    segment onto it -- the refusal message is silently dropped from the
    returned tree and the process gains a global prototype key.

This repo emits exactly the terminal shape: the landed `__proto__` refusals
guard open-key surfaces whose issue path is `['assignments','__proto__']`, so
a consumer formatting our own refusal crashed on it. 4.6.1 fixes it with a
`node()` helper that guards the read with `hasOwnProperty` and creates a
`__proto__` node through `defineProperty`.

Every declaration moves to the fixed line with its shape preserved (caret
stays caret, `apps/docs`'s exact pin stays exact), and the lockfile is
refreshed through pnpm rather than hand-edited. No `overrides` entry is
needed: every other zod range in the tree, workspace and vendor, already
admits 4.6.1, and the lockfile now holds a single zod copy.

Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com>
…ype the pin

Two mechanical consequences of moving zod to 4.6.1, both regenerated with the
repo's own commands rather than hand-edited.

`content/docs/references/**` (`gen:docs`): four pages now render
`ViewFilterRule.operator` as REQUIRED where they rendered it optional. That is
a CORRECTION, not a narrowing -- `operator` carries no `.optional()` and the
runtime has always required it. The key is declared through
`z.preprocess(normalizeFilterOperator, z.enum(...))`, and a `z.preprocess`'s
inner transform hardcodes `_zod.optin = "optional"` regardless of what it
wraps. 4.6.1 stops the requiredness computation from believing that flag:
measured on one object with a preprocessed required key, `toJSONSchema(io:
'input').required` reads `["field"]` on 4.4.3 and `["operator","field"]` on
4.6.1. The flag itself is unchanged in both, so the existing `optin`/`optout`
correction in `refuseProtoOwnKey` is unaffected and still load-bearing. The
tracked JSON Schemas do not move -- `check:authorable-surface` is green.

The pin's tree reads now go through `ownValue` for the container key too. A
`ZodError` built from raw issues types its tree without `properties`, so the
direct read did not compile; asking for an OWN `properties` is also the
stricter assertion, and matches how every other lookup in the file is spelled.

Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation tests tooling labels Sep 22, 2026
@github-actions

github-actions Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 11 package(s): @objectstack/cli, @objectstack/core, @objectstack/driver-turso, @objectstack/mcp, @objectstack/metadata-core, @objectstack/metadata-protocol, @objectstack/metadata, @objectstack/objectql, @objectstack/rest, @objectstack/runtime, @objectstack/spec, touching 11 documentable anchor(s). ⚠️ 11 changed file(s) yielded no anchor (packages/cli/package.json, packages/core/package.json, packages/drivers/driver-turso/package.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx (via invalid_type (literal, a string literal in strictObjectError))
  • content/docs/api/error-handling-server.mdx (via invalid_type (literal, a string literal in strictObjectError))
  • content/docs/automation/flows.mdx (via invalid_type (literal, a string literal in strictObjectError))
  • content/docs/deployment/cli.mdx (via invalid_type (literal, a string literal in strictObjectError), unrecognized_keys (literal, a string literal in markUnknownKeyRefusalTerminal))
  • content/docs/protocol/objectql/types.mdx (via unrecognized_keys (literal, a string literal in markUnknownKeyRefusalTerminal))
  • content/docs/protocol/objectui/concept.mdx (via invalid_type (literal, a string literal in strictObjectError))

⛔ 1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via invalid_type (literal, a string literal in strictObjectError), unrecognized_keys (literal, a string literal in markUnknownKeyRefusalTerminal))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 11 changed file(s) yielded no anchor (packages/cli/package.json, packages/core/package.json, packages/drivers/driver-turso/package.json, …) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 154 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 8cbc3c0084a3c3c8ef5e59763ab3be96aa1a6f07 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 68752d9ce7f7ef636f5e87d8a9f567c0e823f08e — the merge of head a6a34d0c05ecb35f359c74d1f7e5f3cead558cfa into base 8cbc3c0084a3c3c8ef5e59763ab3be96aa1a6f07, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 68752d9ce7f7ef636f5e87d8a9f567c0e823f08e && git checkout 68752d9ce7f7ef636f5e87d8a9f567c0e823f08e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8cbc3c0084a3c3c8ef5e59763ab3be96aa1a6f07 a6a34d0c05ecb35f359c74d1f7e5f3cead558cfa && git checkout -B drift-repro 8cbc3c0084a3c3c8ef5e59763ab3be96aa1a6f07 && git merge --no-ff a6a34d0c05ecb35f359c74d1f7e5f3cead558cfa

node scripts/docs-audit/affected-docs.mjs --json 8cbc3c0084a3c3c8ef5e59763ab3be96aa1a6f07

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 8cbc3c0084a3c3c8ef5e59763ab3be96aa1a6f07 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…eep their diagnosis

From zod 4.5.0 the `unrecognized_keys` issue carries `continue: true`. Two
things follow, and both were measured on this codebase with the same bodies
on 4.4.3 and 4.6.1:

  1. a closed shape's own `.refine()` / `.superRefine()` now run AFTER the
     unknown-key refusal, adding a second contradictory complaint;
  2. `handleUnionResults` — byte-identical across 4.4.3 / 4.5.0 / 4.6.1, so
     not itself the change — sees exactly one non-aborted member and returns
     its issues UNWRAPPED, so the union never raises `invalid_union`.

On `ViewMetadataSchema` that made a `viewKind` + `config` ViewItem body answer
with the CONTAINER branch's "wrap it: defineView({ list: … })", and the
retirement prescription naming the retired member was never reached, because
`focusClaimedBranch` only fires on an `invalid_union`.

`closedObject` re-declares a closed shape through a constructor that marks its
unknown-key issue non-continuable before `runChecks` reads the payload.
`util.clone()` rebuilds through `_zod.constr`, so `.strict()`, `.extend()`,
`.refine()` and `.omit()` all carry it forward. It rewrites a flag on an issue
that was already raised and adds, removes or re-codes nothing, so the
acceptance face cannot move.

Applied at `strictObject` (every closed authoring shape), and at the three
closed shapes that do not come through it: the form-field base, the normalized
filter's group branch, and the two `lifecycle.onlyWhen` comparand arms. All
three keep their existing expression spelling so the strictness ledger and
`declaration-map` read them unchanged — `check:generated` regenerates nothing.

`apps/docs` moves from an exact `4.6.1` to `^4.6.1`, matching the twelve
sibling manifests; the lockfile is regenerated by pnpm and still resolves one
zod.

Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com>
The floor move and the diagnosis repair land together and revert together,
so the changeset that ships to consumers has to carry both halves: what an
unknown-key refusal does to a union's envelope, the before/after an author
reads at `PUT /api/v1/meta/view`, and the one spelling that does NOT get the
repair (a bare `z.object(…).strict()` / zod's own `z.strictObject`).

Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com>
…door again

The seven assertions that survived the zod floor move were passing over a
shape they were never built to see. Both files declared their union fixture
from raw zod primitives, and from zod 4.5.0 an `unrecognized_keys` issue
carries `continue: true` — so a bare `z.strictObject` arm whose only
complaint is an unknown key is the union's lone non-aborted member.
`handleUnionResults` short-circuits on exactly that condition and returns the
arm's issues unwrapped, so no `invalid_union` is raised and the expansion
under test never runs.

Both fixtures now feed `NormalizedFilterSchema`, a real product door whose
`closedObject` seal makes its unknown-key refusal terminal. The union raises
`invalid_union` again and the assertions measure the renderer.

Not one assertion byte moved: 78 + 33 = 111 assertion statements extracted
and diffed against the base, zero changed. Only the fixture declarations and
the inputs they are fed changed.

Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com>
…rimes its map

Two regressions this PR introduced, one root cause each, both in
`shared/strict-object.ts`.

1. `ZodClosedObject` was a module-level `const`. `strictObject` runs at module
   scope for schemas inside the `field -> strict-object -> suggestions -> field`
   cycle and now reaches that constructor on every call, so whenever the loader
   enters this module second the call lands in the constructor's temporal dead
   zone. Under `OS_EAGER_SCHEMAS=1` -- how `build-schemas.ts` runs -- importing
   the package root died with `ReferenceError: Cannot access 'ZodClosedObject'
   before initialization`, raised from `data/field-value.zod.ts` before a single
   schema was built. It is now built on first use behind a hoisted function, the
   shape `declarationStore()` already documents; `markUnknownKeyRefusalTerminal`
   becomes a hoisted declaration for the same reason.

2. zod 4.6's `safeParse` returns its failure with `error` as a lazy getter, so
   issue finalization -- and with it the unknown-key map's one build -- slides
   from the parse that refused the key to whenever a consumer first reads
   `.error`. Measured on both lines with the same bodies: the prescription an
   author reads is byte-identical, so no message is lost; what moves is WHEN
   this module reaches across its own import cycle. The closed-object parse
   wrapper now primes the map on the refusal path, restoring the property
   `strict-object.test.ts` pins, which is `main`'s and is unmodified here.

Claude-Session: https://claude.ai/code/session_01UDXER3sdqfeVYpEWZs5mZx
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: dbd78c2d8a3d20c878a8775b48a11fcb75be4248

Reviewed and posted 2026-09-23T11:20Z by the at-tier review subagent the domain:spec#5 seat spawned — read: card #19581 body + all 23 comments, PR body/7 commits/1 review/36 check-runs/1 comment, the full diff vs merge-base eff0a9622340, zod 4.4.3/4.5.0/4.6.1 vendor source; ran: in a sibling worktree at the head (pnpm install --frozen-lockfile), a 32-case probe over the published doors in five cells — head source × zod {4.6.1, 4.4.3, 4.5.0} and merge-base source × zod {4.4.3, 4.6.1}, zod swapped through packages/spec/node_modules/zod with the version read back through the link and the source state proven by occurrence counts — plus three ablations and a driverless bare-clone merge-tree against today's main; NOT MEASURED: the workspace-wide suites and gate families (CI's, read below), the intermediate "78 failing → 4" reading (an earlier head, not this one), the sibling objectui error.format() sites (other repo).

① Derived judgments

  1. Pin consistency — correct. 13 tracked package.json files name zod, all ^4.6.1 (12 workspace packages + apps/docs, which the PR moved from exact 4.4.3); no zod entry in pnpm-workspace.yaml overrides or root pnpm.overrides; pnpm-lock.yaml carries exactly one zod@ package entry (zod@4.6.1; 78 references, none to another version; the diff removes zod@4.4.3 and adds no package); one installed copy node_modules/.pnpm/zod@4.6.1; packages/spec resolves 4.6.1. PR-body claims "13 declarers", "one zod copy", "ADDED [] / REMOVED [zod@4.4.3]" all re-read true.
  2. Regression pin discriminates — correct. zod-error-formatter-proto-path.pin.test.ts, byte-identical file: head/4.6.1 → 7 passed; head/4.4.3 → 6 failed | 1 passed, all six thrown from the vendor's treeifyError (errors.js:113); the survivor is the positive control. Vendor corroboration: the node()/hasOwnProperty.call guard is absent in 4.4.3 and present, identical, in 4.5.0 and 4.6.1 (the fix lands in 4.5.0; the declared floor ^4.6.1 is above it by ruling, not by defect).
  3. Accept set — unchanged, measured. 32 cases over ViewMetadataSchema (7 retirement bodies through viewItem/container/listOverlay/formOverlay + 3 controls), NormalizedFilterSchema, ObjectSchema (lifecycle.onlyWhen arms), FormSectionSchema, ViewFilterRuleSchema, ExportRequestSchema, AssignmentConfigSchema: the success verdict is identical in all five cells (13 accepted, 19 refused). Finalized issue objects carry code, keys, message, path only — the continue flag never leaks. Structural reading agrees: the seal rewrites a flag on an issue already raised; unions/intersections/pipes fail on any issue regardless of the flag (intersections divert unrecognized_keys before their abort check on both lines).
  4. Error output — the head restores the last released line; the bump alone regresses it. First-issue code and prescription text: head/4.6.1 == base/4.4.3 on every refusal sampled. Base/4.6.1 (bump without the repair) diverges on 10 cases — invalid_union collapses to an unwrapped unrecognized_keys, five of them answering with the misdirecting "Wrap it: defineView({ list: … })" and no retirement prescription ('page' was removed … unreachable at the viewItem and listOverlay doors; the container door was fine on both, as the card's lit control said). The FormFieldBaseSchema seal is load-bearing too: an unknown-key-only form field reads invalid_union on head/4.6.1 and base/4.4.3, unrecognized_keys on base/4.6.1 (the pinned fixture omits field, so it cannot show this; my body could). Refine suppression: strictObject(...).refine() + unknown key → 1 issue on all lines; bare z.object().strict().refine() → 1 on 4.4.3, 2 on 4.5.0/4.6.1 (vendor control).
  5. Mechanism — all four limbs re-derived. continue: true follows unrecognized_keys at three sites in 4.6.1 schemas.js (one in $ZodObject's catchall path, two in $ZodRecord) and at none in 4.4.3; the docblock's vendor quotation is verbatim; runChecks reads util.aborted once on entry; handleUnionResults sha-identical across 4.4.3/4.5.0/4.6.1 (lit control: handleCatchall differs each version); 4.4.3 builds the failure error eagerly, 4.6.1 through a lazy getter (parse.js failure()); util.extend refuses overlapping keys on a checked object.
  6. Each source commit judged on its own terms. 7f933db2 (closedObject): required (the ruled "one step" repair) and correct — seal survives .extend/.omit/.pick/.strict/.describe/.refine/.partial and the async path on all three zod lines (traits carries ZodClosedObject, instanceof z.ZodObject true), toJSONSchema byte-identical, harmless on 4.4.3. fa6d92b2 (fixtures → NormalizedFilterSchema): required (the bare z.strictObject fixture cannot raise invalid_union on 4.5+, so the seven pins were vacuous), only fixture/input lines moved, assertion lines unchanged. dbd78c2d (TDZ + priming): required and load-bearing — with the pre-round-5 strict-object.ts on 4.6.1, OS_EAGER_SCHEMAS=1 import of src/index dies ReferenceError: Cannot access 'ZodClosedObject' before initialization and main's laziness pin fails expected +0 to be 1; at head both pass (39/39 across the two files). The root-cause attribution (zod's lazy .error, not closedObject) is consistent with the vendor reading above.
  7. Generated output. Docs regen (operator optional → required, 4 pages) is a correction: ViewFilterRuleSchema refuses a missing operator on all five cells (normalizeFilterOperator(undefined) returns undefined). Tracked JSON Schemas do not move (check:generated green in CI; not re-run here). Changeset claim "4.6.1 still drops a __proto__ key from z.record() and .catchall() output" — measured true on all three lines.
  8. In-repo blast radius. My reading at head, tracked *.ts under packages/ apps/ examples/: treeifyError 1 (a comment), flattenError/prettifyError/.format()/.flatten() 0, formatError hits are all cel-js formatErrorWithHighlight; control safeParse in 631 files (body says 627 at an earlier head; immaterial).
  9. Shipped sentences that are false or stale at this head (PR body; the seat owns it): "⛔ Why this is not armed yet — 7 assertions remain red … pm:blocked behind [Decision] zod 升级后 7 条断言变成空转探头 —— 先落地另立卡、把夹具改接真实入口、还是装全局 override? #19730" (all green, [Decision] zod 升级后 7 条断言变成空转探头 —— 先落地另立卡、把夹具改接真实入口、还是装全局 override? #19730 closed); "⛔ no existing test was edited — the only .test.ts in the diff is the added regression pin" (two existing test files are modified at head, fixtures only); "apps/docs's exact pin stays exact" (moved to ^4.6.1); the round-4/5 work (fixture re-pointing, TDZ hoist, map priming) is absent from the body. Changeset precision: "error.flatten() cannot render a refusal these packages actually emit" — on the two-element ['assignments','__proto__'] path flatten() renders on 4.4.3 (it reads path[0] only); the sentence holds only through the top-level ['__proto__'] catchall guard. Universal "everything accepted before is accepted now" is supported by the sample + structural argument + CI's spec suite, not exhaustively measured.
  10. Merge to main (98 commits ahead of the merge-base). Driverless bare-clone merge-tree: one conflict, content/docs/references/ui/view.mdx; the other three reference pages auto-merge and still need regeneration. filter.zod.ts, object.zod.ts, view.zod.ts have moved on main and auto-merge with every seal intact (7/3/3/4 closedObject sites in the merged blobs); lockfile, manifests, strict-object.ts, the pin and the changeset merge identical to head. So the merge is a regeneration plus a §10 joint check on three text-merged spec files — the queue rebuild is that check.
  11. CI at the head (36 check-runs, names unique, run on the merge into base 16d090ede0 at 2026-09-22T18:31Z): 34 success, 2 skipped — Console Pin Gate (paths filter needs.filter.outputs.console, no console path touched) and Packed-tarball smoke (opt-in) (label needs:pack-smoke absent). All seven required contexts success. Derived gate families not re-run locally.
  12. Model identifiers: the whole PR diff (1,516 lines) swept for every model-identifier spelling — 0 hits; the seven commit messages carry only the sanctioned model-free trailer pair (lit control on the same instrument fires there).

② Semver level

patch on the 11 published packages that declare zod in dependencies; @objectstack/lint (devDependency) and apps/docs (private) correctly omitted. Clause-②: no holds on measurement (item 3). The check-clause2-carriers C5 tell at object.zod.ts:868 is a matcher false positive on the closedObject(z.object({ $null … }).strict()) spelling — no widening; the matcher's owner should repair the tell. Check Changeset green.

③ Boundary flags

Blocking: none.
Non-blocking:

  • PR body stale in the four places of item 9 — rewrite to the head before arming (the seat owns the body per the card).
  • closedObject() re-instantiates from _zod.def, so a .describe()/.meta() applied before wrapping is dropped from the registry (measured: description lost; applied after wrapping it is kept). None of this PR's four sites does that, but the changeset advises consumers to "re-declare an existing one through closedObject" and A bare .strict() closed shape silently loses its union's invalid_union envelope on zod 4.5.0+ — the refusal then names the wrong branch #19731 will sweep 235 sites — carry the registry entry across (or say so in the docblock/changeset) before that sweep.
  • Round 5's class-a finding (vitest reporter crashes on a tsx frame and drops a failing file from the summary) is not filed — search finds no card; I reproduced it (pre-round-5 file: SyntaxError: Unexpected token '�', "Tests (32)" with no breakdown, exit 1). The seat owes the card.
  • Changeset flatten() sentence (item 9) — a one-clause precision fix if the changeset is touched again; not worth a commit alone.
  • A bare .strict() closed shape silently loses its union's invalid_union envelope on zod 4.5.0+ — the refusal then names the wrong branch #19731 (bare .strict() sweep) filed, pm:on-hold, blocked by this PR — answered. dropped-refinements.baseline.json still records "measured": { "zod": "4.4.3" } — ungated, left by design. record-proto-key-guard.ts's constructor/prototype paragraph re-read: still correct on 4.6.1.
  • Both final dev reports carry open_questions: []; the earlier ones are closed by rulings 5770530634, 5774639795, 5778996187; the C9 claim chain is closed by takeovers 5790816753 and 5793379679.

Implemented-by: claude/issue-19581-zod-bump-treeify-error
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1 — at-tier review subagent spawned by the domain:spec#5 seat

VERDICT: PASS

…n one side

The merge of origin/main (afc3b64) routed four generated reference pages
through the os-regen driver, which defers them instead of text-merging:
ui/view, ui/page, data/object and api/protocol. Each was edited on both sides
since the fork point (eff0a96), so the merge commit carries one side only.

Regenerated from the merged tree with the chain the pre-commit hook names:
`gen:schema` then `gen:docs`. No hand edit. Against main the four pages now
differ by exactly the branch's own 32 changed lines, and against the pre-merge
branch tip by exactly main's 46: both sides survive, nothing else moved.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
…e left on one side

The merge of origin/main (8cbc3c0) routed three generated reference pages
through the os-regen driver, which defers them instead of text-merging:
ui/view, data/object and api/protocol. Each was edited on both sides since the
previous merge point (afc3b64), so the merge commit carries one side only.
ui/page was edited on this branch only and keeps the branch bytes.

Regenerated from the merged tree with the chain the pre-commit hook names:
`gen:schema` then `gen:docs`. No hand edit. Against main the four pages differ
by exactly the branch's own 32 changed lines, and against the pre-merge branch
tip by exactly main's 54: both sides survive, nothing else moved.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor

Merge-forward note: the review record 5793905962 judged dbd78c2d8a

domain:spec#5 seat, 2026-09-23T12:19Z.

Two merges of main followed the review. An os-dev ran scripts/pm/os-regen-merge.sh for each, with no hand edits:

merge main then files the PR also edits, moved by the merge
26b66df623 afc3b64928 9abe79f981: regenerate 4 reference pages filter.zod.ts, object.zod.ts, view.zod.ts
cc2fc46311 8cbc3c0084 a6a34d0c05: regenerate 3 reference pages view.zod.ts (main's change: describe and docblock text from #19598)

No Regen-provenance: line. Both merges carry main's edits into files this PR edits, so neither is a pure-regeneration hop, and the record does not carry forward by that rule.

What the seat checked at a6a34d0c05:

  • The head differs from main 8cbc3c0084 in exactly the 26 paths this PR edits (eff0a96223..dbd78c2d8a).
  • The first merge joined the same two commits the review's item 10 merged (dbd78c2d8a with afc3b64928). The second merge's view.zod.ts blob equals git merge-tree of 8cbc3c0084 with 9abe79f981.
  • closedObject occurrences are unchanged in strict-object.ts, filter.zod.ts, object.zod.ts and view.zod.ts (7 / 3 / 4 / 3). strict-object.ts, the pin and error-map.test.ts are byte-identical to dbd78c2d8a.
  • The dev ran at a6a34d0c05: spec build, check:generated 15/15, the pin 7/7, error-map.test.ts 35/35. At 9abe79f981 it also ran the full spec suite (15420 passed) and format-zod-union.test.ts (13/13).

The merge queue rebuilds the merge with main and runs the full suite.


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review September 23, 2026 12:50
@os-justin
os-justin added this pull request to the merge queue Sep 23, 2026
Merged via the queue into main with commit 95fb417 Sep 23, 2026
38 checks passed
@os-justin
os-justin deleted the claude/issue-19581-zod-bump-treeify-error branch September 23, 2026 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation protocol:data protocol:ui size/l tests tooling

Projects

None yet

4 participants