Files
suaveplan/openspec/archive/2026-04-27-validation-spec-conformance/proposal.md
2026-05-27 23:29:53 +01:00

38 lines
3.5 KiB
Markdown

# OpenSpec Change: validation-spec-conformance
- **Change ID**: validation-spec-conformance
- **Package**: `@suaveplan/validation`
- **Status**: PROPOSED
- **Date**: 2026-04-26
## Summary
Wire two MUST-clauses into the implementation that were specced but not actually plumbed (`.withHooks(...)` lifecycle hooks and `validation.parse.*` OTel emission), then annotate every requirement in `openspec/specs/validation/spec.md` with a `**Verified by:**` line so `bun scripts/openspec-conformance.ts @suaveplan/validation` exits 0. No public-API removals or signature breaks; the additions are purely additive.
## Current state
`@suaveplan/validation` is structurally solid: 12 schema types, immutable builder pattern, sync + async parse paths, structured `ValidationError` with `flatten()` / `format()`, full coercion utilities, fast-check-friendly suite at 272 tests / 100% coverage. The conformance gaps are:
- **VL-10 drift.** The spec mandates `schemas MUST support optional lifecycle hooks { beforeValidate, afterValidate, onError } settable via .withHooks(hooks)`. `ValidationHookMap<T>` was exported as a type, but `BaseSchema` had no `withHooks` method and the parse path never invoked any hook. Pure documentary substrate, not an implementation.
- **VL-11 drift.** The spec mandates `validation.parse.success / .failure counters and validation.parse.duration_ms histogram MUST be emitted via @opentelemetry/api`. The OTel module exported `recordParseSuccess` / `recordParseFailure`, but no source-side call site ever invoked them; a `grep -rn 'recordParse' src/` showed only the test file and the export. The metrics existed only on paper.
- **All 12 requirements** lacked `**Verified by:**` annotations.
## What changes
1. **Wire hooks (VL-10).** Add `withHooks(hooks)` builder on `BaseSchema` returning a clone (immutable) with `_hooks` merged. Invoke `beforeValidate` / `afterValidate` / `onError` in `parse` (fire-and-forget on the sync path) and in `parseAsync` (awaited).
2. **Wire OTel (VL-11).** Call `recordParseSuccess` / `recordParseFailure` from every terminal point of `parse` and `parseAsync` with `schemaType = this.constructor.name` and `durationMs` from `performance.now()`. `safeParse` now delegates to `parse` (single-source-of-truth) so its instrumentation is automatic.
3. **Add `src/core/schema/schema.spec-conformance.test.ts`** covering:
- VL-01 type-level Zod-compatible API surface (`expectTypeOf` on `parse` / `safeParse` / `parseAsync` / `safeParseAsync` / `refine` / `transform` / `optional` / `nullable`).
- VL-02 never-throw contract for `safeParse` / `safeParseAsync` against bizarre input (`Symbol`, type mismatch).
- VL-10 hook invocation across all 6 paths (sync success/failure/refinement-failure, async success/failure/refinement-failure) plus hook-merge across multiple `.withHooks` calls.
- VL-11 OTel emission against a fake `MeterProvider`, asserting both counters and the duration histogram are recorded with `schema.type` attribute.
4. **Annotate `openspec/specs/validation/spec.md`** — one `**Verified by:**` line per requirement.
## Impact
- Public API gains `BaseSchema.withHooks(hooks)`. No removals, no signature breaks.
- One new test file (~280 lines, well under the 500-line budget).
- `safeParse` is now implemented in terms of `parse` (semantic equivalence preserved; covered by the existing 272-test suite).
- Coverage stays at 100/100/100/100.
- No new runtime dependencies — the OTel emission uses the already-declared `@opentelemetry/api` and the existing `recordParseSuccess` / `recordParseFailure` helpers.