/** * @module tests/unit/brainy/persistence-policy * @description THE ENGINE-OWNED FLUSH CADENCE pins (A4, * SELF-ENGINE-LIFECYCLE-SPRINT, David-directed: callers NEVER call flush() * in hot paths). The production disease: 829 caller-scheduled per-write * flushes convoying into 45–66 second write walls — cadence hand-rolled a * layer above the only layer that can see dirty state and IO pressure. * * Pinned here: (1) the write-count trigger fires a BACKGROUND flush without * any caller flush(); (2) the idle trigger; (3) `'manual'` restores * caller-owned cadence exactly; (4) THE ACK LAW — a write acknowledges * without awaiting any background flush, even one that never resolves. */ import { describe, it, expect, afterEach, vi } from 'vitest' import { Brainy } from '../../../src/index.js' import { NounType } from '../../../src/types/graphTypes.js' const brains: Brainy[] = [] async function mk(persistence?: { policy?: 'auto' | 'manual' flushEveryWrites?: number flushIntervalMs?: number flushOnIdleMs?: number }): Promise { const b = new Brainy({ storage: { type: 'memory' }, requireSubtype: false, ...(persistence && { persistence }) }) await b.init() brains.push(b) return b } afterEach(async () => { for (const b of brains.splice(0)) await b.close().catch(() => {}) vi.restoreAllMocks() }) describe('persistence policy — the engine owns its flush cadence', () => { it('write-count trigger: N committed writes fire ONE background flush, no caller flush()', async () => { const brain = await mk({ flushEveryWrites: 5, flushOnIdleMs: 60_000, flushIntervalMs: 600_000 }) const flushSpy = vi.spyOn(brain, 'flush') for (let i = 0; i < 5; i++) { await brain.add({ data: `w${i}`, type: NounType.Document, metadata: { i } }) } await vi.waitFor(() => expect(flushSpy).toHaveBeenCalled(), { timeout: 5000 }) // Single-flight: the threshold crossing kicks exactly one. expect(flushSpy.mock.calls.length).toBe(1) }) it('idle trigger: a quiet store with dirty writes flushes itself', async () => { const brain = await mk({ flushEveryWrites: 10_000, flushIntervalMs: 600_000, flushOnIdleMs: 60 }) const flushSpy = vi.spyOn(brain, 'flush') await brain.add({ data: 'lone write', type: NounType.Document, metadata: {} }) await vi.waitFor(() => expect(flushSpy).toHaveBeenCalled(), { timeout: 5000 }) }) it('idle debounce under load: slow writes never fire a flush per inter-write gap', async () => { // The contended-disk amplifier: writes slower than the idle window make // every gap look idle — without the spacing floor this fired a full // flush per write (measured 15 background flushes in 100 contended adds // on a production-shaped box). The floor (min(interval, 10×idle)) caps // idle fires; deferred, never dropped. const brain = await mk({ flushEveryWrites: 10_000, flushIntervalMs: 600_000, flushOnIdleMs: 50 }) const flushSpy = vi.spyOn(brain, 'flush') // Six writes spaced wider than the idle window (50ms) with the whole // span inside ~one floor window (500ms): the old behavior fires ~an // idle flush per gap (≈6); the debounced behavior fires at most two // (one immediate boot-window fire + one at the floor boundary). for (let i = 0; i < 6; i++) { await brain.add({ data: `slow ${i}`, type: NounType.Document, metadata: {} }) await new Promise((r) => setTimeout(r, 70)) } expect(flushSpy.mock.calls.length, 'no flush-per-gap amplifier').toBeLessThanOrEqual(2) // Deferred, never dropped: the dirty writes still persist once the // floor elapses on the now-quiet store. await vi.waitFor(() => expect(flushSpy).toHaveBeenCalled(), { timeout: 5000 }) }) it("'manual' policy: the engine NEVER flushes on its own", async () => { const brain = await mk({ policy: 'manual', flushEveryWrites: 2, flushOnIdleMs: 30 }) const flushSpy = vi.spyOn(brain, 'flush') for (let i = 0; i < 6; i++) { await brain.add({ data: `m${i}`, type: NounType.Document, metadata: { i } }) } await new Promise((r) => setTimeout(r, 150)) expect(flushSpy).not.toHaveBeenCalled() }) it('THE ACK LAW: writes acknowledge without awaiting the background flush — even a hung one', async () => { const brain = await mk({ flushEveryWrites: 2, flushOnIdleMs: 60_000, flushIntervalMs: 600_000 }) // A flush that NEVER resolves: if any write ack awaited it, the test // would time out. (The engine's background flight must be fire-and-log.) vi.spyOn(brain, 'flush').mockImplementation(() => new Promise(() => {})) for (let i = 0; i < 6; i++) { const id = await brain.add({ data: `a${i}`, type: NounType.Document, metadata: { i } }) expect(id).toBeTruthy() } // All six writes acked while the "flush" hangs forever. const rows = await brain.find({ type: NounType.Document, limit: 10 }) expect(rows.length).toBe(6) // Un-hang before afterEach close(): restore the method AND drop the // never-resolving in-flight promise (close() awaits the flight — with a // real flush that is correct; here it is the test's own artifact). vi.restoreAllMocks() ;(brain as unknown as { _persistBackgroundFlight: Promise | null })._persistBackgroundFlight = null }) })