fix(reads): the readiness gate guards every index read surface — serving empty from a not-ready provider is unrepresentable
A production store acked writes while readback served empty for fifteen minutes. The brainy half: the lazy readiness gate had exactly one caller — find() — while related() and every VFS path served straight from providers that init had deferred (disableAutoRebuild on a large store). A false health verdict from the accelerator pulled the trigger; the unguarded read surfaces were the gun. Every index read funnels through three helpers; the gate now lives at those choke points, so any first read on a cold instance waits for the build and then serves truth — the fast path after the latch is one boolean. The lazy build's start is narrated through the production logger, never the silent-suppressible console: fifteen silent minutes taught that line. Pinned with the production shape: related() as the first-ever read on a fresh lazy instance serves the relation; a filtered find serves the row.
This commit is contained in:
parent
1e046aa115
commit
40e7119b85
2 changed files with 95 additions and 1 deletions
|
|
@ -3907,6 +3907,12 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
uuid: string,
|
||||
options?: { direction?: 'in' | 'out' | 'both'; limit?: number; offset?: number }
|
||||
): Promise<string[]> {
|
||||
// READ-SURFACE READINESS GATE (the 4.2.4 blackout's brainy half): every
|
||||
// index read funnels through this helper, so the gate here makes
|
||||
// serve-while-not-ready UNREPRESENTABLE — a production store once acked
|
||||
// writes while every non-find() read served empty from a not-ready
|
||||
// provider for 15 minutes. Fast path after the latch is one boolean.
|
||||
await this.ensureIndexesLoaded()
|
||||
const entityInt = this.graphEntityInt(uuid)
|
||||
if (entityInt === undefined) return []
|
||||
const neighborInts = await this.graphIndex.getNeighbors(entityInt, options)
|
||||
|
|
@ -11807,6 +11813,12 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
filter: unknown,
|
||||
opts?: { limit?: number; offset?: number }
|
||||
): Promise<string[]> {
|
||||
// READ-SURFACE READINESS GATE (the 4.2.4 blackout's brainy half): every
|
||||
// index read funnels through this helper, so the gate here makes
|
||||
// serve-while-not-ready UNREPRESENTABLE — a production store once acked
|
||||
// writes while every non-find() read served empty from a not-ready
|
||||
// provider for 15 minutes. Fast path after the latch is one boolean.
|
||||
await this.ensureIndexesLoaded()
|
||||
try {
|
||||
return await this.metadataIndex.getIdsForFilter(filter, opts)
|
||||
} catch (err) {
|
||||
|
|
@ -14279,6 +14291,12 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
verbTypes?: Set<VerbType>,
|
||||
limit?: number
|
||||
): Promise<string[]> {
|
||||
// READ-SURFACE READINESS GATE (the 4.2.4 blackout's brainy half): every
|
||||
// index read funnels through this helper, so the gate here makes
|
||||
// serve-while-not-ready UNREPRESENTABLE — a production store once acked
|
||||
// writes while every non-find() read served empty from a not-ready
|
||||
// provider for 15 minutes. Fast path after the latch is one boolean.
|
||||
await this.ensureIndexesLoaded()
|
||||
// 8.0 BigInt boundary: unmapped node → no relations.
|
||||
const nodeInt = this.graphEntityInt(nodeId)
|
||||
if (nodeInt === undefined) return []
|
||||
|
|
@ -16217,7 +16235,15 @@ export class Brainy<T = any> implements BrainyInterface<T> {
|
|||
return
|
||||
}
|
||||
|
||||
// Start lazy rebuild (with mutex to prevent concurrent rebuilds)
|
||||
// Start lazy rebuild (with mutex to prevent concurrent rebuilds).
|
||||
// ALWAYS narrated (prodLog, never the silent-suppressible console): a
|
||||
// read that triggers an index build must be visible to the operator —
|
||||
// fifteen silent minutes of a production blackout taught this line.
|
||||
prodLog.warn(
|
||||
`[Brainy] first read on this instance is building the derived indexes ` +
|
||||
`(deferred at open by disableAutoRebuild) — reads WAIT and then serve; ` +
|
||||
`nothing serves empty. Bounded by store size; progress under [MetadataIndex]/[GraphIndex].`
|
||||
)
|
||||
this.lazyRebuildInProgress = true
|
||||
this.lazyRebuildPromise = this.rebuildIndexesIfNeeded(true)
|
||||
.then(() => {
|
||||
|
|
|
|||
Reference in a new issue