Files
2026-05-27 23:29:53 +01:00

7.0 KiB

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.<code>.<scenario>. 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).