# OpenSpec Change: enterprise-validation - **Change ID**: enterprise-validation - **Package**: `@suaveplan/validation` - **Category**: universal - **Target path**: `packages/universal/validation` - **Status**: IN PROGRESS - **Date**: 2026-04-18 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/validation.md` ## Summary Greenfield implementation of `@suaveplan/validation` for the genesis branch. The existing main-branch validation package provides the baseline public-API surface to preserve; this change delivers a runtime schema-validation library with a Zod-compatible API surface, exhaustive schema types (string, number, boolean, date, array, object, union, intersection, literal, enum, nullable, optional, record, tuple, coerce), async refinements, hooks, structured errors, property-based tests, and benchmarks. The library is entirely self-contained — it does NOT depend on Zod; it is its own parser, used alongside Zod at the monorepo level for other packages. ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. ## Available workspace deps on genesis | Package | Available | |---------|-----------| | `@suaveplan/error` | ✅ | | `@suaveplan/types` | ✅ | | `@suaveplan/events` | ✅ | | `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly | | `@suaveplan/feature-flags` | ❌ not yet built — skip | | `@suaveplan/schema-registry` | ❌ not yet built — skip | External deps (via catalog): - `@opentelemetry/api` → `catalog:observability` - `tslib` → `catalog:build` Dev deps (via catalog): - `vitest`, `@vitest/coverage-istanbul` → `catalog:testing` - `fast-check` → `catalog:testing` - `mitata` → `catalog:testing` ## Preserved public API surface (from main branch) ### Core - `BaseSchema` — abstract base class with `.parse(input)`, `.safeParse(input)`, `.parseAsync(input)`, `.safeParseAsync(input)`, `.refine(fn, msg?)`, `.transform(fn)`, `.optional()`, `.nullable()`, `.describe(description)` ### Types - `AnySchema`, `SchemaInput`, `SchemaOutput` — utility type extractors - `Infer`, `InferInput` — schema type inference helpers - `ParseResult` — `ParseSuccess | ParseFailure` - `ParseSuccess` — `{ success: true; data: T }` - `ParseFailure` — `{ success: false; error: ValidationError }` - `ParseContext` — internal traversal context - `ValidationIssue` — `{ code: ValidationCode; path: (string | number)[]; message: string; input?: unknown }` - `ValidationCode` — string union of all built-in issue codes - `FlattenedErrors` — `{ formErrors: string[]; fieldErrors: Record }` - `RefineFn`, `AsyncRefineFn`, `TransformFn`, `CustomRule` - `StringRuleOptions`, `NumberRuleOptions`, `ObjectShape` ### Errors - `ValidationError` extends `SuaveplanError` — `.flatten()`, `.issues` getter, `.format()` - `ValidationErrorOptions` — constructor options - `FlattenedErrors` — return type of `.flatten()` ### Schema factories - `string()` → `StringSchema` — `.min(n)`, `.max(n)`, `.length(n)`, `.regex(r)`, `.email()`, `.url()`, `.uuid()`, `.startsWith(s)`, `.endsWith(s)`, `.includes(s)`, `.trim()`, `.toLowerCase()`, `.toUpperCase()` - `number()` → `NumberSchema` — `.min(n)`, `.max(n)`, `.int()`, `.positive()`, `.negative()`, `.multipleOf(n)`, `.finite()` - `boolean()` → `BooleanSchema` - `date()` → `DateSchema` — `.min(d)`, `.max(d)` - `array(schema)` → `ArraySchema` — `.min(n)`, `.max(n)`, `.length(n)`, `.nonempty()` - `object(shape)` → `ObjectSchema` — `.strict()`, `.passthrough()`, `.strip()`, `.pick(keys)`, `.omit(keys)`, `.partial()`, `.required()`, `.extend(shape)`, `.merge(other)` - `union(schemas)` → `UnionSchema` - `intersection(a, b)` → `IntersectionSchema` - `literal(value)` → `LiteralSchema` - `enumeration(values)` → `EnumSchema` - `nullable(schema)` → `NullableSchema` - `optional(schema)` → `OptionalSchema` - `record(schema)` → `RecordSchema` - `tuple(schemas)` → `TupleSchema` ### Coerce utilities - `coerce(schema)` → `CoercedSchema` — wraps any schema with string→type coercion - `coerceString(input)` → `string` - `coerceNumber(input)` → `number` - `coerceBoolean(input)` → `boolean` - `coerceDate(input)` → `Date` - `CoercedSchema` (type) ### Compose utilities - `mergeResults(results)` — merges multiple `ParseResult` arrays into one - `pipe(schema, ...transforms)` — sequential schema pipeline ### Hook types - `ValidationHookMap` — lifecycle hooks: `beforeValidate`, `afterValidate`, `onError` - `ValidationHookData` — data passed to each hook - `ValidationValidatingHandler` — handler signature ## New additions for genesis ### Structured errors - `ValidationError` extends `SuaveplanError` with codes under `VALIDATION.*` namespace - Codes: `VALIDATION.STRING.*`, `VALIDATION.NUMBER.*`, `VALIDATION.ARRAY.*`, `VALIDATION.OBJECT.*`, `VALIDATION.UNION.NO_MATCH`, `VALIDATION.CUSTOM.REFINEMENT` ### OTel instrumentation - Counters: `validation.parse.success`, `validation.parse.failure` - Histogram: `validation.parse.duration_ms` (per schema type label) ### Property-based tests (fast-check) - Round-trip: `schema.parse(schema.parse(x)) === schema.parse(x)` (idempotency) - Error propagation: any invalid input to `.parse()` always returns `ParseFailure` (never throws) - Union exhaustiveness: input matching none of the union arms always fails ### Benchmarks (mitata) - `object(shape).parse(data)` for 10-field object — 100k iterations - `array(string()).parse(arr)` for 1 000-element array - `union` with 10 arms — best-case and worst-case branch ## Impact - Adds one new package: `packages/universal/validation` - Adds one new spec capability: `validation` - No existing package consumers on genesis yet