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:
parent
bf0afe8563
commit
d321cf5f33
2 changed files with 65 additions and 19 deletions
|
|
@ -18,7 +18,7 @@ import { NounType, VerbType } from '../../../src/types/graphTypes.js'
|
|||
import { createTestConfig } from '../../helpers/test-factory.js'
|
||||
|
||||
/** A stateful mock GraphAccelerationProvider; the test sets the columnar payloads. */
|
||||
function makeMockAccel() {
|
||||
function makeMockAccel(opts: { isInitialized?: boolean } = {}) {
|
||||
const calls = { traverse: 0, cursorOpen: 0, cursorNext: 0, cursorClose: 0, rank: 0, communities: 0, path: 0 }
|
||||
const state: {
|
||||
calls: typeof calls
|
||||
|
|
@ -44,7 +44,7 @@ function makeMockAccel() {
|
|||
communityCount: 0
|
||||
}
|
||||
const provider = {
|
||||
isInitialized: true,
|
||||
isInitialized: opts.isInitialized ?? true,
|
||||
traverse: async (seeds: any) => {
|
||||
calls.traverse++
|
||||
state.lastTraverseSeeds = seeds
|
||||
|
|
@ -290,4 +290,45 @@ describe('brain.graph.* native routing + columnar hydration (native seam)', () =
|
|||
expect(Array.isArray(mock.state.lastTraverseSeeds)).toBe(true) // bigint[], not a Buffer
|
||||
expect(typeof (mock.state.lastTraverseSeeds as unknown[])[0]).toBe('bigint')
|
||||
})
|
||||
|
||||
// Readiness gate (cor boundary-audit item #1): the native engine reports
|
||||
// isInitialized=false during its cold-start/rebuild window. Brainy must route to the
|
||||
// pure-TS path then — NOT call the not-ready provider (which would throw) — across the
|
||||
// whole brain.graph.* surface, and re-engage the native path once it flips true.
|
||||
it('routes to the TS fallback (never calls the provider) while accel.isInitialized is false', async () => {
|
||||
const notReady = makeMockAccel({ isInitialized: false })
|
||||
const b = new Brainy(createTestConfig())
|
||||
b.use({
|
||||
name: 'mock-graph-accel-not-ready',
|
||||
activate: async (ctx: any) => {
|
||||
ctx.registerProvider('graphAcceleration', notReady.provider)
|
||||
return true
|
||||
}
|
||||
} as any)
|
||||
await b.init()
|
||||
const p = await b.add({ type: NounType.Person, subtype: 'employee', data: 'P' })
|
||||
const q = await b.add({ type: NounType.Person, subtype: 'employee', data: 'Q' })
|
||||
await b.relate({ from: p, to: q, type: VerbType.RelatedTo })
|
||||
|
||||
// Every brain.graph.* route + the query→expand fusion must NOT throw and must serve
|
||||
// from the TS fallback while the engine is cold.
|
||||
const sub = await b.graph.subgraph(p, { depth: 1 })
|
||||
const ranked = await b.graph.rank()
|
||||
const comm = await b.graph.communities()
|
||||
const route = await b.graph.path(p, q)
|
||||
const fused = await b.graph.subgraph({ type: NounType.Person }, { depth: 1 })
|
||||
|
||||
// The provider was never touched — all served by the JS fallback.
|
||||
expect(notReady.state.calls.traverse).toBe(0)
|
||||
expect(notReady.state.calls.rank).toBe(0)
|
||||
expect(notReady.state.calls.communities).toBe(0)
|
||||
expect(notReady.state.calls.path).toBe(0)
|
||||
// And the TS fallback returned real answers (not throws / empties from a cold engine).
|
||||
expect(sub.nodes.some((n) => n.id === p)).toBe(true)
|
||||
expect(ranked.length).toBeGreaterThan(0)
|
||||
expect(comm.count).toBeGreaterThan(0)
|
||||
expect(route?.nodes).toEqual([p, q])
|
||||
expect(fused.nodes.some((n) => n.id === p)).toBe(true)
|
||||
await b.close()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue