12 KiB
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 thenregistry.listSubjects()is read - THEN the list contains exactly
PackerOptions,UnpackOptions,ChunkedPackerOptions,Manifest, andpacker-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 underBACKWARDcompatibility
Scenario: registerPackerSchemas writes into a caller-supplied registry
- WHEN
registerPackerSchemas(externalRegistry)is called against a freshcreateRegistry() - THEN
externalRegistry.listSubjects()contains the same five subjects withformatVersion: "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 noflagsoption - THEN the call returns a
PackedDatawhosemetadatacarries the V1 shape (no V2-specific fields), anddefaultPackerFlags().get("packer.format.v2") === false
Scenario: enabling packer.format.v2 throws FORMAT_VERSION_UNSUPPORTED
- WHEN
Packer.pack(data)is called with aflagsprovider whoseget("packer.format.v2")returnstrue - THEN the call rejects with
PackerError(PACKER.FORMAT_VERSION_UNSUPPORTED)anderror.code === "PACKER.FORMAT_VERSION_UNSUPPORTED"
Scenario: a caller-supplied feature-flags Evaluator drives the gate
- WHEN
Packer.pack(data)is called withflags: { evaluator, context: { userId: "u-1" } }whereevaluatorresolvespacker.format.v2tofalseforuserId: "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<PackerPlugin> 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.<hook> and the original error carried on
error.cause.
Scenario: plugin hooks fire in declaration order
- WHEN
Packer.pack(data)is called with two plugins whoseonPackStart/onEntry/onPackEndpush 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 whoseonEntrythrowsnew Error("boom") - THEN the call rejects with
PackerError(PACKER.PLUGIN_FAILED),error.cause.message === "boom", anderror.messageis the resolvedpacker.error.PLUGIN_FAILED.onEntrycatalog string
Scenario: a double-integrity sidecar plugin contributes SHA-256
- WHEN
Packer.pack(data)is called with a plugin whoseonEntrycomputes SHA-256 of the payload and stashes it on the plugin's internal map, thenUnpacker.unpackis called with the same plugin whoseonEntryDecodedreads 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
datais recovered byte-identical fromunpack(pack(data))
Scenario: streaming chunked pack matches whole-buffer chunked pack
- WHEN the same input is fed to
packChunksonce 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 samedata, the same seededRng, the same fixedClock, and identical encryption options - THEN both
result.dataandresult.metadata.integrity.entryDigestbytes 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 benchmarkis 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"