5.6 KiB
Drive Plugin Chunks — Spec Delta (drive-plugin-chunks-port)
ADDED Requirements
CHUNKS-FORMAT-V1-01 — Chunk binary layout is byte-exact
A version 0x0001 chunk MUST be the byte concatenation metadata(100) ‖ sig(64) ‖ ciphertext(payloadLen), total 164 + payloadLen bytes, big-endian throughout. 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/codec/chunk.spec-conformance.test.ts (CHUNKS-FORMAT-V1-01).
CHUNKS-FORMAT-V1-02 — flags reserved in v1
flags MUST be 0x0000 for v1. Receivers MUST reject chunks with non-zero flags via ChunksError(CHUNKS.FLAGS_RESERVED_NONZERO) before any further parse work.
Verified by: src/codec/chunk.spec-conformance.test.ts (CHUNKS-FORMAT-V1-02).
CHUNKS-FORMAT-V1-03 — Payload length invariants
For non-final chunks (chunkId < totalChunks - 1), payloadLen MUST equal chunkSize - 164. For the final chunk, payloadLen MUST equal fileSize - chunkId * (chunkSize - 164). Mismatches MUST be rejected via ChunksError(CHUNKS.PAYLOAD_LEN_INVALID) before sig verification.
Verified by: src/codec/chunk.spec-conformance.test.ts (CHUNKS-FORMAT-V1-03).
CHUNKS-SIG-01 — Signature covers metadata plus ciphertext
sig MUST be ed25519_sign(serverPrivKey, metadata[0..100] ‖ ciphertext[0..payloadLen]). Verification MUST be performed before AEAD decryption. Any mutation to any signed byte MUST cause sig verification to fail with ChunksError(CHUNKS.SIG_INVALID).
Verified by: src/pipeline/sign-chunk.spec-conformance.test.ts (CHUNKS-SIG-01).
CHUNKS-INVARIANT-01 — Store-as-is
Plugin MUST persist received chunks byte-identical to receipt. No re-signing, no re-encryption, no metadata rewrites. The bytes returned by chunkStore.get(fileId, chunkId) MUST equal the bytes accepted by chunks.putChunk(rawBytes) for that chunk.
Verified by: src/plugin.spec-conformance.test.ts (CHUNKS-INVARIANT-01).
CHUNKS-VERIFY-01 — Sig-verify gate on receipt
chunks.putChunk(rawBytes) MUST run, in order: structural parse → flags check → payload-length invariant → ed25519 sig verification → persist. Any failure aborts persistence and returns the appropriate ChunksError code without writing to storage.
Verified by: src/plugin.spec-conformance.test.ts (CHUNKS-VERIFY-01).
CHUNKS-READ-01 — Read middleware reassembles via streaming
When a fileId is registered for path, drive.read(path) MUST return a ReadableStream<Uint8Array> that yields plaintext by iterating chunk 0..N-1, sig-verifying each, AEAD-decrypting, and enqueueing only the first payloadLen bytes of each chunk's plaintext. The full file MUST NOT be materialised in memory.
Verified by: src/plugin.spec-conformance.test.ts (CHUNKS-READ-01).
CHUNKS-LAYOUT-SPARSE-01 — Sparse-file layout
In sparse-file mode, the plugin MUST pre-allocate a single storage object of length (totalChunks - 1) * chunkSize + (164 + lastPayloadLen) after manifest load. Each chunk MUST be written via storage.setPartial(fileKey, chunkId * chunkSize, rawBytes). A separate BitSet at bitmap:<fileId> MUST track landed chunks. The plugin MUST require storage.capabilities.partialWrites === true and throw ChunksError(CHUNKS.LAYOUT_INCOMPATIBLE) otherwise.
Verified by: src/layout/sparse-file.spec-conformance.test.ts (CHUNKS-LAYOUT-SPARSE-01).
CHUNKS-LAYOUT-KEYS-01 — Chunk-keys layout
In chunk-keys mode, each chunk MUST be stored as its own object at key chunks/<fileId>/<chunkId>. hasChunks(fileId) MUST be derivable from key existence (storage.exists). No bitmap object is required.
Verified by: src/layout/chunk-keys.spec-conformance.test.ts (CHUNKS-LAYOUT-KEYS-01).
CHUNKS-LAYOUT-AUTO-01 — Auto layout selection
In auto mode (default), the plugin MUST pick sparse-file when storage.capabilities.partialWrites === true, otherwise MUST pick chunk-keys. The selected layout MUST be observable via chunks.layout.
Verified by: src/layout/auto.spec-conformance.test.ts (CHUNKS-LAYOUT-AUTO-01).
CHUNKS-MANIFEST-01 — Manifest format and signature
The manifest MUST be a Zod-validated object with fields version, fileId, fileSize, chunkSize, totalChunks, encryption: { algo, fileKeyWrapped, baseNonce }, chunks: [{ chunkId, hash, payloadLen }], and sig. sig MUST be ed25519_sign(serverPrivKey, canonical_serialize(manifest_without_sig)). Load failures (parse, schema, sig) MUST throw ChunksError(CHUNKS.MANIFEST_INVALID).
Verified by: src/manifest/load.spec-conformance.test.ts (CHUNKS-MANIFEST-01).
CHUNKS-MANIFEST-02 — Manifest distribution strategy
Default strategy MUST be manifest-as-chunk: the manifest is itself a server-signed object stored at storage key manifest:<fileId>. server-only strategy MUST be opt-in via manifestSource: "server-only"; in that mode loadManifest(fileId) fetches via an injected fetch against the configured server URL.
Verified by: src/manifest/load.spec-conformance.test.ts (CHUNKS-MANIFEST-02).
CHUNKS-CODES-01 — Error registry namespaced and frozen
CHUNKS_CODES MUST be declared via defineCodes("CHUNKS", [...]). Object.isFrozen(CHUNKS_CODES) MUST be true. Every code value MUST start with CHUNKS.. ChunksError MUST extend SuaveplanError<ChunksErrorCode>.
Verified by: src/error-codes.spec-conformance.test.ts (CHUNKS-CODES-01).