brainy/tests/unit/storage/sharding-new-installation-log.test.ts

129 lines
4.9 KiB
TypeScript
Raw Normal View History

/**
* @module tests/unit/storage/sharding-new-installation-log
* @description Regression for BR-8-BOOT-INDEX symptom 1. Brainy 8.0 stores
* entities in the canonical `entities/nouns/<shard>/<id>/vectors.json` layout,
* but the legacy sharding probe (`detectExistingShardingDepth`) inspected
* `entities/nouns/hnsw` a 7.x directory the 8.0 write path never populates
* so it returned null and logged "📁 New installation" for EVERY store on EVERY
* boot, including established brains holding thousands of entities. The
* new-vs-existing decision now consults the canonical layout the DB actually
* uses (`hasCanonicalEntities`), so an established brain reports its true entity
* count and only a genuinely empty store says "New installation".
*/
import { describe, it, expect, vi, afterEach } from 'vitest'
import { mkdtempSync, rmSync } from 'fs'
import { tmpdir } from 'os'
import { join } from 'path'
import { FileSystemStorage } from '../../../src/storage/adapters/fileSystemStorage.js'
import type { NounMetadata } from '../../../src/coreTypes.js'
const dirs: string[] = []
function mkTmp(): string {
const d = mkdtempSync(join(tmpdir(), 'brainy-newinstall-'))
dirs.push(d)
return d
}
afterEach(() => {
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true })
vi.restoreAllMocks()
})
/** Release a raw FileSystemStorage so a subsequent open of the dir is unblocked. */
async function teardown(s: any): Promise<void> {
try { await s.flush?.() } catch { /* best effort */ }
try { s.stopFlushRequestWatcher?.() } catch { /* best effort */ }
try { await s.releaseWriterLock?.() } catch { /* best effort */ }
}
/** Capture everything written to console.log during `fn()`. */
async function captureLog(fn: () => Promise<void>): Promise<string> {
const lines: string[] = []
const spy = vi
.spyOn(console, 'log')
.mockImplementation((...args: any[]) => {
lines.push(args.map(String).join(' '))
})
try {
await fn()
} finally {
spy.mockRestore()
}
return lines.join('\n')
}
const VEC = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
async function seedOne(s: any, id: string): Promise<void> {
await s.saveNounMetadata(id, {
noun: 'thing',
createdAt: Date.now(),
updatedAt: Date.now()
} as NounMetadata)
await s.saveNoun({ id, vector: VEC, connections: new Map(), level: 0 })
}
describe('FileSystemStorage boot log: an established brain is not "New installation" (BR-8-BOOT-INDEX)', () => {
it('a genuinely fresh store still logs "New installation"', async () => {
const dir = mkTmp()
const storage = new FileSystemStorage(dir)
const out = await captureLog(() => storage.init())
expect(out).toContain('New installation')
await teardown(storage)
})
it('an established store (canonical entities present) does NOT log "New installation" on reopen', async () => {
const dir = mkTmp()
// 1. Author an entity → canonical entities/nouns/<shard>/<id>/vectors.json.
const first = new FileSystemStorage(dir)
await first.init()
await seedOne(first, 'a'.repeat(32))
await teardown(first)
// 2. Reopen: the boot log must reflect the established store, not "New installation".
const second = new FileSystemStorage(dir)
const out = await captureLog(() => second.init())
expect(out).not.toContain('New installation')
expect(out).toContain('Using depth 1 sharding')
await teardown(second)
})
fix: count recovery scans the canonical layout; remove the dead 7.x hnsw sharding machinery Sweeping the vestigial hnsw sharding machinery surfaced two real defects on the counts-recovery path (the code that rebuilds totalNounCount/totalVerbCount when counts.json is lost or corrupted — container restarts, partial copies): - initializeCountsFromDisk counted files in entities/*/hnsw/ — directories the 8.0 write path never populates — so an established store recovered to ZERO counts on real data: wrong getNounCount()/stats, a "New installation"-style boot log, and a mis-sized rebuild-strategy decision at open. The scan now walks the canonical entities/<kind>/<shard>/<id>/ tree (one entity per id directory), with the same sampled type-distribution estimate as before. - The sampler called getNounMetadata — whose ensureInitialized() re-enters init() — from INSIDE init(), a latent deadlock reachable the moment the scan found anything to sample. The sampled metadata files are now read directly with fs (gz-transparent), no guarded accessors inside init. The dead machinery itself (verified zero callers by reachability analysis before deletion): the nine 7.x hnsw-layout entity/edge CRUD methods, the sharding-depth prober + its depth-migration engine (unreachable — the probe always returned null on 8.0 stores), an orphaned streaming verb paginator, their path/scan helpers, and the now-unused type aliases and fields. The init block keeps the truthful new-vs-established boot log introduced in 8.0.13, now decided directly from the canonical layout. Net -1,138 lines; no public API touched. Adds a regression test: delete counts.json from a populated store, reopen, counts recover exactly from the canonical tree.
2026-07-08 15:49:19 -07:00
it('counts RECOVER from the canonical layout when counts.json is lost (container-restart case)', async () => {
const dir = mkTmp()
// Establish a store with 3 entities (canonical layout + counts.json).
const first: any = new FileSystemStorage(dir)
await first.init()
for (const c of ['a', 'b', 'c']) await seedOne(first, c.repeat(32))
await teardown(first)
// Simulate the lost/corrupted counts.json a container restart can leave.
for (const f of ['counts.json', 'counts.json.gz']) {
rmSync(join(dir, '_system', f), { force: true })
}
// Reopen: the recovery scan must count the CANONICAL tree. The removed
// implementation scanned the vestigial `entities/*/hnsw` dirs and
// re-initialized every counter to ZERO on real data.
const second: any = new FileSystemStorage(dir)
await second.init()
expect(await second.getNounCount()).toBe(3)
await teardown(second)
})
it('hasCanonicalEntities() sees the canonical shard tree — independent of counts.json', async () => {
const dir = mkTmp()
const s: any = new FileSystemStorage(dir)
await s.init()
// Fresh: only the vestigial `hnsw` dir exists → no canonical entities.
expect(await s.hasCanonicalEntities()).toBe(false)
// After a canonical write the 2-hex shard dir exists → established, even if
// counts.json were later lost on a container restart (the fallback path).
await seedOne(s, 'c'.repeat(32))
expect(await s.hasCanonicalEntities()).toBe(true)
await teardown(s)
})
})