fix(8.0): gate native graph analytics on the provider readiness flag

The optional native graph-acceleration provider exposes `isInitialized`,
which is false during its cold-start / rebuild window (engine + cursor not
yet loaded). The accessor cached the resolved provider INSTANCE but never
re-checked readiness, so `brain.graph.*` could dispatch to a not-ready
provider and throw instead of answering.

Resolve and cache the instance once, but check `isInitialized` LIVE on every
call: route to the pure-TS analytics path while the provider is not ready,
and re-engage the native path automatically the moment it flips true. This
gates every native route at once — subgraph, export, rank, communities,
path, and the query→expand fusion. Adds a test asserting the provider is
never called (and the TS fallback returns real answers) while not ready.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Snelling 2026-06-24 15:18:50 -07:00
parent bf0afe8563
commit d321cf5f33
2 changed files with 65 additions and 19 deletions

View file

@ -3299,25 +3299,30 @@ export class Brainy<T = any> implements BrainyInterface<T> {
/** Resolve the optional native graph-acceleration provider (feature-detected). */
private graphAccelerationProvider(): GraphAccelerationProvider | undefined {
if (this._graphAccelProvider !== undefined) {
return this._graphAccelProvider ?? undefined
}
// Resolve the optional 'graphAcceleration' provider once and cache it. Accept
// EITHER a ready instance OR a `(storage) => provider` factory (the convention
// the graphIndex/metadataIndex/vector providers use) — a registered factory
// would otherwise fail the isGraphAccelerationProvider duck-test and the native
// path would silently never engage.
const raw = this.pluginRegistry.getProvider<unknown>('graphAcceleration')
let resolved: unknown = raw
if (typeof raw === 'function' && !isGraphAccelerationProvider(raw)) {
try {
resolved = (raw as (storage: StorageAdapter) => unknown)(this.storage)
} catch {
resolved = undefined
// Resolve + cache the provider INSTANCE once. Accept EITHER a ready instance OR
// a `(storage) => provider` factory (the convention the graphIndex/metadataIndex/
// vector providers use) — a registered factory would otherwise fail the
// isGraphAccelerationProvider duck-test and the native path would silently never engage.
if (this._graphAccelProvider === undefined) {
const raw = this.pluginRegistry.getProvider<unknown>('graphAcceleration')
let resolved: unknown = raw
if (typeof raw === 'function' && !isGraphAccelerationProvider(raw)) {
try {
resolved = (raw as (storage: StorageAdapter) => unknown)(this.storage)
} catch {
resolved = undefined
}
}
this._graphAccelProvider = isGraphAccelerationProvider(resolved) ? resolved : null
}
this._graphAccelProvider = isGraphAccelerationProvider(resolved) ? resolved : null
return this._graphAccelProvider ?? undefined
const provider = this._graphAccelProvider ?? undefined
// Readiness gate — checked LIVE each call, never cached: the native engine sets
// `isInitialized` false until its engine + cursor have loaded (the cold-start /
// rebuild window). Until then, route to the pure-TS path rather than calling a
// not-ready provider (which throws) — and re-engage the native path automatically
// the moment `isInitialized` flips true. Gates EVERY `brain.graph.*` route
// (subgraph/export/rank/communities/path + the query→expand fusion) at once.
return provider && provider.isInitialized ? provider : undefined
}
/**