Files
suaveplan/openspec/archive/2026-04-28-packer-port/tasks.md
2026-05-27 23:29:53 +01:00

5.7 KiB

Tasks: packer-port

1. Scaffold

  • 1.1 Create packages/data/packer/ with package.json (@suaveplan/packer@0.0.0), tsconfig.json extending @suaveplan/typescript-config/library.json, vitest.config.ts via createTestConfig(), README.md.
  • 1.2 Workspace deps: @suaveplan/bitpacker, @suaveplan/crypto, @suaveplan/events, @suaveplan/mime, @suaveplan/error, @suaveplan/types, @suaveplan/streams, @opentelemetry/api, zod. All workspace:^.
  • 1.3 Subpath exports: ., ./chunked, ./streaming, ./errors.

2. Foundations

  • 2.1 src/error-codes.ts — defineCodes("PACKER", [...]). Sibling PackerError.
  • 2.2 src/schemas/packer.schemas.ts — Zod boundary schemas for PackerOptions, ChunkedPackerOptions. Marker // schema: boundary-only.
  • 2.3 src/types/packer.types.ts — PackSource, PackerOptions, PackedData, ChunkedPackerOptions, ChunkMetadata, Manifest, EncryptionAlgorithm.

3. Streaming source normaliser

  • 3.1 Port src/streaming/normalize-source.ts from source. Accepts Uint8Array | ReadableStream<Uint8Array> | AsyncIterable<Uint8Array> | Blob. Returns AsyncIterable<Uint8Array>.
  • 3.2 Sibling normalize-source.test.ts, normalize-source.md, normalize-source.types.ts.

4. Abstract base

  • 4.1 src/abstract/abstract.ts — AbstractPacker extending EventBus<PackerEventMap>-backed emitter. Compression (gzip via node:zlib), AEAD wrappers, sign helpers.
  • 4.2 Replace source's untyped EventEmitter with typed EventBus<PackerEventMap> from @suaveplan/events.

5. Whole-file mode (preserves bitpacker schema)

  • 5.1 src/packer/packer.ts — Packer extends AbstractPacker. pack(data, filename?): Promise<PackedData> with optional gzip + AEAD + ed25519 sign. Bitpacker schema retained.
  • 5.2 src/unpacker/unpacker.ts — Unpacker extends AbstractPacker. unpack(packed, key, pubKey?): Promise<{ data, metadata }>.
  • 5.3 Sibling tests + .md for each.

6. Chunked v1 codec

  • 6.1 src/chunked/codec/header.ts — encodeHeader(metadata): Uint8Array(100), parseHeader(buf): ChunkMetadata with structural validation. Big-endian DataView reads/writes at locked offsets.
  • 6.2 src/chunked/codec/chunk.ts — encodeChunk({ metadata, sig, ciphertext }), parseChunk(buf) with length checks.
  • 6.3 src/chunked/codec/chunk.spec-conformance.test.ts — pin every byte offset against CHUNKS-FORMAT-V1-01.

7. Chunked packer

  • 7.1 src/chunked/packer/packer.ts — ChunkedPacker extends AbstractPacker. pack(source: PackSource, fileKey: EncryptKey, signKey: SignKey, opts?: ChunkedPackerOptions): AsyncIterable<{ chunkId, bytes }>.
  • 7.2 Pipeline: normalizeSource → hash → aead-encrypt-stream → chop at (chunkSize - 164) → per-chunk sign → emit.
  • 7.3 pack resolves with the assembled manifest (server-signed, fileId-addressed) at end of stream.
  • 7.4 Per-chunk nonce derivation: nonce = baseNonce ⊕ chunkId-as-LE-bytes (24 B).

8. Chunked unpacker

  • 8.1 src/chunked/unpacker/unpacker.ts — ChunkedUnpacker. unpack(chunks: AsyncIterable<Uint8Array>, fileKey: EncryptKey, serverPubKey: PublicKey): AsyncIterable<Uint8Array> (plaintext stream).
  • 8.2 Per-chunk: structural-parse → flags check → payloadLen invariants → sig-verify → aead-decrypt → emit payloadLen plaintext bytes.

9. Chunk verifier (no decrypt)

  • 9.1 src/chunked/verifier/chunk-verifier.ts — ChunkVerifier. verify(chunkBytes: Uint8Array, serverPubKey: PublicKey): VerifyResult with { ok: true, metadata } or { ok: false, code: PackerErrorCode }.
  • 9.2 Used by mesh peers that forward chunks without holding the file decryption key.

10. Manifest

  • 10.1 src/chunked/manifest/manifest.ts — buildManifest(metadata[], encryption, fileKey, signKey): Manifest, signManifest(manifest, signKey), verifyManifest(manifest, serverPubKey).
  • 10.2 Zod schema for manifest in src/schemas/manifest.schema.ts.

11. Spec

  • 11.1 openspec/specs/packer/spec.md — PACKER-WHOLE-01 … PACKER-WHOLE-04, PACKER-CHUNK-01 … PACKER-CHUNK-12, PACKER-CODES-01. Each with **Verified by:** line.

12. Conformance tests

  • 12.1 src/packer/packer.spec-conformance.test.ts — whole-file pack/unpack round-trip, signed and unsigned, with and without gzip (PACKER-WHOLE-01 … -04).
  • 12.2 src/chunked/codec/chunk.spec-conformance.test.ts — exact byte-offset pinning (PACKER-CHUNK-01 v1 layout, PACKER-CHUNK-02 flags=0 reject, PACKER-CHUNK-03 payloadLen invariants).
  • 12.3 src/chunked/packer/packer.spec-conformance.test.ts — streaming pipeline end-to-end (PACKER-CHUNK-04); fixed non-final size (PACKER-CHUNK-05); short final chunk (PACKER-CHUNK-06).
  • 12.4 src/chunked/unpacker/unpacker.spec-conformance.test.ts — chunks-out → plaintext-in matches original (PACKER-CHUNK-07); rejects mutated chunks at sig stage (PACKER-CHUNK-08).
  • 12.5 src/chunked/verifier/chunk-verifier.spec-conformance.test.ts — verifies without file key (PACKER-CHUNK-09); rejects bad sig (PACKER-CHUNK-10).
  • 12.6 src/chunked/manifest/manifest.spec-conformance.test.ts — manifest sign + verify (PACKER-CHUNK-11); manifest matches chunk metadata (PACKER-CHUNK-12).
  • 12.7 src/error-codes.spec-conformance.test.ts — frozen registry, PACKER.* namespace (PACKER-CODES-01).

13. Verify

  • 13.1 bunx turbo lint typecheck test --filter=@suaveplan/packer green.
  • 13.2 bunx turbo test:coverage --filter=@suaveplan/packer 100/100/100/100.
  • 13.3 bun scripts/openspec-conformance.ts @suaveplan/packer PASS.
  • 13.4 Sibling .md exists for every non-test, non-types, non-barrel .ts in src/.