Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,164 @@
# Enterprise hardening — `@suaveplan/packer` extensibility cluster
**Package**: `@suaveplan/packer`
**Status**: PROPOSED
**Type**: feature
**Design**: (inline below — scope is well-bounded by the 5-item brief)
**Spec delta**: ./specs/packer/spec.md
## Why
Cluster #1 (`enterprise-packer-integrity`, archived 2026-05-19) landed
BLAKE3 content-addressable digests, the bundle-level Merkle root,
mandatory integrity verification on unpack, full OpenTelemetry
instrumentation, and i18n-keyed error messages. Cluster #2
(`enterprise-packer-resilience`, archived 2026-05-19) added retry,
idempotency, whole-file `AbortSignal`, and Clock / Rng injection.
This is the third and final cluster, addressing the remaining
`enterprise/packer.md` audit gaps (L41, L42, L48, L72, L75):
1. No `@suaveplan/schema-registry` integration — the Zod option schemas
exist in `src/schemas/packer.schemas.ts` but are not registered with
the canonical versioned registry, and the chunk-format version byte
has no companion registry entry that records `V1` as the shipping
format and reserves the path to `V2`.
2. No `@suaveplan/feature-flags` rollout surface — the audit calls for
"Feature flags via `@suaveplan/feature-flags` for format-version
rollouts" (L48); today the package writes V1 unconditionally and has
no rollout path for a future V2 wire format.
3. No plugin / extension surface — the audit calls for "A plugin /
extension interface (where it makes sense) so downstream packages
can contribute behaviour without forking" (L72). Today the packer is
closed: every transform, integrity strategy, and codec is hard-wired.
4. No `fast-check` property tests — the audit calls for "Property-based
tests (fast-check) for pack/unpack round-trips and streaming-
equivalence-to-batch invariants" (L42).
5. No mitata benchmark suite — the audit calls for "Performance —
benchmark suite in `src/__benchmarks__`, published numbers in the
README, regression gate in CI" (L75).
After this cluster lands, `@suaveplan/packer` meets every checkbox in
the `enterprise/packer.md` §3 + §4 + §6 acceptance criteria.
## What changes
- ADDED: `src/schemas/registration.ts` — registers every Zod schema in
`src/schemas/packer.schemas.ts` plus a `packer-chunk-format` subject
with V1 as the shipping wire-format version under a `BACKWARD`
compatibility policy. `getPackerSchemaRegistry()` returns the
process-singleton registry; `registerPackerSchemas(registry)`
performs the registration against any caller-supplied
`SchemaRegistry`.
- ADDED: `@suaveplan/schema-registry` as a runtime dependency.
- ADDED: `src/flags/flags.ts` — `PackerFeatureFlags` provider
interface and `defaultPackerFlags()` no-op provider that returns the
V1 default for every flag. `resolvePackerFlags(opts)` adapts a
caller-supplied `@suaveplan/feature-flags` `Evaluator` or a raw
provider into the internal `PackerFeatureFlags` shape.
- ADDED: `flags?: PackerFlagsOptions` on `PackerOptions`. The write
path now reads `packer.format.v2` from the resolved provider — when
enabled, `Packer.pack` throws
`PackerError(PACKER.FORMAT_VERSION_UNSUPPORTED)` with the catalog
message `packer.error.FORMAT_VERSION_UNSUPPORTED.v2-not-implemented`
(versioning gate — V2 is a future change, not a deferral).
- ADDED: `@suaveplan/feature-flags` as a runtime dependency.
- ADDED: `src/plugins/plugins.ts` — `PackerPlugin` contract, six
lifecycle hooks (`onPackStart`, `onEntry`, `onPackEnd`,
`onUnpackStart`, `onEntryDecoded`, `onUnpackEnd`),
order-deterministic invocation via
`PluginPipeline.runHook(name, ctx)`. Plugin errors surface as
`PackerError(PACKER.PLUGIN_FAILED)`.
- ADDED: `plugins?: ReadonlyArray<PackerPlugin>` on `PackerOptions`.
`Packer.pack` and `Unpacker.unpack` invoke every registered plugin's
hooks in declaration order.
- ADDED: `PACKER.FORMAT_VERSION_UNSUPPORTED` and `PACKER.PLUGIN_FAILED`
error codes in `error-codes.ts`.
- ADDED: `src/__property__/round-trip.property.test.ts` and
`src/__property__/determinism.property.test.ts` — `fast-check`
property-based round-trip, streaming↔batch equivalence, determinism.
- ADDED: `src/__benchmarks__/packer.bench.ts` — `mitata` benchmark
suite covering whole-file pack/unpack, chunked pack (single + 1000
chunks), integrity verify, plugin overhead. `bun run benchmark`
script in `package.json` and a Benchmarks section in `README.md`.
- ADDED: `mitata` as a devDependency (already present in the root
catalog as `catalog:testing`).
- MODIFIED: `src/index.ts` — exports `getPackerSchemaRegistry`,
`registerPackerSchemas`, `PackerPlugin`, `defaultPackerFlags`,
`resolvePackerFlags`, and the plugin / flags / registration types.
## Out of scope
- V2 wire format implementation — this cluster lands the registry
entry and the feature-flag gate; the V2 codec is a follow-up change
that flips the throw into real encoding work. The throw is the spec
contract for V2-not-implemented; not a deferral.
- Mutation testing — orthogonal to the audit; opens a follow-up.
- Codemod for breaking option changes — there are none in this
cluster.
## Source mapping
This change derives from `../suaveplan/enterprise/packer.md`.
| Requirement | Source lines | Notes |
|---|---|---|
| PACKER-26 (`@suaveplan/schema-registry` integration) | L41 | Verbatim — "Zod schemas … `@suaveplan/schema-registry` integration, versioned evolution with explicit format-version bytes". |
| PACKER-27 (`@suaveplan/feature-flags` rollouts) | L48, L79 | Verbatim — "Feature flags via `@suaveplan/feature-flags` for format-version rollouts" / "Feature flags for any risky new behaviour". |
| PACKER-28 (plugin / extension interface) | L72 | Verbatim — "A plugin / extension interface (where it makes sense) so downstream packages can contribute behaviour without forking". |
| PACKER-29 (`fast-check` property tests) | L42 | Verbatim — "Property-based tests (fast-check) for pack/unpack round-trips and streaming-equivalence-to-batch invariants". |
| PACKER-30 (mitata benchmark suite) | L75 | Verbatim — "Performance — benchmark suite in `src/__benchmarks__`, published numbers in the README, regression gate in CI". |
### Requirements with no source line
None — every requirement maps directly to an audit bullet.
## Verification approach
- `src/schemas/registration.test.ts` + `.spec-conformance.test.ts` —
pins registry contents, V1 evolution metadata, BACKWARD compatibility
on subject reuse.
- `src/flags/flags.test.ts` + `.spec-conformance.test.ts` — pins
default no-op provider, custom evaluator wiring, V2 throw path.
- `src/plugins/plugins.test.ts` + `.spec-conformance.test.ts` — pins
hook ordering, error surface, downstream plugin example (noop logger
+ double-integrity SHA-256 sidecar).
- `src/__property__/round-trip.property.test.ts` — pack→unpack identity
+ streaming↔batch equivalence under arbitrary inputs.
- `src/__property__/determinism.property.test.ts` — deterministic
output under seeded Rng + fixed Clock.
- `src/__benchmarks__/packer.bench.ts` — runnable via
`bun run benchmark`; baseline numbers published in `README.md`.
## Migration impact
Public-surface additions only. No existing signature changes shape —
the new options are all optional with platform defaults. Two consumer
packages (`@suaveplan/drive-plugin-chunks`, `@suaveplan/drive-webrtc`)
are audited and the sweep is part of the gates; no existing call site
changes shape.
## Impact
### Affected dependents
Run `bunx turbo ls --filter=...@suaveplan/packer` to enumerate.
- `@suaveplan/drive-plugin-chunks` — [x] tests run / [x] docs reviewed / [ ] no action needed
- `@suaveplan/drive-webrtc` — [x] tests run / [x] docs reviewed / [ ] no action needed
If `tests run` is unchecked for any dependent, the change MUST NOT be
folded into the canonical spec.
## Migration
No behavioral change for callers that did not pass the new options.
Existing calls keep their previous semantics:
- **No `plugins` passed**: no hooks fire; behaviour identical to
cluster #2.
- **No `flags` passed**: `defaultPackerFlags()` is used; every flag
returns the V1 default and `Packer.pack` writes the V1 format.
- **No `registry` interaction needed**: callers that did not use
`@suaveplan/schema-registry` before do not need to start now —
registration is a process-singleton side effect available on demand.

View File

@@ -0,0 +1,225 @@
<!-- markdownlint-disable MD024 -->
# 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<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 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"`
<!-- markdownlint-enable MD024 -->

View File

@@ -0,0 +1,93 @@
# Tasks — Enterprise hardening (extensibility cluster) for `@suaveplan/packer`
## 1. Spec authoring
- [x] 1.1 Read `../suaveplan/enterprise/packer.md` end-to-end
- [x] 1.2 Read `openspec/specs/packer/spec.md`
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14
- [x] 1.4 Draft `proposal.md` from `openspec/templates/proposal.md`
- [x] 1.5 Fill in `## Source mapping` table — every requirement cites a line range
- [x] 1.6 Draft `specs/packer/spec.md` from `openspec/templates/spec-delta.md`
- [x] 1.7 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
## 2. Implementation — error codes (E-1 / E-2 / E-3)
- [x] 2.1 Add `FORMAT_VERSION_UNSUPPORTED` and `PLUGIN_FAILED` codes to `src/error-codes.ts` with metadata.
- [x] 2.2 Add catalog keys `packer.error.FORMAT_VERSION_UNSUPPORTED.v2-not-implemented`, `packer.error.PLUGIN_FAILED.<hook>` to `src/i18n/messages.ts`.
## 3. Implementation — schema registry (E-1)
- [x] 3.1 Add `@suaveplan/schema-registry` to `packer/package.json` deps (`workspace:^`).
- [x] 3.2 New `src/schemas/registration.ts` — `registerPackerSchemas(registry)` + `getPackerSchemaRegistry()`, registering every Zod schema in `packer.schemas.ts` plus a `packer-chunk-format` subject with V1 evolution metadata.
- [x] 3.3 Sibling `registration.md` + `registration.types.ts` + `index.ts` barrel.
- [x] 3.4 Subpath export `./schemas` in `package.json`.
## 4. Implementation — feature flags (E-2)
- [x] 4.1 Add `@suaveplan/feature-flags` to `packer/package.json` deps (`workspace:^`).
- [x] 4.2 New `src/flags/flags.ts` — `PackerFeatureFlags` interface, `defaultPackerFlags()` no-op, `resolvePackerFlags(opts)` factory, `PACKER_FLAG_KEYS` constants.
- [x] 4.3 Sibling `flags.md` + `flags.types.ts` + `index.ts` barrel.
- [x] 4.4 `Packer.pack` reads `packer.format.v2` from the resolved provider; when truthy, throws `PackerError(PACKER.FORMAT_VERSION_UNSUPPORTED)`.
- [x] 4.5 `PackerOptions.flags?: PackerFlagsOptions` threaded through `AbstractPacker.resolvedOptions`.
- [x] 4.6 Subpath export `./flags` in `package.json`.
## 5. Implementation — plugin interface (E-3)
- [x] 5.1 New `src/plugins/plugins.ts` — `PackerPlugin` contract, `PluginPipeline.runHook(name, ctx)`, error mapping to `PACKER.PLUGIN_FAILED`.
- [x] 5.2 Sibling `plugins.md` + `plugins.types.ts` + `index.ts` barrel.
- [x] 5.3 `PackerOptions.plugins?: ReadonlyArray<PackerPlugin>` threaded through `AbstractPacker`.
- [x] 5.4 `Packer.pack` invokes `onPackStart` → per-entry `onEntry` → `onPackEnd` in declaration order around the pipeline.
- [x] 5.5 `Unpacker.unpack` invokes `onUnpackStart` → `onEntryDecoded` → `onUnpackEnd`.
- [x] 5.6 Subpath export `./plugins` in `package.json`.
## 6. Tests — schema registry, flags, plugins
- [x] 6.1 `src/schemas/registration.test.ts` + `.spec-conformance.test.ts` — every schema registered, V1 evolution metadata, BACKWARD compatibility, singleton invariant.
- [x] 6.2 `src/flags/flags.test.ts` + `.spec-conformance.test.ts` — default provider returns V1 for every key, V2 throws `PACKER.FORMAT_VERSION_UNSUPPORTED`, custom evaluator wiring, integration with `Packer.pack` write path.
- [x] 6.3 `src/plugins/plugins.test.ts` + `.spec-conformance.test.ts` — declaration-order hook fire, error → `PACKER.PLUGIN_FAILED`, noop-logger + double-integrity example plugins exercised against real `Packer` + `Unpacker`.
## 7. Tests — fast-check property (E-4)
- [x] 7.1 `src/__property__/round-trip.property.test.ts` — pack→unpack identity over arbitrary `Uint8Array` inputs; streaming↔batch equivalence via chunked pack.
- [x] 7.2 `src/__property__/determinism.property.test.ts` — same inputs + seeded Rng + fixed Clock ⇒ byte-identical bundles.
## 8. Benchmarks (E-5)
- [x] 8.1 `src/__benchmarks__/packer.bench.ts` — mitata suite (whole-file pack/unpack, chunked single + 1000 chunks, integrity verify, plugin overhead).
- [x] 8.2 `bun run benchmark` script in `package.json`.
- [x] 8.3 `README.md` Benchmarks section with baseline numbers and hardware/runtime context line.
## 9. Documentation
- [x] 9.1 Sibling `.md` for every new `.ts` (≥200 words, basic + advanced examples).
- [x] 9.2 Update `README.md` Modules list with `flags/`, `plugins/`, `schemas/registration`.
## 10. Pre-archive verification (the 5-gate triple)
- [x] 10.1 `bun run check` — Biome 0/0/0.
- [x] 10.2 `bunx turbo lint typecheck test --filter=@suaveplan/packer` — green.
- [x] 10.3 `bunx turbo test:coverage --filter=@suaveplan/packer` — 100/100/100/100.
- [x] 10.4 `bun scripts/openspec-conformance.ts @suaveplan/packer` — exit 0.
- [x] 10.5 `bunx turbo build --filter=@suaveplan/packer` — green.
## 11. Consumer sweep
- [x] 11.1 `bunx turbo lint typecheck test --filter=@suaveplan/drive-plugin-chunks --filter=@suaveplan/drive-webrtc` — green.
## 12. Pre-archive 9-row checklist
- [x] 12.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim.
- [x] 12.2 Zod schemas wired at every declared boundary.
- [x] 12.3 Every error code in `error-codes.ts` thrown by at least one production path.
- [x] 12.4 OTel instruments emitted from production paths.
- [x] 12.5 No `export *` in feature barrels.
- [x] 12.6 No direct `vitest` / `bun:test` imports.
- [x] 12.7 No retroactive tick — every box above reflects work done in this change.
- [x] 12.8 Sibling `.md` for every new `.ts`.
- [x] 12.9 Items E-1, E-2, E-3, E-4, E-5 each have ≥1 production code path and ≥1 test pinning every named scenario.
## 13. Archive
- [x] 13.1 Fold `specs/packer/spec.md` into `openspec/specs/packer/spec.md`.
- [x] 13.2 Move `openspec/changes/enterprise-packer-extensibility/` → `openspec/archive/<yyyy-mm-dd>-enterprise-packer-extensibility/`.
- [x] 13.3 Refresh `MANIFEST.md`.