open-brainy/tests/unit/metadata-cold-read-guard.test.ts

84 lines
3.6 KiB
TypeScript
Raw Normal View History

/**
* 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,
feat(health): the gate reads the named report — reads refuse loudly, never rebuild; open serves before it returns; the ceremony door The read gate stops consulting the unnamed isReady() boolean: every provider may expose healthReport() (sync, O(1), composed from exact ledgers — HealthReport with a monotonic generation, per-invariant source ledger|deep|unledgered, missing {count, sample}), and one readiness authority (assessProviderHealth) derives the verdict. Unledgered families are UNKNOWN — never healthy, never broken; a report that throws is a loud not-ready, never a shrug. Reads at the four index choke points refuse with the typed NotReady errors, narrated once per (provider, generation) — a read NEVER starts a store walk: - the first-read lazy build retires (open builds instead, regardless of size — the ≥10k deferral and the "lazy loading on first query" branch go; disableAutoRebuild is re-meant honestly in its docs); - the verify*Live read-path rebuild triggers retire (refuse-or-serve); - the read-time consistency probe that could launch a dark rebuild from an ordinary find() retires; - repairIndex({ rebuild: ['metadata'|'graph'|'vector'] | 'all' }) is the one explicit door: rebuilds the named leg unconditionally and reports rebuilt per family; bare repairIndex() stays report-driven. test(lifecycle): the biography lane — a store's whole life, refereed tests/lifecycle/: an independent shadow model referees every read after every chapter (founding, a working day, clean restart, crash, repair, second life). Chapters 1-3 green. Chapters 4-6 assert the true contract and are marked .fails as a release-blocking finding (the kill-matrix convention): after a crash + adopt reopen the metadata index computes its 'catchup' watermark verdict and nothing consumes it — find() serves the pre-crash index while canonical and counts recover. The catchup wiring is the cure; a passing .fails will force the marker's removal. The lane runs in the integration gate (config + coverage guard).
2026-08-24 12:45:51 -07:00
* probes a known persisted value on the first filtered find().
*
* RE-POINTED to the health-gate law: the guard NEVER rebuilds and NEVER walks
* the store from a read a read-path rebuild is exactly the dark-rebuild
* failure mode the law retires (open() alone owns building). When the probe
* cannot serve the known value it raises a loud MetadataIndexNotReadyError
* IMMEDIATELY, with no rebuild attempt in between 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
})
feat(health): the gate reads the named report — reads refuse loudly, never rebuild; open serves before it returns; the ceremony door The read gate stops consulting the unnamed isReady() boolean: every provider may expose healthReport() (sync, O(1), composed from exact ledgers — HealthReport with a monotonic generation, per-invariant source ledger|deep|unledgered, missing {count, sample}), and one readiness authority (assessProviderHealth) derives the verdict. Unledgered families are UNKNOWN — never healthy, never broken; a report that throws is a loud not-ready, never a shrug. Reads at the four index choke points refuse with the typed NotReady errors, narrated once per (provider, generation) — a read NEVER starts a store walk: - the first-read lazy build retires (open builds instead, regardless of size — the ≥10k deferral and the "lazy loading on first query" branch go; disableAutoRebuild is re-meant honestly in its docs); - the verify*Live read-path rebuild triggers retire (refuse-or-serve); - the read-time consistency probe that could launch a dark rebuild from an ordinary find() retires; - repairIndex({ rebuild: ['metadata'|'graph'|'vector'] | 'all' }) is the one explicit door: rebuilds the named leg unconditionally and reports rebuilt per family; bare repairIndex() stays report-driven. test(lifecycle): the biography lane — a store's whole life, refereed tests/lifecycle/: an independent shadow model referees every read after every chapter (founding, a working day, clean restart, crash, repair, second life). Chapters 1-3 green. Chapters 4-6 assert the true contract and are marked .fails as a release-blocking finding (the kill-matrix convention): after a crash + adopt reopen the metadata index computes its 'catchup' watermark verdict and nothing consumes it — find() serves the pre-crash index while canonical and counts recover. The catchup wiring is the cure; a passing .fails will force the marker's removal. The lane runs in the integration gate (config + coverage guard).
2026-08-24 12:45:51 -07:00
it('cold index: verifyMetadataLive REFUSES immediately — find({where}) throws MetadataIndexNotReadyError, NEVER a silent [], and NEVER a rebuild attempt', async () => {
const mi = brain.metadataIndex
const origGetIds = mi.getIdsForFilter.bind(mi)
feat(health): the gate reads the named report — reads refuse loudly, never rebuild; open serves before it returns; the ceremony door The read gate stops consulting the unnamed isReady() boolean: every provider may expose healthReport() (sync, O(1), composed from exact ledgers — HealthReport with a monotonic generation, per-invariant source ledger|deep|unledgered, missing {count, sample}), and one readiness authority (assessProviderHealth) derives the verdict. Unledgered families are UNKNOWN — never healthy, never broken; a report that throws is a loud not-ready, never a shrug. Reads at the four index choke points refuse with the typed NotReady errors, narrated once per (provider, generation) — a read NEVER starts a store walk: - the first-read lazy build retires (open builds instead, regardless of size — the ≥10k deferral and the "lazy loading on first query" branch go; disableAutoRebuild is re-meant honestly in its docs); - the verify*Live read-path rebuild triggers retire (refuse-or-serve); - the read-time consistency probe that could launch a dark rebuild from an ordinary find() retires; - repairIndex({ rebuild: ['metadata'|'graph'|'vector'] | 'all' }) is the one explicit door: rebuilds the named leg unconditionally and reports rebuilt per family; bare repairIndex() stays report-driven. test(lifecycle): the biography lane — a store's whole life, refereed tests/lifecycle/: an independent shadow model referees every read after every chapter (founding, a working day, clean restart, crash, repair, second life). Chapters 1-3 green. Chapters 4-6 assert the true contract and are marked .fails as a release-blocking finding (the kill-matrix convention): after a crash + adopt reopen the metadata index computes its 'catchup' watermark verdict and nothing consumes it — find() serves the pre-crash index while canonical and counts recover. The catchup wiring is the cure; a passing .fails will force the marker's removal. The lane runs in the integration gate (config + coverage guard).
2026-08-24 12:45:51 -07:00
let rebuilds = 0
const origRebuild = mi.rebuild.bind(mi)
brain._metadataVerified = false // re-arm the one-shot for this scenario
feat(health): the gate reads the named report — reads refuse loudly, never rebuild; open serves before it returns; the ceremony door The read gate stops consulting the unnamed isReady() boolean: every provider may expose healthReport() (sync, O(1), composed from exact ledgers — HealthReport with a monotonic generation, per-invariant source ledger|deep|unledgered, missing {count, sample}), and one readiness authority (assessProviderHealth) derives the verdict. Unledgered families are UNKNOWN — never healthy, never broken; a report that throws is a loud not-ready, never a shrug. Reads at the four index choke points refuse with the typed NotReady errors, narrated once per (provider, generation) — a read NEVER starts a store walk: - the first-read lazy build retires (open builds instead, regardless of size — the ≥10k deferral and the "lazy loading on first query" branch go; disableAutoRebuild is re-meant honestly in its docs); - the verify*Live read-path rebuild triggers retire (refuse-or-serve); - the read-time consistency probe that could launch a dark rebuild from an ordinary find() retires; - repairIndex({ rebuild: ['metadata'|'graph'|'vector'] | 'all' }) is the one explicit door: rebuilds the named leg unconditionally and reports rebuilt per family; bare repairIndex() stays report-driven. test(lifecycle): the biography lane — a store's whole life, refereed tests/lifecycle/: an independent shadow model referees every read after every chapter (founding, a working day, clean restart, crash, repair, second life). Chapters 1-3 green. Chapters 4-6 assert the true contract and are marked .fails as a release-blocking finding (the kill-matrix convention): after a crash + adopt reopen the metadata index computes its 'catchup' watermark verdict and nothing consumes it — find() serves the pre-crash index while canonical and counts recover. The catchup wiring is the cure; a passing .fails will force the marker's removal. The lane runs in the integration gate (config + coverage guard).
2026-08-24 12:45:51 -07:00
mi.getIdsForFilter = async () => [] // cold: the known value never resolves
mi.rebuild = async () => { rebuilds++; return origRebuild() }
try {
await expect(brain.find({ where: { status: 'active' }, limit: 100 })).rejects.toBeInstanceOf(
MetadataIndexNotReadyError
)
feat(health): the gate reads the named report — reads refuse loudly, never rebuild; open serves before it returns; the ceremony door The read gate stops consulting the unnamed isReady() boolean: every provider may expose healthReport() (sync, O(1), composed from exact ledgers — HealthReport with a monotonic generation, per-invariant source ledger|deep|unledgered, missing {count, sample}), and one readiness authority (assessProviderHealth) derives the verdict. Unledgered families are UNKNOWN — never healthy, never broken; a report that throws is a loud not-ready, never a shrug. Reads at the four index choke points refuse with the typed NotReady errors, narrated once per (provider, generation) — a read NEVER starts a store walk: - the first-read lazy build retires (open builds instead, regardless of size — the ≥10k deferral and the "lazy loading on first query" branch go; disableAutoRebuild is re-meant honestly in its docs); - the verify*Live read-path rebuild triggers retire (refuse-or-serve); - the read-time consistency probe that could launch a dark rebuild from an ordinary find() retires; - repairIndex({ rebuild: ['metadata'|'graph'|'vector'] | 'all' }) is the one explicit door: rebuilds the named leg unconditionally and reports rebuilt per family; bare repairIndex() stays report-driven. test(lifecycle): the biography lane — a store's whole life, refereed tests/lifecycle/: an independent shadow model referees every read after every chapter (founding, a working day, clean restart, crash, repair, second life). Chapters 1-3 green. Chapters 4-6 assert the true contract and are marked .fails as a release-blocking finding (the kill-matrix convention): after a crash + adopt reopen the metadata index computes its 'catchup' watermark verdict and nothing consumes it — find() serves the pre-crash index while canonical and counts recover. The catchup wiring is the cure; a passing .fails will force the marker's removal. The lane runs in the integration gate (config + coverage guard).
2026-08-24 12:45:51 -07:00
expect(rebuilds).toBe(0) // the guard never rebuilds from a read — it refuses loudly instead
} 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
})
})