6.3 KiB
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
detectChangesis 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
BitpackerErrorwith codeBITPACKER.DELTA_FIELD_LIMIT_EXCEEDEDand the offending field count incontext.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
BitpackerErrorwith codeBITPACKER.DELTA_INVALID_CONFIGandcontext.mode === "magic"
Scenario: equalityMode: "json" opts into stringify-based equality
- WHEN
equalityModeis"json" - THEN the manager uses
JSON.stringifyfor equality (parity with upstream behavior)
Scenario: structural equality is the default
- WHEN
equalityModeis 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
getPreviousStatereturns the unmutated snapshot
Scenario: oldest state evicted at capacity
- WHEN
maxStatesis 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.tsL22-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
Clockadvancing 7ms pernow()call is injected anddetectChangesis called twice - THEN the OTel histogram
bitpacker.delta.changes_detectedrecords 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".