Summary
getRange is a lazy generator over the live working array. tx.delete (or tx.set that shifts indices) during iteration silently skips entries — the classic "delete while scanning" bug. Pattern: scan prefix, delete matches → every other entry skipped, operation reports success.
Audit ID: B6 (High) — pre-announcement blocker
Root cause
*getRange(start, end) {
for (let i = locate(working, start).index; i < working.length; i++) {
const entry = working[i] as StoredEntry;
if (compareKeys(entry.key, end) >= 0) break;
yield entry;
}
}
Index-based walk over a mutating array.
Proposed fix (pick one)
Option A — Snapshot on first next() (recommended)
Collect matching entries (keys + values) into an array at iterator start; yield from snapshot. Cost: O(k) for range size k — acceptable for v1.
Option B — Detect modification and throw
Track working.length/version; throw libredb: getRange iterator invalidated by transaction mutation.
Either is valid; snapshot matches SQLite-style stable read in a transaction better for the delete-while-scan use case.
Tests (mandatory)
- Insert
a, b, c in range; iterate and delete each → all three deleted.
- Insert-only during scan — define expected behavior (throw or snapshot).
- Document semantics in JSDoc on
getRange.
Acceptance criteria
Related issues
- Independent kernel API contract issue.
Agent constraints
- Keep generator API; do not break sync transaction model.
Verification
Summary
getRangeis a lazy generator over the liveworkingarray.tx.delete(ortx.setthat shifts indices) during iteration silently skips entries — the classic "delete while scanning" bug. Pattern: scan prefix, delete matches → every other entry skipped, operation reports success.Audit ID: B6 (High) — pre-announcement blocker
Root cause
Index-based walk over a mutating array.
Proposed fix (pick one)
Option A — Snapshot on first
next()(recommended)Collect matching entries (keys + values) into an array at iterator start; yield from snapshot. Cost: O(k) for range size k — acceptable for v1.
Option B — Detect modification and throw
Track
working.length/version; throwlibredb: getRange iterator invalidated by transaction mutation.Either is valid; snapshot matches SQLite-style stable read in a transaction better for the delete-while-scan use case.
Tests (mandatory)
a,b,cin range; iterate and delete each → all three deleted.getRange.Acceptance criteria
Transaction.getRange.src/core.test.ts.bun run gategreen.Related issues
Agent constraints
Verification