test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
/ * *
* @module tests / unit / brainy / graph - native - routing
* @description Graph engine — NATIVE seam coverage . brain . graph . subgraph / export
* route to a registered GraphAccelerationProvider and hydrate its columnar
* ` Subgraph ` ( node ints - > ids , depth alignment , edge verb - ints - > Relations ) .
* In production that provider is cor ' s native engine , cross - layer - tested on
* bxl9000 ; brainy CI never registers one , so graphSubgraphNative /
* graphExportNative / hydrateNativeSubgraph + the provider - resolution + routing
* were previously UNEXERCISED — a return - shape or hydration - alignment drift would
* pass CI silently . This registers a faithful MOCK provider ( returning a columnar
* Subgraph built from the brain ' s REAL ints , so hydration resolves to real
* entities / relations ) to lock those paths .
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import { Brainy } from '../../../src/index.js'
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. */
2026-06-24 15:18:50 -07:00
function makeMockAccel ( opts : { isInitialized? : boolean } = { } ) {
feat(8.0): graph analytics — brain.graph.rank / communities / path
Adds three intent-level graph reads to the `brain.graph` namespace, each
native-dispatched to the optional `@soulcraft/cor` 3.0 graph engine when present
and served from pure-TS kernels otherwise (identical public shapes, default
visibility filter respected on both paths):
- `rank(opts?)` → `{ id, score }[]` descending — importance / centrality.
TS fallback: PageRank power-iteration with dangling-mass redistribution.
- `communities(opts?)` → `{ groups, count }` — connected grouping. TS fallback:
union-find weakly-connected components, or iterative Tarjan SCC when
`{ directed: true }`.
- `path(from, to, opts?)` → `{ nodes, relationships, cost } | null` — best route.
TS fallback: BFS for fewest hops, Dijkstra (min-heap) for least summed edge
weight (`by: 'weight'`); on-demand frontier expansion so short paths terminate
early. `direction` / `type` / `maxDepth` filters apply.
These are intent contracts, not algorithm contracts — the question is the
promise, the algorithm is the engine's choice.
Pure kernels live in src/graph/analyticsFallback.ts (PageRank, connected
components, Tarjan SCC, MinHeap) — unit-tested in isolation. The full surface is
tested end-to-end through the TS fallback, and the native dispatch + int↔uuid
hydration paths are covered by a mock provider in graph-native-routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:32:03 -07:00
const calls = { traverse : 0 , cursorOpen : 0 , cursorNext : 0 , cursorClose : 0 , rank : 0 , communities : 0 , path : 0 }
const state : {
calls : typeof calls
traverseResult : any
cursorChunks : any [ ]
rankResult : any
communitiesResult : any
pathResult : any
2026-06-23 13:30:30 -07:00
lastTraverseSeeds : any
feat(8.0): graph analytics — brain.graph.rank / communities / path
Adds three intent-level graph reads to the `brain.graph` namespace, each
native-dispatched to the optional `@soulcraft/cor` 3.0 graph engine when present
and served from pure-TS kernels otherwise (identical public shapes, default
visibility filter respected on both paths):
- `rank(opts?)` → `{ id, score }[]` descending — importance / centrality.
TS fallback: PageRank power-iteration with dangling-mass redistribution.
- `communities(opts?)` → `{ groups, count }` — connected grouping. TS fallback:
union-find weakly-connected components, or iterative Tarjan SCC when
`{ directed: true }`.
- `path(from, to, opts?)` → `{ nodes, relationships, cost } | null` — best route.
TS fallback: BFS for fewest hops, Dijkstra (min-heap) for least summed edge
weight (`by: 'weight'`); on-demand frontier expansion so short paths terminate
early. `direction` / `type` / `maxDepth` filters apply.
These are intent contracts, not algorithm contracts — the question is the
promise, the algorithm is the engine's choice.
Pure kernels live in src/graph/analyticsFallback.ts (PageRank, connected
components, Tarjan SCC, MinHeap) — unit-tested in isolation. The full surface is
tested end-to-end through the TS fallback, and the native dispatch + int↔uuid
hydration paths are covered by a mock provider in graph-native-routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:32:03 -07:00
} = {
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
calls ,
traverseResult : null ,
feat(8.0): graph analytics — brain.graph.rank / communities / path
Adds three intent-level graph reads to the `brain.graph` namespace, each
native-dispatched to the optional `@soulcraft/cor` 3.0 graph engine when present
and served from pure-TS kernels otherwise (identical public shapes, default
visibility filter respected on both paths):
- `rank(opts?)` → `{ id, score }[]` descending — importance / centrality.
TS fallback: PageRank power-iteration with dangling-mass redistribution.
- `communities(opts?)` → `{ groups, count }` — connected grouping. TS fallback:
union-find weakly-connected components, or iterative Tarjan SCC when
`{ directed: true }`.
- `path(from, to, opts?)` → `{ nodes, relationships, cost } | null` — best route.
TS fallback: BFS for fewest hops, Dijkstra (min-heap) for least summed edge
weight (`by: 'weight'`); on-demand frontier expansion so short paths terminate
early. `direction` / `type` / `maxDepth` filters apply.
These are intent contracts, not algorithm contracts — the question is the
promise, the algorithm is the engine's choice.
Pure kernels live in src/graph/analyticsFallback.ts (PageRank, connected
components, Tarjan SCC, MinHeap) — unit-tested in isolation. The full surface is
tested end-to-end through the TS fallback, and the native dispatch + int↔uuid
hydration paths are covered by a mock provider in graph-native-routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:32:03 -07:00
cursorChunks : [ ] ,
rankResult : null ,
communitiesResult : null ,
2026-06-23 13:30:30 -07:00
pathResult : null ,
lastTraverseSeeds : undefined
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
}
const empty = { nodeInts : new BigInt64Array ( 0 ) , scores : new Float64Array ( 0 ) }
feat(8.0): graph analytics — brain.graph.rank / communities / path
Adds three intent-level graph reads to the `brain.graph` namespace, each
native-dispatched to the optional `@soulcraft/cor` 3.0 graph engine when present
and served from pure-TS kernels otherwise (identical public shapes, default
visibility filter respected on both paths):
- `rank(opts?)` → `{ id, score }[]` descending — importance / centrality.
TS fallback: PageRank power-iteration with dangling-mass redistribution.
- `communities(opts?)` → `{ groups, count }` — connected grouping. TS fallback:
union-find weakly-connected components, or iterative Tarjan SCC when
`{ directed: true }`.
- `path(from, to, opts?)` → `{ nodes, relationships, cost } | null` — best route.
TS fallback: BFS for fewest hops, Dijkstra (min-heap) for least summed edge
weight (`by: 'weight'`); on-demand frontier expansion so short paths terminate
early. `direction` / `type` / `maxDepth` filters apply.
These are intent contracts, not algorithm contracts — the question is the
promise, the algorithm is the engine's choice.
Pure kernels live in src/graph/analyticsFallback.ts (PageRank, connected
components, Tarjan SCC, MinHeap) — unit-tested in isolation. The full surface is
tested end-to-end through the TS fallback, and the native dispatch + int↔uuid
hydration paths are covered by a mock provider in graph-native-routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:32:03 -07:00
const emptyCommunities = {
nodeInts : new BigInt64Array ( 0 ) ,
communityIds : new Uint32Array ( 0 ) ,
communityCount : 0
}
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
const provider = {
2026-06-24 15:18:50 -07:00
isInitialized : opts.isInitialized ? ? true ,
2026-06-23 13:30:30 -07:00
traverse : async ( seeds : any ) = > {
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
calls . traverse ++
2026-06-23 13:30:30 -07:00
state . lastTraverseSeeds = seeds
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
return state . traverseResult
} ,
edgesForNode : async ( ) = > state . traverseResult ,
graphCursorOpen : async ( ) = > {
calls . cursorOpen ++
return 'mock-handle'
} ,
graphCursorNext : async ( ) = > {
calls . cursorNext ++
const subgraph = state . cursorChunks . shift ( )
return { subgraph , done : state.cursorChunks.length === 0 }
} ,
graphCursorClose : async ( ) = > {
calls . cursorClose ++
} ,
feat(8.0): graph analytics — brain.graph.rank / communities / path
Adds three intent-level graph reads to the `brain.graph` namespace, each
native-dispatched to the optional `@soulcraft/cor` 3.0 graph engine when present
and served from pure-TS kernels otherwise (identical public shapes, default
visibility filter respected on both paths):
- `rank(opts?)` → `{ id, score }[]` descending — importance / centrality.
TS fallback: PageRank power-iteration with dangling-mass redistribution.
- `communities(opts?)` → `{ groups, count }` — connected grouping. TS fallback:
union-find weakly-connected components, or iterative Tarjan SCC when
`{ directed: true }`.
- `path(from, to, opts?)` → `{ nodes, relationships, cost } | null` — best route.
TS fallback: BFS for fewest hops, Dijkstra (min-heap) for least summed edge
weight (`by: 'weight'`); on-demand frontier expansion so short paths terminate
early. `direction` / `type` / `maxDepth` filters apply.
These are intent contracts, not algorithm contracts — the question is the
promise, the algorithm is the engine's choice.
Pure kernels live in src/graph/analyticsFallback.ts (PageRank, connected
components, Tarjan SCC, MinHeap) — unit-tested in isolation. The full surface is
tested end-to-end through the TS fallback, and the native dispatch + int↔uuid
hydration paths are covered by a mock provider in graph-native-routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:32:03 -07:00
rank : async ( ) = > {
calls . rank ++
return state . rankResult ? ? empty
} ,
communities : async ( ) = > {
calls . communities ++
return state . communitiesResult ? ? emptyCommunities
} ,
path : async ( ) = > {
calls . path ++
return state . pathResult
} ,
2026-06-22 09:54:01 -07:00
sample : async ( ) = > state . traverseResult ,
mostConnected : async ( ) = > empty
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
}
return { provider , state }
}
describe ( 'brain.graph.* native routing + columnar hydration (native seam)' , ( ) = > {
let brain : Brainy
let mock : ReturnType < typeof makeMockAccel >
let a : string , b : string , c : string
// Build a faithful columnar Subgraph from the brain's REAL ints, so brainy's
// hydration resolves the node ints to ids and the verb ints to Relations.
async function realSubgraph ( withUnresolvable = false ) {
const gei = ( id : string ) : bigint = > ( brain as any ) . graphEntityInt ( id )
const gi = ( brain as any ) . graphIndex
const intA = gei ( a ) , intB = gei ( b ) , intC = gei ( c )
const vAB = ( await gi . getVerbIdsBySource ( intA ) ) [ 0 ] as bigint // verb int a->b
const vBC = ( await gi . getVerbIdsBySource ( intB ) ) [ 0 ] as bigint // verb int b->c
const nodeInts = [ intA , intB , intC ]
const depths = [ 0 , 1 , 2 ]
if ( withUnresolvable ) {
nodeInts . push ( 999 _999_999n ) // a never-assigned int (simulates a deleted/unknown node)
depths . push ( 3 )
}
return {
nodes : BigInt64Array.from ( nodeInts ) ,
nodeDepth : Uint8Array.from ( depths ) ,
edgeSources : BigInt64Array.from ( [ intA , intB ] ) ,
edgeTargets : BigInt64Array.from ( [ intB , intC ] ) ,
edgeVerbInts : BigInt64Array.from ( [ vAB , vBC ] ) ,
edgeTypes : Uint16Array.from ( [ 0 , 0 ] ) ,
truncated : false
}
}
beforeEach ( async ( ) = > {
mock = makeMockAccel ( )
brain = new Brainy ( createTestConfig ( ) )
brain . use ( {
name : 'mock-graph-accel' ,
activate : async ( ctx : any ) = > {
ctx . registerProvider ( 'graphAcceleration' , mock . provider )
return true
}
} as any )
await brain . init ( )
a = await brain . add ( { type : NounType . Person , subtype : 'employee' , data : 'A' } )
b = await brain . add ( { type : NounType . Person , subtype : 'employee' , data : 'B' } )
c = await brain . add ( { type : NounType . Project , subtype : 'milestone' , data : 'C' } )
await brain . relate ( { from : a , to : b , type : VerbType . RelatedTo , subtype : 'colleague' } )
await brain . relate ( { from : b , to : c , type : VerbType . ParticipatesIn , subtype : 'assignment' } )
} )
afterEach ( async ( ) = > {
await brain . close ( )
} )
it ( 'subgraph() routes to the native provider and hydrates the columnar result' , async ( ) = > {
mock . state . traverseResult = await realSubgraph ( )
const view = await brain . graph . subgraph ( a , { depth : 2 } )
expect ( mock . state . calls . traverse ) . toBe ( 1 ) // native path, not the TS fallback
const byId = new Map ( view . nodes . map ( ( n ) = > [ n . id , n ] ) )
expect ( new Set ( byId . keys ( ) ) ) . toEqual ( new Set ( [ a , b , c ] ) ) // node int -> id
expect ( byId . get ( a ) ? . depth ) . toBe ( 0 )
expect ( byId . get ( c ) ? . depth ) . toBe ( 2 ) // depth column aligned to node column
expect ( byId . get ( c ) ? . type ) . toBe ( NounType . Project ) // node type hydrated via batchGet
const pairs = view . edges . map ( ( e ) = > ` ${ e . from } -> ${ e . to } ` ) . sort ( )
expect ( pairs ) . toEqual ( [ ` ${ a } -> ${ b } ` , ` ${ b } -> ${ c } ` ] . sort ( ) ) // verb int -> Relation
} )
it ( 'keeps node<->depth alignment when a node int does not resolve (deleted/unknown)' , async ( ) = > {
mock . state . traverseResult = await realSubgraph ( true ) // appends an unresolvable int at depth 3
const view = await brain . graph . subgraph ( a , { depth : 3 } )
const byId = new Map ( view . nodes . map ( ( n ) = > [ n . id , n ] ) )
// The 3 real nodes keep their CORRECT depths — the unresolvable int is dropped,
// not collapsed into the array (which would shift every later depth).
expect ( byId . get ( a ) ? . depth ) . toBe ( 0 )
expect ( byId . get ( b ) ? . depth ) . toBe ( 1 )
expect ( byId . get ( c ) ? . depth ) . toBe ( 2 )
expect ( view . nodes . length ) . toBe ( 3 )
} )
it ( 'export() routes to the native graph cursor, hydrates chunks, and always closes' , async ( ) = > {
mock . state . cursorChunks = [ await realSubgraph ( ) ]
const chunks : any [ ] = [ ]
for await ( const v of brain . graph . export ( ) ) chunks . push ( v )
expect ( mock . state . calls . cursorOpen ) . toBe ( 1 )
expect ( mock . state . calls . cursorClose ) . toBe ( 1 ) // cursor released even on normal completion
const nodes = new Set ( chunks . flatMap ( ( c ) = > c . nodes . map ( ( n : any ) = > n . id ) ) )
expect ( nodes ) . toEqual ( new Set ( [ a , b , c ] ) )
const edges = chunks . flatMap ( ( c ) = > c . edges . map ( ( e : any ) = > ` ${ e . from } -> ${ e . to } ` ) ) . sort ( )
expect ( edges ) . toEqual ( [ ` ${ a } -> ${ b } ` , ` ${ b } -> ${ c } ` ] . sort ( ) )
} )
it ( 'resolves a provider registered as a FACTORY (storage) => provider, not just an instance' , async ( ) = > {
const m = makeMockAccel ( )
const fb = new Brainy ( createTestConfig ( ) )
fb . use ( {
name : 'mock-graph-accel-factory' ,
activate : async ( ctx : any ) = > {
ctx . registerProvider ( 'graphAcceleration' , ( ) = > m . provider ) // factory form
return true
}
} as any )
await fb . init ( )
const x = await fb . add ( { type : NounType . Person , subtype : 'employee' , data : 'X' } )
const y = await fb . add ( { type : NounType . Person , subtype : 'employee' , data : 'Y' } )
await fb . relate ( { from : x , to : y , type : VerbType . RelatedTo , subtype : 'colleague' } )
const gei = ( id : string ) : bigint = > ( fb as any ) . graphEntityInt ( id )
const gi = ( fb as any ) . graphIndex
const ix = gei ( x ) , iy = gei ( y )
const vxy = ( await gi . getVerbIdsBySource ( ix ) ) [ 0 ] as bigint
m . state . traverseResult = {
nodes : BigInt64Array.from ( [ ix , iy ] ) ,
nodeDepth : Uint8Array.from ( [ 0 , 1 ] ) ,
edgeSources : BigInt64Array.from ( [ ix ] ) ,
edgeTargets : BigInt64Array.from ( [ iy ] ) ,
edgeVerbInts : BigInt64Array.from ( [ vxy ] ) ,
edgeTypes : Uint16Array.from ( [ 0 ] ) ,
truncated : false
}
const view = await fb . graph . subgraph ( x , { depth : 1 } )
expect ( m . state . calls . traverse ) . toBe ( 1 ) // factory was invoked + provider routed
expect ( new Set ( view . nodes . map ( ( n ) = > n . id ) ) ) . toEqual ( new Set ( [ x , y ] ) )
await fb . close ( )
} )
feat(8.0): graph analytics — brain.graph.rank / communities / path
Adds three intent-level graph reads to the `brain.graph` namespace, each
native-dispatched to the optional `@soulcraft/cor` 3.0 graph engine when present
and served from pure-TS kernels otherwise (identical public shapes, default
visibility filter respected on both paths):
- `rank(opts?)` → `{ id, score }[]` descending — importance / centrality.
TS fallback: PageRank power-iteration with dangling-mass redistribution.
- `communities(opts?)` → `{ groups, count }` — connected grouping. TS fallback:
union-find weakly-connected components, or iterative Tarjan SCC when
`{ directed: true }`.
- `path(from, to, opts?)` → `{ nodes, relationships, cost } | null` — best route.
TS fallback: BFS for fewest hops, Dijkstra (min-heap) for least summed edge
weight (`by: 'weight'`); on-demand frontier expansion so short paths terminate
early. `direction` / `type` / `maxDepth` filters apply.
These are intent contracts, not algorithm contracts — the question is the
promise, the algorithm is the engine's choice.
Pure kernels live in src/graph/analyticsFallback.ts (PageRank, connected
components, Tarjan SCC, MinHeap) — unit-tested in isolation. The full surface is
tested end-to-end through the TS fallback, and the native dispatch + int↔uuid
hydration paths are covered by a mock provider in graph-native-routing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:32:03 -07:00
it ( 'rank() routes to the native provider and hydrates node ints -> ids, order preserved' , async ( ) = > {
const gei = ( id : string ) : bigint = > ( brain as any ) . graphEntityInt ( id )
// Provider returns DESCENDING scores: c, then a, then b.
mock . state . rankResult = {
nodeInts : BigInt64Array.from ( [ gei ( c ) , gei ( a ) , gei ( b ) ] ) ,
scores : Float64Array.from ( [ 0.5 , 0.3 , 0.2 ] )
}
const ranked = await brain . graph . rank ( )
expect ( mock . state . calls . rank ) . toBe ( 1 ) // native path, not the TS PageRank fallback
expect ( ranked . map ( ( r ) = > r . id ) ) . toEqual ( [ c , a , b ] ) // int -> id, provider order kept
expect ( ranked [ 0 ] . score ) . toBe ( 0.5 )
const top2 = await brain . graph . rank ( { topK : 2 } )
expect ( top2 . map ( ( r ) = > r . id ) ) . toEqual ( [ c , a ] )
} )
it ( 'communities() routes to the native provider and buckets ids by community label' , async ( ) = > {
const gei = ( id : string ) : bigint = > ( brain as any ) . graphEntityInt ( id )
// a,b in community 0; c alone in community 1.
mock . state . communitiesResult = {
nodeInts : BigInt64Array.from ( [ gei ( a ) , gei ( b ) , gei ( c ) ] ) ,
communityIds : Uint32Array.from ( [ 0 , 0 , 1 ] ) ,
communityCount : 2
}
const { groups , count } = await brain . graph . communities ( )
expect ( mock . state . calls . communities ) . toBe ( 1 ) // native path, not the TS fallback
expect ( count ) . toBe ( 2 )
const asSets = groups . map ( ( g ) = > new Set ( g ) )
expect ( asSets ) . toContainEqual ( new Set ( [ a , b ] ) )
expect ( asSets ) . toContainEqual ( new Set ( [ c ] ) )
expect ( groups [ 0 ] ) . toHaveLength ( 2 ) // largest group first
} )
it ( 'path() routes to the native provider and hydrates node + verb ints' , async ( ) = > {
const gei = ( id : string ) : bigint = > ( brain as any ) . graphEntityInt ( id )
const gi = ( brain as any ) . graphIndex
const vAB = ( await gi . getVerbIdsBySource ( gei ( a ) ) ) [ 0 ] as bigint
const vBC = ( await gi . getVerbIdsBySource ( gei ( b ) ) ) [ 0 ] as bigint
mock . state . pathResult = {
nodeInts : BigInt64Array.from ( [ gei ( a ) , gei ( b ) , gei ( c ) ] ) ,
edgeVerbInts : BigInt64Array.from ( [ vAB , vBC ] ) ,
cost : 2
}
const route = await brain . graph . path ( a , c )
expect ( mock . state . calls . path ) . toBe ( 1 ) // native path, not the TS BFS/Dijkstra fallback
expect ( route ? . nodes ) . toEqual ( [ a , b , c ] ) // node ints -> ids
expect ( route ? . relationships ) . toHaveLength ( 2 ) // verb ints -> verb-id strings
expect ( route ? . cost ) . toBe ( 2 )
} )
it ( 'path() returns null when the native provider reports unreachable' , async ( ) = > {
mock . state . pathResult = null
expect ( await brain . graph . path ( a , c ) ) . toBeNull ( )
expect ( mock . state . calls . path ) . toBe ( 1 )
} )
2026-06-23 13:30:30 -07:00
it ( 'subgraph(query) forwards the metadata universe to traverse as an OpaqueIdSet (query→expand #61)' , async ( ) = > {
// The native metadata index would return its roaring filter result as a Buffer;
// stub that producer and assert it reaches traverse WITHOUT id materialization.
const sentinel = new Uint8Array ( [ 0x01 , 0x02 , 0x03 ] )
; ( brain as any ) . metadataIndex . getIdSetForFilter = async ( ) = > sentinel
mock . state . traverseResult = await realSubgraph ( )
await brain . graph . subgraph ( { type : NounType . Person } , { depth : 1 } )
expect ( mock . state . calls . traverse ) . toBe ( 1 )
// The opaque Buffer is the traverse seed argument — passed straight through.
expect ( mock . state . lastTraverseSeeds ) . toBe ( sentinel )
} )
it ( 'subgraph(query) materializes seeds via find() when no opaque producer exists' , async ( ) = > {
// No getIdSetForFilter on the (real) metadata index → general path: find() runs,
// its matched ids resolve to entity ints, and those seed the native traverse.
mock . state . traverseResult = await realSubgraph ( )
await brain . graph . subgraph ( { type : NounType . Person } , { depth : 1 } )
expect ( mock . state . calls . traverse ) . toBe ( 1 )
expect ( Array . isArray ( mock . state . lastTraverseSeeds ) ) . toBe ( true ) // bigint[], not a Buffer
expect ( typeof ( mock . state . lastTraverseSeeds as unknown [ ] ) [ 0 ] ) . toBe ( 'bigint' )
} )
2026-06-24 15:18:50 -07:00
// 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 ( )
} )
test(8.0): cover the native graph seam + make provider resolution factory-tolerant
The brain.graph.subgraph/export NATIVE routing (graphSubgraphNative /
graphExportNative / hydrateNativeSubgraph + provider resolution) had ZERO brainy
CI coverage — in production it's exercised only cross-layer against cor's engine,
so a columnar return-shape or hydration-alignment drift would pass brainy CI
silently. This registers a faithful MOCK GraphAccelerationProvider returning a
columnar Subgraph built from the brain's REAL ints, locking the seam:
- subgraph() routes native (traverse called) and hydrates node int->id, the
nodeDepth column aligned to the node column, node type via batchGet, and edge
verb-int->Relation via verbIntsToIds + getVerbsBatchCached.
- node<->depth alignment is preserved when a node int does NOT resolve
(deleted/unknown) — the unresolvable int is dropped, not collapsed (which would
shift every later depth). This is the exact hydration risk the release audit flagged.
- export() routes to the graph cursor, hydrates chunks, and ALWAYS closes the handle.
Also fixes a latent contract bug found writing the test: graphAccelerationProvider()
only accepted a ready instance, so a provider registered as a (storage)=>provider
FACTORY (the convention graphIndex/metadataIndex/vector use) would fail the duck-test
and the native path would silently never engage. Now resolves instance OR factory,
cached. Covered by the factory-registration test.
2026-06-22 09:34:09 -07:00
} )