/** * @module tests/integration/fact-log-contracts * @description Pinned durability + stability contracts for the fact log. * * (1) FSYNC-BEFORE-ACK: an acknowledged write's fact survives an abrupt * process end (no flush, no close — reopen from disk). * - transact(): HOLDS TODAY — the fact is fsync'd before transact returns. * - single-op: PINNED AS `it.fails` — today's group-commit batches * DURABILITY (ack precedes the group fsync; a hard kill loses the fact * AND the generation together, coherently — the documented Model-B * contract, fine while the tree is authoritative). The destination * (ack-at-log) requires group commit to become LATENCY batching: the * ack waits for the shared fsync. When that lands, this pin flips red — * remove `.fails` and the contract is permanent. No cliff to discover. * * (2) SCAN STABILITY UNDER ROTATION: a scan handle opened before segment * rotation yields exactly its snapshot — byte-identical facts, no gaps, * no duplicates, and no bleed-in of facts appended after the snapshot. * (The reclaim-during-scan variant lands with fact-log compaction, which * does not exist yet — segments only rotate today, never reclaim.) */ import { describe, it, expect, beforeEach, afterEach } from 'vitest' import * as fs from 'node:fs' import * as os from 'node:os' import * as path from 'node:path' import { Brainy, type CommitFact } from '../../src/index.js' import { MemoryStorage } from '../../src/storage/adapters/memoryStorage.js' import { FactLog, type FactLogStorage } from '../../src/db/factLog.js' describe('fsync-before-ack contract (fact durability at the ack boundary)', () => { let dir: string let brain: any const open = async () => { const b: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, silent: true, dimensions: 384 }) await b.init() return b } beforeEach(async () => { process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true' dir = fs.mkdtempSync(path.join(os.tmpdir(), 'brainy-factack-')) brain = await open() }) afterEach(async () => { await brain.close?.().catch(() => {}) fs.rmSync(dir, { recursive: true, force: true }) }) it('transact(): the fact is durable the moment the ack returns (kill-after-ack safe)', async () => { const receipt = await brain.transact([ { op: 'add', type: 'document', metadata: { durable: 1 }, data: 'ack-at-commit' } ]) // Abrupt end: no flush(), no close() — a new instance reads only disk. brain = await open() const facts: CommitFact[] = [] for await (const b of brain.scanFacts()!.batches()) facts.push(...b.facts) expect(facts.some((f) => f.generation === receipt.generation)).toBe(true) }) // PINNED (flips red when group commit becomes latency batching — then // remove `.fails` and the ack-at-log contract is permanent on every path). it.fails('single-op: the fact is durable the moment the ack returns (the ack-at-log target)', async () => { await brain.add({ data: 'acked single-op', type: 'document', metadata: { n: 1 } }) const ackedHead = brain.scanFacts()!.headGeneration // Abrupt end immediately after the ack — before any flush window. brain = await open() const facts: CommitFact[] = [] for await (const b of brain.scanFacts()!.batches()) facts.push(...b.facts) expect(facts.some((f) => f.generation === ackedHead)).toBe(true) }) }) describe('scan stability under rotation (the snapshot contract)', () => { const UUID = (n: number): string => `00000000-0000-4000-8000-${String(n).padStart(12, '0')}` const fact = (generation: number): CommitFact => ({ generation, timestamp: 1_700_000_000_000 + generation, ops: [ { kind: 'noun', id: UUID(generation), // Padding makes each frame ~1KB so a small rotateBytes forces rotations. record: { metadata: { noun: 'document', pad: 'x'.repeat(900), g: generation }, vector: null } } ] }) it('a scan opened before rotations yields its exact snapshot — no gaps, dups, or bleed-in', async () => { const mem: any = new MemoryStorage() await mem.init() const log = new FactLog(mem as FactLogStorage, { rotateBytes: 4096 }) // ~4 facts per segment await log.open(0) for (let g = 1; g <= 10; g++) await log.append(fact(g)) await log.sync() // Open the snapshot, THEN keep appending — forcing further rotations. const scan = log.scanFacts() expect(scan.headGeneration).toBe(10) for (let g = 11; g <= 25; g++) await log.append(fact(g)) await log.sync() expect(log.headGeneration()).toBe(25) const seen: number[] = [] for await (const batch of scan.batches()) { for (const f of batch.facts) seen.push(f.generation) } // Exactly the snapshot: 1..10 in order, nothing appended-after bleeds in. expect(seen).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) expect(scan.summary().factsYielded).toBe(10) // And a fresh scan sees everything, across all rotated segments. const all: number[] = [] for await (const batch of log.scanFacts().batches()) { for (const f of batch.facts) all.push(f.generation) } expect(all).toEqual(Array.from({ length: 25 }, (_, i) => i + 1)) expect(log.segmentPaths().length).toBeGreaterThanOrEqual(2) // rotations actually happened }) })