# Enterprise hardening — `@suaveplan/packer` integrity cluster **Package**: `@suaveplan/packer` **Status**: PROPOSED **Type**: feature **Design**: (inline below — small scope) **Spec delta**: ./specs/packer/spec.md ## Why `@suaveplan/packer` currently ships ed25519 signing and a SHA-256 per-chunk wire hash, but neither the whole-file nor the chunked paths emit a content-addressable digest scheme or a bundle-level Merkle root. There is also no OpenTelemetry instrumentation on the public entrypoints (no counters, no histograms, no spans) and every user-visible error carries a hard-coded English string. The `enterprise/packer.md` audit calls all three out as gaps blocking the ENTERPRISE label. This is cluster #1 of a 3-cluster enterprise hardening of `@suaveplan/packer` (integrity, resilience, extensibility). Clusters #2 and #3 are deliberately out of scope here — see "Out of scope" below. ## What changes - ADDED: `src/integrity/integrity.ts` — `entryDigestBlake3(data)` and `merkleRootBlake3(digests)` primitives over `@noble/hashes/blake3`. - ADDED: `integrity?: PackedDataIntegrity` field on `PackedDataMetadata` carrying `{ entryDigest, merkleRoot }`. Always populated by `Packer.pack`. - ADDED: `PACKER_INTEGRITY_FAILED` error code; thrown by `Unpacker.unpack` when integrity is missing or does not match the recomputed digests. - ADDED: `entryDigest` field on `ManifestChunkEntry`; `merkleRoot` field on `Manifest`. `buildManifest` populates both; `verifyManifest` continues to cover them via the existing canonical-encode signature. - ADDED: `unpackChunksWithManifest(chunks, manifest, opts)` — chunked unpacker variant that verifies per-chunk BLAKE3 against `manifest.chunks[i].entryDigest` and the manifest's Merkle root before yielding plaintext. - ADDED: `src/observability/observability.ts` — OTel instruments (`packer_bundle_size_bytes`, `packer_entry_count`, `packer_pack_total`, `packer_unpack_total`, `packer_integrity_failure_total`, `packer_pack_duration_ms`, `packer_unpack_duration_ms`, `packer_entry_decode_ms`) and `withPackerSpan` helper. - ADDED: `PackerObservability` injection on `PackerOptions` and on `PackChunkedOptions` / `UnpackChunkedOptions` — defaults to a no-op recorder pulled from `@opentelemetry/api`. - ADDED: `src/i18n/messages.ts` — English-defaults message catalog keyed by `packer.error..`. Constructor option `PackerOptions.messageResolver?: MessageResolver` (and equivalent on chunked option bundles); default resolver looks up the local catalog. - MODIFIED: every `throw new PackerError(CODE, "english string")` site now resolves the message through the injected `MessageResolver`. ## Out of scope - `@suaveplan/retry` / `@suaveplan/idempotency` integration → cluster #2 (resilience). - Clock / Rng injection audit (no `Date.now()` / `Math.random()` at module scope) → cluster #2. - Whole-file `AbortSignal` propagation → cluster #2. - `@suaveplan/schema-registry` integration → cluster #3 (extensibility). - `@suaveplan/feature-flags` integration → cluster #3. - Plugin / extension interface → cluster #3. - `fast-check` property tests for round-trip invariants → cluster #3. - mitata benchmarks → cluster #3. ## Source mapping This change derives from `../suaveplan/enterprise/packer.md`. | Requirement | Source lines | Notes | |---|---|---| | PACKER-18 (BLAKE3 per-entry + Merkle root, mandatory verify) | L40 | Verbatim — "per-entry BLAKE3 plus bundle-level Merkle root; verification is mandatory on read". | | PACKER-19 (`PACKER_INTEGRITY_FAILED`) | L45 | Reworded — audit line: "Structured errors … with stable error codes for integrity failures". | | PACKER-20 (OpenTelemetry counters / histograms / spans) | L44 | Verbatim — "OpenTelemetry instrumentation via `@suaveplan/opentelemetry` for all public entrypoints (bundle size, entry count, pack/unpack latency)". | | PACKER-21 (i18n-keyed messages, `MessageResolver` injection) | L49 | Verbatim — "i18n-ready — no hard-coded user-visible strings, all messages keyed". | ### Requirements with no source line None — every requirement in this cluster maps directly to an audit bullet. ## Verification approach - `src/integrity/integrity.test.ts` — pins BLAKE3 KAT + Merkle root vectors (PACKER-18 primitive correctness). - `src/integrity/integrity.spec-conformance.test.ts` — pins the wire-level integrity-field shape on `PackedDataMetadata` and `ManifestChunkEntry` (PACKER-18 surface). - `src/packer/packer.spec-conformance.test.ts` (extended) — `pack` always emits `integrity`; `unpack` rejects with `PACKER.INTEGRITY_FAILED` on mismatch and absence (PACKER-18, PACKER-19). - `src/chunked/unpacker/unpacker-with-manifest.spec-conformance.test.ts` — `unpackChunksWithManifest` verifies per-chunk BLAKE3 + manifest Merkle root and rejects mutations (PACKER-18, PACKER-19). - `src/observability/observability.spec-conformance.test.ts` — uses a real in-memory `MeterProvider` + `InMemoryMetricExporter` and a real `BasicTracerProvider` + `InMemorySpanExporter`, asserts every named counter / histogram / span fires from production paths (PACKER-20). - `src/i18n/messages.spec-conformance.test.ts` — pins the catalog shape and proves a custom `MessageResolver` is consulted at every throw site (PACKER-21). ## Migration impact Public-surface additions only. 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. Whole-file `unpack` now rejects packed data that lacks the new `integrity` field — this is the only consumer-visible behavior change and is intentional per the cluster scope ("verification is mandatory on read"). A `## Migration` section below names the rollover. ## 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 - **Old behavior**: `Unpacker.unpack(packed)` succeeded on any `PackedData` whose envelope verified through the existing signature / AEAD pipeline, regardless of whether `metadata.integrity` was present. - **New behavior**: `Unpacker.unpack(packed)` throws `PackerError(PACKER.INTEGRITY_FAILED)` when `metadata.integrity` is missing OR when the recomputed `(entryDigest, merkleRoot)` does not match the envelope's recorded values. - **Search command**: `grep -rln "Unpacker\|@suaveplan/packer" packages/ | grep -v "/dist/\|/coverage/"`. - **Replacement**: regenerate stale packed bundles with `Packer.pack` (which always emits `integrity`), or — for callers that intentionally ship pre-integrity envelopes — pass `verifyIntegrity: false` on `UnpackOptions` (additive, additive, additive: this opt-out exists to make the rollover one-line; the default is verify).