feat(open): open never waits for a provider that is rebuilding itself
MEASURED on a production store: a metadata provider that had to rebuild made init() pay the ENTIRE rebuild on the foreground — 641 seconds — with every other family idle behind it. The cause is a missing distinction: a provider reporting serving:false because it is BUSY BUILDING ITSELF and one reporting serving:false because it is BROKEN looked identical through healthReport(), and both were answered the same way — call rebuild(), and wait for it. The contract that tells them apart is one optional, synchronous, O(1) hook: `rebuildInProgress(): ProviderRebuildProgress | null`, reporting a phase name and whatever the provider actually measures (done/total/startedAt) — never an estimate dressed as a fact. A provider without the hook behaves exactly as before. With it, a provider owns its own rebuild: - the open gate neither starts a second rebuild nor waits for the provider's, and narrates that it is not waiting and what will refuse meanwhile; - init() returns and every other family serves; - that family's doors refuse BY NAME, carrying the provider's own progress, and say plainly that the door opens by itself and no action is needed — distinct from a broken index, which names repairIndex(); - the epoch stamp does not advance while any family is still being built. Nothing is ever served empty: a not-serving family refuses, as it already did. Pins: tests/integration/open-does-not-wait-for-a-rebuilding-provider.test.ts — init() returns in milliseconds against a provider claiming a 6s rebuild, brainy starts no rebuild of its own, a filtered read refuses naming the phase and the 4,096/14,056 progress, and the door answers once the provider reports serving. The pin fails loudly rather than vacuously if its stub never installs.
This commit is contained in:
parent
f5a6cb3f61
commit
131daa08cd
3 changed files with 306 additions and 4 deletions
|
|
@ -153,3 +153,83 @@ export function assessProviderHealth(provider: unknown): ProviderHealthAssessmen
|
|||
reasons: readiness === 'not-ready' ? ['isReady() returned false'] : []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description A provider's self-report that it is REBUILDING ITS OWN index
|
||||
* right now. Returned by the optional `rebuildInProgress()` hook.
|
||||
*
|
||||
* The distinction this exists to make: a provider reporting `serving: false`
|
||||
* because it is BROKEN and a provider reporting `serving: false` because it is
|
||||
* BUSY BUILDING ITSELF look identical through `healthReport()` alone, and
|
||||
* brainy treated both the same way — it called `rebuild()` and waited for it,
|
||||
* on the foreground of `init()`. A production store whose metadata provider
|
||||
* had to rebuild paid 641 SECONDS of that wait before `init()` returned, with
|
||||
* every other family idle behind it.
|
||||
*
|
||||
* A provider that reports progress here owns its own rebuild: brainy neither
|
||||
* starts one nor waits for it, `init()` returns, the other families serve, and
|
||||
* THAT family's doors refuse by name — carrying this progress — until the
|
||||
* provider reports itself serving.
|
||||
*
|
||||
* Every field but `phase` is optional and every field is a MEASUREMENT: a
|
||||
* provider reports only what it actually tracks, never an estimate dressed as
|
||||
* a fact.
|
||||
*/
|
||||
export interface ProviderRebuildProgress {
|
||||
/** The provider's own name for what it is doing. Quoted verbatim in refusals. */
|
||||
phase: string
|
||||
/** Units completed so far, if the provider counts them. */
|
||||
done?: number
|
||||
/** Units expected in total, if the provider knows it. */
|
||||
total?: number
|
||||
/** Epoch millis when this rebuild started, if the provider tracks it. */
|
||||
startedAt?: number
|
||||
}
|
||||
|
||||
/** A provider that can report a rebuild it is running itself. */
|
||||
interface MaybeRebuildingProvider {
|
||||
rebuildInProgress?: () => ProviderRebuildProgress | null
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Ask a provider whether it is rebuilding itself right now.
|
||||
* Synchronous, O(1), feature-detected: a provider without the hook reports
|
||||
* nothing and is treated exactly as before.
|
||||
* @param provider - Any index provider, or `null`/`undefined`.
|
||||
* @returns The provider's progress, or `null` when it is not rebuilding (or
|
||||
* does not implement the hook).
|
||||
*/
|
||||
export function assessProviderRebuild(provider: unknown): ProviderRebuildProgress | null {
|
||||
const p = provider as MaybeRebuildingProvider | null | undefined
|
||||
if (p == null || typeof p.rebuildInProgress !== 'function') return null
|
||||
try {
|
||||
const progress = p.rebuildInProgress()
|
||||
if (!progress || typeof progress.phase !== 'string' || progress.phase.length === 0) {
|
||||
return null
|
||||
}
|
||||
return progress
|
||||
} catch {
|
||||
// A throwing hook says nothing trustworthy about a rebuild; fall through to
|
||||
// the ordinary health verdict rather than inventing one.
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Render a rebuild progress report as one operator-facing clause,
|
||||
* for a refusal message. Includes only what the provider actually measured.
|
||||
* @param progress - The provider's report.
|
||||
* @returns A clause such as `rebuilding ("metadata shadow build", 4,096/14,056, 12s elapsed)`.
|
||||
*/
|
||||
export function describeRebuildProgress(progress: ProviderRebuildProgress): string {
|
||||
const parts: string[] = [`"${progress.phase}"`]
|
||||
if (typeof progress.done === 'number' && typeof progress.total === 'number') {
|
||||
parts.push(`${progress.done.toLocaleString()}/${progress.total.toLocaleString()}`)
|
||||
} else if (typeof progress.done === 'number') {
|
||||
parts.push(`${progress.done.toLocaleString()} done`)
|
||||
}
|
||||
if (typeof progress.startedAt === 'number') {
|
||||
parts.push(`${Math.round((Date.now() - progress.startedAt) / 1000)}s elapsed`)
|
||||
}
|
||||
return `rebuilding (${parts.join(', ')})`
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue