From 3783e61b30b63c740a7e3905060653daa6a03aa1 Mon Sep 17 00:00:00 2001 From: David Snelling Date: Tue, 23 Jun 2026 11:11:43 -0700 Subject: [PATCH] test(8.0): cover pending-tier range queries + setRetentionBudget adaptive reclaim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes the two coverage gaps a temporal-completeness audit surfaced (the code was already correct, just untested): - changedBetween / generationsTouching include un-flushed PENDING generations and stay identical across the flush — the building blocks of since/diff/history see single-op writes with no forced flush. - setRetentionBudget() drives adaptive auto-compaction on flush() down to the byte budget: history is reclaimed, the live record is untouched (the cor #65 retention-consume wiring, proven end-to-end). 65 generation/temporal tests green; 1528 unit green. --- tests/integration/db-mvcc.test.ts | 20 ++++++++++++++++++++ tests/unit/db/generationStore.test.ts | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/tests/integration/db-mvcc.test.ts b/tests/integration/db-mvcc.test.ts index a5fab68d..b172e3be 100644 --- a/tests/integration/db-mvcc.test.ts +++ b/tests/integration/db-mvcc.test.ts @@ -1240,6 +1240,26 @@ describe('8.0 Db API — generational MVCC', () => { await expect(reopened.asOf(1)).rejects.toBeInstanceOf(GenerationCompactedError) }) + it('Model-B retention — setRetentionBudget drives adaptive reclaim on flush; live data intact', async () => { + // Default brain → ADAPTIVE retention. A coordinator (e.g. cor's ResourceManager) + // pushes a byte budget via setRetentionBudget(); auto-compaction on flush() reclaims + // oldest history down toward it. Each update's before-image carries the full prior + // 384-dim vector (~KBs), so ~13 generations far exceed a few-KB budget. + const { brain } = await openFsBrain() + const a = uid('ret-budget') + await brain.add({ id: a, type: NounType.Document, data: 'v0', vector: vec(1), metadata: { v: 0 } }) + for (let v = 1; v <= 12; v++) await brain.update({ id: a, metadata: { v } }) + + brain.setRetentionBudget(6000) // ~6 KB — well below the accumulated history + await brain.flush() // group-commit + adaptive auto-compaction under the budget + + // History was reclaimed (the horizon advanced past the oldest generations)… + expect(generationStoreOf(brain).horizon()).toBeGreaterThan(0) + await expect(brain.asOf(1)).rejects.toBeInstanceOf(GenerationCompactedError) + // …but the budget reclaims HISTORY only — the live record is untouched. + expect((await brain.get(a))?.metadata?.v).toBe(12) + }) + // ========================================================================== // 9. asOf() / released-Db error paths (Y.15 spot-checks) // ========================================================================== diff --git a/tests/unit/db/generationStore.test.ts b/tests/unit/db/generationStore.test.ts index 12fce749..8073d7ee 100644 --- a/tests/unit/db/generationStore.test.ts +++ b/tests/unit/db/generationStore.test.ts @@ -363,6 +363,24 @@ describe('db/GenerationStore', () => { expect(store.hasCommittedAfter(g1)).toBe(true) }) + it('range queries (changedBetween / generationsTouching) include un-flushed pending generations', async () => { + // Two single-op writes to different ids, NEITHER flushed (still in the + // pending tier). diff()/since()/history() are built on these, so they must + // see un-flushed single-ops with NO forced flush. + const g1 = await singleOpWrite(ID_A, 1) + const g2 = await singleOpWrite(ID_B, 1) + expect(store.committedGeneration()).toBe(0) // nothing on disk yet + + const changed = await store.changedBetween(0, store.generation()) + expect(changed.nouns).toEqual([ID_A, ID_B]) // resolves over committed ∪ pending + expect(await store.generationsTouching('noun', ID_A, 0, store.generation())).toEqual([g1]) + expect(await store.generationsTouching('noun', ID_B, 0, store.generation())).toEqual([g2]) + + // …and the range stays identical across the flush (now reading from disk). + await store.flushPendingSingleOps() + expect((await store.changedBetween(0, store.generation())).nouns).toEqual([ID_A, ID_B]) + }) + it('flush persists pending single-op generations (groupCommit-marked) and advances committed', async () => { await singleOpWrite(ID_A, 1) await singleOpWrite(ID_A, 2)