fix(health): one contract for a throwing probe — heal is none, serving is not withheld; repair report gains missing/rebuilt/reason
Some checks are pending
CI / Node 22 (push) Waiting to run
CI / Node 24 (push) Waiting to run
CI / Integration + conformance (Node 22) (push) Waiting to run
CI / Bun (latest) (push) Waiting to run

A validateInvariants() that threw was re-synthesized by the host's catch as
heal:'rebuild' with serving:false — a rebuild lever one transient exception
away, while the native provider's own composer reports the same event as
heal:'none'. Two components disagreeing on what a thrown check means is how
a flaky probe becomes an outage. Both now agree: the report is named
('validate-invariants-threw'), loud (healthy:false, the error in detail),
unverified — and it never buys a rebuild and never withholds serving; the
provider's serving verdict is the provider's to compose, not inferred from a
probe that failed to run. The synthesized report also carries the provider's
name instead of 'unknown'.

RepairFamilyReport gains `missing: {count, sample}` (an exact count plus a
capped id sample — a verdict, not a dump), `rebuilt` (a full generational
rebuild ran, as opposed to an incremental heal) and `reason`, aligning the
receipt's shape with the provider-side health report.

Pinned in tests/unit/validate-invariants-delegation.test.ts.
This commit is contained in:
David Snelling 2026-08-24 09:54:25 -07:00
parent 7c8c8be30c
commit 116550eb16
3 changed files with 54 additions and 3 deletions

View file

@ -14221,16 +14221,29 @@ export class Brainy<T = any> implements BrainyInterface<T> {
const report = await fn.call(provider)
if (report && Array.isArray(report.invariants)) reports.push(report)
} catch (err) {
// ONE CONTRACT FOR A THROWING PROBE, both engines: a probe that throws
// is `heal: 'none'` with the error in `detail` — flakiness can never
// buy a rebuild, and a thrown check never changes `serving` (the
// provider's serving verdict is composed by the provider, not inferred
// from a probe that failed to run). This catch used to synthesize
// `heal: 'rebuild'` — the read-triggered dark-rebuild lever one
// transient exception away — while the native composer said 'none';
// two components disagreeing on what a throw means is how a flaky
// probe became an outage. `healthy: false` stays: an unrunnable probe
// is a named, loud, unverified state, never a clean bill.
const name = typeof (provider as { name?: string })?.name === 'string'
? (provider as { name: string }).name
: 'unknown'
reports.push({
provider: 'unknown',
provider: name,
healthy: false,
serving: false,
serving: true,
invariants: [
{
name: 'validate-invariants-threw',
holds: false,
detail: `validateInvariants() threw (contract violation — it must never throw): ${(err as Error).message}`,
heal: 'rebuild'
heal: 'none'
}
],
checkedAt: Date.now(),

View file

@ -1200,10 +1200,23 @@ export interface RelateManyParams<T = any> {
*/
export interface RepairFamilyReport {
family: string
/** The family was actually examined (false = skipped; see `skipped`/`reason`). */
checked: boolean
/** Items re-posted / corrected in place — the incremental heal count. */
healed: number
/**
* What the check found missing or divergent, when it can name it: an exact
* count plus a capped sample of ids (never the whole list a report is a
* verdict, not a dump). Absent when the family has nothing to name.
*/
missing?: { count: number; sample: string[] }
/** A full generational rebuild of this family ran (as opposed to an incremental heal). */
rebuilt?: boolean
detail?: string
/** Why the family was not checked (`checked: false`). */
skipped?: string
/** Why the outcome is what it is when neither `detail` nor `skipped` says it. */
reason?: string
}
/** The full receipt returned by repairIndex(). */