6.5 KiB
Packer — Spec Delta (packer-port)
ADDED Requirements
PACKER-01 — Whole-file pack/unpack round-trip
Packer.pack(data, filename?) followed by Unpacker.unpack(packed, key, pubKey?) MUST yield bytes equal to the original data. Optional MIME detection MUST surface in the returned metadata.
Verified by: src/packer/packer.spec-conformance.test.ts (PACKER-01).
PACKER-02 — Whole-file gzip compression
When compressionLevel > 0, pack MUST gzip the payload before AEAD; unpack MUST gunzip after AEAD. Any compression failure MUST throw PackerError(PACKER.COMPRESSION_FAILED).
Verified by: src/packer/packer.spec-conformance.test.ts (PACKER-02).
PACKER-03 — Whole-file signing
When a SignKey is provided to pack, the produced PackedData MUST include an ed25519 signature over the canonical packed bytes. unpack with a corresponding PublicKey MUST verify the signature and throw PackerError(PACKER.VERIFICATION_FAILED) on mismatch.
Verified by: src/packer/packer.spec-conformance.test.ts (PACKER-03).
PACKER-04 — Whole-file event lifecycle
Packer MUST emit typed events via EventBus<PackerEventMap>: pack:start, pack:complete, pack:error, with corresponding unpack:* events on the unpacker. Events MUST fire in deterministic FIFO order on completion.
Verified by: src/packer/packer.spec-conformance.test.ts (PACKER-04).
PACKER-05 — Chunked v1 byte layout
A v1 chunk produced by ChunkedPacker MUST be the byte concatenation metadata(100) ‖ sig(64) ‖ ciphertext(payloadLen), total 164 + payloadLen bytes, big-endian. Field offsets within metadata MUST be exactly: version 0–2, fileId 2–34, chunkId 34–38, totalChunks 38–42, fileSize 42–50, chunkSize 50–54, payloadLen 54–58, flags 58–60, nonce 60–84, aeadTag 84–100. Sig occupies 100–164. Ciphertext starts at 164.
Verified by: src/chunked/codec/chunk.spec-conformance.test.ts (PACKER-05).
PACKER-06 — flags reserved in v1
flags MUST be 0x0000 in v1. parseHeader MUST reject non-zero flags with PackerError(PACKER.FLAGS_RESERVED_NONZERO) before any further parse work.
Verified by: src/chunked/codec/chunk.spec-conformance.test.ts (PACKER-06).
PACKER-07 — payloadLen invariants
For non-final chunks (chunkId < totalChunks - 1), payloadLen MUST equal chunkSize - 164. For the final chunk, payloadLen MUST equal fileSize - chunkId * (chunkSize - 164). Mismatch MUST be rejected with PackerError(PACKER.PAYLOAD_LEN_INVALID) before sig verification.
Verified by: src/chunked/codec/chunk.spec-conformance.test.ts (PACKER-07).
PACKER-08 — Streaming pipeline produces v1 chunks
ChunkedPacker.pack(source, fileKey, signKey, options) MUST stream-process the input via normalizeSource → hash → aead-encrypt-stream → chop → per-chunk sign → emit. The output MUST be an AsyncIterable<{ chunkId, bytes }> where each bytes is a v1-format chunk. The full source MUST NOT be buffered in memory during pack.
Verified by: src/chunked/packer/packer.spec-conformance.test.ts (PACKER-08).
PACKER-09 — Non-final chunks are exactly chunkSize bytes
Every emitted chunk with chunkId < totalChunks - 1 MUST be exactly chunkSize bytes on the wire (default chunkSize = 1 MiB → payloadLen = 1048412). No zero padding is added.
Verified by: src/chunked/packer/packer.spec-conformance.test.ts (PACKER-09).
PACKER-10 — Final chunk is naturally short
The final chunk MUST be 164 + lastPayloadLen bytes, where lastPayloadLen = fileSize - (totalChunks - 1) * (chunkSize - 164). The final chunk MUST NOT be zero-padded to chunkSize.
Verified by: src/chunked/packer/packer.spec-conformance.test.ts (PACKER-10).
PACKER-11 — Round-trip parity
ChunkedUnpacker.unpack(chunks, fileKey, serverPubKey) over the chunks emitted by ChunkedPacker.pack(...) MUST yield a plaintext stream byte-equal to the original input. Order MUST be chunkId ascending; out-of-order delivery is the consumer's responsibility to reorder before passing to unpacker.
Verified by: src/chunked/unpacker/unpacker.spec-conformance.test.ts (PACKER-11).
PACKER-12 — Mutation detection
Any mutation to any signed byte (metadata field or any byte of ciphertext) MUST cause ChunkedUnpacker.unpack to throw PackerError(PACKER.VERIFICATION_FAILED) from the per-chunk sig-verify step before AEAD decryption is attempted.
Verified by: src/chunked/unpacker/unpacker.spec-conformance.test.ts (PACKER-12).
PACKER-13 — Verifier without file key
ChunkVerifier.verify(chunkBytes, serverPubKey) MUST validate the structural invariants and the ed25519 signature without the file decryption key. It MUST return { ok: true, metadata } for valid chunks and { ok: false, code } for invalid chunks. It MUST NOT attempt AEAD decryption.
Verified by: src/chunked/verifier/chunk-verifier.spec-conformance.test.ts (PACKER-13).
PACKER-14 — Verifier rejects forged sig
ChunkVerifier.verify MUST return { ok: false, code: "PACKER.VERIFICATION_FAILED" } for a chunk whose signature does not validate under serverPubKey. Verifier MUST NOT consult the chunks plugin or any persistence layer.
Verified by: src/chunked/verifier/chunk-verifier.spec-conformance.test.ts (PACKER-14).
PACKER-15 — Manifest signature
The manifest produced by buildManifest(...) MUST be ed25519-signed via signManifest(manifest, signKey). verifyManifest(manifest, serverPubKey) MUST return false for any manifest whose signature does not validate; mutation of any signed field MUST cause verification failure.
Verified by: src/chunked/manifest/manifest.spec-conformance.test.ts (PACKER-15).
PACKER-16 — Manifest matches chunk metadata
The manifest produced by a successful ChunkedPacker.pack(...) MUST satisfy: manifest.fileId == chunkMeta[i].fileId for all i, manifest.totalChunks == chunkCount, manifest.chunkSize == options.chunkSize, manifest.fileSize == sum(payloadLen), and manifest.chunks[i].payloadLen == chunkMeta[i].payloadLen for all i.
Verified by: src/chunked/manifest/manifest.spec-conformance.test.ts (PACKER-16).
PACKER-17 — Error registry namespaced and frozen
PACKER_CODES MUST be declared via defineCodes("PACKER", [...]) from @suaveplan/error. Object.isFrozen(PACKER_CODES) MUST be true. Every code value MUST start with PACKER.. PackerError MUST extend SuaveplanError<PackerErrorCode>.
Verified by: src/error-codes.spec-conformance.test.ts (PACKER-17).