A downstream deployment reported find({where}) returning a silent [] on a
freshly-opened brain (a native metadata provider that reports data but hasn't
loaded its field postings), blanking filtered pages after every restart. Brainy
had a cold-read guard for GRAPH reads (verifyGraphAdjacencyLive → self-heal or a
loud GraphIndexNotReadyError) but no equivalent for metadata `where`.
Add verifyMetadataLive() — the field-index counterpart, one-shot per brain on the
first filtered find(): probe a known persisted entity's plain field value; if the
index serves it, live (the only cost on a warm brain — one O(1) probe). If not,
the postings didn't load: rebuild from canonical + re-probe; if still unserved,
throw the new exported MetadataIndexNotReadyError rather than let a silent [] pass.
Inconclusive cases (empty store, no plain field, shared-store foreign entity,
migrating provider) resolve to live — never a false rebuild.
The 8.0 open-core JS index already cold-loads correctly (verified: cold where 3/3,
cold related 2/2) — this guards the NATIVE path, where the durable cold-load cure
is cortex-side (same shape as the graph 2.7.8 cure). 4 unit tests (warm no-rebuild,
self-heal, loud-fail, no-filter-no-probe). Gates: typecheck 0, build 0, test:unit
1757/1757.
97 lines
3.9 KiB
TypeScript
97 lines
3.9 KiB
TypeScript
/**
|
|
* Metadata cold-read guard (verifyMetadataLive) — a downstream deployment
|
|
* reported cold `find({ where })` returning a silent `[]` on a freshly-opened
|
|
* brain (a native metadata index that reports data but has not loaded its field
|
|
* postings). This guard, the field-index counterpart of verifyGraphAdjacencyLive,
|
|
* probes a known persisted value on the first filtered find(): if the index does
|
|
* not serve it, brainy rebuilds and re-probes, and raises a loud
|
|
* MetadataIndexNotReadyError only if the rebuild still can't serve — never a
|
|
* silent empty result that misrepresents existing data.
|
|
*
|
|
* The 8.0 JS index cold-loads correctly, so we simulate the cold native failure
|
|
* mode by intercepting the provider's getIdsForFilter/rebuild.
|
|
*/
|
|
import { describe, it, expect, beforeEach } from 'vitest'
|
|
import { Brainy, NounType, MetadataIndexNotReadyError } from '../../src/index.js'
|
|
|
|
const V = () => Array.from({ length: 384 }, (_, i) => Math.sin(i * 0.1) + 0.001)
|
|
|
|
describe('Metadata cold-read guard (#venue silent-[])', () => {
|
|
let brain: any
|
|
|
|
beforeEach(async () => {
|
|
brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
|
|
await brain.init()
|
|
await brain.add({ vector: V(), type: NounType.Concept, metadata: { status: 'active' } })
|
|
await brain.add({ vector: V(), type: NounType.Concept, metadata: { status: 'archived' } })
|
|
await brain.flush()
|
|
})
|
|
|
|
it('warm brain: filtered find is correct and the guard does not rebuild', async () => {
|
|
const mi = brain.metadataIndex
|
|
let rebuilds = 0
|
|
const origRebuild = mi.rebuild.bind(mi)
|
|
mi.rebuild = async () => {
|
|
rebuilds++
|
|
return origRebuild()
|
|
}
|
|
const res = await brain.find({ where: { status: 'active' }, limit: 100 })
|
|
expect(res.length).toBe(1)
|
|
expect(rebuilds).toBe(0) // served live — no rebuild
|
|
expect(brain._metadataVerified).toBe(true) // one-shot latched
|
|
mi.rebuild = origRebuild
|
|
})
|
|
|
|
it('cold index: verifyMetadataLive self-heals via rebuild — find({where}) is correct, NOT silent []', async () => {
|
|
const mi = brain.metadataIndex
|
|
const origGetIds = mi.getIdsForFilter.bind(mi)
|
|
const origRebuild = mi.rebuild.bind(mi)
|
|
let cold = true
|
|
brain._metadataVerified = false // re-arm the one-shot for this scenario
|
|
mi.getIdsForFilter = async (...a: any[]) => (cold ? [] : origGetIds(...a))
|
|
mi.rebuild = async () => {
|
|
await origRebuild()
|
|
cold = false // the rebuild warms the postings
|
|
}
|
|
try {
|
|
const res = await brain.find({ where: { status: 'active' }, limit: 100 })
|
|
expect(res.length).toBe(1) // self-healed — the known entity is returned
|
|
} finally {
|
|
mi.getIdsForFilter = origGetIds
|
|
mi.rebuild = origRebuild
|
|
}
|
|
})
|
|
|
|
it('unrecoverably cold index: find({where}) throws MetadataIndexNotReadyError — never a silent []', async () => {
|
|
const mi = brain.metadataIndex
|
|
const origGetIds = mi.getIdsForFilter.bind(mi)
|
|
const origRebuild = mi.rebuild.bind(mi)
|
|
brain._metadataVerified = false
|
|
mi.getIdsForFilter = async () => [] // always cold; rebuild can't fix it
|
|
mi.rebuild = async () => {}
|
|
try {
|
|
await expect(brain.find({ where: { status: 'active' }, limit: 100 })).rejects.toBeInstanceOf(
|
|
MetadataIndexNotReadyError
|
|
)
|
|
} finally {
|
|
mi.getIdsForFilter = origGetIds
|
|
mi.rebuild = origRebuild
|
|
}
|
|
})
|
|
|
|
it('a query with no filter does not trigger the metadata probe', async () => {
|
|
const mi = brain.metadataIndex
|
|
let probes = 0
|
|
const origGetIds = mi.getIdsForFilter.bind(mi)
|
|
mi.getIdsForFilter = async (...a: any[]) => {
|
|
probes++
|
|
return origGetIds(...a)
|
|
}
|
|
brain._metadataVerified = false
|
|
// A pure vector query (no where/type) must not run verifyMetadataLive's probe.
|
|
await brain.find({ vector: V(), limit: 5 })
|
|
expect(brain._metadataVerified).toBe(false) // guard never ran
|
|
mi.getIdsForFilter = origGetIds
|
|
void probes
|
|
})
|
|
})
|