79 lines
5.7 KiB
Markdown
79 lines
5.7 KiB
Markdown
# 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/`.
|