3.5 KiB
3.5 KiB
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, butBaseSchemahad nowithHooksmethod 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 exportedrecordParseSuccess/recordParseFailure, but no source-side call site ever invoked them; agrep -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
- Wire hooks (VL-10). Add
withHooks(hooks)builder onBaseSchemareturning a clone (immutable) with_hooksmerged. InvokebeforeValidate/afterValidate/onErrorinparse(fire-and-forget on the sync path) and inparseAsync(awaited). - Wire OTel (VL-11). Call
recordParseSuccess/recordParseFailurefrom every terminal point ofparseandparseAsyncwithschemaType = this.constructor.nameanddurationMsfromperformance.now().safeParsenow delegates toparse(single-source-of-truth) so its instrumentation is automatic. - Add
src/core/schema/schema.spec-conformance.test.tscovering:- VL-01 type-level Zod-compatible API surface (
expectTypeOfonparse/safeParse/parseAsync/safeParseAsync/refine/transform/optional/nullable). - VL-02 never-throw contract for
safeParse/safeParseAsyncagainst 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
.withHookscalls. - VL-11 OTel emission against a fake
MeterProvider, asserting both counters and the duration histogram are recorded withschema.typeattribute.
- VL-01 type-level Zod-compatible API surface (
- 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).
safeParseis now implemented in terms ofparse(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/apiand the existingrecordParseSuccess/recordParseFailurehelpers.