Files
2026-05-27 23:29:53 +01:00

5.7 KiB

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 <T = unknown> 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": "<tag>", "v": <encoded> } 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 <T = unknown> 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: <iso-string> } (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 <T = unknown> 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.