# OpenSpec Change: enterprise-serialize - **Change ID**: enterprise-serialize - **Package**: `@suaveplan/serialize` - **Category**: universal - **Target path**: `packages/universal/serialize` - **Status**: IN PROGRESS - **Date**: 2026-04-18 - **Reference spec**: `/mnt/s/web/suaveplan/enterprise/serialize.md` ## Summary Greenfield implementation of `@suaveplan/serialize` for the genesis branch. The existing main-branch serialize package provides the baseline public-API surface to preserve; this change delivers round-trip-preserving structured serialization — tagged JSON (preserving `Date`, `Map`, `Set`, `BigInt`, `Uint8Array`, typed arrays, `URL`, `RegExp`, user-defined tags) plus MessagePack binary profile and streaming variants. Eval-safe by default; opt-in legacy adapters isolated under `@suaveplan/serialize/unsafe`. ## 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/streams` | ❌ not yet built — implement chunked stream via Web Streams API internally | | `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly | | `@suaveplan/feature-flags` | ❌ not yet built — skip | | `@suaveplan/schema-registry` | ❌ not yet built — export Zod schemas directly | External deps: - `zod` → `catalog:forms` - `@opentelemetry/api` → `catalog:observability` - `@msgpack/msgpack` → add to `catalog:serialization` (new catalog group) at `^3.0.0` - `tslib` → `catalog:build` Unsafe-subpath-only (isolated, not in main bundle): - `serialize-javascript` → add to `catalog:serialization` at `^6.0.0` — only imported by `src/adapters/eval-safe/` which lives under the `/unsafe` subpath export Dev deps (via catalog): - `vitest`, `@vitest/coverage-istanbul` → `catalog:testing` - `fast-check` → `catalog:testing` - `mitata` → `catalog:testing` ## Preserved public API surface (from main branch) ### Main export (`@suaveplan/serialize`) #### Codec - `serialize(value, opts?)` → `string` — tagged JSON serialization - `deserialize(json, opts?)` → `unknown` — tagged JSON deserialization - `encodeBinary(value, opts?)` → `Uint8Array` — MessagePack encoding - `decodeBinary(bytes, opts?)` → `unknown` — MessagePack decoding - `encodeStream(value, opts?)` → `ReadableStream` — streaming MessagePack - `decodeStream(stream, opts?)` → `ReadableStream` — streaming decode - `deepClone(value)` → `unknown` — structured clone via serialize/deserialize round-trip - `SerializeOptions`, `DeserializeOptions`, `Codec` (types) #### Type registry - `registerType(def)` — register a custom type tag - `unregisterType(tag)` — remove a custom type tag - `listTypes()` → `TypeDefinition[]` — all registered types - `getType(tag)` → `TypeDefinition | undefined` - `getTypeByCtor(ctor)` → `TypeDefinition | undefined` - `TypeDefinition` — `{ tag: string; ctor: Function; encode: (v) => unknown; decode: (v) => unknown }` #### Errors - `SerializationError` extends `SuaveplanError` - `CircularReferenceError` extends `SerializationError` - `MaxDepthExceededError` extends `SerializationError` - `MaxSizeExceededError` extends `SerializationError` - `UnsupportedTypeError` extends `SerializationError` - `SerializationErrorCode`, `SerializationErrorContext`, `SerializationErrorOptions` (types) #### Error codes (re-exported) - `registerSerializeErrorCodes()`, `SERIALIZE_ERROR_METADATA`, `SerializeErrorCode` #### Legacy - `legacySerialize(value)` → `string` — old format compat (no new tags; for migration only) - `legacyDeserialize(json)` → `unknown` — old format compat ### Unsafe subpath (`@suaveplan/serialize/unsafe`) - `serializeEvalSafe(value, opts?)` → `string` — uses `serialize-javascript` under the hood; emits a security warning in dev - `deserializeEvalSafe(json, opts?)` → `unknown` - `EvalSafeOptions` (type) ## New additions for genesis ### Catalog entry - Add `serialization` catalog group to root `package.json`: ```json "serialization": { "@msgpack/msgpack": "^3.0.0", "serialize-javascript": "^6.0.0" } ``` ### Structured errors - `SerializationError` extends `SuaveplanError` with codes under `SERIALIZE.*` namespace - Codes: `SERIALIZE.CODEC.CIRCULAR_REFERENCE`, `SERIALIZE.CODEC.MAX_DEPTH`, `SERIALIZE.CODEC.MAX_SIZE`, `SERIALIZE.CODEC.UNKNOWN_TAG`, `SERIALIZE.REGISTRY.DUPLICATE_TAG`, `SERIALIZE.LEGACY.REFUSED` ### Zod schemas - `SerializeOptionsSchema`, `DeserializeOptionsSchema`, `TypeDefinitionSchema` — all public config types validated at boundary ### AbortSignal support - `encodeStream` and `decodeStream` honour `AbortSignal` passed via options ### OTel instrumentation - Histogram: `serialize.payload_bytes` (encode + decode, by format label json/msgpack) - Counter: `serialize.error.total` (by error code label) ### Property-based tests (fast-check) - Round-trip for every built-in type: `deserialize(serialize(x)) deep-equals x` - Binary round-trip: `decodeBinary(encodeBinary(x)) deep-equals x` - JSON–binary structural equivalence: both formats produce semantically equal output - Cycle detection: any circular graph always throws `CircularReferenceError` - Custom tag round-trip: registered tag encodes and decodes without data loss ### Benchmarks (mitata) - `serialize` / `deserialize` 10 KB nested object - `encodeBinary` / `decodeBinary` 10 KB nested object - `deepClone` 10 KB object vs `structuredClone` baseline ## Impact - Adds one new package: `packages/universal/serialize` - Adds `serialization` catalog group to root `package.json` - Adds one new spec capability: `serialization` - No existing package consumers on genesis yet