# Capability Spec Delta: packer (enterprise-packer-extensibility) **Package**: `@suaveplan/packer` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. No exceptions. ## ADDED Requirements ### Requirement: PACKER-26 — `@suaveplan/schema-registry` integration The package SHALL register every Zod schema in `src/schemas/packer.schemas.ts` (`PackerOptionsSchema`, `UnpackOptionsSchema`, `ChunkedPackerOptionsSchema`, `ManifestSchema`) plus a `packer-chunk-format` subject capturing the wire-format version byte, into a process-singleton `SchemaRegistry` exposed via `getPackerSchemaRegistry()`. The `packer-chunk-format` subject MUST register V1 (the current wire format) under a `BACKWARD` compatibility policy with evolution metadata `{ formatVersion: "1", encoding: "binary", description: "v1 fixed-width 164-byte header + AEAD payload" }` so a future V2 entry can be added without disturbing V1. Every other schema MUST be registered under its own subject (`PackerOptions`, `UnpackOptions`, `ChunkedPackerOptions`, `Manifest`) carrying the metadata `{ formatVersion: "1" }`. A `registerPackerSchemas(registry)` function MUST allow callers to register the same schemas into any external `SchemaRegistry` instance for cross-package validation. #### Scenario: getPackerSchemaRegistry returns a populated singleton - **WHEN** `getPackerSchemaRegistry()` is called and then `registry.listSubjects()` is read - **THEN** the list contains exactly `PackerOptions`, `UnpackOptions`, `ChunkedPackerOptions`, `Manifest`, and `packer-chunk-format` #### Scenario: packer-chunk-format records V1 with evolution metadata - **WHEN** `registry.getLatest("packer-chunk-format")` is read - **THEN** `metadata.formatVersion === "1"`, `metadata.encoding === "binary"`, and registering an identical V1 candidate succeeds under `BACKWARD` compatibility #### Scenario: registerPackerSchemas writes into a caller-supplied registry - **WHEN** `registerPackerSchemas(externalRegistry)` is called against a fresh `createRegistry()` - **THEN** `externalRegistry.listSubjects()` contains the same five subjects with `formatVersion: "1"` metadata, and no mutation to the global singleton has occurred **Verified by:** `src/schemas/registration.test.ts::"registers every option schema as a subject with formatVersion 1 metadata"` AND `src/schemas/registration.test.ts::"records packer-chunk-format V1 with binary encoding and BACKWARD compatibility"` AND `src/schemas/registration.spec-conformance.test.ts::"getPackerSchemaRegistry returns a process-singleton populated with the canonical subjects"` AND `src/schemas/registration.spec-conformance.test.ts::"registerPackerSchemas writes into a caller-supplied registry without mutating the global singleton"` --- ### Requirement: PACKER-27 — `@suaveplan/feature-flags` for format-version rollouts The package SHALL accept a `flags?: PackerFlagsOptions` bundle on `PackerOptions` and route the wire-format selection through the resolved `PackerFeatureFlags` provider. The exported constant `PACKER_FLAG_KEYS.FORMAT_V2 === "packer.format.v2"` MUST identify the V2 rollout flag. When no provider is supplied, `defaultPackerFlags()` MUST act as a no-op that returns the V1 default (`false`) for every flag. When the resolved provider reports `packer.format.v2 === true`, `Packer.pack` MUST throw `PackerError(PACKER.FORMAT_VERSION_UNSUPPORTED)` with the catalog message `packer.error.FORMAT_VERSION_UNSUPPORTED.v2-not-implemented`; this is a versioning gate — the V2 codec is a future change. A caller-supplied `@suaveplan/feature-flags` `Evaluator` MUST be acceptable as the provider via `resolvePackerFlags({ evaluator, context? })`. #### Scenario: default flags resolve V2 to false and pack succeeds in V1 - **WHEN** `new Packer().pack(data)` is called with no `flags` option - **THEN** the call returns a `PackedData` whose `metadata` carries the V1 shape (no V2-specific fields), and `defaultPackerFlags().get("packer.format.v2") === false` #### Scenario: enabling packer.format.v2 throws FORMAT_VERSION_UNSUPPORTED - **WHEN** `Packer.pack(data)` is called with a `flags` provider whose `get("packer.format.v2")` returns `true` - **THEN** the call rejects with `PackerError(PACKER.FORMAT_VERSION_UNSUPPORTED)` and `error.code === "PACKER.FORMAT_VERSION_UNSUPPORTED"` #### Scenario: a caller-supplied feature-flags Evaluator drives the gate - **WHEN** `Packer.pack(data)` is called with `flags: { evaluator, context: { userId: "u-1" } }` where `evaluator` resolves `packer.format.v2` to `false` for `userId: "u-1"` - **THEN** the call returns a V1 bundle and the evaluator's internal counter records one evaluation for `flag_key: "packer.format.v2"` **Verified by:** `src/flags/flags.test.ts::"defaultPackerFlags returns false for packer.format.v2"` AND `src/flags/flags.test.ts::"resolvePackerFlags adapts a feature-flags Evaluator into a PackerFeatureFlags provider"` AND `src/flags/flags.spec-conformance.test.ts::"Packer.pack throws PACKER.FORMAT_VERSION_UNSUPPORTED when packer.format.v2 is enabled"` AND `src/flags/flags.spec-conformance.test.ts::"Packer.pack succeeds in V1 when flags option is omitted"` AND `src/flags/flags.spec-conformance.test.ts::"a caller-supplied feature-flags Evaluator drives the format-version gate"` --- ### Requirement: PACKER-28 — Plugin / extension interface The package SHALL expose a `PackerPlugin` contract carrying six lifecycle hooks (`onPackStart(ctx)`, `onEntry(ctx)`, `onPackEnd(ctx)`, `onUnpackStart(ctx)`, `onEntryDecoded(ctx)`, `onUnpackEnd(ctx)`), each optional and each invoked in plugin-declaration order. Plugins are registered via `plugins?: ReadonlyArray` on `PackerOptions`. `Packer.pack` MUST invoke `onPackStart` exactly once before the pipeline, `onEntry` exactly once per entry packed (one for whole-file mode), and `onPackEnd` exactly once after the final result is composed. `Unpacker.unpack` MUST invoke `onUnpackStart` / `onEntryDecoded` / `onUnpackEnd` symmetrically. Any plugin error MUST be caught and re-thrown as `PackerError(PACKER.PLUGIN_FAILED)` with a catalog message `packer.error.PLUGIN_FAILED.` and the original error carried on `error.cause`. #### Scenario: plugin hooks fire in declaration order - **WHEN** `Packer.pack(data)` is called with two plugins whose `onPackStart` / `onEntry` / `onPackEnd` push their name onto a shared array - **THEN** the array equals `["A:start", "B:start", "A:entry", "B:entry", "A:end", "B:end"]` #### Scenario: an unpack pass fires onUnpackStart / onEntryDecoded / onUnpackEnd - **WHEN** `Unpacker.unpack(packed)` is called with a plugin that records each unpack hook - **THEN** the recorded order is `["unpackStart", "entryDecoded", "unpackEnd"]` #### Scenario: a plugin error surfaces as PACKER.PLUGIN_FAILED - **WHEN** `Packer.pack(data)` is called with a plugin whose `onEntry` throws `new Error("boom")` - **THEN** the call rejects with `PackerError(PACKER.PLUGIN_FAILED)`, `error.cause.message === "boom"`, and `error.message` is the resolved `packer.error.PLUGIN_FAILED.onEntry` catalog string #### Scenario: a double-integrity sidecar plugin contributes SHA-256 - **WHEN** `Packer.pack(data)` is called with a plugin whose `onEntry` computes SHA-256 of the payload and stashes it on the plugin's internal map, then `Unpacker.unpack` is called with the same plugin whose `onEntryDecoded` reads back and asserts equality - **THEN** both calls succeed and the plugin observed identical SHA-256 bytes on the way out and the way in **Verified by:** `src/plugins/plugins.test.ts::"runs onPackStart / onEntry / onPackEnd in declaration order"` AND `src/plugins/plugins.test.ts::"runs onUnpackStart / onEntryDecoded / onUnpackEnd in declaration order"` AND `src/plugins/plugins.test.ts::"wraps thrown plugin errors as PackerError(PACKER.PLUGIN_FAILED) with cause"` AND `src/plugins/plugins.spec-conformance.test.ts::"a noop-logger plugin observes every lifecycle hook"` AND `src/plugins/plugins.spec-conformance.test.ts::"a double-integrity sidecar plugin contributes SHA-256 alongside BLAKE3"` --- ### Requirement: PACKER-29 — `fast-check` property-based round-trip + determinism The package SHALL include `fast-check` property tests pinning the pack/unpack round-trip identity, streaming↔batch equivalence, and seeded-Rng determinism invariants. Property assertions MUST run over arbitrary `Uint8Array` inputs of length 0..1024 (at least 50 runs per property). Round-trip identity: for every `data`, `unpack(pack(data))` returns bytes byte-equal to `data`. Streaming equivalence: the concatenation of every `PackedChunk.bytes` emitted by `packChunks(asyncIter(data), { ...same fileKey/signKey/baseNonce })` MUST byte-equal the wire bytes of `packChunks(data, { ...same opts })` consumed from a whole-buffer source. Determinism: two `Packer.pack` runs with identical inputs, identical options, the same seeded `Rng` and the same fixed `Clock` MUST produce byte-identical `PackedData` (both `data` and every byte of `metadata.integrity`). #### Scenario: round-trip identity over arbitrary Uint8Array inputs - **WHEN** `fc.assert(fc.asyncProperty(fc.uint8Array({ maxLength: 1024 }), async (data) => { … pack/unpack … }))` runs - **THEN** every drawn `data` is recovered byte-identical from `unpack(pack(data))` #### Scenario: streaming chunked pack matches whole-buffer chunked pack - **WHEN** the same input is fed to `packChunks` once as a whole-buffer source and once as a 1-byte-at-a-time async-iterable source, with identical options - **THEN** concatenated wire bytes are byte-identical between the two runs #### Scenario: seeded Rng + fixed Clock produces byte-identical bundles - **WHEN** two `Packer.pack(data)` calls run with the same `data`, the same seeded `Rng`, the same fixed `Clock`, and identical encryption options - **THEN** both `result.data` and `result.metadata.integrity.entryDigest` bytes are equal **Verified by:** `src/__property__/round-trip.property.test.ts::"pack then unpack recovers the original bytes for arbitrary inputs"` AND `src/__property__/round-trip.property.test.ts::"chunked streaming pack matches whole-buffer chunked pack byte-for-byte"` AND `src/__property__/determinism.property.test.ts::"two packs with identical inputs and seeded Rng produce byte-identical bundles"` --- ### Requirement: PACKER-30 — mitata benchmark suite The package SHALL ship a runnable mitata benchmark suite at `src/__benchmarks__/packer.bench.ts` covering: (a) whole-file `Packer.pack` over a fixed 1 KiB payload, (b) whole-file `Unpacker.unpack` over the result of (a), (c) chunked `packChunks` over a single-chunk source, (d) chunked `packChunks` over a 1000-chunk source, (e) `computeWholeFileIntegrity` over a fixed 1 KiB payload, and (f) a plugin-overhead comparison (`Packer.pack` with zero plugins vs. with a no-op plugin). The benchmark MUST be runnable via `bun run benchmark` declared in `package.json` and MUST execute without error. Baseline numbers MUST be published in `README.md` under a `## Benchmarks` section with a hardware/runtime context line. #### Scenario: bun run benchmark executes the mitata suite - **WHEN** `bun run benchmark` is invoked at the package root - **THEN** the suite completes without throwing and emits mitata's standard ops/sec summary for every named case #### Scenario: every named case is registered with mitata - **WHEN** the benchmark module is statically inspected for `bench(` invocations - **THEN** there are at least six distinct `bench("...")` calls matching the named cases (whole-file pack, whole-file unpack, chunked single, chunked 1000, integrity verify, plugin overhead) **Verified by:** `src/benchmarks-conformance/packer.bench.spec-conformance.test.ts::"declares the six named benchmark cases"` AND `src/benchmarks-conformance/packer.bench.spec-conformance.test.ts::"package.json declares a bun run benchmark script"` AND `src/benchmarks-conformance/packer.bench.spec-conformance.test.ts::"README.md publishes a Benchmarks section with a hardware context line"`