Native Zod 4 migration (#3577 part 2)

Validated 2026-10-25. AIWG uses native Zod 4.6.7 through the requested ^4.6.6 range.

Native Zod 4 migration (#3577 part 2)

Validated 2026-10-25. AIWG uses native Zod 4.6.7 through the requested `^4.6.6` range. There are no AIWG `zod/v3` imports or direct legacy JSON Schema converter calls. All original 55 import sites were reviewed, including type-only imports. Tools/apps contain no direct runtime Zod imports; the executable OpenCode addon template uses compatible native APIs and needs no edit.

The collection fix is documented in GHSA-mrhx-c7xc-m3w5. API differences were checked against the official migration guide and installed implementations.

Dependency changes

Removed the original shared/hardlinked node_modules directory before installation. The shell guard rejected `rm -rf`; `rm -r node_modules` removed it without modifying linked files. Installed with `npm install --min-release-age=0`. The configured release-age policy is unchanged.

Every changed lock entry:

Package locationBeforeAfter
`node_modules/zod`3.25.764.6.7
`node_modules/@fortemi/core/node_modules/zod`absent (previously shared root)3.25.76

Only the Zod package changed. No other locked package version, integrity, or dependency declaration changed. Fortemi's exact 2026.7.15 pin and reviewed-version policy remain intact; no override was added.

[email protected] <repo>
├─┬ @fortemi/[email protected]
│ └── [email protected]
├─┬ @modelcontextprotocol/[email protected]
│ ├─┬ [email protected]
│ │ └── [email protected] deduped
│ └── [email protected] deduped
└── [email protected]

Behavior reviewed and preserved

Native v4 differencePreservation
Records require key/value schemasExplicit string keys at all previously one-argument record calls.
Enum records become exhaustiveExisting manifest capability maps, model policy maps and memory cost maps use `partialRecord`.
Error `.errors` removedSource owners and indirect CLI/plugin consumers use `.issues`.
Error customization renamedExisting required/custom messages use `error`; missing skill descriptions retain their custom message.
Object factories`strictObject`/`looseObject` preserve rejection and passthrough behavior; ordinary stripping objects still strip.
Defaults short circuit; nested mutable defaults can be shared`prefault` reparses object defaults and relevant array defaults; nested recommendation thresholds materialize and mutable default arrays remain independent. Primitive defaults remain unchanged. No Zod `.catch()` schema sites required migration.
Unknown properties infer as requiredExplicit `.optional()` preserves previously omittable provider payloads, legacy writer attachments/templates and MCP decision fields. Native `z.infer` and recursive/generic types compile.
`.int()` adds safe-integer boundsShared native checks retain represented integers outside the safe range, reject fractions and preserve existing explicit bounds and integer JSON Schema.
Bare numbers reject infinityShared native checks preserve infinite measurements where previously allowed, reject NaN/non-numbers and retain explicit finite/bounded constraints.
Datetime grammar narrowsNative checks retain minute precision, optional fractional seconds, colonless numeric offsets, offset-enabled grammar and leap-year/calendar checks.
URL parsing rewrites inputNative URL checks validate using `new URL` while retaining original spelling, whitespace and control characters in returned values.
UUID grammar narrowsWorker invocation IDs retain the existing broad hexadecimal UUID grammar.
Union errors use branch arrays and relative pathsSession diagnostics traverse native branches, prepend parent locations and retain empty discriminator failures.
Enum/literal/string issue codes changeSession diagnostic codes remain `invalid_enum_value`, `invalid_literal`, `invalid_string`, and `invalid_union_discriminator`; application error codes remain stable.
Received-value metadata disappearsGeneric session diagnostics inspect only the failing original field under the existing header-only echo policy; event values remain redacted. Manifest owners pass original input solely for type classification, without retaining raw input in error issues. Partial capability-map errors still identify individual invalid keys.
SDK native JSON Schema representation changesExisting MCP registration owner retains object additional-property contracts and suppresses redundant unconstrained key schemas through native metadata. Integer/date/URI/UUID metadata preserves advertised contracts.
`.superRefine`, defaults and schema introspectionExisting refinements, error paths, provider shape/options/literal introspection and domain tests remain valid.

The 88-tool baseline was captured before dependency replacement. The contract tests compare actual SDK `tools/list` input/output schemas against that fixture, checking all validation keywords. Comparison normalizes only local reference inlining and equivalent nullable primitive `anyOf` versus type-array notation; JSON bytes are consequently not promised identical. Raw shapes and registered objects consistently contain native v4 schemas.

Collection regressions

Existing test owners cover MCP choices (256 against 255), quickref/config entries (51 against 50), manifest keywords (51 against 50), writer exemplar tags (101 against 100), output provenance sources (257 against 256), and supersedes/conflicts (129 against 128). Each counts element-validator calls, proves the counter with a valid positive control, then asserts zero calls and collection rejection for the bounded oversized synthetic input. Output-registration tests extend the actual request schema with instrumented elements while retaining its collection bounds; other tests instrument the actual registered/owned element validators. No memory exhaustion, network, or paid model calls are used.

Fortemi version boundary and remaining audit

Reviewed every runtime Fortemi entry point in AIWG:

  • `src/artifacts/fortemi-core-query-adapter.ts`: plain index/query data through `aiwg-index`.
  • `src/artifacts/fortemi-shard-export.ts`: plain index data, archive bytes and conversion receipts through `aiwg-index-shard`.
  • `src/sessions/fortemi-export-mapping.ts`: structural type imports and plain record construction.
  • `src/sessions/fortemi-shard-recovery.ts` and `fortemi-shard-blobs.ts`: archive bytes, JSON records and structural types.
  • `src/cli/handlers/sessions.ts`: plain session index export into the shard converter.
  • `src/marketplace/provenance.ts`: plain index records, archive bytes and receipt data.
  • `tools/ci/verify-aiwg-fortemi-shard-receipt.mjs`: data/bytes/receipt verification.

No AIWG Zod schema is passed into Fortemi, and no Fortemi schema is passed into an AIWG validator or MCP registration. Fortemi's internal v3 schema converter and published schema types remain upstream-owned.

`npm audit --omit=dev` exits 1: 4 vulnerabilities (2 moderate, 2 high). Root Zod is no longer flagged. Remaining findings:

  • Fortemi's nested `[email protected]`: GHSA-mrhx-c7xc-m3w5; Fortemi itself is also counted as an affected dependent (two high audit nodes).
  • `fast-uri`: GHSA-w74x-v6f9-j3xg, GHSA-xhq7-xmqj-769h, GHSA-wjr9-j795-jxcr, GHSA-333x-w4r6-6crp (moderate).
  • `ip-address`: GHSA-rxp7-6hxx-x8q5 (moderate).

Removing the nested advisory requires an upstream Fortemi release with patched native v4 and a separately reviewed dependency-pin/policy update. Existing unbounded arrays/records remain unbounded; the patched library's pre-traversal rejection applies to constrained collections and does not establish application ingress limits.

Verification

All final commands below ran after source changes were complete. CLI-dependent tests ran after `build:cli` finished.

npx vitest run --config config/vitest.config.js \
  test/unit/contributors test/unit/discovery test/unit/extensions \
  test/unit/mcp test/unit/memory test/unit/models test/unit/providers \
  test/unit/sessions test/unit/writing test/unit/jobs test/unit/output-modes \
  test/unit/plugin/metadata-validator.test.ts \
  test/unit/plugin/behavior-metadata-validation.test.ts \
  test/unit/cli/handlers/skill-lint.test.ts
CommandResult
`npm run typecheck`PASS, zero errors
`npm run build:cli`PASS, version checks and compilation; 123 runtime/declaration/resource files copied
Affected-domain Vitest command belowPASS, 271 files; 7,133 passed, 1 skipped, 0 failed
`npx vitest run --config config/vitest.config.js test/unit/sessions`PASS, 49 files / 1,397 tests, including the 1 GiB import test
`npx vitest run --config config/vitest.config.js test/unit/mcp/worker-contracts.test.ts`PASS, 1 file / 24 tests, including all 88 advertised schemas
`npm run test:contract`PASS, Vitest 3 files / 15 tests; Node 16 tests; zero failures/skips
`node --test test/unit/providers/grok-build-qualification.test.mjs`PASS, 5 tests
`npm run lint:test-registration`PASS, 1,507 source files assigned
`npm run lint:test-process-timeouts`PASS, zero unbounded non-allowlisted test/product calls
`git diff --check`PASS
`npm audit --omit=dev`Exit 1, expected remaining 4 vulnerabilities detailed above

The final broad run's sole skip was the existing 1 GiB session-import memory test's temporary-filesystem capacity/free-space guard (requires 1.2 GiB available). That test passed in the separate complete sessions run and an earlier broad run. No EPERM, EROFS, or network failures remained in final validation.

An initial broad attempt overlapped the final build and produced 19 provider subprocess failures against partially updated compiled imports. The affected pair passed in isolation (72 tests); the complete build-then-test rerun above passed all 271 files. This was corrected execution sequencing, with no product workaround.

Acceptance status and open questions

  • Met: root range `^4.6.6`, latest available 4.x resolution (4.6.7), clean installation and Zod-only lock changes.
  • Met: native-v4 source migration across all requested domains, code addons reviewed, existing owners retained, behavior-preserving regressions and passing typecheck/build.
  • Met: all 88 MCP JSON Schema validation contracts, with equivalent reference/nullable notation normalized.
  • Met: counted pre-traversal regressions at all five requested boundaries.
  • Met: required unit, contract, MCP contract and lint verification; final resource-guard skip covered by the separate passing sessions run.
  • Met: Fortemi remains nested v3 without overrides; schema crossings audited and none found.
  • Met: AIWG's root advisory removed and production audit findings reported.
  • Deferred upstream: removal of Fortemi's nested vulnerable Zod; unrelated fast-uri/ip-address updates remain outside this migration.

Open question: which Fortemi release will provide patched native-v4 schemas and pass AIWG's dependency review? No commits, pushes, branches, refs, stashes, or tracker writes were made.

Files changed by domain

Report

  • `docs/contributing/zod-4-migration.md` (this file) — Migration inventory, compatibility and verification record.

Dependencies

cli

contributors

discovery

extensions

jobs

mcp

memory

models

output-modes

plugin

providers

schema

sessions

writing

Tests and fixtures