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)andmerkleRootBlake3(digests)primitives over@noble/hashes/blake3. - ADDED:
integrity?: PackedDataIntegrityfield onPackedDataMetadatacarrying{ entryDigest, merkleRoot }. Always populated byPacker.pack. - ADDED:
PACKER_INTEGRITY_FAILEDerror code; thrown byUnpacker.unpackwhen integrity is missing or does not match the recomputed digests. - ADDED:
entryDigestfield onManifestChunkEntry;merkleRootfield onManifest.buildManifestpopulates both;verifyManifestcontinues to cover them via the existing canonical-encode signature. - ADDED:
unpackChunksWithManifest(chunks, manifest, opts)— chunked unpacker variant that verifies per-chunk BLAKE3 againstmanifest.chunks[i].entryDigestand 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) andwithPackerSpanhelper. - ADDED:
PackerObservabilityinjection onPackerOptionsand onPackChunkedOptions/UnpackChunkedOptions— defaults to a no-op recorder pulled from@opentelemetry/api. - ADDED:
src/i18n/messages.ts— English-defaults message catalog keyed bypacker.error.<code>.<scenario>. Constructor optionPackerOptions.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 injectedMessageResolver.
Out of scope
@suaveplan/retry/@suaveplan/idempotencyintegration → cluster #2 (resilience).- Clock / Rng injection audit (no
Date.now()/Math.random()at module scope) → cluster #2. - Whole-file
AbortSignalpropagation → cluster #2. @suaveplan/schema-registryintegration → cluster #3 (extensibility).@suaveplan/feature-flagsintegration → cluster #3.- Plugin / extension interface → cluster #3.
fast-checkproperty 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 onPackedDataMetadataandManifestChunkEntry(PACKER-18 surface).src/packer/packer.spec-conformance.test.ts(extended) —packalways emitsintegrity;unpackrejects withPACKER.INTEGRITY_FAILEDon mismatch and absence (PACKER-18, PACKER-19).src/chunked/unpacker/unpacker-with-manifest.spec-conformance.test.ts—unpackChunksWithManifestverifies per-chunk BLAKE3 + manifest Merkle root and rejects mutations (PACKER-18, PACKER-19).src/observability/observability.spec-conformance.test.ts— uses a real in-memoryMeterProvider+InMemoryMetricExporterand a realBasicTracerProvider+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 customMessageResolveris 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 anyPackedDatawhose envelope verified through the existing signature / AEAD pipeline, regardless of whethermetadata.integritywas present. - New behavior:
Unpacker.unpack(packed)throwsPackerError(PACKER.INTEGRITY_FAILED)whenmetadata.integrityis 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 emitsintegrity), or — for callers that intentionally ship pre-integrity envelopes — passverifyIntegrity: falseonUnpackOptions(additive, additive, additive: this opt-out exists to make the rollover one-line; the default is verify).