This repository has been archived on 2026-09-03. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.
open-brainy/tests/unit/brainy/degraded-reads-surfaced.test.ts

80 lines
3.6 KiB
TypeScript
Raw Normal View History

/**
* @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\(\)/)
})
})