feat: validateIndexConsistency delegates to provider invariants (ADR-004 Pass 3)
validateIndexConsistency() was blind to native providers — it only ran the JS metadata index's own check, so a native provider whose manifest/segments/counts had diverged still read as "healthy". Per ADR-004 §6 it now feature-detects and aggregates each provider's optional validateInvariants() (a never-throwing, <50ms self-report of its own cross-layer invariants), names any failing invariant with its numbers in the recommendation, and exposes the per-provider reports. A provider that violates the never-throw contract is surfaced as unhealthy, never swallowed. repairIndex() now reconciles NATIVE derived state from canonical too: it consults each provider's invariants and calls rebuild() on any whose failing invariant asks for heal:'rebuild' — the native counterpart of detectAndRepairCorruption(). New provider surface: optional validateInvariants(); new exported types ProviderInvariantReport / InvariantResult / InvariantHeal. Additive, no break. Cor implements the hook in 3.0.15 (M2); brainy builds against the shape now (feature-detected, inert until a provider exposes it). 5 tests.
This commit is contained in:
parent
7b75f932d4
commit
6bcb54f0d9
4 changed files with 299 additions and 19 deletions
|
|
@ -115,6 +115,62 @@ export interface BrainyPluginContext {
|
|||
// implementation then fails to compile until it provides the member.
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* @description How a failed provider invariant should be remediated (ADR-004 §6):
|
||||
* - `'none'` — informational; the invariant held or nothing to do.
|
||||
* - `'repair'` — a targeted, cheap fix exists (e.g. re-derive a count/manifest field).
|
||||
* - `'rebuild'` — the derived state must be rebuilt from canonical (`provider.rebuild()`).
|
||||
*/
|
||||
export type InvariantHeal = 'none' | 'repair' | 'rebuild'
|
||||
|
||||
/**
|
||||
* @description The result of ONE provider invariant check (ADR-004 §6). A failure
|
||||
* (`holds === false`) NAMES what diverged, with numbers, so it is diagnosable
|
||||
* from the report alone — never a bare boolean. `name` is a stable kebab-case id
|
||||
* for telemetry / remediation routing.
|
||||
*/
|
||||
export interface InvariantResult {
|
||||
/** Stable kebab-case id, e.g. `'manifest-residency'` / `'posted-count-floor'`. */
|
||||
name: string
|
||||
/** `true` when the invariant holds. */
|
||||
holds: boolean
|
||||
/** Human-readable detail; on failure, names the divergence WITH numbers. */
|
||||
detail: string
|
||||
/** The value the invariant expected (optional, for diagnosis). */
|
||||
expected?: unknown
|
||||
/** The value actually observed (optional, for diagnosis). */
|
||||
actual?: unknown
|
||||
/** How a failure should be remediated. Ignored when `holds === true`. */
|
||||
heal: InvariantHeal
|
||||
}
|
||||
|
||||
/**
|
||||
* @description A provider's self-report of its own cross-layer invariants
|
||||
* (ADR-004 §6 — the `validateInvariants()` hook). Contract:
|
||||
* - It NEVER throws — a failure is DATA (`healthy: false` + a failing invariant),
|
||||
* not an exception.
|
||||
* - It is BOUNDED (<50ms): residency checks + O(1) counts only, NO canonical
|
||||
* walks — safe to call on a live brain, repeatedly.
|
||||
* - `serving` = can the provider answer queries right now (the `isReady()` truth);
|
||||
* `healthy` = do ALL invariants hold. A provider can be `serving` while an
|
||||
* invariant flags a latent divergence, or `healthy` but not-yet-`serving` on a
|
||||
* cold open.
|
||||
*/
|
||||
export interface ProviderInvariantReport {
|
||||
/** Which provider produced this report, e.g. `'vector'` / `'graph'` / `'metadata'` / `'column'`. */
|
||||
provider: string
|
||||
/** `true` iff every invariant in {@link invariants} holds. */
|
||||
healthy: boolean
|
||||
/** `true` iff the provider can serve queries now (the `isReady()` truth). */
|
||||
serving: boolean
|
||||
/** Each checked invariant and its verdict. */
|
||||
invariants: InvariantResult[]
|
||||
/** Epoch millis when the check ran. */
|
||||
checkedAt: number
|
||||
/** How long the check took (must stay well under 50ms). */
|
||||
durationMs: number
|
||||
}
|
||||
|
||||
/**
|
||||
* The `'metadataIndex'` provider — a drop-in for `MetadataIndexManager`.
|
||||
* Brainy calls this surface via `this.metadataIndex.*` (see `brainy.ts`) and
|
||||
|
|
@ -139,6 +195,18 @@ export interface MetadataIndexProvider {
|
|||
*/
|
||||
isReady?(): boolean
|
||||
|
||||
/**
|
||||
* @description OPTIONAL (ADR-004 §6). The provider's self-report of its own
|
||||
* cross-layer invariants (manifest ↔ segments ↔ counts residency/coherence).
|
||||
* MUST NOT throw — a failure is DATA (`healthy: false` + a failing invariant).
|
||||
* MUST be bounded (<50ms): residency + O(1) counts only, NO canonical walks, so
|
||||
* brainy's {@link } `validateIndexConsistency()` can call it on a live brain.
|
||||
* Absent → brainy skips this provider in the cross-layer check (feature-detected).
|
||||
* `repairIndex()` maps any failing invariant with `heal: 'rebuild'` to this
|
||||
* provider's `rebuild()`.
|
||||
*/
|
||||
validateInvariants?(): Promise<ProviderInvariantReport>
|
||||
|
||||
/**
|
||||
* @description OPTIONAL. A native provider returns true from the moment its
|
||||
* `init()` detects a large epoch-drift until its background
|
||||
|
|
@ -289,6 +357,18 @@ export interface GraphIndexProvider {
|
|||
*/
|
||||
isReady?(): boolean
|
||||
|
||||
/**
|
||||
* @description OPTIONAL (ADR-004 §6). The provider's self-report of its own
|
||||
* cross-layer invariants (manifest ↔ segments ↔ counts residency/coherence).
|
||||
* MUST NOT throw — a failure is DATA (`healthy: false` + a failing invariant).
|
||||
* MUST be bounded (<50ms): residency + O(1) counts only, NO canonical walks, so
|
||||
* brainy's {@link } `validateIndexConsistency()` can call it on a live brain.
|
||||
* Absent → brainy skips this provider in the cross-layer check (feature-detected).
|
||||
* `repairIndex()` maps any failing invariant with `heal: 'rebuild'` to this
|
||||
* provider's `rebuild()`.
|
||||
*/
|
||||
validateInvariants?(): Promise<ProviderInvariantReport>
|
||||
|
||||
/**
|
||||
* @description OPTIONAL eager cold-load. Called once during brain init — AFTER
|
||||
* the metadata provider's `init()` (so the id-mapper is hydrated; a native int
|
||||
|
|
@ -943,6 +1023,18 @@ export interface VectorIndexProvider {
|
|||
*/
|
||||
isReady?(): boolean
|
||||
|
||||
/**
|
||||
* @description OPTIONAL (ADR-004 §6). The provider's self-report of its own
|
||||
* cross-layer invariants (manifest ↔ segments ↔ counts residency/coherence).
|
||||
* MUST NOT throw — a failure is DATA (`healthy: false` + a failing invariant).
|
||||
* MUST be bounded (<50ms): residency + O(1) counts only, NO canonical walks, so
|
||||
* brainy's {@link } `validateIndexConsistency()` can call it on a live brain.
|
||||
* Absent → brainy skips this provider in the cross-layer check (feature-detected).
|
||||
* `repairIndex()` maps any failing invariant with `heal: 'rebuild'` to this
|
||||
* provider's `rebuild()`.
|
||||
*/
|
||||
validateInvariants?(): Promise<ProviderInvariantReport>
|
||||
|
||||
/**
|
||||
* @description OPTIONAL. A native provider returns true from the moment its
|
||||
* `init()` detects a large epoch-drift until its background
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue