# Capability Spec Delta: bitpacker-compressed (bitpacker-stub-completion) **Package**: `@suaveplan/bitpacker-compressed` **Status**: PROPOSED > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: CompressedBitPacker subclass The package SHALL export `CompressedBitPacker extends BitPacker`. The subclass SHALL run packed bytes through a configured `CompressionAdapter` before returning them, and SHALL run incoming bytes through `decompressData` before delegating to `BitPacker.unpack`. Compression behavior SHALL be configurable via `BitPackerConfig.compression` (`algorithm`, `options`, `enabled`); accessors `compressionAlgorithm`, `compressionOptions`, `compressionEnabled` SHALL surface and mutate the current settings. #### Scenario: pack → decompress round-trip is byte-identical - **WHEN** a `CompressedBitPacker` packs `{ a: 1, b: "hello" }` with `algorithm: "gzip"` and the result is decompressed and unpacked - **THEN** the unpacked value deep-equals the input #### Scenario: `enabled: false` skips compression entirely - **WHEN** `compressionEnabled` is `false` - **THEN** `pack` returns bytes byte-identical to `BitPacker.pack` (no compression header, no codec invocation) #### Scenario: `algorithm: "none"` invokes NoCompressionAdapter - **WHEN** `algorithm` is `"none"` - **THEN** `pack` produces the same bytes as `BitPacker.pack` AND the OTel counter `bitpacker.compress.bytes_in` records the input length while `bitpacker.compress.bytes_out` records the same value **Verified by:** `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts::"pack-decompress round-trip is byte-identical for gzip"` AND `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts::"compressionEnabled false skips codec invocation"` AND `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts::"algorithm 'none' yields BitPacker.pack output"` --- ### Requirement: CompressionAdapter contract and built-in adapters The package SHALL expose a `CompressionAdapter` interface with `compress(data, options): Promise` and `decompress(data, options): Promise`. The package SHALL ship `NoCompressionAdapter`, `GzipCompressionAdapter`, and `DeflateCompressionAdapter` matching upstream behavior (`compression.ts` L121-L336). #### Scenario: gzip adapter round-trip - **WHEN** `GzipCompressionAdapter` compresses 1 KiB of zeros, then decompresses - **THEN** the output is the original 1 KiB AND the compressed length is < 50 bytes #### Scenario: deflate adapter round-trip - **WHEN** `DeflateCompressionAdapter` compresses random 1 KiB bytes, then decompresses - **THEN** the decompressed output deep-equals the original #### Scenario: NoCompressionAdapter passes bytes through - **WHEN** `NoCompressionAdapter.compress(input)` is called - **THEN** the result is byte-identical to the input **Verified by:** `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.test.ts::"GzipCompressionAdapter round-trip"` AND `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.test.ts::"DeflateCompressionAdapter round-trip"` AND `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.test.ts::"NoCompressionAdapter passes bytes through"` --- ### Requirement: CompressionRegistry replacement and unknown-algorithm error The package SHALL expose a `CompressionRegistry` allowing callers to `register(algorithm, adapter)` and `resolve(algorithm)`. Resolving an unknown algorithm SHALL throw `BitpackerError` with code `BITPACKER.UNKNOWN_COMPRESSION_ALGORITHM` and `context.algorithm`. #### Scenario: registering an adapter overrides the default - **WHEN** a custom `BrotliCompressionAdapter` is registered under key `"brotli"` then `resolve("brotli")` is called - **THEN** the registered adapter is returned; subsequent `pack` calls with `algorithm: "brotli"` route through it #### Scenario: resolving an unknown algorithm throws coded error - **WHEN** `resolve("zstd")` is called and no adapter is registered for that key - **THEN** the call throws `BitpackerError` with code `BITPACKER.UNKNOWN_COMPRESSION_ALGORITHM` and `context.algorithm === "zstd"` **Verified by:** `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts::"register overrides default adapter"` AND `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts::"resolve throws UNKNOWN_COMPRESSION_ALGORITHM for missing key"` --- ### Requirement: Compression failure surfaces coded error When the underlying codec rejects (e.g. truncated gzip stream), the package SHALL wrap the cause and throw `BitpackerError` with code `BITPACKER.COMPRESSION_FAILED` (during compress) or `BITPACKER.DECOMPRESSION_FAILED` (during decompress); `context.algorithm` SHALL identify the codec. #### Scenario: truncated gzip stream emits DECOMPRESSION_FAILED - **WHEN** a half-truncated gzip-compressed buffer is passed to `decompressData(buf, "gzip")` - **THEN** a `BitpackerError` with code `BITPACKER.DECOMPRESSION_FAILED` is thrown with the underlying codec error chained as `cause` **Verified by:** `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts::"truncated gzip stream emits DECOMPRESSION_FAILED with chained cause"` ## MODIFIED Requirements ### Requirement: RLE pass-through helpers The package SHALL keep `compressRle` / `decompressRle` as exported free functions for the bytes-only fast path; they SHALL be available both at the package root (one-release deprecation) and at the `./rle` subpath. Their behavior SHALL match the existing 61-line implementation byte-for-byte (no semantic change). #### Scenario: existing RLE call sites still resolve from package root - **WHEN** a consumer imports `{ compressRle } from "@suaveplan/bitpacker-compressed"` - **THEN** the import resolves and `compressRle` produces the same bytes as before this change #### Scenario: canonical import location is `./rle` - **WHEN** a consumer imports `{ compressRle } from "@suaveplan/bitpacker-compressed/rle"` - **THEN** the import resolves and the function is the same reference as the root export **Verified by:** `packages/bitpacker/bitpacker-compressed/src/rle/rle.test.ts::"compressRle output unchanged from pre-rewrite snapshot"` AND `packages/bitpacker/bitpacker-compressed/src/rle/rle.test.ts::"./rle subpath and root export resolve to the same function"`