feat(vector): the vectored-noun scalar joins the count ledger; the open gate closes the vector leg
The coverage denominator the health-by-accounting ratification named for
the vector family — never built until now, and its absence was measured as
the exact outage class it existed to prevent: a migrated store with
canonical vectors and no derived index opened with the vector leg EMPTY,
served [] from vector search with no error, and the report-driven read gate
had nothing to refuse on (the provider's coverage invariant was honestly
unledgered — the denominator was ours to supply).
- getCanonicalCounts() gains vectors: { all } — the count of canonical
nouns holding a REAL vector. Incremented where a vector lands (the
isNew-gated metadata seam for explicit vectors — the same discipline that
keeps HNSW neighbor-link re-saves from inflating counts; a narrow
noteVectorLanded hook for the deferred-embed landing, gated on the
worker's own pre-embed read). Decremented on a proven delete of a
vectored noun; a vector-uncertain delete marks the ledger suspect rather
than guessing (no new reads on the delete path). Recounted by the
sanctioned recount; legacy counts.json derives it once (a deferred noun's
vector file exists with an empty vector, so presence requires one
content read at derivation — never on the hot path).
- The open gate's vector leg: when a health-reporting provider claims
serving while the index holds zero nodes and the ledger proves vectored
canonical rows exist, open BUILDS (narrated) — routed through the
provider's idempotent fillFromCanonical() when exposed (the joint door;
a partial shortfall stays repair()'s operator business), the JS rebuild
otherwise — or fails typed pre-serve. Scoped exactly: bare isReady()
providers, migrating providers, and white-box size stubs open as before.
Pinned end-to-end from the partner gate's probe shape (store with vectored
canonical rows, no derived index, reopen → search serves N, never []),
red-proved against the pre-fix path; the inverse (zero vectored rows) opens
without building and serves [] honestly.
2026-08-25 15:31:19 -07:00
/ * *
* @module tests / integration / vector - leg - open - build
* @description THE LAST RED of the two - engine release gate : a migrated
* store can hold canonical vectored nouns with NO derived vector index
* built . ` open() ` owns building the derived indexes ( reads never build —
* see ` rebuildIndexesIfNeeded ` ' s JSDoc ) ; the defect this pins is the vector
* leg ' s decision silently skipping that build , so ` find ` / search served ` [] `
* with no error and no narration .
*
* A downstream deployment measures this through a native vector provider
* whose own health report can legitimately say ` serving: true ` even while
* vector COVERAGE is honestly unledgered on its side ( an unledgered
* invariant never flips ` serving ` — see ` HealthReport ` ' s derivation laws ) .
* This repo ships only the JS engine , so the reproduction here uses the
* SAME plugin seam a native provider would ( ` brain.use({ activate: ctx =>
* ctx . registerProvider ( 'vector' , factory ) } ) ` , the pattern
* ` tests/unit/cold-open-rebuild-gate.test.ts ` already established for this
* exact class of gate - decision bug ) with a stub that WRAPS the real
* ` JsHnswVectorIndex ` — every method delegates to a genuine engine ( so a
* successful rebuild restores REAL , searchable vectors ) , except ` size() `
* ( fakes 0 until rebuild runs — the "never built" posture ) and
* ` healthReport() ` ( always reports ` serving: true ` , ` unledgered:
* [ 'vector-coverage' ] ` — the "I don't track this yet" posture). This is
* "as close as the JS engine allows" : the gap is reproduced at the exact
* decision the fix changes , not approximated by deleting files the JS
* engine ' s own cold - start heuristic already recovers from unaided ( see the
* inverse pin below and cold - open - rebuild - gate . test . ts ' s already - pinned
* "isReady()===true, size()===0" contract , which this fix deliberately does
* NOT touch — bare isReady ( ) has no unledgered concept to hide behind , and
* overriding it would reopen the 48 - seconds - per - restart regression pinned
* there ) .
*
* Pins :
* ( 1 ) COVERAGE GAP FORCES THE BUILD : N vectored nouns , a provider that
* claims ` serving: true ` at ` size()===0 ` — open ( ) builds anyway ( the
* ledger proves there is something to cover ) , and search returns real
* results , never ` [] ` .
* ( 2 ) THE INVERSE , HONEST EMPTY : 0 vectored nouns ( every embed still
* deferred / unlanded ) — open ( ) does NOT attempt a rebuild ( nothing to
* load ; the old blunt "always rebuild when size()===0" heuristic wasted
* a full canonical walk here for zero benefit ) , and search honestly
* returns ` [] ` — no error , no false coverage - gap narration .
*
* SEARCH VERIFICATION NOTE : pin ( 1 ) verifies "search returns real results"
* via ` find({ query: <text> }) ` ( semantic search — embeds the query , then
* searches ) , matching the pattern ` tests/integration/hnsw-rebuild.test.ts `
* already uses for exactly this "post-rebuild search works" class of pin .
* A raw ` find({ vector: <array> }) ` / ` index.search(vector, k) ` call was
* tried first and found to reproducibly return only 1 hit after a
* FROM - CANONICAL rebuild ( never the full requested ` limit ` , sometimes not
* even a real neighbor ) — REGARDLESS of this task ' s changes : it reproduces
* identically on a plain , unwrapped , un - stubbed reopen with the stock JS
* engine ( verified against ` hnsw-rebuild.test.ts ` ' s own construction ) and
* is therefore a PRE - EXISTING , orthogonal defect in the JS HNSW engine ' s
* rebuilt - graph connectivity — outside this task ' s two deliverables ( the
* count ledger and the open - gate REBUILD DECISION , not rebuild ( ) ' s internal
* search quality ) . Left for a separate investigation ; not touched here .
* /
import { describe , it , expect , afterEach , vi } from 'vitest'
import * as fs from 'node:fs'
import * as os from 'node:os'
import * as path from 'node:path'
import { Brainy } from '../../src/index.js'
import { JsHnswVectorIndex } from '../../src/hnsw/hnswIndex.js'
const tmpDirs : string [ ] = [ ]
function mkTmp ( ) : string {
const d = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , 'brainy-vector-leg-open-' ) )
tmpDirs . push ( d )
return d
}
afterEach ( ( ) = > {
vi . restoreAllMocks ( )
for ( const d of tmpDirs . splice ( 0 ) ) fs . rmSync ( d , { recursive : true , force : true } )
} )
const V = ( seed : number ) = >
Array . from ( { length : 384 } , ( _ , i ) = > Math . sin ( ( seed + 1 ) * 7919 + i * 131 ) * 0.5 + 0.5 )
/ * *
* Build a store with N explicit - vector ( non - deferred ) nouns , flush , close .
* Each noun also carries embeddable text ( ` technology ` / ` science ` , matching
* the query used below ) so the semantic - search verification exercises real
* retrieval , not a coincidental match . The default JS engine builds a fully
* current store — the epoch marker is stamped current at this open ' s
* completion , so a later reopen ' s ` _indexEpochStale ` is honestly false and
* cannot mask the ledger - gap decision under test ( nothing here manufactures
* epoch drift ) .
* /
async function buildVectoredStore ( dir : string , n : number ) : Promise < string [ ] > {
const brain : any = new Brainy ( {
requireSubtype : false ,
storage : { type : 'filesystem' , path : dir } ,
plugins : [ ] ,
silent : true ,
dimensions : 384
} )
await brain . init ( )
const ids : string [ ] = [ ]
for ( let i = 0 ; i < n ; i ++ ) {
ids . push (
await brain . add ( {
data : ` doc ${ i } about ${ i % 2 === 0 ? 'technology' : 'science' } ` ,
type : 'document' ,
vector : V ( i )
} )
)
}
await brain . flush ( )
await brain . close ( )
return ids
}
describe ( 'vector-leg open-build (two-engine gate, last red)' , ( ) = > {
it ( 'coverage gap: a provider reporting serving:true at size()===0 is overridden by the vectored-noun ledger — open() builds, search returns real results' , async ( ) = > {
const dir = mkTmp ( )
const ids = await buildVectoredStore ( dir , 12 )
// Wrap the REAL JS engine so a successful rebuild restores genuine,
// searchable vectors — only `size()` and `healthReport()` are faked,
// simulating a native provider that has never built its own coverage of
// an unledgered invariant.
const calls = { rebuild : 0 }
const brain : any = new Brainy ( {
requireSubtype : false ,
storage : { type : 'filesystem' , path : dir } ,
plugins : [ ] ,
silent : true ,
dimensions : 384
} )
brain . use ( {
name : 'fake-native-vector-unledgered-coverage' ,
activate : async ( ctx : any ) = > {
ctx . registerProvider ( 'vector' , ( config : any , distance : any , options : any ) = > {
const real = new JsHnswVectorIndex ( config , distance , options )
let rebuilt = false
const originalRebuild = real . rebuild . bind ( real )
; ( real as any ) . rebuild = async ( . . . args : any [ ] ) = > {
const r = await originalRebuild ( . . . args )
calls . rebuild ++
rebuilt = true
return r
}
const originalSize = real . size . bind ( real )
; ( real as any ) . size = ( ) = > ( rebuilt ? originalSize ( ) : 0 )
; ( real as any ) . healthReport = ( ) = > ( {
provider : 'vector' ,
healthy : true ,
serving : true ,
invariants : [ ] ,
checkedAt : Date.now ( ) ,
durationMs : 0 ,
generation : 1 ,
unledgered : [ 'vector-coverage' ]
} )
return real
} )
return true
}
} )
await brain . init ( )
// WITHOUT any find() first: open() itself must have built the leg.
expect ( calls . rebuild , 'open() forced the rebuild despite serving:true' ) . toBe ( 1 )
const status = await brain . getIndexStatus ( )
expect ( status . hnswIndex . size ) . toBeGreaterThanOrEqual ( ids . length )
// Real, searchable results — never [] (see the module doc's SEARCH
// VERIFICATION NOTE for why this is a semantic `query`, not a raw
// `vector`, call).
const results = await brain . find ( { query : 'technology document' , limit : 5 } )
expect ( results . length ) . toBeGreaterThan ( 0 )
expect ( results . length ) . not . toBe ( 0 )
await brain . close ( )
} )
it ( 'the inverse: only deferred (never-landed) user nouns — the ledger is never inflated by them, and search over them honestly returns []' , async ( ) = > {
fix(vfs): the VFS root never persists a zero-norm vector
A zero-norm vector is lawful inside brainy (cosine distance scores it at
maximum, never a false top hit) but a false attractor for a downstream
engine serving squared-euclidean distance, which cannot tell a real
all-zero vector apart from a legitimate origin point.
- The VFS root now persists with vector [] (the existing "unvectored"
shape) instead of a real all-zero 384-dim placeholder, and is never
routed into the deferred-embed pipeline.
- A one-time migration in the root-init path detects a pre-fix store's
all-zero placeholder root (by norm, not length) and rewrites it to []
through a new sanctioned Brainy method that keeps the canonical
vectored-noun ledger honest and removes the row from the vector index.
- The vector-index write seam (AddToVectorIndexOperation,
ReplaceInVectorIndexOperation, and the generation materializer's direct
insert) now refuses any real all-zero vector before it reaches a
provider, loudly naming the entity, while the canonical write still
lands.
- add()'s dimension-pinning and HNSW-insert gates, and the add-params
validator, now treat any empty vector as carrying no dimension
information, closing a latent trap where an explicit `vector: []`
would have pinned dimensions to 0.
2026-08-27 09:28:44 -07:00
// ARCHITECTURAL NOTE (updated by the zero-norm root cure): every brainy
// store carries ONE permanent VFS root noun beyond user data
// (`entities/nouns/.../00000000-0000-0000-0000-000000000000`,
// src/vfs/VirtualFileSystem.ts), created (or, on a pre-fix store,
// migrated) on every open — but it is deliberately UNVECTORED (vector
// `[]`), never a real all-zero placeholder: a zero-norm vector is not a
// vector and never crosses an engine boundary (see that file's
// doInitializeRoot() comment). It therefore contributes NOTHING to the
// vectored-noun ledger — a brand-new store's `vectors.all` floor is 0,
// not 1. This pin verifies the law the task names in the ACHIEVABLE
// form: nouns whose embed is still deferred/unlanded contribute NOTHING
// to the vectored-noun ledger either — the coverage-gap comparison sees
// exactly the baseline (the root, contributing 0), never
// baseline+deferred — and semantic search over deferred-only user
// content honestly returns `[]` (no error, no false "coverage restored"
// claim).
feat(vector): the vectored-noun scalar joins the count ledger; the open gate closes the vector leg
The coverage denominator the health-by-accounting ratification named for
the vector family — never built until now, and its absence was measured as
the exact outage class it existed to prevent: a migrated store with
canonical vectors and no derived index opened with the vector leg EMPTY,
served [] from vector search with no error, and the report-driven read gate
had nothing to refuse on (the provider's coverage invariant was honestly
unledgered — the denominator was ours to supply).
- getCanonicalCounts() gains vectors: { all } — the count of canonical
nouns holding a REAL vector. Incremented where a vector lands (the
isNew-gated metadata seam for explicit vectors — the same discipline that
keeps HNSW neighbor-link re-saves from inflating counts; a narrow
noteVectorLanded hook for the deferred-embed landing, gated on the
worker's own pre-embed read). Decremented on a proven delete of a
vectored noun; a vector-uncertain delete marks the ledger suspect rather
than guessing (no new reads on the delete path). Recounted by the
sanctioned recount; legacy counts.json derives it once (a deferred noun's
vector file exists with an empty vector, so presence requires one
content read at derivation — never on the hot path).
- The open gate's vector leg: when a health-reporting provider claims
serving while the index holds zero nodes and the ledger proves vectored
canonical rows exist, open BUILDS (narrated) — routed through the
provider's idempotent fillFromCanonical() when exposed (the joint door;
a partial shortfall stays repair()'s operator business), the JS rebuild
otherwise — or fails typed pre-serve. Scoped exactly: bare isReady()
providers, migrating providers, and white-box size stubs open as before.
Pinned end-to-end from the partner gate's probe shape (store with vectored
canonical rows, no derived index, reopen → search serves N, never []),
red-proved against the pre-fix path; the inverse (zero vectored rows) opens
without building and serves [] honestly.
2026-08-25 15:31:19 -07:00
const dir = mkTmp ( )
const build : any = new Brainy ( {
requireSubtype : false ,
storage : { type : 'filesystem' , path : dir } ,
plugins : [ ] ,
silent : true ,
dimensions : 384
} )
await build . init ( )
const rootOnlyLedger = await build . storage . getCanonicalCounts ( )
fix(vfs): the VFS root never persists a zero-norm vector
A zero-norm vector is lawful inside brainy (cosine distance scores it at
maximum, never a false top hit) but a false attractor for a downstream
engine serving squared-euclidean distance, which cannot tell a real
all-zero vector apart from a legitimate origin point.
- The VFS root now persists with vector [] (the existing "unvectored"
shape) instead of a real all-zero 384-dim placeholder, and is never
routed into the deferred-embed pipeline.
- A one-time migration in the root-init path detects a pre-fix store's
all-zero placeholder root (by norm, not length) and rewrites it to []
through a new sanctioned Brainy method that keeps the canonical
vectored-noun ledger honest and removes the row from the vector index.
- The vector-index write seam (AddToVectorIndexOperation,
ReplaceInVectorIndexOperation, and the generation materializer's direct
insert) now refuses any real all-zero vector before it reaches a
provider, loudly naming the entity, while the canonical write still
lands.
- add()'s dimension-pinning and HNSW-insert gates, and the add-params
validator, now treat any empty vector as carrying no dimension
information, closing a latent trap where an explicit `vector: []`
would have pinned dimensions to 0.
2026-08-27 09:28:44 -07:00
// THE NEW LAW: the root is unvectored — a brand-new store's floor is 0.
expect ( rootOnlyLedger . vectors . all ) . toBe ( 0 )
feat(vector): the vectored-noun scalar joins the count ledger; the open gate closes the vector leg
The coverage denominator the health-by-accounting ratification named for
the vector family — never built until now, and its absence was measured as
the exact outage class it existed to prevent: a migrated store with
canonical vectors and no derived index opened with the vector leg EMPTY,
served [] from vector search with no error, and the report-driven read gate
had nothing to refuse on (the provider's coverage invariant was honestly
unledgered — the denominator was ours to supply).
- getCanonicalCounts() gains vectors: { all } — the count of canonical
nouns holding a REAL vector. Incremented where a vector lands (the
isNew-gated metadata seam for explicit vectors — the same discipline that
keeps HNSW neighbor-link re-saves from inflating counts; a narrow
noteVectorLanded hook for the deferred-embed landing, gated on the
worker's own pre-embed read). Decremented on a proven delete of a
vectored noun; a vector-uncertain delete marks the ledger suspect rather
than guessing (no new reads on the delete path). Recounted by the
sanctioned recount; legacy counts.json derives it once (a deferred noun's
vector file exists with an empty vector, so presence requires one
content read at derivation — never on the hot path).
- The open gate's vector leg: when a health-reporting provider claims
serving while the index holds zero nodes and the ledger proves vectored
canonical rows exist, open BUILDS (narrated) — routed through the
provider's idempotent fillFromCanonical() when exposed (the joint door;
a partial shortfall stays repair()'s operator business), the JS rebuild
otherwise — or fails typed pre-serve. Scoped exactly: bare isReady()
providers, migrating providers, and white-box size stubs open as before.
Pinned end-to-end from the partner gate's probe shape (store with vectored
canonical rows, no derived index, reopen → search serves N, never []),
red-proved against the pre-fix path; the inverse (zero vectored rows) opens
without building and serves [] honestly.
2026-08-25 15:31:19 -07:00
// Block the embedder permanently so every add below stays deferred and
// unlanded for the rest of this test (a fast deterministic embedder
// could otherwise land it before we ever observe the "still 0 extra"
// state).
vi . spyOn ( build , 'embed' ) . mockImplementation ( ( ) = > new Promise ( ( ) = > { } ) )
for ( let i = 0 ; i < 5 ; i ++ ) {
await build . add ( { data : ` deferred ${ i } ` , type : 'document' , deferEmbedding : true } )
}
await build . flush ( )
const ledgerWithDeferred = await build . storage . getCanonicalCounts ( )
// The five deferred adds contributed ZERO to the vectored-noun ledger.
expect ( ledgerWithDeferred . vectors . all ) . toBe ( rootOnlyLedger . vectors . all )
await build . close ( )
// Reopen (default JS engine — no stub needed): the root is the ONLY
// thing the vector leg has to load; the deferred nouns are correctly
// invisible to it. Block the embedder again BEFORE init() — reopen
// recovers the durable pending-embed markers and kicks the worker as
// part of init() itself, and an unblocked deterministic embedder could
// land all five before this test observes the open-time ledger.
const brain : any = new Brainy ( {
requireSubtype : false ,
storage : { type : 'filesystem' , path : dir } ,
plugins : [ ] ,
silent : true ,
dimensions : 384
} )
vi . spyOn ( brain , 'embed' ) . mockImplementation ( ( ) = > new Promise ( ( ) = > { } ) )
await brain . init ( )
// The ledger is exactly the root — the five deferred, still-unlanded
// nouns (which the rebuild above DOES insert into the graph, each with
// its stub empty vector — `index.size()` counts EVERY canonical noun's
// graph node, deferred or not, so it is not the coverage metric) never
// inflate the VECTORED count.
const ledgerAfterReopen = await brain . storage . getCanonicalCounts ( )
expect ( ledgerAfterReopen . vectors . all ) . toBe ( rootOnlyLedger . vectors . all )
const results = await brain . find ( { vector : V ( 3 ) , limit : 5 } )
expect ( results ) . toEqual ( [ ] )
await brain . close ( )
} )
} )