/** * @module tests/unit/brainy/warm * @description Coverage for `brain.warm()` / `warmOnOpen` / the provider * `warm?()` contract (the cold-restart readiness fix): first operations after * a cold restart should run at steady-state cost instead of paying * demand-load latency on the critical path. * * Uses a real, in-process fake plugin provider implementing the actual * `VectorIndexProvider` contract from `src/plugin.ts` — the real seam a * native provider (e.g. a disk-native accelerator) plugs into. The real * built-in JS metadata index and graph adjacency index run against real * (filesystem or in-memory) storage with pre-existing data, so their * hydration paths are exercised for real, not mocked. */ import { describe, it, expect, 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' import type { VectorIndexProvider } from '../../../src/plugin.js' import type { VectorDocument, Vector } from '../../../src/coreTypes.js' import { MetadataIndexManager } from '../../../src/utils/metadataIndex.js' import { GraphAdjacencyIndex } from '../../../src/graph/graphAdjacencyIndex.js' const tmpDirs: string[] = [] function mkTmp(): string { const d = fs.mkdtempSync(path.join(os.tmpdir(), 'brainy-warm-')) tmpDirs.push(d) return d } afterEach(() => { for (const d of tmpDirs.splice(0)) fs.rmSync(d, { recursive: true, force: true }) }) // Brainy's ValidationConfig fixes vectors at exactly 384 dimensions // (src/utils/paramValidation.ts) — match it so `add()` doesn't reject test data. const DIM = 384 const V = (seed = 1): number[] => Array.from({ length: DIM }, (_, i) => Math.sin(seed + i)) /** * A real (not mocked) VectorIndexProvider implementation, backed by a plain * Map, that optionally implements `warm()` — the exact seam * `AddToVectorIndexOperation` / `brain.warm()` call through. */ class FakeVectorProvider implements VectorIndexProvider { readonly name = 'fake-vector-provider' readonly items = new Map() warmCalls = 0 searchCalls: Array<{ k?: number }> = [] // Only present on the instance when the constructor is told to — mirrors a // real provider that may or may not implement the optional hook. Assigned // in the constructor BODY (not as a field initializer): under native ES // class fields, field initializers run before constructor-body statements // — including the parameter-property assignment — so referencing a // parameter property from a field initializer would see it as still // `undefined`. warm?: () => Promise constructor(hasWarm: boolean) { if (hasWarm) { this.warm = async (): Promise => { this.warmCalls++ } } } async addItem(item: VectorDocument): Promise { this.items.set(item.id, item.vector) return item.id } async removeItem(id: string): Promise { return this.items.delete(id) } async search(_queryVector: Vector, k?: number): Promise> { this.searchCalls.push({ k }) return [...this.items.keys()].slice(0, k ?? 10).map((id) => [id, 0]) } size(): number { return this.items.size } clear(): void { this.items.clear() } async rebuild(): Promise {} async flush(): Promise { return 0 } getPersistMode(): 'immediate' | 'deferred' { return 'deferred' } } /** Registers `provider` under the `'vector'` plugin key, before `init()`. */ function useFakeVectorProvider(brain: Brainy, provider: FakeVectorProvider): void { brain.use({ name: 'fake-vector-provider', activate: async (ctx: any) => { ctx.registerProvider('vector', () => provider) return true } }) } describe('brain.warm()', () => { it('(a) calls the vector provider\'s warm() when present and reports "warmed"', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) const provider = new FakeVectorProvider(true) useFakeVectorProvider(brain, provider) await brain.init() await brain.add({ data: 'a', type: NounType.Thing, vector: V(1) }) const report = await brain.warm() expect(provider.warmCalls).toBe(1) expect(provider.searchCalls.length).toBe(0) // warm() ran — no probe fallback expect(report.vector.outcome).toBe('warmed') await brain.close() }) it('(b) falls back to a probe search() when warm() is absent and reports "probed", never "warmed"', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) const provider = new FakeVectorProvider(false) useFakeVectorProvider(brain, provider) await brain.init() await brain.add({ data: 'a', type: NounType.Thing, vector: V(1) }) await brain.add({ data: 'b', type: NounType.Thing, vector: V(2) }) const report = await brain.warm() expect(provider.warmCalls).toBe(0) // no warm() on this provider expect(provider.searchCalls.length).toBe(1) // the probe ran expect(provider.searchCalls[0].k).toBe(Math.min(100, provider.size())) // k = min(100, size) expect(report.vector.outcome).toBe('probed') expect(report.vector.outcome).not.toBe('warmed') // never conflated await brain.close() }) it('vector: reports "unavailable" when there is nothing to probe (empty index, unknown dimension)', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) // A disk-native provider MAY honestly report 0 resident entries while // durable data exists on disk (the same posture documented on // `VectorIndexProvider.isReady` — "an mmap/disk-native index may // legitimately report 0 resident entries"). warm()'s probe fallback // reads `size()`, so this is the real trigger for "nothing to probe": // never a k=0 search, an honest skip. const provider = new FakeVectorProvider(false) provider.size = () => 0 useFakeVectorProvider(brain, provider) await brain.init() // the VFS root bootstrap write sets `dimensions`, but size() still reports 0 const report = await brain.warm() expect(provider.searchCalls.length).toBe(0) // nothing probed expect(report.vector.outcome).toBe('unavailable') await brain.close() }) it('(c) metadata + graph hydration paths actually execute against filesystem storage with pre-existing data', async () => { const dir = mkTmp() // Build a brain with real data (including a field NOT in the metadata // index's own common-fields warm subset — 'wave' — so hydrateAll()'s // FULL hydration is distinguishable from init()'s partial warmCache()), // and real graph edges, then close it (persisting everything). const seed = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, silent: true }) await seed.init() const ids: string[] = [] for (let i = 0; i < 6; i++) { ids.push( await seed.add({ data: `entity ${i}`, type: NounType.Thing, metadata: { wave: i % 3 }, vector: V(i + 1) }) ) } for (let i = 0; i + 1 < ids.length; i++) { await seed.relate({ from: ids[i], to: ids[i + 1], type: VerbType.RelatedTo }) } await seed.close() // Cold-reopen a FRESH instance and spy on the real hydration seams before // calling warm(), so we assert they actually ran (not just that the // report claims they did). const metaHydrateCalls: number[] = [] const loadedFields: string[] = [] const graphInitCalls: number[] = [] const origHydrateAll = MetadataIndexManager.prototype.hydrateAll const origLoadSparseIndex = (MetadataIndexManager.prototype as any).loadSparseIndex const origGraphInit = GraphAdjacencyIndex.prototype.init MetadataIndexManager.prototype.hydrateAll = async function (...args: any[]) { metaHydrateCalls.push(1) return origHydrateAll.apply(this, args as any) } ;(MetadataIndexManager.prototype as any).loadSparseIndex = async function ( field: string, ...args: any[] ) { loadedFields.push(field) return origLoadSparseIndex.apply(this, [field, ...args] as any) } GraphAdjacencyIndex.prototype.init = async function (...args: any[]) { graphInitCalls.push(1) return origGraphInit.apply(this, args as any) } try { const brain = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, silent: true }) await brain.init() const report = await brain.warm() expect(metaHydrateCalls.length).toBe(1) // hydrateAll() actually ran expect(loadedFields).toContain('wave') // a NON-common field was loaded — full hydration, not the heuristic subset expect(report.metadata.outcome).toBe('warmed') expect(graphInitCalls.length).toBeGreaterThanOrEqual(1) // graph's full-load seam ran (once at brain init, once via warm()) expect(report.graph.outcome).toBe('warmed') // Correctness survives: the hydrated data still answers queries. const byWhere = await brain.find({ type: NounType.Thing, where: { wave: 1 } }) expect(byWhere.length).toBe(2) // waves 1,4 of 0..5 await brain.close() } finally { MetadataIndexManager.prototype.hydrateAll = origHydrateAll ;(MetadataIndexManager.prototype as any).loadSparseIndex = origLoadSparseIndex GraphAdjacencyIndex.prototype.init = origGraphInit } }) it('(d) warmOnOpen: true runs warm() during init() — observable via the fake provider', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, warmOnOpen: true, silent: true }) const provider = new FakeVectorProvider(true) useFakeVectorProvider(brain, provider) // No explicit brain.warm() call — warmOnOpen must have run it as part of init(). await brain.init() expect(provider.warmCalls).toBe(1) await brain.close() }) it('warmOnOpen defaults to false — init() does NOT run warm() unless opted in', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) const provider = new FakeVectorProvider(true) useFakeVectorProvider(brain, provider) await brain.init() expect(provider.warmCalls).toBe(0) await brain.close() }) it('(e) WarmReport shape: outcome literal + durationMs per surface, plus totalDurationMs', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) const provider = new FakeVectorProvider(true) useFakeVectorProvider(brain, provider) await brain.init() await brain.add({ data: 'a', type: NounType.Thing, vector: V(1) }) const report = await brain.warm() for (const surface of ['vector', 'metadata', 'graph'] as const) { expect(['warmed', 'probed', 'unavailable']).toContain(report[surface].outcome) expect(typeof report[surface].durationMs).toBe('number') expect(report[surface].durationMs).toBeGreaterThanOrEqual(0) } expect(typeof report.totalDurationMs).toBe('number') expect(report.totalDurationMs).toBeGreaterThanOrEqual(0) await brain.close() }) // --- Metadata leg routes through the ACTIVE provider (8.10.1) ----------- // // `MetadataIndexProvider` is a ~50-method interface (src/plugin.ts) — far // too large to hand-write a compliant fake class the way `FakeVectorProvider` // fakes the ~8-method `VectorIndexProvider` above. Test (c) already // establishes this file's pattern for the metadata leg: exercise the REAL // `MetadataIndexManager` instance and shape just the probe points brain.ts // reads (`typeof provider.warm === 'function'` / // `typeof provider.hydrateAll === 'function'`) directly on that instance. // Shadowing an own property on the live object stands in for "a different // provider implementation" without needing a hand-rolled full fake — the // rest of the real manager (used by add()/init() above) is untouched. describe('metadata leg — warm() routes through the active provider', () => { it('(f) calls the ACTIVE metadata provider\'s warm() when present and reports "warmed", never falling back to hydrateAll', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) await brain.init() await brain.add({ data: 'a', type: NounType.Thing, vector: V(1) }) const metadataIndex = (brain as any).metadataIndex let warmCalls = 0 let hydrateAllCalls = 0 const origHydrateAll = metadataIndex.hydrateAll.bind(metadataIndex) metadataIndex.hydrateAll = async (...args: unknown[]) => { hydrateAllCalls++ return origHydrateAll(...args) } // Simulates a native metadata provider declaring the optional `warm()` // hook added to `MetadataIndexProvider` (src/plugin.ts) in 8.10.1. metadataIndex.warm = async () => { warmCalls++ } const report = await brain.warm() expect(warmCalls).toBe(1) expect(hydrateAllCalls).toBe(0) // warm() ran — no hydrateAll fallback expect(report.metadata.outcome).toBe('warmed') await brain.close() }) it('reports "unavailable" when the active metadata provider implements neither warm() nor hydrateAll() (the honest branch a native provider without either hook must hit)', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) await brain.init() await brain.add({ data: 'a', type: NounType.Thing, vector: V(1) }) const metadataIndex = (brain as any).metadataIndex // Shadow away BOTH optional hooks — models a genuinely native provider // that (unlike the built-in JS manager) offers neither seam. This must // never fall back to calling init() as a stand-in for warmth. metadataIndex.warm = undefined metadataIndex.hydrateAll = undefined const report = await brain.warm() expect(report.metadata.outcome).toBe('unavailable') await brain.close() }) it('the built-in JS manager (no warm()) still reports "warmed" via its existing hydrateAll() duck-type — unchanged by the new provider hook', async () => { const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, silent: true }) await brain.init() await brain.add({ data: 'a', type: NounType.Thing, vector: V(1) }) // No patching at all — the default built-in MetadataIndexManager has // hydrateAll() but no warm(), exactly as it did before this change. const metadataIndex = (brain as any).metadataIndex expect(typeof metadataIndex.warm).not.toBe('function') expect(typeof metadataIndex.hydrateAll).toBe('function') const report = await brain.warm() expect(report.metadata.outcome).toBe('warmed') await brain.close() }) }) })