Files
suaveplan/openspec/archive/2026-05-19-enterprise-packer-extensibility/proposal.md
2026-05-27 23:29:53 +01:00

165 lines
8.3 KiB
Markdown

# 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.