fix: metadata cold-read guard — no more silent [] on cold find({where}) (7.33.5)
Companion to 7.33.4's graph cold-read guard, now for the metadata field index. 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.
verifyMetadataLive() — one-shot per brain on the first filtered find(): probe a
known persisted field value; if served, live (one O(1) probe on a warm brain). If
not, 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) resolve to live, no
false rebuild. Also exports BrainyError + GraphIndexNotReadyError (were internal).
The JS index cold-loads correctly; this guards the native path (durable cure is
cortex-side, same shape as the graph 2.7.8 cure). 4 unit tests. tsc 0, guard 4/4,
full unit 1538 pass (+1 pre-existing flaky VFS perf test, green in isolation).
This commit is contained in:
parent
2be3d0f88b
commit
9dc4c5e62b
5 changed files with 268 additions and 1 deletions
89
tests/unit/metadata-cold-read-guard.test.ts
Normal file
89
tests/unit/metadata-cold-read-guard.test.ts
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* Metadata cold-read guard (verifyMetadataLive) — 7.33.5 hotfix.
|
||||
*
|
||||
* 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), blanking filtered pages after every restart.
|
||||
* This guard, the field-index counterpart of verifyGraphAdjacencyLive (7.33.4),
|
||||
* 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 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 (7.33.5)', () => {
|
||||
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)
|
||||
expect(brain._metadataVerified).toBe(true)
|
||||
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
|
||||
mi.getIdsForFilter = async (...a: any[]) => (cold ? [] : origGetIds(...a))
|
||||
mi.rebuild = async () => {
|
||||
await origRebuild()
|
||||
cold = false
|
||||
}
|
||||
try {
|
||||
const res = await brain.find({ where: { status: 'active' }, limit: 100 })
|
||||
expect(res.length).toBe(1)
|
||||
} 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 () => []
|
||||
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 () => {
|
||||
brain._metadataVerified = false
|
||||
await brain.find({ vector: V(), limit: 5 })
|
||||
expect(brain._metadataVerified).toBe(false)
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue