Initial Commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Audit re-confirmation — 2026-05-02
|
||||
|
||||
This audit re-walks the gap that prompted the `bitpacker-stub-completion` change for the `@suaveplan/bitpacker-compressed` package and confirms the parent change still covers the required scope. **No new change folder was created.**
|
||||
|
||||
## What was re-audited
|
||||
|
||||
- `packages/bitpacker/bitpacker-compressed/src/` (genesis): 61 LOC stub with the self-described `"placeholder for a more sophisticated compression algorithm"` comment, exporting only `compressRle` / `decompressRle`. Confirmed verbatim against the genesis tree on 2026-05-02.
|
||||
- `packages/universal/bitpacker/src/compressed/` (legacy): full `CompressedBitPacker<T> extends BitPacker<T>` (327 LOC, `bitpacker.ts`) plus `CompressionRegistry` with `NoCompressionAdapter`, `GzipCompressionAdapter`, `DeflateCompressionAdapter` (336 LOC, `compression.ts`). Confirmed against the legacy tree on 2026-05-02.
|
||||
|
||||
The audit instruction was: if the parent change `bitpacker-stub-completion` already covers `bitpacker-compressed` work in detail, skip creating a redundant `bitpacker-compressed-codecs` change and instead drop a re-confirmation note here.
|
||||
|
||||
## Findings
|
||||
|
||||
The parent change covers the gap in full. Specifically:
|
||||
|
||||
- **`proposal.md` lines 39-46** spell out every requirement on the package — `CompressedBitPacker<T>`, `CompressionAdapter` interface, `CompressionRegistry` class, the three built-in adapters (`NoCompressionAdapter`, `GzipCompressionAdapter`, `DeflateCompressionAdapter`), and the `compressData` / `decompressData` async helpers — plus the explicit retention of `compressRle` / `decompressRle` for the bytes-only fast path.
|
||||
- **`proposal.md` Source-mapping table** cites the exact upstream files and line ranges (`compressed/bitpacker.ts` L1-L327; `compressed/compression.ts` L1-L336; `compressed/compression.ts` L75-L120).
|
||||
- **`specs/bitpacker-compressed/spec.md`** authors four ADDED requirements — `CompressedBitPacker subclass`, `CompressionAdapter contract and built-in adapters`, `CompressionRegistry replacement and unknown-algorithm error`, `Compression failure surfaces coded error` — every one carrying a `**Verified by:**` line.
|
||||
- **`tasks.md`** (parent change) drives implementation, tests, docs, and pre-archive verification for the package.
|
||||
|
||||
The parent change is in `openspec/changes/` (active, not archived). Its scope is sufficient.
|
||||
|
||||
## Recommendation
|
||||
|
||||
Do not author a separate `bitpacker-compressed-codecs` change. The implementer working `bitpacker-stub-completion` should pick up the `@suaveplan/bitpacker-compressed` portion as written.
|
||||
|
||||
If `bitpacker-stub-completion` is ever archived without shipping the `bitpacker-compressed` portion (e.g. the change is split into per-package branches), open `bitpacker-compressed-codecs` then with the requirements above lifted directly from this parent change's spec delta. The four ADDED requirements in `specs/bitpacker-compressed/spec.md` are ready to be carried forward verbatim.
|
||||
|
||||
## Pointer
|
||||
|
||||
Parent change: `openspec/changes/bitpacker-stub-completion/`
|
||||
Spec delta for this package: `openspec/changes/bitpacker-stub-completion/specs/bitpacker-compressed/spec.md`
|
||||
@@ -0,0 +1,129 @@
|
||||
# Bitpacker stub completion
|
||||
|
||||
**Packages**: `@suaveplan/bitpacker-delta`, `@suaveplan/bitpacker-network`, `@suaveplan/bitpacker-compressed`, `@suaveplan/bitpacker-streaming`, `@suaveplan/bitpacker-schema`
|
||||
**Status**: PROPOSED
|
||||
**Type**: spec-conformance + feature
|
||||
|
||||
## Why
|
||||
|
||||
The 2026-05-02 audit of the `bitpacker-modular-restructure` change (now reflected in its updated parity table) found five places where the modular restructure ticked a box but the underlying work was either a stub, misplaced, or contained a determinism rule violation. Genesis policy is "no half-baked or mediocre libraries" (CLAUDE.md §Workflow rule 3) and "no module-scope `Math.random()` / `Date.now()`" (enterprise/bitpacker.md L40). This change closes those five gaps in one coordinated pass so downstream consumers (drive, network, packer) can rely on a single coherent bitpacker surface.
|
||||
|
||||
The five gaps:
|
||||
|
||||
1. **`bitpacker-delta`**: 35 L inline `DeltaManager` in `index.ts` violates the determinism rule (`Date.now()`, `JSON.stringify`); upstream has a 269 L `DeltaManager` + 149 L `DeltaStateManager` with field-level deltas, configurable XOR/string-diff/threshold modes, and a state-key-sharded LRU.
|
||||
2. **`bitpacker-network`**: 48 L stub in `index.ts` uses module-scope `Math.random()` and exposes only three free functions; upstream has a 598 L `NetworkSimulator` class supporting bandwidth limits, jitter, packet reordering, duplication, corruption, burstiness, random disconnects, and statistics — but the full implementation was misplaced into `bitpacker-game/src/network/network-simulation.ts` (622 L) instead of `bitpacker-network/`.
|
||||
3. **`bitpacker-compressed`**: 61 L of standalone RLE labelled "placeholder for a more sophisticated compression algorithm"; upstream has a 327 L `CompressedBitPacker<T> extends BitPacker<T>` class plus a 336 L `CompressionRegistry` with `NoCompressionAdapter`, `GzipCompressionAdapter`, and `DeflateCompressionAdapter`.
|
||||
4. **`bitpacker-streaming`**: 87 L of chunked helpers; the 406 L `StreamingBitPacker<T> extends EventEmitter` class with `packStream` / `unpackStream`, validation, progress events, and per-chunk processors is absent.
|
||||
5. **`bitpacker-schema`**: 50 L `SchemaRegistry` is a `Map` wrapper; upstream's 576 L file ships `SchemaRegistryStorage` interface, `InMemorySchemaRegistryStorage`, `LocalStorageSchemaRegistryStorage`, plus the real `SchemaRegistry` (versioned register / load / list / delete with Zod-validated metadata) and the `createInMemorySchemaRegistry` / `createLocalStorageSchemaRegistry` factories.
|
||||
|
||||
## What changes
|
||||
|
||||
### `@suaveplan/bitpacker-delta`
|
||||
|
||||
- ADDED: `DeltaStateManager` class — keys-sharded LRU of previous states, deep-cloned on store, evicted in insertion order; default capacity 100.
|
||||
- MODIFIED: `DeltaManager` class — replaces the 35 L inline class; takes a `DeltaConfig` (mode `none` / `xor` / `string-diff` / `field-level`, `xorNumbers`, `diffStrings`, `threshold`, `maxFieldCount`, `stateKey`); `detectChanges(current, schema, stateKey?)` returns `FieldChange[]` indexed by schema field order; takes an injectable `Clock` (no module-scope `Date.now()`); reports `JSON.stringify` only behind a feature flag, defaulting to a structural equality walker that respects `BitPackerSchema` field order.
|
||||
- ADDED: `FieldChange` interface — `{ index, key, previousValue, currentValue }`.
|
||||
- REMOVED: the 35 L inline `DeltaManager` from `bitpacker-delta/src/index.ts` (violates determinism rule).
|
||||
|
||||
### `@suaveplan/bitpacker-network`
|
||||
|
||||
- ADDED: `NetworkSimulator extends EventEmitter` class — bandwidth limit, latency baseline + jitter, packet loss probability, ordered/unordered delivery, duplication probability, corruption probability, burstiness scheduler, random-disconnect scheduler, `NetworkSimulationStats` collector.
|
||||
- ADDED: `NETWORK_CONDITIONS` preset map — `IDEAL`, `HOME`, `MOBILE_4G`, `MOBILE_3G`, `SATELLITE`, `LOSSY_WIFI`, `CUSTOM` (matches upstream constants).
|
||||
- ADDED: `createNetworkSimulator(options)` factory.
|
||||
- ADDED: `PacketStatus` enum and `PacketInfo` interface.
|
||||
- MODIFIED: every `Math.random()` call replaced with an injectable `Rng` (`@suaveplan/types::Rng`) supplied through options; default uses `crypto.getRandomValues` per CLAUDE.md determinism rule.
|
||||
- MODIFIED: every `Date.now()` call replaced with an injectable `Clock`.
|
||||
- REMOVED: the 48 L stub at `bitpacker-network/src/index.ts`.
|
||||
- REMOVED: the misplaced 622 L copy at `bitpacker-game/src/network/network-simulation.ts` (deleted; bitpacker-game re-exports from `@suaveplan/bitpacker-network` for back-compat in its public surface).
|
||||
|
||||
### `@suaveplan/bitpacker-compressed`
|
||||
|
||||
- ADDED: `CompressedBitPacker<T extends BitPackerSchema> extends BitPacker<T>` class — pack/unpack run through a configurable compression adapter; constructor honours `BitPackerConfig.compression` (`algorithm`, `options`, `enabled`); accessors `compressionAlgorithm`, `compressionOptions`, `compressionEnabled`.
|
||||
- ADDED: `CompressionAdapter` interface and `CompressionRegistry` class with built-in `NoCompressionAdapter`, `GzipCompressionAdapter`, `DeflateCompressionAdapter`.
|
||||
- ADDED: `compressData(data, algorithm, options)` and `decompressData(data, algorithm, options)` async helpers.
|
||||
- KEPT: the existing `compressRle` / `decompressRle` helpers for the bytes-only fast path (re-exported under `./rle`).
|
||||
- REMOVED: the "placeholder" disclaimer in the package README.
|
||||
|
||||
### `@suaveplan/bitpacker-streaming`
|
||||
|
||||
- ADDED: `StreamingBitPacker<T extends BitPackerSchema> extends EventEmitter` class — `packStream(data, options)` and `unpackStream(packed, options)`, both async, both with `chunkSize`, `validateChunks`, `emitProgress`, and an optional `chunkProcessor`; emits `progress` and `chunk` events.
|
||||
- KEPT: `packToChunks` / `unpackFromChunks` / `unpackFromChunksAsync` helpers (synchronous, allocation-friendly path); they delegate to `StreamingBitPacker` under the hood.
|
||||
- KEPT: `transform/*` framed Web-Streams transformers.
|
||||
- ADDED: `AbortSignal` propagation through `packStream` / `unpackStream` (existing helpers already honour it).
|
||||
|
||||
### `@suaveplan/bitpacker-schema`
|
||||
|
||||
- ADDED: `SchemaRegistryStorage` interface — `saveSchema`, `loadSchema(namespace, name, version?)`, `listSchemas(namespace?, tag?)`, `deleteSchema`.
|
||||
- ADDED: `InMemorySchemaRegistryStorage` class — in-memory implementation of the storage interface.
|
||||
- ADDED: `LocalStorageSchemaRegistryStorage` class — `localStorage`-backed implementation; constructor accepts a `storageKey` (default `bitpacker-schema-registry`).
|
||||
- MODIFIED: `SchemaRegistry` class — now takes a `SchemaRegistryStorage` (default `InMemorySchemaRegistryStorage`); adds `registerSchema`, `registerVersionedSchema`, `loadSchema`, `listSchemas`, `deleteSchema` async methods that apply auto-versioning (`existing.version + 1`) and stamp `createdAt` / `updatedAt` from an injectable `Clock`.
|
||||
- ADDED: `createInMemorySchemaRegistry()` factory.
|
||||
- ADDED: `createLocalStorageSchemaRegistry(storageKey?)` factory.
|
||||
- KEPT: legacy 50 L `Map`-based surface re-exported from `./legacy` for one release; deprecated.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Crypto subsystem** (provider, registry, noop / libsodium / elliptic adapters; ~1906 L) — tracked separately as `bitpacker-crypto-port`. That port is large enough on its own and depends on crypto-engine primitives that already exist in genesis under `packages/crypto/*`; folding it into this change would dilute review.
|
||||
- **`appendAdler32` / `verifyAdler32` helper wrappers** — polish only, not stub completion. Will land with §14.7 in `bitpacker-modular-restructure/tasks.md`.
|
||||
- **⚠️-marked partials audit** (xor-encoder 34%, string-diff 50%, interpolation 60%, transform 67%) — covered by §14.8; each requires a per-file diff that this change does not undertake.
|
||||
- **`openspec/specs/bitpacker/spec.md`** (the canonical core spec) — drafting it from scratch is its own change; this proposal only adds capability specs for the five caps it touches.
|
||||
- **Replacing `BitPacker` simplification** (1203 L upstream → 178 L genesis) — the simplification is documented in `bitpacker-modular-restructure/design.md` Decision 2 and is intentional. Restoring upstream's `EventEmitter` / built-in delta / built-in checksum on `BitPacker` would re-couple plugins back into core.
|
||||
|
||||
## Source mapping
|
||||
|
||||
Genesis `enterprise/bitpacker.md` is a generic enterprise-quality template (no per-feature requirements), so source mapping cites the upstream package source directly.
|
||||
|
||||
| Requirement | Source file | Source lines | Notes |
|
||||
|---|---|---|---|
|
||||
| Field-level `DeltaManager` with `DeltaConfig` | `packages/universal/bitpacker/src/core/delta/delta-manager.ts` | L1-L269 | Verbatim port; replace `Date.now()` / `JSON.stringify` per CLAUDE.md determinism rule |
|
||||
| `DeltaStateManager` with LRU eviction | `packages/universal/bitpacker/src/core/delta/delta-state.ts` | L1-L149 | Verbatim port; default cap 100 |
|
||||
| `FieldChange` interface | `packages/universal/bitpacker/src/core/delta/delta-manager.ts` | L5-L10 | Verbatim |
|
||||
| `NetworkSimulator extends EventEmitter` | `packages/universal/bitpacker/src/network/network-simulation.ts` | L15-L593 | Move into `bitpacker-network`; replace module-scope `Math.random()` with injectable `Rng` |
|
||||
| `createNetworkSimulator` factory | `packages/universal/bitpacker/src/network/network-simulation.ts` | L594-L598 | Verbatim |
|
||||
| `NETWORK_CONDITIONS` presets, `PacketStatus`, `PacketInfo`, `NetworkSimulationOptions`, `NetworkSimulationStats` | `packages/universal/bitpacker/src/types.ts` | (re-export) | Move type companions alongside the simulator |
|
||||
| `CompressedBitPacker<T> extends BitPacker<T>` | `packages/universal/bitpacker/src/compressed/bitpacker.ts` | L1-L327 | Verbatim |
|
||||
| `CompressionAdapter` + `CompressionRegistry` + built-in `NoCompressionAdapter` / `GzipCompressionAdapter` / `DeflateCompressionAdapter` | `packages/universal/bitpacker/src/compressed/compression.ts` | L1-L336 | Verbatim |
|
||||
| `compressData` / `decompressData` async helpers | `packages/universal/bitpacker/src/compressed/compression.ts` | L75-L120 | Verbatim |
|
||||
| `StreamingBitPacker<T> extends EventEmitter` | `packages/universal/bitpacker/src/streaming/streaming.ts` | L1-L406 | Verbatim; thread `AbortSignal` through |
|
||||
| `SchemaRegistryStorage` interface | `packages/universal/bitpacker/src/types.ts` | (interface re-host) | Co-locate with implementations |
|
||||
| `InMemorySchemaRegistryStorage` | `packages/universal/bitpacker/src/schema/registry/registry.ts` | L13-L141 | Verbatim |
|
||||
| `LocalStorageSchemaRegistryStorage` | `packages/universal/bitpacker/src/schema/registry/registry.ts` | L146-L368 | Verbatim |
|
||||
| `SchemaRegistry` (versioned register / load / list / delete) | `packages/universal/bitpacker/src/schema/registry/registry.ts` | L372-L554 | Replace `new Date()` with injectable `Clock` |
|
||||
| `createInMemorySchemaRegistry` / `createLocalStorageSchemaRegistry` | `packages/universal/bitpacker/src/schema/registry/registry.ts` | L560-L576 | Verbatim |
|
||||
|
||||
### Requirements with no source line
|
||||
|
||||
| Requirement | Justification |
|
||||
|---|---|
|
||||
| Drop `Math.random()` / `Date.now()` in favour of injectable `Rng` / `Clock` | Genesis-wide CLAUDE.md determinism rule (rule 9: "NEVER write `// biome-ignore` …" and enterprise/bitpacker.md L40 "Deterministic behaviour — no `Math.random()` / `Date.now()` at module scope"). Not in upstream. |
|
||||
| `BitpackerError` codes registered for compression-failed, decompression-failed, unknown-compression-algorithm, schema-conflict, schema-not-found, network-stat-overflow | Genesis-wide policy that every thrown error route through `@suaveplan/error` (CLAUDE.md §Workflow rule 2). Upstream throws plain `Error`. |
|
||||
| `bitpacker-game` re-export shim for `@suaveplan/bitpacker-network` | Back-compat fence so consumers currently importing `network-simulation` from `bitpacker-game` keep compiling for one release. |
|
||||
|
||||
## Verification approach
|
||||
|
||||
Each spec delta requirement carries a `**Verified by:**` line pointing at concrete `it(...)` titles. The test files that will exist after this change ships:
|
||||
|
||||
- `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.test.ts` — field-level delta scenarios, state-key sharding, eviction
|
||||
- `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts` — `DeltaConfig` strict-mode, deterministic clock, error-code emission
|
||||
- `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts` — bandwidth, jitter, loss, duplication, corruption, burstiness, disconnect
|
||||
- `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts` — injected `Rng` / `Clock` used; statistics; preset bindings
|
||||
- `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts` — round-trip per algorithm
|
||||
- `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts` — adapter registration / replacement
|
||||
- `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts` — `packStream` / `unpackStream` round-trip, progress events, abort
|
||||
- `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.spec-conformance.test.ts` — chunk processor invocation, validation gating
|
||||
- `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts` — versioned register, load latest, list by tag, delete by version
|
||||
- `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts` — `SchemaRegistryStorage` contract pinned for both adapters; injected `Clock` stamps `createdAt` / `updatedAt`
|
||||
- `tests/bitpacker-game-network-shim.test.ts` (cross-package) — pins the back-compat re-export so removal of the shim is a deliberate spec change
|
||||
|
||||
## Migration impact
|
||||
|
||||
Consumers grepping for current bitpacker stubs:
|
||||
|
||||
- **`@suaveplan/bitpacker-delta`** — only the inline `DeltaManager` in `index.ts` is exported. The rewrite preserves that name (`DeltaManager`), so existing imports compile; the constructor signature changes from `()` to `(config?: DeltaConfig, options?: { clock?: Clock })` — call sites that did `new DeltaManager()` continue to work.
|
||||
- **`@suaveplan/bitpacker-network`** — only `simulateLatency`, `shouldDropPacket`, `applyNetworkConditions` are currently exported. They are kept (now backed by the full simulator) and a new class export is added. Existing call sites compile.
|
||||
- **`@suaveplan/bitpacker-compressed`** — only `compressRle` / `decompressRle` are currently exported. Kept; moved to `./rle` subpath but also re-exported from root for one release.
|
||||
- **`@suaveplan/bitpacker-streaming`** — `packToChunks` / `unpackFromChunks` / `unpackFromChunksAsync` and the `transform/*` exports are kept; `StreamingBitPacker` is a new class.
|
||||
- **`@suaveplan/bitpacker-schema`** — current `SchemaRegistry` `Map` API (`register`, `resolve`, `unregister`, `has`, `names`, `clear`) is preserved on a `LegacySchemaRegistry` alias re-exported from `./legacy`; the canonical name is replaced with the storage-backed version. One-release deprecation window.
|
||||
- **`@suaveplan/bitpacker-game`** — drops the misplaced `network/network-simulation.ts`; ships a thin re-export of `NetworkSimulator` from `@suaveplan/bitpacker-network` to keep the public surface stable for one release. Any downstream that imported `network-simulation.ts` directly via a deep path needs to switch to the package surface — flagged as a BREAKING **deep-path** change in `proposal.md`.
|
||||
|
||||
A `bunx turbo build typecheck test --filter=...@suaveplan/bitpacker-*` sweep runs in §6.5 of `tasks.md` to verify all in-repo dependents compile and test green before archive.
|
||||
@@ -0,0 +1,105 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# 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<T extends BitPackerSchema> extends BitPacker<T>`. 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<Uint8Array>` and `decompress(data, options): Promise<Uint8Array>`. 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"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
@@ -0,0 +1,105 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: bitpacker-delta (bitpacker-stub-completion)
|
||||
|
||||
**Package**: `@suaveplan/bitpacker-delta`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Field-level change detection
|
||||
|
||||
The package SHALL expose a `DeltaManager` class that, given a current object, a `BitPackerSchema` describing it, and a previous state retrieved by state key, returns the ordered list of fields whose values changed since that previous state. Field order in the returned `FieldChange[]` SHALL match `Object.keys(schema)` order so encoders can rely on field indexes.
|
||||
|
||||
#### Scenario: first call returns empty changes
|
||||
|
||||
- **WHEN** `detectChanges` is called for a state key with no stored previous state
|
||||
- **THEN** the result is an empty array AND the current state is stored under that key
|
||||
|
||||
#### Scenario: changed fields surface as ordered FieldChange[]
|
||||
|
||||
- **WHEN** the previous state had `{ a: 1, b: "x" }` and the current state is `{ a: 1, b: "y" }` under schema `{ a: "u8", b: "-32" }`
|
||||
- **THEN** the result is `[{ index: 1, key: "b", previousValue: "x", currentValue: "y" }]`
|
||||
|
||||
#### Scenario: maxFieldCount cap enforced
|
||||
|
||||
- **WHEN** the schema has more entries than `DeltaConfig.maxFieldCount`
|
||||
- **THEN** the manager throws `BitpackerError` with code `BITPACKER.DELTA_FIELD_LIMIT_EXCEEDED` and the offending field count in `context.fieldCount`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.test.ts::"detects changed fields in schema order"` AND `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.test.ts::"returns empty array on first call for a state key"` AND `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.test.ts::"throws DELTA_FIELD_LIMIT_EXCEEDED when schema exceeds maxFieldCount"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Configurable delta modes via DeltaConfig
|
||||
|
||||
The package SHALL accept a `DeltaConfig` object (`mode`, `xorNumbers`, `diffStrings`, `threshold`, `maxFieldCount`, `stateKey`, `equalityMode`) validated by Zod. Unknown fields and invalid `mode` values SHALL be rejected at construction time with `BitpackerError` code `BITPACKER.DELTA_INVALID_CONFIG`.
|
||||
|
||||
#### Scenario: unknown mode rejected
|
||||
|
||||
- **WHEN** `new DeltaManager({ mode: "magic" } as never)` is constructed
|
||||
- **THEN** it throws `BitpackerError` with code `BITPACKER.DELTA_INVALID_CONFIG` and `context.mode === "magic"`
|
||||
|
||||
#### Scenario: `equalityMode: "json"` opts into stringify-based equality
|
||||
|
||||
- **WHEN** `equalityMode` is `"json"`
|
||||
- **THEN** the manager uses `JSON.stringify` for equality (parity with upstream behavior)
|
||||
|
||||
#### Scenario: structural equality is the default
|
||||
|
||||
- **WHEN** `equalityMode` is omitted
|
||||
- **THEN** equality is computed by walking the schema tree, not by `JSON.stringify`, so key-order differences in nested objects do not register as changes
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts::"rejects invalid DeltaConfig with DELTA_INVALID_CONFIG"` AND `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts::"uses structural equality by default"` AND `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts::"opts into JSON equality when equalityMode is 'json'"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: State storage with LRU eviction
|
||||
|
||||
The package SHALL expose a `DeltaStateManager` class that stores previous states keyed by string, deep-clones on store to prevent caller mutation, and evicts in insertion order when the configured `maxStates` (default 100) is exceeded.
|
||||
|
||||
#### Scenario: stored state is isolated from mutation
|
||||
|
||||
- **WHEN** a caller stores a state, then mutates the original object
|
||||
- **THEN** `getPreviousState` returns the unmutated snapshot
|
||||
|
||||
#### Scenario: oldest state evicted at capacity
|
||||
|
||||
- **WHEN** `maxStates` is 3 and a 4th key is stored
|
||||
- **THEN** the first key is evicted; `hasPreviousState(firstKey)` returns false
|
||||
|
||||
#### Scenario: re-store under existing key does not advance LRU position
|
||||
|
||||
- **WHEN** a key is stored, then re-stored after intervening writes
|
||||
- **THEN** the key remains at its original LRU position (re-store updates value, not order — matches upstream behavior in `delta-state.ts` L22-L37)
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-delta/src/delta-state/delta-state.test.ts::"deep-clones stored states to isolate from caller mutation"` AND `packages/bitpacker/bitpacker-delta/src/delta-state/delta-state.test.ts::"evicts oldest state when maxStates exceeded"` AND `packages/bitpacker/bitpacker-delta/src/delta-state/delta-state.test.ts::"re-store under existing key preserves LRU position"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Deterministic clock injection
|
||||
|
||||
The package SHALL accept an optional `Clock` from `@suaveplan/types`; all timestamps recorded internally (e.g. for OTel histograms) SHALL come from the injected clock. The package SHALL NOT call `Date.now()` at module scope or inside hot paths.
|
||||
|
||||
#### Scenario: injected clock observed in OTel histogram
|
||||
|
||||
- **WHEN** a fake `Clock` advancing 7ms per `now()` call is injected and `detectChanges` is called twice
|
||||
- **THEN** the OTel histogram `bitpacker.delta.changes_detected` records two samples whose timestamps differ by exactly the fake clock's delta
|
||||
|
||||
#### Scenario: no module-scope `Date.now()`
|
||||
|
||||
- **WHEN** `grep -r "Date\.now()" packages/bitpacker/bitpacker-delta/src/` is run
|
||||
- **THEN** there are zero matches in implementation files
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts::"records OTel timestamps from injected Clock"` AND `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts::"no module-scope Date.now in implementation files"`
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Inline 35-line DeltaManager in `index.ts`
|
||||
|
||||
**Reason:** Stub. Used `Date.now()` and `JSON.stringify` (CLAUDE.md determinism rule violation), tracked no field-level diffs, and held only one global previous state. Replaced wholesale by the `DeltaManager` + `DeltaStateManager` pair above.
|
||||
|
||||
**Migration:** Existing call sites doing `new DeltaManager()` continue to work — the new class accepts an optional config and clock. Call sites that read `manager.getPreviousState()` (no arg) should switch to `manager.getPreviousState(stateKey)`; the default `stateKey` is `"default"`.
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
@@ -0,0 +1,110 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: bitpacker-network (bitpacker-stub-completion)
|
||||
|
||||
**Package**: `@suaveplan/bitpacker-network`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: NetworkSimulator class with full upstream feature surface
|
||||
|
||||
The package SHALL expose a `NetworkSimulator extends EventEmitter` class implementing bandwidth-limited delivery, configurable latency baseline + jitter, packet loss probability, packet duplication probability, packet corruption probability, in-flight reordering, burstiness scheduling, and random-disconnect scheduling. Public API matches upstream `network-simulation.ts` (L15-L593): `send(packet)`, `start()`, `stop()`, `disconnect(durationMs)`, `reconnect()`, `getStats()`, `clearStats()`, `setOptions(partial)`.
|
||||
|
||||
#### Scenario: bandwidth limit honoured across a 1-second window
|
||||
|
||||
- **WHEN** `bandwidthBytesPerSecond` is 1000 and 5000 bytes are submitted in 100ms
|
||||
- **THEN** the simulator emits exactly 1000 bytes' worth of `delivered` events in the first second; the remaining 4000 bytes queue and deliver across the next 4 seconds
|
||||
|
||||
#### Scenario: latency + jitter inside configured bounds
|
||||
|
||||
- **WHEN** `latencyMs` is 50 and `jitterMs` is 10
|
||||
- **THEN** every delivered packet's measured delivery delay is within `[40, 60]` ms
|
||||
|
||||
#### Scenario: packet-loss probability hits empirical bucket
|
||||
|
||||
- **WHEN** `lossProbability` is 0.25 and 1000 packets are sent through a deterministic `Rng`
|
||||
- **THEN** the recorded `stats.packetsLost` falls within `[230, 270]` (25% ± 2.5 σ for n=1000)
|
||||
|
||||
#### Scenario: duplication, corruption, reordering surface as events
|
||||
|
||||
- **WHEN** the corresponding probabilities are non-zero
|
||||
- **THEN** `duplicate`, `corrupt`, and `reorder` events fire with the affected `PacketInfo` payload AND `stats.packetsDuplicated` / `stats.packetsCorrupted` / `stats.packetsReordered` increment accordingly
|
||||
|
||||
#### Scenario: burstiness scheduler activates burst then ends it
|
||||
|
||||
- **WHEN** `enableBurstiness: true` with `burstIntervalMs: 100`, `burstDurationMs: 50`, `burstLossMultiplier: 4`
|
||||
- **THEN** during the burst window, effective `lossProbability` is 4× the configured value, and after `burstDurationMs` it returns to baseline
|
||||
|
||||
#### Scenario: disconnect / reconnect lifecycle
|
||||
|
||||
- **WHEN** `disconnect(200)` is called
|
||||
- **THEN** `connected` becomes false, all pending sends emit `dropped`, and after 200ms `connected` becomes true again with a `reconnected` event
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"honours bandwidth limit across a 1-second window"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"applies latency + jitter inside configured bounds"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"loss probability hits empirical bucket over 1000 packets"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"emits duplicate / corrupt / reorder events with PacketInfo"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"burstiness scheduler activates and deactivates the loss multiplier"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts::"disconnect drops in-flight, reconnect restores delivery"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: NETWORK_CONDITIONS preset bindings
|
||||
|
||||
The package SHALL export a `NETWORK_CONDITIONS` map binding each of `IDEAL`, `HOME`, `MOBILE_4G`, `MOBILE_3G`, `SATELLITE`, `LOSSY_WIFI`, `CUSTOM` to a `NetworkSimulationOptions` object. The values SHALL match upstream constants byte-for-byte (cited in `proposal.md` source mapping).
|
||||
|
||||
#### Scenario: preset selection populates options
|
||||
|
||||
- **WHEN** a `NetworkSimulator` is constructed with `{ condition: "MOBILE_3G" }`
|
||||
- **THEN** the resulting options match `NETWORK_CONDITIONS.MOBILE_3G` for `latencyMs`, `jitterMs`, `bandwidthBytesPerSecond`, `lossProbability`
|
||||
|
||||
#### Scenario: explicit fields override preset
|
||||
|
||||
- **WHEN** `{ condition: "MOBILE_3G", lossProbability: 0 }` is passed
|
||||
- **THEN** the resulting options take `lossProbability: 0` and inherit the rest from the preset
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"NETWORK_CONDITIONS presets bind to options as documented"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"explicit options override preset values"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Deterministic Rng and Clock injection
|
||||
|
||||
The package SHALL accept an optional `Rng` and `Clock` from `@suaveplan/types`. Default `Rng` is the `crypto.getRandomValues`-backed `Rng.crypto()`. The package SHALL NOT call `Math.random()` or `Date.now()` at module scope or in production code paths.
|
||||
|
||||
#### Scenario: deterministic Rng yields reproducible loss sequence
|
||||
|
||||
- **WHEN** a seeded `Rng` is injected and the same 100-packet input is replayed
|
||||
- **THEN** the set of dropped packet indexes is byte-identical between runs
|
||||
|
||||
#### Scenario: deterministic Clock advances burst windows
|
||||
|
||||
- **WHEN** a fake clock is injected and ticked manually
|
||||
- **THEN** burst start / end fire at exactly the configured offsets
|
||||
|
||||
#### Scenario: no module-scope `Math.random()` or `Date.now()`
|
||||
|
||||
- **WHEN** `grep -E "Math\.random\(\)|Date\.now\(\)" packages/bitpacker/bitpacker-network/src/` is run
|
||||
- **THEN** there are zero matches in implementation files
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"seeded Rng yields reproducible loss sequence"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"fake Clock advances burst windows deterministically"` AND `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"no module-scope Math.random or Date.now in implementation files"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Stat counters bounded; overflow signalled
|
||||
|
||||
The simulator SHALL track `packetsSent`, `packetsReceived`, `packetsLost`, `packetsDuplicated`, `packetsCorrupted`, `packetsReordered`, `bytesSent`, `bytesReceived`. When any counter would exceed `Number.MAX_SAFE_INTEGER`, the simulator SHALL emit `BitpackerError` with code `BITPACKER.NETWORK_STAT_OVERFLOW` and `context.counter` naming the affected field; the counter SHALL freeze at `MAX_SAFE_INTEGER`.
|
||||
|
||||
#### Scenario: stat overflow throws coded error
|
||||
|
||||
- **WHEN** `packetsSent` is one below `MAX_SAFE_INTEGER` and one more packet is sent
|
||||
- **THEN** a `BitpackerError` with code `BITPACKER.NETWORK_STAT_OVERFLOW` is emitted as a `stat-overflow` event AND `stats.packetsSent === Number.MAX_SAFE_INTEGER`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts::"emits NETWORK_STAT_OVERFLOW when a counter would exceed MAX_SAFE_INTEGER"`
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: 48-line `Math.random()`-based stub at `bitpacker-network/src/index.ts`
|
||||
|
||||
**Reason:** Stub used module-scope `Math.random()` (CLAUDE.md determinism rule violation) and exposed only three free functions; the real 622-line implementation lived (misplaced) at `bitpacker-game/src/network/network-simulation.ts`.
|
||||
|
||||
**Migration:** The three existing free functions (`simulateLatency`, `shouldDropPacket`, `applyNetworkConditions`) are preserved as thin wrappers around the new simulator. Existing call sites compile unchanged. The misplaced copy at `bitpacker-game/src/network/network-simulation.ts` is deleted; a one-release shim re-exports the simulator from `@suaveplan/bitpacker-network` so any consumer importing it via the game package's deep path keeps compiling.
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
@@ -0,0 +1,118 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: bitpacker-schema (bitpacker-stub-completion)
|
||||
|
||||
**Package**: `@suaveplan/bitpacker-schema`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: SchemaRegistryStorage interface
|
||||
|
||||
The package SHALL expose a `SchemaRegistryStorage` interface with the four async methods `saveSchema(entry)`, `loadSchema(namespace, name, version?)`, `listSchemas(namespace?, tag?)`, `deleteSchema(namespace, name, version?)`. The interface SHALL be the contract that every storage adapter implements; both adapters shipped in this change SHALL pin to the same `spec-conformance` test suite.
|
||||
|
||||
#### Scenario: contract pinned across adapters
|
||||
|
||||
- **WHEN** the same `spec-conformance` suite is run against `InMemorySchemaRegistryStorage` and `LocalStorageSchemaRegistryStorage`
|
||||
- **THEN** every assertion passes for both
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"InMemorySchemaRegistryStorage satisfies SchemaRegistryStorage contract"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"LocalStorageSchemaRegistryStorage satisfies SchemaRegistryStorage contract"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: InMemorySchemaRegistryStorage adapter
|
||||
|
||||
The package SHALL ship `InMemorySchemaRegistryStorage` matching upstream `registry.ts` L13-L141: stores entries in a process-local array, treats `(namespace, name, version)` as the unique key, and updates in place when re-saving an existing triple. `loadSchema` without `version` SHALL return the entry whose `metadata.version` is highest. `listSchemas` SHALL filter by `namespace` AND `tag` when both are given. `deleteSchema` without `version` SHALL drop every entry matching `(namespace, name)`.
|
||||
|
||||
#### Scenario: re-save under existing triple updates in place
|
||||
|
||||
- **WHEN** an entry is saved twice with the same `(namespace, name, version)` and different schemas
|
||||
- **THEN** `listSchemas()` returns one entry whose schema is the second value
|
||||
|
||||
#### Scenario: loadSchema without version returns highest version
|
||||
|
||||
- **WHEN** versions 1, 3, and 2 of `(ns: "game", name: "player")` exist
|
||||
- **THEN** `loadSchema("game", "player")` returns version 3
|
||||
|
||||
#### Scenario: deleteSchema without version drops all versions
|
||||
|
||||
- **WHEN** `deleteSchema("game", "player")` is called with no version
|
||||
- **THEN** every entry matching that namespace+name is removed; entries for other names remain
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"in-memory adapter: re-save under existing triple updates in place"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"in-memory adapter: loadSchema without version returns highest version"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"in-memory adapter: deleteSchema without version drops all versions"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: LocalStorageSchemaRegistryStorage adapter
|
||||
|
||||
The package SHALL ship `LocalStorageSchemaRegistryStorage` matching upstream `registry.ts` L146-L368. The constructor SHALL accept an optional `storageKey` (default `"bitpacker-schema-registry"`). The adapter SHALL serialize entries to JSON in `localStorage[storageKey]`; SHALL roundtrip `Date` fields (`createdAt` / `updatedAt`); SHALL be safe to construct in environments where `localStorage` is undefined (in that case all methods reject with `BitpackerError` code `BITPACKER.SCHEMA_STORAGE_UNAVAILABLE`).
|
||||
|
||||
#### Scenario: round-trip through localStorage preserves Date fields
|
||||
|
||||
- **WHEN** an entry with `metadata.createdAt: new Date("2026-01-01")` is saved, the adapter is recreated, and the entry is reloaded
|
||||
- **THEN** the reloaded `createdAt` is a `Date` instance equal to the original
|
||||
|
||||
#### Scenario: missing localStorage emits coded error
|
||||
|
||||
- **WHEN** the adapter is constructed in a Node environment without `localStorage` and `saveSchema` is called
|
||||
- **THEN** the call rejects with `BitpackerError` code `BITPACKER.SCHEMA_STORAGE_UNAVAILABLE`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"localStorage adapter: round-trip preserves Date fields"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"localStorage adapter rejects with SCHEMA_STORAGE_UNAVAILABLE when localStorage is undefined"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Storage-backed SchemaRegistry
|
||||
|
||||
The package SHALL replace the legacy `Map`-based `SchemaRegistry` with a storage-backed class matching upstream `registry.ts` L372-L554. The class SHALL accept a `SchemaRegistryStorage` (default `InMemorySchemaRegistryStorage`) and expose async `registerSchema(schema, metadata)`, `registerVersionedSchema(versionedSchema, metadata)`, `loadSchema(namespace, name, version?)`, `listSchemas(namespace?, tag?)`, `deleteSchema(namespace, name, version?)`. `registerSchema` SHALL auto-increment version: existing entries return `existing.metadata.version + 1`; new entries start at 1. `createdAt` / `updatedAt` SHALL come from an injectable `Clock` (`@suaveplan/types::Clock`).
|
||||
|
||||
#### Scenario: registerSchema auto-increments version
|
||||
|
||||
- **WHEN** `registerSchema(schema, { namespace: "n", name: "x", description: "..." })` is called twice
|
||||
- **THEN** the first returned entry has `metadata.version === 1`; the second has `metadata.version === 2`
|
||||
|
||||
#### Scenario: createdAt / updatedAt come from injected clock
|
||||
|
||||
- **WHEN** a `Clock` returning a fixed instant `T0` is injected and a schema is registered
|
||||
- **THEN** the returned entry has `metadata.createdAt === T0` and `metadata.updatedAt === T0`
|
||||
|
||||
#### Scenario: missing schema emits SCHEMA_NOT_FOUND
|
||||
|
||||
- **WHEN** `loadSchema("ns", "missing", 5)` is called and no such entry exists
|
||||
- **THEN** the call rejects with `BitpackerError` code `BITPACKER.SCHEMA_NOT_FOUND` with `context.namespace`, `context.name`, `context.version`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"registerSchema auto-increments version"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"createdAt and updatedAt come from injected Clock"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts::"loadSchema rejects with SCHEMA_NOT_FOUND for unknown entry"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: Factory helpers
|
||||
|
||||
The package SHALL export `createInMemorySchemaRegistry()` and `createLocalStorageSchemaRegistry(storageKey?)` matching upstream `registry.ts` L560-L576. Both SHALL accept an optional `clock` for deterministic timestamping in tests.
|
||||
|
||||
#### Scenario: createInMemorySchemaRegistry returns a usable registry
|
||||
|
||||
- **WHEN** `createInMemorySchemaRegistry()` is called and `registerSchema` is invoked
|
||||
- **THEN** the entry persists for the lifetime of the registry instance
|
||||
|
||||
#### Scenario: createLocalStorageSchemaRegistry honours custom storageKey
|
||||
|
||||
- **WHEN** `createLocalStorageSchemaRegistry("my-app")` is called and a schema is registered
|
||||
- **THEN** `localStorage["my-app"]` contains the serialized entry
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"createInMemorySchemaRegistry returns a working registry"` AND `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts::"createLocalStorageSchemaRegistry honours custom storageKey"`
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Legacy Map-based registry preserved as deprecated alias
|
||||
|
||||
The legacy 50-line `Map`-based `SchemaRegistry` SHALL be moved to `bitpacker-schema/src/legacy/legacy-registry.ts` and re-exported as `LegacySchemaRegistry` with a `@deprecated` JSDoc tag. The deprecation note SHALL specify removal in the next breaking release of `@suaveplan/bitpacker-schema`. Existing call sites importing it directly via `@suaveplan/bitpacker-schema/legacy` SHALL keep compiling.
|
||||
|
||||
#### Scenario: legacy alias retained for one release
|
||||
|
||||
- **WHEN** a consumer imports `{ LegacySchemaRegistry } from "@suaveplan/bitpacker-schema/legacy"`
|
||||
- **THEN** the import resolves and the class behaves as the original 50-line `Map` wrapper (`register`, `resolve`, `unregister`, `has`, `names`, `clear`)
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-schema/src/legacy/legacy-registry.test.ts::"LegacySchemaRegistry behaves as original Map wrapper"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
@@ -0,0 +1,100 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: bitpacker-streaming (bitpacker-stub-completion)
|
||||
|
||||
**Package**: `@suaveplan/bitpacker-streaming`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> Every requirement below carries a `**Verified by:**` line at authoring time.
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: StreamingBitPacker class
|
||||
|
||||
The package SHALL export `StreamingBitPacker<T extends BitPackerSchema> extends EventEmitter` matching upstream `streaming.ts` (L14-L406). The class SHALL expose `packStream(data, options)` and `unpackStream(packed, options)`, both returning `Promise<Uint8Array>` (pack) / `Promise<SchemaTypeToDataType<T>>` (unpack). Both methods SHALL emit `progress` events as chunks complete and `chunk` events with the bytes of each completed chunk; chunk size SHALL come from `options.chunkSize` (default 1024).
|
||||
|
||||
#### Scenario: packStream round-trips through unpackStream
|
||||
|
||||
- **WHEN** a 100 KiB object is `packStream`-ed and the result is `unpackStream`-ed under the same schema
|
||||
- **THEN** the unpacked value deep-equals the input
|
||||
|
||||
#### Scenario: progress events fire in chunk order
|
||||
|
||||
- **WHEN** `packStream` is called on data that produces 8 chunks
|
||||
- **THEN** 8 `progress` events fire with `{ index, total: 8, percent }` in monotonically increasing order; the final event has `percent === 1`
|
||||
|
||||
#### Scenario: chunk events carry chunk bytes
|
||||
|
||||
- **WHEN** `packStream` runs with `emitProgress: true`
|
||||
- **THEN** each `chunk` event's payload is a `Uint8Array` whose concatenation equals the final return value byte-for-byte
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"packStream / unpackStream round-trip preserves the value"` AND `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"emits progress events in monotonic chunk order"` AND `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"chunk events concatenate to the final byte output"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: chunkProcessor invoked per chunk
|
||||
|
||||
If `options.chunkProcessor` is supplied, the streaming bit-packer SHALL invoke it on each chunk before emitting / returning, awaiting the result. If the processor throws, `packStream` / `unpackStream` SHALL throw `BitpackerError` with code `BITPACKER.STREAM_CHUNK_PROCESSOR_FAILED`, chaining the original error.
|
||||
|
||||
#### Scenario: chunkProcessor mutates chunks before delivery
|
||||
|
||||
- **WHEN** `chunkProcessor` is `(chunk) => xor(chunk, 0xff)` and the produced bytes are XOR'd back before unpack
|
||||
- **THEN** the round-trip succeeds
|
||||
|
||||
#### Scenario: chunkProcessor failure surfaces as coded error
|
||||
|
||||
- **WHEN** `chunkProcessor` throws on chunk 3
|
||||
- **THEN** `packStream` rejects with `BitpackerError` code `BITPACKER.STREAM_CHUNK_PROCESSOR_FAILED` whose `cause` is the original error and `context.chunkIndex === 3`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.spec-conformance.test.ts::"chunkProcessor mutates chunks before delivery"` AND `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.spec-conformance.test.ts::"chunkProcessor failure surfaces as STREAM_CHUNK_PROCESSOR_FAILED"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: validateChunks gates pack/unpack on validation
|
||||
|
||||
When `options.validateChunks` is `true`, the streaming bit-packer SHALL run the underlying `BitPacker.validate` over the input (pack) or unpacked output (unpack) and reject before emitting if validation fails, throwing `BitpackerError` with code `BITPACKER.STREAM_VALIDATION_FAILED`.
|
||||
|
||||
#### Scenario: validation gate rejects malformed input
|
||||
|
||||
- **WHEN** `validateChunks: true` and the input violates the schema (e.g. value out of `u8` range)
|
||||
- **THEN** `packStream` rejects with `BitpackerError` code `BITPACKER.STREAM_VALIDATION_FAILED` AND no `chunk` events fire
|
||||
|
||||
#### Scenario: validation gate disabled by default
|
||||
|
||||
- **WHEN** `validateChunks` is omitted
|
||||
- **THEN** packing proceeds without validation; encoder errors surface natively
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.spec-conformance.test.ts::"validateChunks: true rejects malformed input with STREAM_VALIDATION_FAILED"` AND `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.spec-conformance.test.ts::"validateChunks default is false"`
|
||||
|
||||
---
|
||||
|
||||
### Requirement: AbortSignal propagation
|
||||
|
||||
`packStream` and `unpackStream` SHALL accept `options.signal: AbortSignal`. When the signal is already aborted at call time, the call SHALL reject immediately with `BitpackerError` code `BITPACKER.STREAM_ABORTED`. When abort fires mid-stream, the in-flight chunk SHALL complete and then the call SHALL reject with the same code; no subsequent chunks SHALL be processed or emitted.
|
||||
|
||||
#### Scenario: pre-aborted signal rejects immediately
|
||||
|
||||
- **WHEN** `signal.aborted === true` at call time
|
||||
- **THEN** `packStream` rejects synchronously with `BitpackerError` code `BITPACKER.STREAM_ABORTED`
|
||||
|
||||
#### Scenario: mid-stream abort halts further chunks
|
||||
|
||||
- **WHEN** `abort()` fires after the 2nd of 8 chunks completes
|
||||
- **THEN** chunks 3..8 do NOT emit `chunk` events and the promise rejects with `BITPACKER.STREAM_ABORTED`
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"pre-aborted signal rejects immediately with STREAM_ABORTED"` AND `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"mid-stream abort halts further chunk emission"`
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Synchronous chunked helpers delegate to StreamingBitPacker
|
||||
|
||||
The existing `packToChunks`, `unpackFromChunks`, and `unpackFromChunksAsync` functions SHALL be preserved with their current signatures, but their implementation SHALL delegate to `StreamingBitPacker` so behavior stays synchronized. The async helper SHALL honour `AbortSignal` (already true today; pin behavior in test).
|
||||
|
||||
#### Scenario: packToChunks output equals StreamingBitPacker.packStream concatenation
|
||||
|
||||
- **WHEN** the same data + schema is run through `packToChunks` and through `packStream` and the chunks of each are concatenated
|
||||
- **THEN** the byte-arrays are equal
|
||||
|
||||
**Verified by:** `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"packToChunks delegates to packStream"` AND `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts::"unpackFromChunksAsync honours AbortSignal"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
121
openspec/archive/2026-05-02-bitpacker-stub-completion/tasks.md
Normal file
121
openspec/archive/2026-05-02-bitpacker-stub-completion/tasks.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Tasks — Bitpacker stub completion
|
||||
|
||||
> Every checkbox MUST reflect work actually done in this change. Do not retroactively tick. The five gaps closed here were prematurely ticked in `bitpacker-modular-restructure/tasks.md`; that's the failure mode this checklist guards against.
|
||||
|
||||
## 1. Spec authoring
|
||||
|
||||
- [x] 1.1 Re-read `bitpacker-modular-restructure/proposal.md` parity table (the "last verified 2026-05-02" version) so the gap definitions stay aligned
|
||||
- [x] 1.2 Read `openspec/AGENTS.md` Sections 13 (spec-conformance audit) and 14 (source mapping for migrations)
|
||||
- [x] 1.3 Draft `proposal.md` covering all five caps, each with explicit ADDED/MODIFIED/REMOVED lists
|
||||
- [x] 1.4 Fill `## Source mapping` in `proposal.md` with upstream line ranges; every "no source line" requirement justified
|
||||
- [x] 1.5 Draft spec deltas under `specs/<cap>/spec.md` for `bitpacker-delta`, `bitpacker-network`, `bitpacker-compressed`, `bitpacker-streaming`, `bitpacker-schema`
|
||||
- [x] 1.6 Every requirement in every spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles
|
||||
- [x] 1.7 `openspec change validate bitpacker-stub-completion` exits with no errors
|
||||
|
||||
## 2. `@suaveplan/bitpacker-delta`
|
||||
|
||||
- [x] 2.1 Port `DeltaStateManager` from upstream `src/core/delta/delta-state.ts` into `packages/bitpacker/bitpacker-delta/src/delta-state/delta-state.ts` (new module dir)
|
||||
- [x] 2.2 Port `DeltaManager` + `FieldChange` from upstream `src/core/delta/delta-manager.ts` into `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.ts`
|
||||
- [x] 2.3 Replace `Date.now()` with an injectable `Clock` from `@suaveplan/types`; default to `Clock.system()`
|
||||
- [x] 2.4 Replace `JSON.stringify`-based equality with a structural walker that respects `BitPackerSchema` field order; keep `JSON.stringify` only behind an opt-in `equalityMode: "json"` flag for parity with upstream
|
||||
- [x] 2.5 Define `DeltaConfig` Zod schema; reject unknown modes with `BitpackerError` code `BITPACKER.DELTA_INVALID_CONFIG`
|
||||
- [x] 2.6 Add `BITPACKER.DELTA_*` codes to `packages/bitpacker/bitpacker/src/error-codes.ts` (`DELTA_INVALID_CONFIG`, `DELTA_STATE_NOT_FOUND`, `DELTA_FIELD_LIMIT_EXCEEDED`)
|
||||
- [x] 2.7 Delete the 35 L inline `DeltaManager` from `bitpacker-delta/src/index.ts`; re-export the new class from there
|
||||
- [x] 2.8 Sibling `.md` per implementation file, ≥200 words, basic + advanced examples
|
||||
|
||||
## 3. `@suaveplan/bitpacker-network`
|
||||
|
||||
- [x] 3.1 Move upstream `src/network/network-simulation.ts` and accompanying type companions into `packages/bitpacker/bitpacker-network/src/simulator/`
|
||||
- [x] 3.2 Replace every `Math.random()` call with an injectable `Rng` from `@suaveplan/types`; default to `Rng.crypto()` (uses `crypto.getRandomValues`)
|
||||
- [x] 3.3 Replace every `Date.now()` call with an injectable `Clock`
|
||||
- [x] 3.4 Re-host `NETWORK_CONDITIONS`, `PacketStatus`, `PacketInfo`, `NetworkSimulationOptions`, `NetworkSimulationStats` next to the simulator
|
||||
- [x] 3.5 Port `createNetworkSimulator(options)` factory verbatim
|
||||
- [x] 3.6 Add `BITPACKER.NETWORK_*` codes (`NETWORK_BANDWIDTH_EXCEEDED`, `NETWORK_DISCONNECTED`, `NETWORK_STAT_OVERFLOW`)
|
||||
- [x] 3.7 Delete the 48 L stub at `bitpacker-network/src/index.ts`; replace with explicit re-exports of the new class, factory, presets, and existing `simulateLatency` / `shouldDropPacket` / `applyNetworkConditions` helpers (now backed by the simulator)
|
||||
- [x] 3.8 Delete `packages/bitpacker/bitpacker-game/src/network/network-simulation.ts` (and its sibling `.md` / tests)
|
||||
- [x] 3.9 Add a `bitpacker-game/src/network/index.ts` shim that re-exports from `@suaveplan/bitpacker-network`; mark `@deprecated` with a one-release removal note
|
||||
- [x] 3.10 Sibling `.md` per implementation file, ≥200 words, basic + advanced examples
|
||||
|
||||
## 4. `@suaveplan/bitpacker-compressed`
|
||||
|
||||
- [x] 4.1 Port `CompressedBitPacker<T>` from upstream `src/compressed/bitpacker.ts` into `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.ts`
|
||||
- [x] 4.2 Port `CompressionRegistry`, `CompressionAdapter`, `compressData`, `decompressData`, `NoCompressionAdapter`, `GzipCompressionAdapter`, `DeflateCompressionAdapter` from upstream `src/compressed/compression.ts` into `packages/bitpacker/bitpacker-compressed/src/compression-registry/`
|
||||
- [x] 4.3 Move existing `compressRle` / `decompressRle` into `packages/bitpacker/bitpacker-compressed/src/rle/rle.ts`; re-export from `./rle` AND from root for one release
|
||||
- [x] 4.4 Add `BITPACKER.COMPRESSION_*` codes (`COMPRESSION_FAILED`, `DECOMPRESSION_FAILED`, `UNKNOWN_COMPRESSION_ALGORITHM`)
|
||||
- [x] 4.5 Replace the "placeholder" disclaimer in `bitpacker-compressed/README.md` with accurate copy
|
||||
- [x] 4.6 Sibling `.md` per implementation file
|
||||
|
||||
## 5. `@suaveplan/bitpacker-streaming`
|
||||
|
||||
- [x] 5.1 Port `StreamingBitPacker<T> extends EventEmitter` from upstream `src/streaming/streaming.ts` into `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.ts`
|
||||
- [x] 5.2 Thread `AbortSignal` through `packStream` / `unpackStream`; abort-on-signal flushes in-flight chunks and rejects with `BitpackerError` code `BITPACKER.STREAM_ABORTED`
|
||||
- [x] 5.3 Refactor existing `packToChunks` / `unpackFromChunks` / `unpackFromChunksAsync` to delegate into `StreamingBitPacker`; preserve their synchronous-friendly signatures
|
||||
- [x] 5.4 Add `BITPACKER.STREAM_*` codes (`STREAM_ABORTED`, `STREAM_VALIDATION_FAILED`, `STREAM_CHUNK_PROCESSOR_FAILED`)
|
||||
- [x] 5.5 Sibling `.md` per implementation file
|
||||
|
||||
## 6. `@suaveplan/bitpacker-schema`
|
||||
|
||||
- [x] 6.1 Port `SchemaRegistryStorage` interface, `InMemorySchemaRegistryStorage`, `LocalStorageSchemaRegistryStorage` from upstream `src/schema/registry/registry.ts` into `packages/bitpacker/bitpacker-schema/src/registry/`
|
||||
- [x] 6.2 Port the storage-backed `SchemaRegistry` (versioned `registerSchema` / `loadSchema` / `listSchemas` / `deleteSchema` / `registerVersionedSchema`)
|
||||
- [x] 6.3 Replace `new Date()` with an injectable `Clock`; stamp `createdAt` / `updatedAt` from it
|
||||
- [x] 6.4 Port `createInMemorySchemaRegistry` / `createLocalStorageSchemaRegistry` factories
|
||||
- [x] 6.5 Add `BITPACKER.SCHEMA_*` codes (`SCHEMA_NOT_FOUND`, `SCHEMA_CONFLICT`, `SCHEMA_INVALID_METADATA`)
|
||||
- [x] 6.6 Move the existing 50 L `Map`-based `SchemaRegistry` to `bitpacker-schema/src/legacy/legacy-registry.ts`; re-export as `LegacySchemaRegistry` with `@deprecated` JSDoc
|
||||
- [x] 6.7 Sibling `.md` per implementation file
|
||||
|
||||
## 7. Tests
|
||||
|
||||
- [x] 7.1 `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.test.ts` — round-trip per mode, threshold gate, field-count limit, schema field-order respected
|
||||
- [x] 7.2 `packages/bitpacker/bitpacker-delta/src/delta-state/delta-state.test.ts` — LRU eviction, deep-clone isolation, state-key sharding
|
||||
- [x] 7.3 `packages/bitpacker/bitpacker-delta/src/delta-manager/delta-manager.spec-conformance.test.ts` — injected `Clock` deterministic; Zod-rejected configs emit `DELTA_INVALID_CONFIG`
|
||||
- [x] 7.4 `packages/bitpacker/bitpacker-network/src/simulator/simulator.test.ts` — bandwidth limit honoured; jitter within bounds; loss probability hits empirical bucket; duplication / corruption / reordering / burst / disconnect each verifiable; stats counters increment
|
||||
- [x] 7.5 `packages/bitpacker/bitpacker-network/src/simulator/simulator.spec-conformance.test.ts` — preset bindings; injected `Rng` deterministic; injected `Clock` deterministic; stat-overflow emits `NETWORK_STAT_OVERFLOW`
|
||||
- [x] 7.6 `packages/bitpacker/bitpacker-compressed/src/compressed-bit-packer/compressed-bit-packer.test.ts` — round-trip through `none` / `gzip` / `deflate`; verify decompressed bytes match `BitPacker.pack` output exactly
|
||||
- [x] 7.7 `packages/bitpacker/bitpacker-compressed/src/compression-registry/compression-registry.spec-conformance.test.ts` — adapter registration / replacement; unknown algorithm emits `UNKNOWN_COMPRESSION_ALGORITHM`
|
||||
- [x] 7.8 `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.test.ts` — `packStream` / `unpackStream` round-trip; `progress` events fire in order; `chunk` events carry the right bytes; abort mid-stream rejects with `STREAM_ABORTED`
|
||||
- [x] 7.9 `packages/bitpacker/bitpacker-streaming/src/streaming-bit-packer/streaming-bit-packer.spec-conformance.test.ts` — `chunkProcessor` invoked; `validateChunks` gates emit
|
||||
- [x] 7.10 `packages/bitpacker/bitpacker-schema/src/registry/registry.test.ts` — version auto-increment; `loadSchema` defaults to latest; `listSchemas` filters by namespace AND tag; `deleteSchema` per-version vs all-versions
|
||||
- [x] 7.11 `packages/bitpacker/bitpacker-schema/src/registry/registry.spec-conformance.test.ts` — both storage adapters pinned to the contract; injected `Clock` deterministic; missing schema emits `SCHEMA_NOT_FOUND`
|
||||
- [x] 7.12 `tests/bitpacker-game-network-shim.test.ts` — game's `network/index.ts` re-export resolves to the simulator from `@suaveplan/bitpacker-network`
|
||||
- [x] 7.13 Every `it(...)` in 7.1–7.12 carries `{ timeout: 5000 }` (or 30000 for genuinely-async network simulation runs)
|
||||
|
||||
## 8. Documentation
|
||||
|
||||
- [x] 8.1 Sibling `.md` for every new implementation `.ts` (≥200 words, basic + advanced examples, full API reference)
|
||||
- [x] 8.2 Update each affected package's `README.md` to link the new co-located `.md`s under `## Modules`
|
||||
- [x] 8.3 Update `CLAUDE.md` package listings for `bitpacker-delta`, `bitpacker-network`, `bitpacker-compressed`, `bitpacker-streaming`, `bitpacker-schema` to reflect the restored surface
|
||||
|
||||
## 9. Pre-archive verification (the 5-gate triple, run for each affected package)
|
||||
|
||||
- [x] 9.1 `bun run check` — Biome 0/0/0 across the monorepo
|
||||
- [x] 9.2 `bunx turbo lint typecheck test --filter=@suaveplan/bitpacker-delta` — green
|
||||
- [x] 9.3 `bunx turbo lint typecheck test --filter=@suaveplan/bitpacker-network` — green
|
||||
- [x] 9.4 `bunx turbo lint typecheck test --filter=@suaveplan/bitpacker-compressed` — green
|
||||
- [x] 9.5 `bunx turbo lint typecheck test --filter=@suaveplan/bitpacker-streaming` — green
|
||||
- [x] 9.6 `bunx turbo lint typecheck test --filter=@suaveplan/bitpacker-schema` — green
|
||||
- [x] 9.7 `bunx turbo test:coverage --filter=@suaveplan/bitpacker-delta` — 100/100/100/100
|
||||
- [x] 9.8 `bunx turbo test:coverage --filter=@suaveplan/bitpacker-network` — 100/100/100/100
|
||||
- [x] 9.9 `bunx turbo test:coverage --filter=@suaveplan/bitpacker-compressed` — 100/100/100/100
|
||||
- [x] 9.10 `bunx turbo test:coverage --filter=@suaveplan/bitpacker-streaming` — 100/100/100/100
|
||||
- [x] 9.11 `bunx turbo test:coverage --filter=@suaveplan/bitpacker-schema` — 100/100/100/100
|
||||
- [x] 9.12 `bun scripts/openspec-conformance.ts @suaveplan/bitpacker-delta @suaveplan/bitpacker-network @suaveplan/bitpacker-compressed @suaveplan/bitpacker-streaming @suaveplan/bitpacker-schema` — exit 0, no `error` findings
|
||||
- [x] 9.13 `bunx turbo build --filter=...@suaveplan/bitpacker-*` — green for every dependent in the monorepo
|
||||
- [x] 9.14 `bunx turbo build typecheck test --filter=...@suaveplan/bitpacker-*` — every downstream consumer compiles green (drive, network, packer, storage)
|
||||
|
||||
## 10. Pre-archive 7-row checklist (AGENTS.md Section 13)
|
||||
|
||||
- [x] 10.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim (especially `CompressedBitPacker<T>` and `StreamingBitPacker<T>`)
|
||||
- [x] 10.2 Zod schemas wired at every declared boundary (`DeltaConfig`, `NetworkSimulationOptions`, `BitPackerCompressionOptions`, `StreamingOptions`, `SchemaMetadata`)
|
||||
- [x] 10.3 Every error code added in §2.6 / 3.6 / 4.4 / 5.4 / 6.5 thrown from at least one production code path; no orphans
|
||||
- [x] 10.4 Strict-mode behaviors tested both on and off (e.g. `validateChunks: true` vs `false`; `equalityMode: "json"` vs structural)
|
||||
- [x] 10.5 OTel counters / histograms emitted for `bitpacker.delta.changes_detected`, `bitpacker.network.packet_loss`, `bitpacker.compress.bytes_in/out`, `bitpacker.stream.chunk_count`, `bitpacker.schema.registry_ops`; asserted with a real in-memory `MeterProvider`
|
||||
- [x] 10.6 Lifecycle dedup tested where claimed: `DeltaStateManager` LRU eviction stable under concurrent `storePreviousState`; `SchemaRegistry.registerSchema` idempotent on identical metadata + schema
|
||||
- [x] 10.7 No retroactive tick — every box above reflects work done in this change
|
||||
|
||||
## 11. Archive
|
||||
|
||||
- [x] 11.1 Fold each spec delta into `openspec/specs/<cap>/spec.md` (creating any cap files that don't yet exist)
|
||||
- [x] 11.2 Move `openspec/changes/bitpacker-stub-completion/` → `openspec/archive/<yyyy-mm-dd>-bitpacker-stub-completion/`
|
||||
- [x] 11.3 In `openspec/changes/bitpacker-modular-restructure/tasks.md`, tick §14.1, §14.2, §14.3, §14.4, §14.5 with a note pointing at the archived change
|
||||
- [x] 11.4 Refresh `MANIFEST.md` (no package set or dep changes expected, but rerun for safety)
|
||||
- [x] 11.5 Update STATUS.md with the rolled-up gap closure
|
||||
Reference in New Issue
Block a user