/** * @module tests/integration/read-your-writes-contract * @description The read-your-writes contract — brainy's spine guarantee that a * write which has RETURNED is immediately readable, under the single writer, by * BOTH id and every index (metadata filter, vector search, graph traversal), * with NO await, delay, or retry between the write returning and the read. * * Why it holds by construction on the canonical + JS-index path: every mutation * runs its canonical writes AND all derived-index projections (HNSW, metadata * index, graph index) as operations inside ONE transaction that commits before * the method returns — there is no post-return "eventual" indexing window. get() * additionally rides the storage write-through cache (read-after-write within * the process). The generation counter is the {seq} a caller can pin: a write * returns generation N and every subsequent live read observes ≥ N. * * This pins the guarantee so no future change can quietly move index maintenance * off the commit path (which would turn a returned write into a not-yet-queryable * one — the "a 200 is still not durability/queryability" failure the spine * program exists to make impossible). The native accelerator must honor the same * in-commit contract; a background consolidation/optimization must never gate * queryability of an acknowledged write. */ 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 } from '../../src/brainy.js' import { NounType, VerbType } from '../../src/types/graphTypes.js' let seq = 0 const freshId = (): string => `00000000-0000-4000-8000-${(++seq).toString(16).padStart(12, '0')}` describe('read-your-writes contract (single-writer, no await between write and read)', () => { let dir: string let brain: any beforeEach(async () => { process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true' dir = fs.mkdtempSync(path.join(os.tmpdir(), 'brainy-ryw-')) brain = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, dimensions: 384, silent: true }) await brain.init() }) afterEach(async () => { await brain.close() fs.rmSync(dir, { recursive: true, force: true }) }) it('add(): the entity is immediately readable by id, metadata filter, AND vector search', async () => { const id = freshId() const marker = `ryw-${id}` await brain.add({ id, data: 'read your writes probe alpha', type: NounType.Concept, metadata: { marker } }) // id read (write-through cache / canonical) expect(await brain.get(id)).not.toBeNull() // metadata-filter read (metadata index committed in-transaction) const byMeta = await brain.find({ where: { marker }, limit: 10 }) expect(byMeta.map((r: any) => r.id)).toContain(id) // vector read (HNSW committed in-transaction) const byVector = await brain.find({ query: 'read your writes probe alpha', limit: 10 }) expect(byVector.map((r: any) => r.id)).toContain(id) // type-filter read const byType = await brain.find({ type: NounType.Concept, limit: 100 }) expect(byType.map((r: any) => r.id)).toContain(id) }) it('update(): the new metadata is immediately queryable and the old value is immediately gone', async () => { const id = freshId() await brain.add({ id, data: 'updatable', type: NounType.Thing, metadata: { phase: 'before' } }) await brain.update({ id, metadata: { phase: 'after' } }) const after = await brain.find({ where: { phase: 'after' }, limit: 10 }) expect(after.map((r: any) => r.id)).toContain(id) const before = await brain.find({ where: { phase: 'before' }, limit: 10 }) expect(before.map((r: any) => r.id)).not.toContain(id) }) it('relate(): the edge is immediately traversable', async () => { const a = await brain.add({ id: freshId(), data: 'A', type: NounType.Thing }) const b = await brain.add({ id: freshId(), data: 'B', type: NounType.Thing }) await brain.relate({ from: a, to: b, type: VerbType.RelatedTo }) expect((await brain.related(a)).map((r: any) => r.to)).toContain(b) }) it('remove(): the entity is immediately gone from id, metadata, and vector reads', async () => { const id = freshId() const marker = `gone-${id}` await brain.add({ id, data: 'to be removed promptly', type: NounType.Thing, metadata: { marker } }) expect(await brain.get(id)).not.toBeNull() await brain.remove(id) expect(await brain.get(id)).toBeNull() expect((await brain.find({ where: { marker }, limit: 10 })).map((r: any) => r.id)).not.toContain(id) expect((await brain.find({ query: 'to be removed promptly', limit: 10 })).map((r: any) => r.id)).not.toContain(id) }) it('transact(): every item of a committed batch is immediately readable by id, index, and traversal', async () => { const a = freshId() const b = freshId() const marker = `batch-${a}` const db = await brain.transact([ { op: 'add', id: a, data: 'batch node A', type: NounType.Thing, metadata: { marker } }, { op: 'add', id: b, data: 'batch node B', type: NounType.Thing, metadata: { marker } }, { op: 'relate', from: a, to: b, type: VerbType.Contains } ]) expect(db.generation).toBeGreaterThan(0) expect(await brain.get(a)).not.toBeNull() expect(await brain.get(b)).not.toBeNull() const byMeta = (await brain.find({ where: { marker }, limit: 10 })).map((r: any) => r.id) expect(byMeta).toContain(a) expect(byMeta).toContain(b) expect((await brain.related(a)).map((r: any) => r.to)).toContain(b) }) it('the write returns a generation ({seq}) and a live read observes it — the awaitable-read primitive', async () => { const id = freshId() const before = brain.generationStore.generation() await brain.add({ id, data: 'generation stamped', type: NounType.Thing }) const after = brain.generationStore.generation() // The write advanced the generation… expect(after).toBeGreaterThan(before) // …and a read pinned to that generation observes the write (asOf ≥ N sees it). const pinned = await brain.asOf(after) expect(await pinned.get(id)).not.toBeNull() }) })