test(8.0): cover pending-tier range queries + setRetentionBudget adaptive reclaim

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.
This commit is contained in:
David Snelling 2026-06-23 11:11:43 -07:00
parent 5c3bb2c864
commit 3783e61b30
2 changed files with 38 additions and 0 deletions

View file

@ -1240,6 +1240,26 @@ describe('8.0 Db API — generational MVCC', () => {
await expect(reopened.asOf(1)).rejects.toBeInstanceOf(GenerationCompactedError) 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) // 9. asOf() / released-Db error paths (Y.15 spot-checks)
// ========================================================================== // ==========================================================================

View file

@ -363,6 +363,24 @@ describe('db/GenerationStore', () => {
expect(store.hasCommittedAfter(g1)).toBe(true) 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 () => { it('flush persists pending single-op generations (groupCommit-marked) and advances committed', async () => {
await singleOpWrite(ID_A, 1) await singleOpWrite(ID_A, 1)
await singleOpWrite(ID_A, 2) await singleOpWrite(ID_A, 2)