/** * Cold-open rebuild gate — the readiness contract (8.0.12). * * A production deployment measured 48 seconds on EVERY boot because the * rebuild gate keyed off in-memory size()/count heuristics that are dishonest * for durable indexes: a disk-native provider legitimately reports 0 resident * entries while fully durable, and the JS graph loaded nothing at gate time * (its LSM initialized lazily) — so `rebuildIndexesIfNeeded()` re-derived * indexes from a full canonical scan on every open. * * The contract pinned here: * - JS reopen: the graph must COLD-LOAD its persisted LSM (no re-derive; the * old `_initializeGraphIndex` rebuilt from a full verb scan every boot), * the metadata leg must not rebuild (id-mapper signal is honest), and * queries stay correct. The JS vector leg still runs `rebuild()` — that IS * its load path. * - A vector provider exposing `isReady() === true` is never rebuilt, even * at `size() === 0`; `isReady() === false` gets its rebuild. `init?()` is * eagerly awaited before the gate. */ 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, NounType, VerbType } from '../../src/index.js' import { GraphAdjacencyIndex } from '../../src/graph/graphAdjacencyIndex.js' import { MetadataIndexManager } from '../../src/utils/metadataIndex.js' const tmpDirs: string[] = [] function mkTmp(): string { const d = fs.mkdtempSync(path.join(os.tmpdir(), 'brainy-cold-open-')) tmpDirs.push(d) return d } afterEach(() => { for (const d of tmpDirs.splice(0)) fs.rmSync(d, { recursive: true, force: true }) }) const V = () => Array.from({ length: 384 }, () => Math.random()) /** Populate a brain with nouns + edges and close it. */ async function buildBrain(dir: string, n = 12): Promise { const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, plugins: [], silent: true }) await brain.init() const ids: string[] = [] for (let i = 0; i < n; i++) { ids.push( await brain.add({ data: `entity ${i}`, type: NounType.Concept, subtype: 's', metadata: { wave: i % 3 }, vector: V() }) ) } for (let i = 0; i + 1 < ids.length; i++) { await brain.relate({ from: ids[i], to: ids[i + 1], type: VerbType.RelatedTo, subtype: 's' }) } await brain.close() return ids } describe('Cold-open rebuild gate (readiness contract)', () => { it('JS reopen: graph cold-loads its LSM (no re-derive), metadata skips, queries correct', async () => { const dir = mkTmp() const ids = await buildBrain(dir) // Spy on the two rebuilds that must NOT run on a healthy reopen. const graphRebuilds: number[] = [] const metadataRebuilds: number[] = [] const origGraphRebuild = GraphAdjacencyIndex.prototype.rebuild const origMetaRebuild = MetadataIndexManager.prototype.rebuild GraphAdjacencyIndex.prototype.rebuild = async function (...args: any[]) { graphRebuilds.push(1) return origGraphRebuild.apply(this, args as any) } MetadataIndexManager.prototype.rebuild = async function (...args: any[]) { metadataRebuilds.push(1) return origMetaRebuild.apply(this, args as any) } try { const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, plugins: [], silent: true }) await brain.init() expect(graphRebuilds.length).toBe(0) // persisted LSM loaded — no O(E) re-derive expect(metadataRebuilds.length).toBe(0) // id-mapper signal honest — no rebuild expect(await brain.graphIndex.size()).toBeGreaterThan(0) // durable edges COLD-LOADED at boot // Correctness after the load — the whole point of not rebuilding. const byWhere = await brain.find({ type: NounType.Concept, where: { wave: 1 } }) expect(byWhere.length).toBe(4) // waves 1,4,7,10 of 12 const connected = await brain.find({ connected: { from: ids[0], depth: 1 } }) expect(connected.length).toBe(1) await brain.close() } finally { GraphAdjacencyIndex.prototype.rebuild = origGraphRebuild MetadataIndexManager.prototype.rebuild = origMetaRebuild } }) it('a vector provider with isReady()===true is never rebuilt, even at size()===0 (init eagerly awaited)', async () => { const dir = mkTmp() await buildBrain(dir, 6) const calls = { init: 0, rebuild: 0 } const stubVector = { addItem: async (item: any) => item?.id ?? 'stub-id', removeItem: async () => true, search: async () => [] as Array<[string, number]>, size: () => 0, // disk-native posture: durable, zero resident clear: () => {}, rebuild: async () => { calls.rebuild++ }, flush: async () => 0, getPersistMode: () => 'deferred' as const, init: async () => { calls.init++ }, isReady: () => true } const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, plugins: [], silent: true }) brain.use({ name: 'fake-native-vector', activate: async (ctx: any) => { ctx.registerProvider('vector', () => stubVector); return true } }) await brain.init() expect(calls.init).toBe(1) // the eager cold-load ran before the gate expect(calls.rebuild).toBe(0) // isReady()===true replaced the size()===0 heuristic await brain.close() }) it('a vector provider with isReady()===false gets its rebuild (honest in both directions)', async () => { const dir = mkTmp() await buildBrain(dir, 6) const calls = { rebuild: 0 } let ready = false const stubVector = { addItem: async (item: any) => item?.id ?? 'stub-id', removeItem: async () => true, search: async () => [] as Array<[string, number]>, size: () => 999, // even a non-zero size must NOT mask a not-ready provider clear: () => {}, rebuild: async () => { calls.rebuild++ ready = true // rebuild restores readiness }, flush: async () => 0, getPersistMode: () => 'deferred' as const, isReady: () => ready } const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, plugins: [], silent: true }) brain.use({ name: 'fake-native-vector', activate: async (ctx: any) => { ctx.registerProvider('vector', () => stubVector); return true } }) await brain.init() expect(calls.rebuild).toBe(1) await brain.close() }) it('self-heal survives: durable graph state deleted → one rebuild from canonical on reopen', async () => { const dir = mkTmp() const ids = await buildBrain(dir, 6) // Simulate lost durable graph state: remove the persisted LSM artifacts // (metadata channel keys live under _system hash buckets — nuke the graph // manifests via the storage API instead of guessing paths). const wipe: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, plugins: [], silent: true }) await wipe.init() // Clear both LSM trees' persisted manifests through the live index, then // close WITHOUT letting them re-flush a fresh manifest state. const gi: any = wipe.graphIndex await gi.lsmTreeVerbsBySource.clear?.() await gi.lsmTreeVerbsByTarget.clear?.() await wipe.close() const brain: any = new Brainy({ requireSubtype: false, storage: { type: 'filesystem', path: dir }, plugins: [], silent: true }) await brain.init() // Canonical verbs still exist → the boot self-heal must have restored the adjacency. const connected = await brain.find({ connected: { from: ids[0], depth: 1 } }) expect(connected.length).toBe(1) await brain.close() }) })