fix: base-id coverage for sub-id projections in prune + sync (#231) - #233
fix: base-id coverage for sub-id projections in prune + sync (#231)#233ranxianglei wants to merge 1 commit into
Conversation
[bot] 🏷 Independent review — verified, looks good to merge (pending the 2nd-agent + human merge per repo policy). I didn't trust the PR description — I re-ran everything and re-derived the logic from the diff. Re-verification (clean checkout of the PR head, not the description's claims):
The regression tests are meaningful, not vacuous. I checked out master (pre-fix) with only the two new test files applied and ran them: 6 of 8 fail ( Fix logic — sound, and it closes all three leak directions:
I walked the non-obvious cases by hand: non-contiguous base groups (summary anchors at the earliest projection, all projections still removed), and false-positive coverage (impossible, since the base is the full original id and sub-ids are Deferred sites are genuinely latent, and properly tracked. I confirmed the kernel only emits plain base ids — Nit (non-blocking): exporting Nothing to fix here. Ready for the second-agent sign-off and human merge. |
Fixes #231.
Bug
prune()decided coverage with exact string membership (covered.has(message.id), covered built verbatim fromblock.effectiveMessageIds). When a block recorded sub-id projections (base#callId,base#r0, …) and the current view emitted the plainbase(or vice versa), coverage failed and already-compressed messages re-entered the visible view resurrected (762K-token leak measured in replay).Fix
Normalize to the base id on both sides before the membership test:
baseIdOf(id)— everything before the first#(a#at position 0 or#-free ids map to themselves, soh_abcdeis never confused with covered baseh_abc— no prefix matching).isCovered(id, coveredBases)— O(1) lookup against a precomputed base set (built once per pass), instead of per-message linear scans.Sibling fix (same root cause, found during triage)
syncBlocks()(src/sync.ts) had the same exact-match presence check and runs before prune in the pipeline: in the primary direction it deactivated the block (summary lost) before prune could cover the message. Fixed with the same base-set normalization. Fixing prune alone would not have stopped the leak.Note: the
isCoveredin closed PR #225 early-returned for#-free ids, so it missed the primary direction (block recorded sub-ids, view emits base); this normalizes both sides.Tests
8 new regression tests (5 in
tests/state-prune.test.ts, 3 intests/sync-config.test.ts), covering: block-sub-id/view-base, block-base/view-sub-id, sibling sub-ids, distinct-id negative case, summary anchor placement, and sync deactivation in both directions. All 588 tests pass; typecheck + build clean.Remaining same-class exact-match sites (
recommend.ts,decompress.ts,compress.tsoverlap detection,boundaries.ts) are tracked in a follow-up issue.