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).
This commit is contained in:
parent
a8b5ca0c8f
commit
f8f64780b1
19 changed files with 2160 additions and 652 deletions
|
|
@ -1,15 +1,18 @@
|
|||
/**
|
||||
* @module tests/unit/brainy/metadata-provider-contract
|
||||
* @description Brainy-side wiring of the two metadata-provider contract additions
|
||||
* confirmed with cor for the lockstep:
|
||||
* @description Brainy-side wiring of the metadata-provider contract.
|
||||
*
|
||||
* 1. `probeConsistency()` — an OPTIONAL O(1) cold-open consistency sampler. On the
|
||||
* first read, brainy calls it once; on `false` it self-heals via
|
||||
* `detectAndRepairCorruption()` (the metadata counterpart of the graph cold-load
|
||||
* guard). The native provider implements it; the JS index omits it (no-op).
|
||||
* 2. `getIdsForFilter(filter, opts?)` — brainy passes a page bound on the UNSORTED
|
||||
* `find({ type, where, limit })` path so a native provider can early-stop. The JS
|
||||
* index ignores `opts`.
|
||||
* `getIdsForFilter(filter, opts?)` — brainy passes a page bound on the UNSORTED
|
||||
* `find({ type, where, limit })` path so a native provider can early-stop. The JS
|
||||
* index ignores `opts`.
|
||||
*
|
||||
* RETIRED (health-gate law): `probeConsistency()` / `ensureMetadataConsistencyProbed()`
|
||||
* — a read-time consistency probe that launches `detectAndRepairCorruption()` on
|
||||
* `false` was exactly the read-triggered dark rebuild the law forbids (a read must
|
||||
* never start a store walk or a rebuild). The probe's diagnostic value lives on in
|
||||
* `validateIndexConsistency()` / `repairIndex()`, which remain explicit, operator-invoked
|
||||
* calls. The pin below confirms the retirement: `probeConsistency()` is never called by
|
||||
* a read, even when a provider exposes it.
|
||||
*
|
||||
* These are unit tests of brainy's CALL behaviour (the real end-to-end honoring is
|
||||
* exercised by cor's combined matrix); they inject probe/spy hooks onto the live JS
|
||||
|
|
@ -19,7 +22,7 @@ import { describe, it, expect, beforeEach } from 'vitest'
|
|||
import { Brainy } from '../../../src/brainy'
|
||||
import { NounType } from '../../../src/types/graphTypes'
|
||||
|
||||
describe('metadata-provider contract wiring (probeConsistency + getIdsForFilter opts)', () => {
|
||||
describe('metadata-provider contract wiring (getIdsForFilter opts)', () => {
|
||||
let brain: Brainy<any>
|
||||
let mi: any
|
||||
|
||||
|
|
@ -29,47 +32,23 @@ describe('metadata-provider contract wiring (probeConsistency + getIdsForFilter
|
|||
await brain.add({ data: 'a', type: NounType.Thing, metadata: { kind: 'x' } })
|
||||
await brain.add({ data: 'b', type: NounType.Thing, metadata: { kind: 'y' } })
|
||||
mi = (brain as any).metadataIndex
|
||||
;(brain as any)._metadataConsistencyProbed = false // reset the one-shot guard
|
||||
})
|
||||
|
||||
it('calls probeConsistency once on cold open and self-heals via detectAndRepairCorruption on false', async () => {
|
||||
it('RETIRED: a read never calls probeConsistency() / self-heals via detectAndRepairCorruption — that is the read-triggered dark rebuild the health-gate law forbids', async () => {
|
||||
let probes = 0
|
||||
let repairs = 0
|
||||
mi.probeConsistency = async () => { probes++; return false } // corrupt → must repair
|
||||
mi.probeConsistency = async () => { probes++; return false } // would-be corrupt signal
|
||||
const origRepair = mi.detectAndRepairCorruption.bind(mi)
|
||||
mi.detectAndRepairCorruption = async () => { repairs++; return origRepair() }
|
||||
|
||||
await brain.find({ where: { kind: 'x' } })
|
||||
expect(probes).toBe(1)
|
||||
expect(repairs).toBe(1)
|
||||
|
||||
// Second read must NOT re-probe (once per brain).
|
||||
await brain.find({ where: { kind: 'y' } })
|
||||
expect(probes).toBe(1)
|
||||
expect(repairs).toBe(1)
|
||||
})
|
||||
|
||||
it('does NOT repair when the probe reports healthy', async () => {
|
||||
let repairs = 0
|
||||
mi.probeConsistency = async () => true // clean
|
||||
const origRepair = mi.detectAndRepairCorruption.bind(mi)
|
||||
mi.detectAndRepairCorruption = async () => { repairs++; return origRepair() }
|
||||
expect(probes).toBe(0) // no read-time probe exists anymore
|
||||
expect(repairs).toBe(0) // and therefore no read-triggered self-heal either
|
||||
|
||||
await brain.find({ where: { kind: 'x' } })
|
||||
expect(repairs).toBe(0)
|
||||
})
|
||||
|
||||
it('a probe failure never breaks the read (best-effort, retried next time)', async () => {
|
||||
let probes = 0
|
||||
mi.probeConsistency = async () => { probes++; throw new Error('probe boom') }
|
||||
|
||||
// The read still succeeds despite the throwing probe.
|
||||
const rows = await brain.find({ where: { kind: 'x' } })
|
||||
expect(rows.length).toBe(1)
|
||||
expect(probes).toBe(1)
|
||||
// Guard reset on failure → the next read retries the probe.
|
||||
await brain.find({ where: { kind: 'y' } })
|
||||
expect(probes).toBe(2)
|
||||
delete mi.probeConsistency
|
||||
mi.detectAndRepairCorruption = origRepair
|
||||
})
|
||||
|
||||
it('passes a page bound to getIdsForFilter on the unsorted find path (offset 0, brainy re-windows)', async () => {
|
||||
|
|
|
|||
Reference in a new issue