fix(reads): the read gate is per-family; a write carrying unchanged data never re-embeds
Two cures from the pair's first production adoption, both measured live.
THE READ GATE IS PER-FAMILY. The report-driven gate refused on ANY
provider's not-ready verdict at every read choke point — so a pure
metadata find({ where }) was refused because the VECTOR leg was not
serving, and a deployment's badge reads returned errors for a verdict that
had nothing to do with them. A read may only be refused by the family it
actually consults: metadata reads by the metadata leg (plus graph for a
`connected` filter), vector search by the vector leg, traversal by the
graph leg. Callers name what they need; the existing narration-once-per-
generation and typed-refusal laws are unchanged within a family.
NO RE-EMBED ON UNCHANGED DATA. update() — and its transact() planner —
treated any write that carried `data` as a data change: with
deferEmbedding it queued a landing, and the worker re-embedded and re-landed
a vector for content that had not changed. A host heartbeat re-writing an
unchanged row every few seconds therefore fed a live index-row loop on a
production store. A write carrying the row's current data (structural
compare, key order normalized) is now not a data change: no re-embed, no
deferred landing, no vector rewrite; the metadata write itself still
commits. A real change re-embeds exactly as before.
Pinned in tests/integration/read-gate-scope-and-no-reembed.test.ts — both
pins red-proved against the unfixed code with the production shapes
verbatim. Two health-gate pins that encoded the old brain-global scope are
re-pointed to the family their reads consult.
This commit is contained in:
parent
21e506e802
commit
c039411e08
3 changed files with 152 additions and 13 deletions
|
|
@ -186,6 +186,8 @@ describe('health gate (b) — unledgered is unknown: never blocks a serving prov
|
|||
})
|
||||
|
||||
describe('health gate (c) — degraded-but-serving narrates once per generation', () => {
|
||||
// PER-FAMILY LAW (10.4.1): a metadata find() consults the METADATA leg only — the
|
||||
// degraded report lives on the family the read actually consults.
|
||||
it('a heal:"repair" failure serves; narrates once per generation, twice across a generation bump', async () => {
|
||||
const brain = new Brainy(createTestConfig({ silent: true }))
|
||||
await brain.init()
|
||||
|
|
@ -195,7 +197,7 @@ describe('health gate (c) — degraded-but-serving narrates once per generation'
|
|||
|
||||
const internals = internalsOf(brain)
|
||||
let generation = 1
|
||||
internals.index.healthReport = () =>
|
||||
internals.metadataIndex.healthReport = () =>
|
||||
healthReport({
|
||||
provider: 'vector',
|
||||
serving: true,
|
||||
|
|
@ -216,7 +218,7 @@ describe('health gate (c) — degraded-but-serving narrates once per generation'
|
|||
await expect(brain.find({ where: { team: 'atlas' } })).resolves.toHaveLength(1)
|
||||
expect(countNarrations()).toBe(2) // generation bumped — a second narration
|
||||
|
||||
delete internals.index.healthReport
|
||||
delete internals.metadataIndex.healthReport
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -332,6 +334,7 @@ describe('health gate (f) — the ceremony door: explicit rebuild bypasses invar
|
|||
})
|
||||
|
||||
describe('health gate (g) — a throwing healthReport() is a contract violation, never read as healthy', () => {
|
||||
// PER-FAMILY LAW (10.4.1): the throwing report sits on the family the read consults.
|
||||
it('healthReport() that throws refuses loudly with the typed NotReady error naming the throw', async () => {
|
||||
const brain = new Brainy(createTestConfig({ silent: true }))
|
||||
await brain.init()
|
||||
|
|
@ -340,13 +343,13 @@ describe('health gate (g) — a throwing healthReport() is a contract violation,
|
|||
await brain.flush()
|
||||
|
||||
const internals = internalsOf(brain)
|
||||
internals.index.healthReport = () => {
|
||||
internals.metadataIndex.healthReport = () => {
|
||||
throw new Error('accelerator: mmap window busy')
|
||||
}
|
||||
|
||||
await expect(brain.find({ where: { team: 'atlas' } })).rejects.toBeInstanceOf(VectorIndexNotReadyError)
|
||||
await expect(brain.find({ where: { team: 'atlas' } })).rejects.toBeInstanceOf(MetadataIndexNotReadyError)
|
||||
await expect(brain.find({ where: { team: 'atlas' } })).rejects.toThrow(/mmap window busy/)
|
||||
|
||||
delete internals.index.healthReport
|
||||
delete internals.metadataIndex.healthReport
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Reference in a new issue