ADR 0011: Schema Library — Re-export Zod from @aidokit/core
| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-05-12 |
| Deciders | Project maintainers |
| Supersedes | — |
| Superseded by | — |
| Related ADRs | 0001 (Runtime + language), 0005 (Adapter shape), 0006 (Stack-pack shape), 0007 (MCP catalog) |
Summary
Use Zod as the schema/validation library, re-exported from @aidokit/core under the @aidokit/core/schema subpath. Adapter and stack-pack authors import their schemas from @aidokit/core/schema and never depend on zod directly. This gives us best-in-class TypeScript-first runtime validation with type inference, while keeping the option to swap implementations behind the @aidokit/core versioning surface.
Two alternatives were considered: defining bespoke schema types in @aidokit/core (rejected — operational cost) and using Valibot (rejected for v0.1 on maturity, but flagged as a credible swap-target if Zod's bundle weight becomes an issue).
Maintainer confirmed Zod on 2026-05-12. Valibot remains a viable swap target if bundle weight becomes a real concern during the v1.x window; the subpath export protects downstream consumers from the swap.
Context
Several @aidokit packages need runtime schema validation:
@aidokit/core— validatesAdapterManifest,StackPackManifest,ProjectContextinputs, JSON Schemas for task briefs / change summaries / test reports (consumed by the futureaidokit validatecommand in v0.5).@aidokit/cli— validates options before passing them to command handlers; validates the on-diskadapter.mdmanifest.@aidokit/adapter-claude-code— validates emitted manifest data before write.@aidokit/mcp-catalog— validates catalog entries at type-check time and at load time (defensive against hand-edited entries in user installs).
The validation library is public surface if adapter authors need to validate their own manifests using the same primitives. That implies two design constraints:
- The library must be stable enough that minor version bumps don't break downstream consumers.
- The library must be importable in TypeScript without ceremony.
Earlier ADRs fix the surrounding environment:
- ADR-0001 — TypeScript, ESM, Node 20+.
- ADR-0005, ADR-0006 — Adapter and StackPack contracts are TypeScript interfaces. Manifests have a TS shape and a runtime shape that must match.
- ADR-0007 — MCP catalog is a TypeScript
constarray; entries are validated at load.
.docs/ARCHITECTURE.md §21 lists "Whether @aidokit/core should re-export zod or define its own schemas" as an open question.
Decision
1. Zod is the implementation; @aidokit/core/schema is the API surface
Zod is added to @aidokit/core as a direct dependency. @aidokit/core exposes a subpath export:
// packages/core/src/schema.ts
import { z } from 'zod';
export { z };
// And our own schemas, built using z:
export const AdapterManifestSchema = z.object({
name: z.string(),
cliTarget: z.string(),
specVersion: z.literal('2.0'),
sdkVersion: z.string(),
conformance: z.enum(['minimum', 'standard', 'strict']),
capabilityDeclarations: z.object({
writesPaths: z.array(z.string()),
runsShellCommands: z.array(z.string()),
networkCalls: z.array(z.string()),
}),
});
export type AdapterManifest = z.infer<typeof AdapterManifestSchema>;
@aidokit/core package.json declares:
{
"exports": {
".": "./dist/index.js",
"./schema": "./dist/schema.js"
}
}
Downstream packages import only from the subpath:
import { z, AdapterManifestSchema } from '@aidokit/core/schema';
Adapters and stack packs MUST NOT add zod as a direct dependency. They consume it via @aidokit/core/schema.
2. Schemas drive both runtime validation and TypeScript types
We pick one source of truth — Zod schemas — and infer TypeScript types from them via z.infer<typeof Schema>. Hand-written types that duplicate the schemas are forbidden; they drift.
The AdapterManifest, StackPackManifest, ProjectContext, MCPDef, and PrereqDef types currently sketched in the specs are recoded as Zod schemas. The inferred TS types are exported alongside.
3. JSON Schemas (for aidokit validate) are derived from Zod
aidokit validate (v0.5) validates task briefs / change summaries / test reports against JSON Schemas. Those JSON Schemas are generated from Zod via zod-to-json-schema, not hand-written. This keeps a single source of truth.
The generated JSON Schemas are checked into packages/core/src/schemas/*.json so they can be consumed by:
- Editor tooling (VS Code's
json.schemasfor autocomplete on.json/.yamlartefacts). - The
aidokit validatecommand at runtime. - Third-party adapter authors who don't depend on
@aidokit/core/schemabut want to validate without it.
Generation is a build-time step in @aidokit/core. Drift between Zod schemas and generated JSON Schemas fails CI.
4. Zod version pinned at the @aidokit/core boundary
@aidokit/core pins Zod to a tested minor range (^3.23 or current at v0.1 release). Zod major-version bumps require:
- A new minor of
@aidokit/core. - A Changeset entry.
- A migration note in CHANGELOG.
This shields downstream consumers from upstream churn while still letting us upgrade on our schedule.
5. @aidokit/core/schema does NOT re-export every Zod helper
Only z (the schema-builder) plus our project-specific schemas are re-exported. Zod's deprecated APIs and the @zod/v3 namespace are not re-exported. This lets us trim the surface if Zod ships large API changes.
6. The Zod runtime is small enough to ship
Zod adds ~50KB minified to @aidokit/core. This is acceptable for a CLI tool; the alternative ("custom validator") would cost more in maintenance time than the install ever costs in download time.
Consequences
Positive
- Single source of truth. Zod schemas define both runtime validation and TS types. No hand-written types to drift.
- Best-in-class TS inference. Zod's
z.infer<>produces precise types, including discriminated unions, branded types, and recursive structures. - Error messages are first-class. Zod's
ZodError.issuesis structured;AidoError.detailscan include them directly for downstream consumers. - Mature. Zod is on v3.x with a very large user base; failure modes are well documented.
- Subpath export protects the SDK surface. We can swap Zod for Valibot (or anything else) in a future major bump of
@aidokit/corewithout forcing adapter authors to rewrite their imports — only the implementation behind@aidokit/core/schemachanges. - JSON Schema generation is automatic.
zod-to-json-schemahandles the conversion; no hand-written JSON Schemas to maintain.
Negative
- Public API surface includes Zod's shape. Adapter authors who use
@aidokit/core/schemasee Zod-specific patterns (z.object({}).strict(),.refine(), etc.). If we swap implementations later, those patterns become migration work for downstream consumers. Mitigated: the subpath export gives us one indirection level; for Valibot we could ship a thin compatibility shim or accept the migration as a major bump. - Bundle weight. ~50KB for
@aidokit/core. Not large in CLI terms, but worth tracking. Mitigated: Valibot is the obvious lighter alternative if weight becomes a problem. - Build step required. Generating JSON Schemas from Zod adds a CI step. Drift is a CI failure. Mitigated: we wanted CI to catch drift anyway.
- Recursive schemas need ceremony. Zod's
z.lazy()works but is verbose. Mitigated: very few schemas are recursive; ceremony cost is one-off.
Neutral
- Zod's parsing throws by default. We use
Schema.safeParse(value)everywhere;parse()is forbidden by lint rule because uncaught throws bypassAidoErrorhandling. @aidokit/core/schemais documented separately from@aidokit/core. A small README in the subpath explains the import path and the swap policy.
Alternatives considered
Alternative 1: Define bespoke schema types in @aidokit/core
Write our own tiny schema builder in @aidokit/core with just the primitives we need (object, array, string, number, union, literal, enum).
Pros
- Zero external dependencies in
@aidokit/core. - Smallest possible install footprint.
- Total control over error message shape.
Cons
- TypeScript inference for the schema-to-type translation is genuinely hard. Zod's implementation took years of refinement; ours would be incomplete for months.
- Every primitive we add becomes maintenance.
- JSON Schema generation would need a custom serializer.
- No community knowledge — every contributor learns our library.
Reason for not recommending: the operational cost is large; the install-size saving is small (≤50KB) and most users never see it.
Alternative 2: Valibot
Modern alternative to Zod; ~10x smaller bundle, similar API shape, growing user base.
Pros
- Significantly smaller (~5KB vs Zod's ~50KB).
- ESM-first, tree-shakeable by construction.
- API shape is similar to Zod but more functional (
object({...})rather thanz.object({...})).
Cons
- Newer; smaller community.
- Fewer integrations (notably
zod-to-json-schemahas avalibot-to-json-schemaequivalent, but it's less mature). - The functional-style API is divisive — Zod's chaining is more discoverable for newcomers.
Reason for not recommending (right now): Valibot is a credible choice and may become the better default within v1.0's lifetime. Today, Zod's ecosystem maturity wins. The @aidokit/core/schema subpath export keeps the swap option open.
If the maintainer prefers Valibot now, the only material change in this ADR is the implementation detail; the rest of the design (subpath export, generated JSON Schemas, schema-as-source-of-truth) is identical.
Alternative 3: arktype
Type-first runtime validator with TypeScript-like syntax inside strings.
Pros
- Smallest mental gap between TS types and runtime schemas.
Cons
- The string-based DSL doesn't play well with refactoring tools.
- Smaller community than Zod or Valibot.
- Error messages are less mature.
Reason for not recommending: intriguing technology, wrong maturity stage for our timeline.
Alternative 4: ajv (JSON Schema validator)
The de-facto JSON Schema runtime. Write JSON Schemas directly; validate at runtime with ajv.
Pros
- The JSON Schema is the source of truth, not a derived artefact.
- Best ecosystem support for editor tooling.
Cons
- TypeScript types must be hand-written or generated from JSON Schemas via
json-schema-to-typescript. Both options drift. - JSON Schemas are verbose; refactoring is painful.
- The "TypeScript-first" benefit of Zod is exactly the gap we'd reopen.
Reason for not recommending: we want TS-first development, and Zod gives us JSON Schemas as a derived artefact at no cost via zod-to-json-schema.
Alternative 5: runtypes / io-ts / others
Older TS-first validation libraries.
Pros
- Mature.
Cons
- Smaller communities than Zod.
io-tsrequiresfp-ts, dragging in a functional-programming runtime that's overkill for our use.
Reason for not recommending: Zod dominates them on every relevant axis.
Implementation notes
If accepted:
- Add
zodandzod-to-json-schematopackages/core/package.json. - Create
packages/core/src/schema.tsexportingzand all project schemas. - Add
packages/core/scripts/generate-json-schemas.tsthat runs inprepublishand writes topackages/core/src/schemas/*.json. - Add a CI step that runs the generator and fails if
git diffshows changes (drift detection). - ESLint rule:
no-restricted-importsblockszodoutsidepackages/core/; everyone else imports from@aidokit/core/schema. - ESLint rule: prefer
.safeParse()over.parse(); the latter throws and bypassesAidoError.
If Valibot is accepted instead:
- Replace
zodwithvalibotandvalibot-to-json-schema. - The subpath export remains
@aidokit/core/schema; consumers see no import change. - The schema syntax inside
@aidokit/corechanges from Zod's chained form to Valibot's pipeline form.
Validation
This decision is validated when:
- [ ]
AdapterManifestSchema,StackPackManifestSchema,ProjectContextSchema,MCPDefSchema,PrereqDefSchemaare defined as Zod schemas in@aidokit/core/schema. - [ ] All TypeScript types for those entities are inferred via
z.infer<>; no hand-written types duplicate the schemas. - [ ]
aidokit validate(v0.5) uses the generated JSON Schemas, not hand-written ones. - [ ] ESLint rule blocks direct
zodimports outsidepackages/core/. - [ ] CI fails when generated JSON Schemas drift from their Zod sources.
- [ ] No adapter or stack-pack package in the v1.0 workspace lists
zodas a direct dependency.
References
Internal
- ADR 0001 — Runtime + language
- ADR 0005 — Adapter contract shape (the schemas this library validates)
- ADR 0006 — Stack-pack contract shape (sibling)
- ADR 0007 — MCP catalog shape (entries validated at load)
- ADR 0009 — Argument parser
- ADR 0010 — Prompts library
.docs/ARCHITECTURE.md§4 — Package responsibilities (mentions JSON schemas).docs/ARCHITECTURE.md§21 — open question on schema library (this ADR closes it)
External
- Zod — https://github.com/colinhacks/zod
zod-to-json-schema— https://github.com/StefanTerdell/zod-to-json-schema- Valibot — https://github.com/fabian-hiller/valibot
arktype— https://arktype.io/ajv— https://github.com/ajv-validator/ajvruntypes— https://github.com/runtypes/runtypes