# OpenSpec Change: serialize-spec-conformance - **Change ID**: serialize-spec-conformance - **Package**: `@suaveplan/serialize` - **Status**: PROPOSED - **Date**: 2026-04-26 - **Reference**: `openspec/specs/serialization/spec.md`, `openspec/archive/2026-04-19-enterprise-serialize/` ## Summary Annotate every requirement in `openspec/specs/serialization/spec.md` with a `**Verified by:**` line, pin previously-implicit scenarios with explicit tests, close the SZ-10 (wire-format + options validation) and SZ-11 (OTel instrumentation) gaps in scope, thread `` generics through the public deserialize entry points, and rectify the historical "retroactively ticked" hazard in `2026-04-19-enterprise-serialize/tasks.md` so `bun scripts/openspec-conformance.ts @suaveplan/serialize` exits 0. ## Current state `@suaveplan/serialize` ships a complete tagged-JSON codec, MessagePack profile, Web Streams encoder/decoder, `serialize-javascript` `/unsafe` adapter, Zod-validated `TypeDefinition` registry, structured error hierarchy, and 100/100/100/100 coverage. The conformance gaps are documentary plus three previously-implicit scenarios that no test pinned explicitly: - 12/12 requirements lack `**Verified by:**` lines. - SZ-02 typed-array round-trip — only `Uint8Array` is tested through the public `serialize → deserialize` round-trip; the other seven typed arrays (`Int8Array`, `Int16Array`, `Int32Array`, `Uint16Array`, `Uint32Array`, `Float32Array`, `Float64Array`) only exercise the registry directly. - SZ-03 envelope shape — the `{ "__t": "", "v": }` wire format is implicit in encode tests but not asserted as a structural contract. - SZ-06 MessagePack bit-identical output — "structurally equivalent" is asserted, but the spec MUST is bit-identical bytes for the same input. - `archive/2026-04-19-enterprise-serialize/tasks.md` carries `retroactively ticked`. ## Non-goals (deferred to follow-up changes) _None._ Two implementation gaps were originally deferred here and have since been folded back into this change at the user's request: - **SZ-10 schema validation at the public boundary** — closed in scope. New `WireNodeSchema` + `TaggedEnvelopeSchema` + `validateWireNode` defend `deserialize` / `decodeBinary` against malformed inputs (throws `MalformedWireError`, code `SERIALIZE.CODEC.MALFORMED_WIRE`); the existing `SerializeOptionsSchema` / `DeserializeOptionsSchema` are now `.safeParse()`'d at every public entry point (throws `InvalidOptionsError`, code `SERIALIZE.OPTIONS.INVALID`). - **SZ-11 OTel instrumentation** — closed in scope. New `src/observability/metrics.ts` exports a lazy `getSerializeMetrics()` singleton built from `metrics.getMeter('suaveplan.serialize')`. Six instruments wired into all four public entry points: `serialize.encode.bytes`, `serialize.decode.bytes`, `serialize.payload_bytes`, `serialize.encode.duration_ms`, `serialize.decode.duration_ms` (histograms) and `serialize.error.total` (counter, `code` + `format` labels). Verified by tests that wire a real, in-memory `MeterProvider` (not `vi.mock`). A third refinement was added in the same pass: the public `deserialize`, `decodeBinary`, `legacyDeserialize`, and `deserializeEvalSafe` entry points now accept an optional `` generic so callers receive `T` instead of `unknown` at the call site (type-only change, runtime unchanged). ## What changes 1. Add `**Verified by:**` line under every `### SZ-NN` block in `openspec/specs/serialization/spec.md`. 2. Add `src/codec/json/json-encode.spec-conformance.test.ts` covering: - SZ-02: `Int8Array`, `Int16Array`, `Int32Array`, `Uint16Array`, `Uint32Array`, `Float32Array`, `Float64Array` end-to-end `serialize → deserialize` round-trip. - SZ-03: explicit assertion that a `Date` value encodes to `{ __t: "__Date", v: }` (envelope shape contract). 3. Add `src/codec/msgpack/msgpack-encode.spec-conformance.test.ts` covering: - SZ-06: `encodeBinary(x)` produces byte-identical output across two calls for the same structured input (Map keys insertion-order varied). 4. **SZ-10**: add `src/codec/wire/wire.schemas.ts` (`WireNodeSchema`, `TaggedEnvelopeSchema`, `validateWireNode`); add `MalformedWireError` and `InvalidOptionsError` to `src/errors/serialize-error.ts`; tighten `src/codec/json/json.schemas.ts` to `.strict()` with duck-typed `signal`; wire schema validation into all four public entry points; add `src/codec/wire/wire.schemas.test.ts` pinning the rejection set. 5. **SZ-11**: add `src/observability/metrics.ts` exposing `getSerializeMetrics()` + `resetSerializeMetrics()` with lazy `metrics.getMeter('suaveplan.serialize')`; emit `encode.bytes` / `decode.bytes` / `payload_bytes` / `encode.duration_ms` / `decode.duration_ms` histograms and `error.total` counter from every public entry point; add `src/observability/metrics.test.ts` using a real in-memory `MeterProvider` to assert emission. 6. **Generic threading**: thread `` through `deserialize`, `decodeBinary`, `legacyDeserialize`, `deserializeEvalSafe` with `expectTypeOf` pins. ## Impact - Public API gains: `MalformedWireError`, `InvalidOptionsError`, `WireNodeSchema`, `TaggedEnvelopeSchema`, `validateWireNode`, `getSerializeMetrics`, `resetSerializeMetrics`, `SerializeMetrics` type. New error codes `SERIALIZE.CODEC.MALFORMED_WIRE` and `SERIALIZE.OPTIONS.INVALID`. - New tests across three files; new `src/codec/wire/` and `src/observability/` directories with co-located `.md` docs. - `openspec/specs/serialization/spec.md` gains 12 `**Verified by:**` lines (SZ-10 / SZ-11 now reference real test IDs, no longer say "follow-up change"). - Coverage stays at 100/100/100/100. - The `*-spec-conformance` change folder name auto-rectifies the historical retroactive-ticking flag in the conformance script.