validateIndexConsistency() was blind to native providers — it only ran the JS metadata index's own check, so a native provider whose manifest/segments/counts had diverged still read as "healthy". Per ADR-004 §6 it now feature-detects and aggregates each provider's optional validateInvariants() (a never-throwing, <50ms self-report of its own cross-layer invariants), names any failing invariant with its numbers in the recommendation, and exposes the per-provider reports. A provider that violates the never-throw contract is surfaced as unhealthy, never swallowed. repairIndex() now reconciles NATIVE derived state from canonical too: it consults each provider's invariants and calls rebuild() on any whose failing invariant asks for heal:'rebuild' — the native counterpart of detectAndRepairCorruption(). New provider surface: optional validateInvariants(); new exported types ProviderInvariantReport / InvariantResult / InvariantHeal. Additive, no break. Cor implements the hook in 3.0.15 (M2); brainy builds against the shape now (feature-detected, inert until a provider exposes it). 5 tests.
99 lines
4.1 KiB
TypeScript
99 lines
4.1 KiB
TypeScript
/**
|
|
* @module tests/unit/validate-invariants-delegation
|
|
* @description Pass 3 (ADR-004 §6): validateIndexConsistency() was blind to native
|
|
* providers — it only saw the JS metadata index, so a native manifest↔segments↔count
|
|
* divergence read as "healthy". It now feature-detects + aggregates each provider's
|
|
* validateInvariants(), and repairIndex() maps a failing invariant with heal:'rebuild'
|
|
* to that provider's rebuild(). "healthy-while-broken must be impossible."
|
|
*/
|
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { Brainy, NounType } from '../../src/index.js'
|
|
import type { ProviderInvariantReport } from '../../src/index.js'
|
|
|
|
const healthyReport = (provider: string): ProviderInvariantReport => ({
|
|
provider,
|
|
healthy: true,
|
|
serving: true,
|
|
invariants: [{ name: 'manifest-residency', holds: true, detail: 'ok', heal: 'none' }],
|
|
checkedAt: 1,
|
|
durationMs: 1
|
|
})
|
|
|
|
const brokenReport = (provider: string): ProviderInvariantReport => ({
|
|
provider,
|
|
healthy: false,
|
|
serving: true,
|
|
invariants: [
|
|
{ name: 'manifest-residency', holds: true, detail: 'ok', heal: 'none' },
|
|
{
|
|
name: 'posted-count-floor',
|
|
holds: false,
|
|
detail: 'posted 2304 < canonical 2354',
|
|
expected: 2354,
|
|
actual: 2304,
|
|
heal: 'rebuild'
|
|
}
|
|
],
|
|
checkedAt: 1,
|
|
durationMs: 2
|
|
})
|
|
|
|
describe('validateIndexConsistency delegates to provider validateInvariants() (Pass 3)', () => {
|
|
let brain: any
|
|
beforeEach(async () => {
|
|
process.env.BRAINY_DETERMINISTIC_EMBEDDINGS = 'true'
|
|
brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' }, dimensions: 384 })
|
|
await brain.init()
|
|
await brain.add({ data: 'x', type: NounType.Concept })
|
|
await brain.flush()
|
|
})
|
|
|
|
it('a broken provider report makes the store unhealthy and names the failing invariant', async () => {
|
|
brain.index.validateInvariants = async () => brokenReport('vector')
|
|
const v = await brain.validateIndexConsistency()
|
|
expect(v.healthy).toBe(false)
|
|
expect(v.recommendation).toMatch(/posted-count-floor/)
|
|
expect(v.recommendation).toMatch(/posted 2304 < canonical 2354/)
|
|
expect(v.recommendation).toMatch(/repairIndex\(\)/)
|
|
expect(v.providers?.some((p: ProviderInvariantReport) => p.provider === 'vector' && !p.healthy)).toBe(true)
|
|
delete brain.index.validateInvariants
|
|
})
|
|
|
|
it('all-healthy provider reports do not flip the store unhealthy', async () => {
|
|
brain.index.validateInvariants = async () => healthyReport('vector')
|
|
brain.graphIndex.validateInvariants = async () => healthyReport('graph')
|
|
const v = await brain.validateIndexConsistency()
|
|
expect(v.healthy).toBe(true)
|
|
expect(v.providers?.length).toBe(2)
|
|
delete brain.index.validateInvariants
|
|
delete brain.graphIndex.validateInvariants
|
|
})
|
|
|
|
it('a validateInvariants() that THROWS is surfaced as unhealthy, never swallowed', async () => {
|
|
brain.index.validateInvariants = async () => { throw new Error('provider blew up') }
|
|
const v = await brain.validateIndexConsistency()
|
|
expect(v.healthy).toBe(false)
|
|
expect(v.recommendation).toMatch(/validate-invariants-threw/)
|
|
delete brain.index.validateInvariants
|
|
})
|
|
|
|
it('providers without validateInvariants() are omitted (JS baseline unchanged)', async () => {
|
|
const v = await brain.validateIndexConsistency()
|
|
expect(v.providers).toBeUndefined()
|
|
expect(typeof v.healthy).toBe('boolean')
|
|
})
|
|
|
|
it('repairIndex() rebuilds a provider whose failing invariant asks for it', async () => {
|
|
let rebuilt = false
|
|
brain.index.validateInvariants = async () => (rebuilt ? healthyReport('vector') : brokenReport('vector'))
|
|
const origRebuild = brain.index.rebuild.bind(brain.index)
|
|
brain.index.rebuild = async (...a: any[]) => { rebuilt = true; return origRebuild(...a) }
|
|
await brain.repairIndex()
|
|
expect(rebuilt).toBe(true)
|
|
// After repair, the store validates healthy again.
|
|
const v = await brain.validateIndexConsistency()
|
|
expect(v.providers?.find((p: ProviderInvariantReport) => p.provider === 'vector')?.healthy).toBe(true)
|
|
brain.index.rebuild = origRebuild
|
|
delete brain.index.validateInvariants
|
|
})
|
|
})
|