/** * @module tests/integration/repair-report * @description repairIndex() returns the per-family receipt (checked / * healed / skipped-with-reason per family) and narrates a summary — the * "repair that shows its work" half of the graph-trust program's ask. A * repair nobody can audit is a repair nobody can trust. */ import { describe, it, expect, afterEach } from 'vitest' import { mkdtempSync, rmSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { Brainy } from '../../src/index.js' import { NounType } from '../../src/types/graphTypes.js' type RawBox = { storage: { writeNounRaw(id: string, r: { metadata: unknown; vector: unknown }): Promise } } const dirs: string[] = [] const brains: Brainy[] = [] afterEach(async () => { for (const b of brains.splice(0)) await b.close().catch(() => {}) for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }) }) describe('repairIndex per-family receipt', () => { it('a healthy store gets a complete zero-heal receipt — every family accounted, none silent', async () => { const dir = mkdtempSync(join(tmpdir(), 'brainy-repair-clean-')) dirs.push(dir) const brain = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false, silent: true }) await brain.init() brains.push(brain) await brain.add({ data: 'healthy row', type: NounType.Document, metadata: { n: 1 } }) await brain.flush() const report = await brain.repairIndex() expect(report.families.length, 'every family reports a row').toBeGreaterThanOrEqual(5) const names = report.families.map((f) => f.family) for (const expected of ['orphaned-containers', 'count-rollups', 'metadata-corruption']) { expect(names, `family ${expected} accounted`).toContain(expected) } // Every row is either checked or carries its skip reason — no silent rows. for (const f of report.families) { expect(f.checked || !!f.skipped, `${f.family} is checked or explains itself`).toBe(true) } expect(report.healedTotal).toBe(0) expect(report.durationMs).toBeGreaterThanOrEqual(0) }, 120000) it('a manufactured ghost container appears in the receipt as a heal', async () => { const dir = mkdtempSync(join(tmpdir(), 'brainy-repair-ghost-')) dirs.push(dir) const brain = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false, silent: true }) await brain.init() brains.push(brain) await brain.add({ data: 'real row', type: NounType.Document, metadata: { n: 1 } }) await brain.flush() // The pre-8.3.1 ghost shape: a vector leg with no content leg. const storage = (brain as unknown as RawBox).storage await storage.writeNounRaw('00000000-0000-7000-8000-00000000dead', { metadata: null, vector: { vector: [0.1, 0.2], noun: 'document' } }) const report = await brain.repairIndex() const orphans = report.families.find((f) => f.family === 'orphaned-containers') expect(orphans?.checked).toBe(true) expect(orphans!.healed, 'the ghost was pruned and receipted').toBeGreaterThan(0) expect(report.healedTotal).toBeGreaterThan(0) }, 120000) it("a heal:'repair' verdict routes to the provider's own repair(), and the re-read decides", async () => { // A fake provider report: one failing invariant asking for the INCREMENTAL // heal. repairIndex must call repair() (never rebuild()) and count the heal // only when the post-repair re-read clears the same verdict. const dir = mkdtempSync(join(tmpdir(), 'brainy-repair-route-')) const brain: any = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false, silent: true }) await brain.init() brains.push(brain) let repairCalls = 0 let rebuildCalls = 0 let healed = false const failing = { provider: 'vector', healthy: false, serving: true, invariants: [{ name: 'node-coverage', holds: false, detail: 'short 3', heal: 'repair' as const }], checkedAt: 1, durationMs: 1 } const clean = { provider: 'vector', healthy: true, serving: true, invariants: [{ name: 'node-coverage', holds: true, detail: 'ok', heal: 'none' as const }], checkedAt: 2, durationMs: 1 } ;(brain.index as any).validateInvariants = async () => (healed ? clean : failing) ;(brain.index as any).repair = async () => { repairCalls++; healed = true; return { repaired: 3 } } const origRebuild = (brain.index as any).rebuild ;(brain.index as any).rebuild = async () => { rebuildCalls++ } try { const report = await brain.repairIndex() const row = report.families.find((f: any) => f.family === 'provider:vector') expect(row, 'the provider family is in the receipt').toBeDefined() expect(repairCalls, 'repair() ran exactly once').toBe(1) expect(rebuildCalls, "a heal:'repair' verdict never runs rebuild()").toBe(0) expect(row!.healed, 'the cleared verdict counts as healed').toBe(1) expect(String(row!.detail)).toMatch(/incremental repair cleared: node-coverage/) } finally { delete (brain.index as any).validateInvariants delete (brain.index as any).repair ;(brain.index as any).rebuild = origRebuild } }) })