fix(find): connected finds are graph-first — neighbours, then the filter over those ids, then the page
With `connected` present, find() materialized the whole-store filtered id
list, paged it, hydrated the page, and only then intersected with the
neighbour set. Every such call paid O(store) for the filter and the
hydration of rows that were never neighbours, and a neighbour outside the
first page of the filtered STORE was silently dropped — the answer depended
on the store's order and the page size.
The neighbour set is now the candidate universe: resolved first from the
adjacency, the metadata filter evaluated over those ids only through the
provider's own evaluation (a new optional `filterIdsWithin` door on
MetadataIndexProvider; the reference index implements it from its own
getIdsForFilter so the two can never disagree; a provider without it is
served by the whole-store answer intersected here), `orderBy` sorts the
whole neighbour set before the page is cut, and the vector leg walks the
neighbours as its candidate set. The text leg of a hybrid find keeps its
post-intersection — it has no candidate door.
Pinned in tests/integration/find-connected-order.test.ts: paging reaches
every matching neighbour and never a non-neighbour; a `missing` negation is
evaluated over the neighbours; the index is asked about the neighbour ids
only and hydration is one page; orderBy sorts the whole set; the vector leg
stays inside the neighbours; an edgeless anchor answers [] before the
filter is asked.
2026-09-01 11:29:44 -07:00
/ * *
* @module tests / integration / find - connected - order
* @description The graph - first law for ` find({ connected }) ` ( 10.4 . 8 ) .
*
* With ` connected ` present the neighbour set is the candidate universe : it is
* resolved from the adjacency first , the metadata filter is evaluated over
* those ids only , and the page is cut last . The earlier order materialized the
* whole - store filtered id list , paged it , hydrated the page , and only then
* intersected with the neighbours — so a neighbour outside the first page of
* the filtered STORE was silently dropped , and every call paid O ( store ) .
*
* These pins hold both halves . The answer : every matching neighbour is
* reachable by paging , a non - neighbour never appears , a negation ( ` missing ` )
* is evaluated over the neighbours , ` orderBy ` sorts the whole neighbour set
* before the page is cut , and the vector leg walks the neighbours only . The
* cost shape : the metadata index is asked about the neighbour ids only , and
* hydration is one page — never the store .
* /
import { describe , it , expect , beforeAll , afterAll , vi } from 'vitest'
import { Brainy } from '../../src/brainy'
import { NounType , VerbType } from '../../src/types/graphTypes'
import { v5 } from '../../src/universal/uuid'
import { generateTestVector } from '../helpers/test-factory'
/** Matching rows that are NOT neighbours — added FIRST, so the whole-store filtered list leads with them. */
const NOISE = 120
/** Matching rows that ARE neighbours of the anchor. */
const NEIGHBOURS = 30
/** Neighbours carrying `retracted: true` — excluded by the `missing` negation. */
const RETRACTED = 4
describe ( 'find({ connected }) is graph-first: neighbours → filter → page' , ( ) = > {
let brain : Brainy < any >
const anchor = 'anchor'
const sharedVector = generateTestVector ( )
const neighbourIds = new Set ( Array . from ( { length : NEIGHBOURS } , ( _ , i ) = > v5 ( ` nb- ${ i } ` ) ) )
beforeAll ( async ( ) = > {
brain = new Brainy ( { requireSubtype : false , storage : { type : 'memory' } } )
await brain . init ( )
await brain . add ( {
id : anchor ,
data : 'the anchor' ,
type : NounType . Person ,
metadata : { kind : 'anchor' } ,
vector : generateTestVector ( )
} )
for ( let i = 0 ; i < NOISE ; i ++ ) {
await brain . add ( {
id : ` noise- ${ i } ` ,
data : ` noise ${ i } ` ,
type : NounType . Person ,
metadata : { kind : 'note' , rank : 1000 + i } ,
vector : sharedVector
} )
}
for ( let i = 0 ; i < NEIGHBOURS ; i ++ ) {
await brain . add ( {
id : ` nb- ${ i } ` ,
data : ` neighbour ${ i } ` ,
type : NounType . Person ,
metadata : { kind : 'note' , rank : i + 1 , . . . ( i < RETRACTED ? { retracted : true } : { } ) } ,
vector : sharedVector
} )
await brain . relate ( { from : anchor , to : ` nb- ${ i } ` , type : VerbType . Knows } )
}
} )
afterAll ( async ( ) = > {
test(find-connected): close the brain this file leaks, and name the half a short answer came from
TWO THINGS, both about the same file.
THE LEAK, which is a defect of the test. `afterAll` set `brain = null`. That
does not close a brain — it only makes it unreachable from here. The instance
stayed open and registered with its unref'd cadence timer running, and the gate
config runs the whole suite in ONE process (pool: 'forks', singleFork: true —
two files report the same process.pid), so a brain leaked in this file goes on
narrating its flushes into every file that runs after it. This one holds 151
entities and 30 relations. It is closed now.
It is not the only leaker in the suite — a create-versus-close scan turns up 67
files with the same shape, and this is one of them, not the cause of anything on
its own. Fixing the file I was already in.
THE DIAGNOSTIC. 'walks the vector leg over the neighbours only' went red on the
gate box (1 row of a requested 5) while passing here in isolation eight runs out
of eight, beside its own box predecessor, and under a perturbed random stream —
and it passed on the box one gate earlier behind the IDENTICAL predecessor. So
the cause is process state accumulated by the time this file runs, and a bare
count mismatch says nothing about which half broke.
The case now runs the same query without the vector leg first, as a control,
and reports both counts: both short means the neighbour set or the filter, only
the vector leg short means the walk — which matters here because every row in
this corpus carries an IDENTICAL vector, so the walk is ranking an exact tie and
a tie has no defined order to return 5 of.
The assertion is unchanged: still exactly 5, still every row a neighbour.
2026-09-02 16:14:24 -07:00
// CLOSE IT. Dropping the reference does not close a brain — it only makes
// it unreachable from here. The instance stays open and registered, its
// unref'd cadence timer keeps running, and because the gate config runs the
// whole suite in ONE process (pool: 'forks', singleFork: true) it goes on
// narrating its flushes into every test file that runs after this one.
// A test that leaks a brain is a defect of the test.
await brain ? . close ( )
fix(find): connected finds are graph-first — neighbours, then the filter over those ids, then the page
With `connected` present, find() materialized the whole-store filtered id
list, paged it, hydrated the page, and only then intersected with the
neighbour set. Every such call paid O(store) for the filter and the
hydration of rows that were never neighbours, and a neighbour outside the
first page of the filtered STORE was silently dropped — the answer depended
on the store's order and the page size.
The neighbour set is now the candidate universe: resolved first from the
adjacency, the metadata filter evaluated over those ids only through the
provider's own evaluation (a new optional `filterIdsWithin` door on
MetadataIndexProvider; the reference index implements it from its own
getIdsForFilter so the two can never disagree; a provider without it is
served by the whole-store answer intersected here), `orderBy` sorts the
whole neighbour set before the page is cut, and the vector leg walks the
neighbours as its candidate set. The text leg of a hybrid find keeps its
post-intersection — it has no candidate door.
Pinned in tests/integration/find-connected-order.test.ts: paging reaches
every matching neighbour and never a non-neighbour; a `missing` negation is
evaluated over the neighbours; the index is asked about the neighbour ids
only and hydration is one page; orderBy sorts the whole set; the vector leg
stays inside the neighbours; an edgeless anchor answers [] before the
filter is asked.
2026-09-01 11:29:44 -07:00
brain = null as any
} )
it ( 'returns the matching neighbours page by page — none dropped, never a non-neighbour' , async ( ) = > {
const seen = new Set < string > ( )
for ( let offset = 0 ; offset <= NEIGHBOURS ; offset += 10 ) {
const page = await brain . find ( {
connected : { from : anchor , direction : 'out' } ,
where : { kind : 'note' } ,
limit : 10 ,
offset
} )
expect ( page ) . toHaveLength ( offset < NEIGHBOURS ? 10 : 0 )
for ( const r of page ) {
expect ( neighbourIds . has ( r . entity . id ) ) . toBe ( true )
expect ( seen . has ( r . entity . id ) ) . toBe ( false )
seen . add ( r . entity . id )
}
}
expect ( seen . size ) . toBe ( NEIGHBOURS )
} )
it ( 'evaluates a negation (`missing`) over the neighbour set, not the store' , async ( ) = > {
const results = await brain . find ( {
connected : { from : anchor , direction : 'out' } ,
where : { kind : 'note' , retracted : { missing : true } } ,
limit : 100
} )
expect ( results ) . toHaveLength ( NEIGHBOURS - RETRACTED )
for ( const r of results ) {
expect ( neighbourIds . has ( r . entity . id ) ) . toBe ( true )
expect ( r . entity . metadata . retracted ) . toBeUndefined ( )
}
} )
it ( 'asks the metadata index about the neighbour ids only, and hydrates one page' , async ( ) = > {
const index = ( brain as any ) . metadataIndex
const within = vi . spyOn ( index , 'filterIdsWithin' )
const hydrate = vi . spyOn ( brain as any , 'batchGet' )
try {
const results = await brain . find ( {
connected : { from : anchor , direction : 'out' } ,
where : { kind : 'note' } ,
limit : 10
} )
expect ( results ) . toHaveLength ( 10 )
expect ( within ) . toHaveBeenCalledTimes ( 1 )
const askedIds = within . mock . calls [ 0 ] [ 1 ] as string [ ]
expect ( askedIds ) . toHaveLength ( NEIGHBOURS )
for ( const id of askedIds ) expect ( neighbourIds . has ( id ) ) . toBe ( true )
expect ( hydrate ) . toHaveBeenCalledTimes ( 1 )
expect ( hydrate . mock . calls [ 0 ] [ 0 ] ) . toHaveLength ( 10 )
} finally {
within . mockRestore ( )
hydrate . mockRestore ( )
}
} )
it ( 'orders the WHOLE neighbour set before cutting the page' , async ( ) = > {
const results = await brain . find ( {
connected : { from : anchor , direction : 'out' } ,
where : { kind : 'note' } ,
orderBy : 'rank' ,
order : 'desc' ,
limit : 5
} )
expect ( results . map ( ( r ) = > r . entity . metadata . rank ) ) . toEqual ( [ 30 , 29 , 28 , 27 , 26 ] )
} )
it ( 'walks the vector leg over the neighbours only' , async ( ) = > {
test(find-connected): close the brain this file leaks, and name the half a short answer came from
TWO THINGS, both about the same file.
THE LEAK, which is a defect of the test. `afterAll` set `brain = null`. That
does not close a brain — it only makes it unreachable from here. The instance
stayed open and registered with its unref'd cadence timer running, and the gate
config runs the whole suite in ONE process (pool: 'forks', singleFork: true —
two files report the same process.pid), so a brain leaked in this file goes on
narrating its flushes into every file that runs after it. This one holds 151
entities and 30 relations. It is closed now.
It is not the only leaker in the suite — a create-versus-close scan turns up 67
files with the same shape, and this is one of them, not the cause of anything on
its own. Fixing the file I was already in.
THE DIAGNOSTIC. 'walks the vector leg over the neighbours only' went red on the
gate box (1 row of a requested 5) while passing here in isolation eight runs out
of eight, beside its own box predecessor, and under a perturbed random stream —
and it passed on the box one gate earlier behind the IDENTICAL predecessor. So
the cause is process state accumulated by the time this file runs, and a bare
count mismatch says nothing about which half broke.
The case now runs the same query without the vector leg first, as a control,
and reports both counts: both short means the neighbour set or the filter, only
the vector leg short means the walk — which matters here because every row in
this corpus carries an IDENTICAL vector, so the walk is ranking an exact tie and
a tie has no defined order to return 5 of.
The assertion is unchanged: still exactly 5, still every row a neighbour.
2026-09-02 16:14:24 -07:00
// The SAME query without the vector leg, first. Both legs draw from the
// one neighbour set, so this is the control: it says whether a short answer
// came from the adjacency/filter (both legs short) or from the vector walk
// alone (only the vector leg short). Cheap, and it turns a bare count
// mismatch into a named half — this case has gone red on the gate box
// while passing in isolation and beside its own predecessor, so the next
// red must arrive already carrying the half it belongs to.
const control = await brain . find ( {
connected : { from : anchor , direction : 'out' } ,
where : { kind : 'note' } ,
limit : 5
} )
fix(find): connected finds are graph-first — neighbours, then the filter over those ids, then the page
With `connected` present, find() materialized the whole-store filtered id
list, paged it, hydrated the page, and only then intersected with the
neighbour set. Every such call paid O(store) for the filter and the
hydration of rows that were never neighbours, and a neighbour outside the
first page of the filtered STORE was silently dropped — the answer depended
on the store's order and the page size.
The neighbour set is now the candidate universe: resolved first from the
adjacency, the metadata filter evaluated over those ids only through the
provider's own evaluation (a new optional `filterIdsWithin` door on
MetadataIndexProvider; the reference index implements it from its own
getIdsForFilter so the two can never disagree; a provider without it is
served by the whole-store answer intersected here), `orderBy` sorts the
whole neighbour set before the page is cut, and the vector leg walks the
neighbours as its candidate set. The text leg of a hybrid find keeps its
post-intersection — it has no candidate door.
Pinned in tests/integration/find-connected-order.test.ts: paging reaches
every matching neighbour and never a non-neighbour; a `missing` negation is
evaluated over the neighbours; the index is asked about the neighbour ids
only and hydration is one page; orderBy sorts the whole set; the vector leg
stays inside the neighbours; an edgeless anchor answers [] before the
filter is asked.
2026-09-01 11:29:44 -07:00
const results = await brain . find ( {
vector : sharedVector ,
connected : { from : anchor , direction : 'out' } ,
where : { kind : 'note' } ,
limit : 5
} )
test(find-connected): close the brain this file leaks, and name the half a short answer came from
TWO THINGS, both about the same file.
THE LEAK, which is a defect of the test. `afterAll` set `brain = null`. That
does not close a brain — it only makes it unreachable from here. The instance
stayed open and registered with its unref'd cadence timer running, and the gate
config runs the whole suite in ONE process (pool: 'forks', singleFork: true —
two files report the same process.pid), so a brain leaked in this file goes on
narrating its flushes into every file that runs after it. This one holds 151
entities and 30 relations. It is closed now.
It is not the only leaker in the suite — a create-versus-close scan turns up 67
files with the same shape, and this is one of them, not the cause of anything on
its own. Fixing the file I was already in.
THE DIAGNOSTIC. 'walks the vector leg over the neighbours only' went red on the
gate box (1 row of a requested 5) while passing here in isolation eight runs out
of eight, beside its own box predecessor, and under a perturbed random stream —
and it passed on the box one gate earlier behind the IDENTICAL predecessor. So
the cause is process state accumulated by the time this file runs, and a bare
count mismatch says nothing about which half broke.
The case now runs the same query without the vector leg first, as a control,
and reports both counts: both short means the neighbour set or the filter, only
the vector leg short means the walk — which matters here because every row in
this corpus carries an IDENTICAL vector, so the walk is ranking an exact tie and
a tie has no defined order to return 5 of.
The assertion is unchanged: still exactly 5, still every row a neighbour.
2026-09-02 16:14:24 -07:00
expect (
results . length ,
` the vector leg returned ${ results . length } of a requested 5. The same query ` +
` WITHOUT the vector returned ${ control . length } : if that is also short the ` +
` neighbour set or the filter is the cause, and if it is 5 the vector walk is — ` +
` note every row in this corpus carries an identical vector, so the walk is ` +
` ranking an exact tie. `
) . toBe ( 5 )
fix(find): connected finds are graph-first — neighbours, then the filter over those ids, then the page
With `connected` present, find() materialized the whole-store filtered id
list, paged it, hydrated the page, and only then intersected with the
neighbour set. Every such call paid O(store) for the filter and the
hydration of rows that were never neighbours, and a neighbour outside the
first page of the filtered STORE was silently dropped — the answer depended
on the store's order and the page size.
The neighbour set is now the candidate universe: resolved first from the
adjacency, the metadata filter evaluated over those ids only through the
provider's own evaluation (a new optional `filterIdsWithin` door on
MetadataIndexProvider; the reference index implements it from its own
getIdsForFilter so the two can never disagree; a provider without it is
served by the whole-store answer intersected here), `orderBy` sorts the
whole neighbour set before the page is cut, and the vector leg walks the
neighbours as its candidate set. The text leg of a hybrid find keeps its
post-intersection — it has no candidate door.
Pinned in tests/integration/find-connected-order.test.ts: paging reaches
every matching neighbour and never a non-neighbour; a `missing` negation is
evaluated over the neighbours; the index is asked about the neighbour ids
only and hydration is one page; orderBy sorts the whole set; the vector leg
stays inside the neighbours; an edgeless anchor answers [] before the
filter is asked.
2026-09-01 11:29:44 -07:00
for ( const r of results ) expect ( neighbourIds . has ( r . entity . id ) ) . toBe ( true )
} )
it ( 'an anchor without neighbours answers [] before the filter is asked' , async ( ) = > {
const index = ( brain as any ) . metadataIndex
const within = vi . spyOn ( index , 'filterIdsWithin' )
try {
const results = await brain . find ( {
connected : { from : 'noise-0' , direction : 'out' } ,
where : { kind : 'note' } ,
limit : 10
} )
expect ( results ) . toEqual ( [ ] )
expect ( within ) . not . toHaveBeenCalled ( )
} finally {
within . mockRestore ( )
}
} )
} )