/** * @module tests/unit/brainy/degraded-reads-surfaced * @description Finding 10: a known-degraded derived index must be SURFACED, not * silently served as authoritative. Two degraded sources: * (a) a non-fatal index rebuild failure at init() (`_indexRebuildFailed`), and * (b) an adopt-forward failed-rollback recovery (`commitSingleOp`'s `degraded` * ids, previously dropped on the floor by persistSingleOp). * Both now fold into checkHealth()/validateIndexConsistency() and emit ONE loud * read-path warning per degraded window; repairIndex() reconciles + clears them. * * The private fields are the observable contract of the fix, so the test drives * them directly (a real failed rollback is exercised elsewhere). */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { Brainy } from '../../../src/brainy.js' import { NounType } from '../../../src/types/graphTypes.js' import { prodLog } from '../../../src/utils/logger.js' const UUID = (suffix: string): string => `00000000-0000-4000-8000-0000000000${suffix}` describe('Finding 10 — degraded derived-index state is surfaced on reads', () => { beforeEach(() => { process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true' }) afterEach(() => vi.restoreAllMocks()) it('checkHealth() reports adopt-forward degraded ids as unhealthy', async () => { const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false }) await brain.init() ;(brain as any)._indexDegradedIds.add(UUID('de')) const health = await brain.checkHealth() expect(health.healthy).toBe(false) expect(health.recommendation).toMatch(/repairIndex\(\)/) }) it('find()/get() warn loudly while degraded, ONCE, then repairIndex() clears it', async () => { const warn = vi.spyOn(prodLog, 'warn').mockImplementation(() => {}) const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false }) await brain.init() await brain.add({ id: UUID('a1'), data: 'x', type: NounType.Document }) ;(brain as any)._indexRebuildFailed = new Error('rebuild boom') await brain.find({ type: NounType.Document }) await brain.get(UUID('a1')) // second read: must NOT double-warn const degradedWarns = warn.mock.calls.filter((c) => String(c[0]).includes('derived index is INCOMPLETE') ) expect(degradedWarns.length).toBe(1) await brain.repairIndex() expect((brain as any)._indexRebuildFailed).toBeNull() expect((brain as any)._indexDegradedIds.size).toBe(0) warn.mockClear() await brain.find({ type: NounType.Document }) expect(warn.mock.calls.filter((c) => String(c[0]).includes('INCOMPLETE')).length).toBe(0) }) it('persistSingleOp records receipt.degraded (widened return type, not dropped)', async () => { const brain = new Brainy({ storage: { type: 'memory' }, dimensions: 384, requireSubtype: false }) await brain.init() // Simulate a degraded receipt by wrapping the generation store's commitSingleOp. const gs: any = (brain as any).generationStore const realCommit = gs.commitSingleOp.bind(gs) vi.spyOn(gs, 'commitSingleOp').mockImplementation(async (args: any) => { const r = await realCommit(args) return { ...r, degraded: [...(args.touched.nouns ?? [])] } }) await brain.add({ id: UUID('b2'), data: 'y', type: NounType.Document }) expect((brain as any)._indexDegradedIds.size).toBeGreaterThan(0) expect((brain as any)._indexDegradedIds.has(UUID('b2'))).toBe(true) const health = await brain.checkHealth() expect(health.healthy).toBe(false) expect(health.recommendation).toMatch(/repairIndex\(\)/) }) })