feat(8.0): asOf at-gen vector defer — provider-served historical semantic search (#35)

A filtered semantic read at a historical generation (db.asOf(g).find({ query, where }))
no longer unconditionally rebuilds an ephemeral JS-HNSW over every at-g vector
(O(n@G), the OOM ceiling the 2026-06-24 scaling audit flagged). When the vector
index is a VersionedIndexProvider that advertises isGenerationVisible(g), Brainy:
  1. resolves the at-g metadata∩graph universe from the record-overlay path (no
     materialization — it's the metadata-only historical find),
  2. routes the vector leg to the provider with { allowedIds, generation }, and
  3. composes the at-g entities ranked by the provider's at-g vector distance.
Without a versioned provider (the JS index, or a native one that refuses the
generation) it falls through to the existing materialization — unchanged.

- Seam (A): VectorIndexProvider.search gains an optional `generation?: bigint`
  ("omitted = now"), the vector mirror of the graph provider's trailing-gen + the
  #46 allowedIds field. JsHnswVectorIndex accepts and ignores it (no per-gen
  segments → refuse/fall-back posture; Brainy never routes a historical read there).
- Gate: Db.find tries the native at-gen vector path before materialize(); host
  exposes canServeVectorAtGeneration + vectorSearchAtGeneration.
- generation is Brainy's u64 commit counter — the same value handed to the graph
  index on writes (graphWriteGeneration), so it maps 1:1 to the native side.

Decided lockstep with the cor team (handoff #35 thread: (A) + vector-only). The
gen-g candidate-vector supply for cor's exact-rerank (part-3) is a separate,
non-blocking seam still being confirmed; the honesty guard keeps this path inactive
(falls through to materialize) until cor's native at-gen rerank is live.

Tested: provider routing + at-gen universe correctness (excludes future-born,
applies the filter) + page window via a mock versioned provider; seam-ignore on the
JS index; 38 db-mvcc/db-temporal historical-read tests green (materialize fallback).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Snelling 2026-06-23 15:52:09 -07:00
parent 450084b6ce
commit 1c363e8c4b
7 changed files with 292 additions and 0 deletions

View file

@ -0,0 +1,108 @@
/**
* @module tests/integration/db-asof-vector-defer
* @description 8.0 #35 a FILTERED semantic/vector read at a historical generation
* defers to a native at-gen `VersionedIndexProvider` instead of the O(n@G) JS-HNSW
* materialization. Brainy resolves the at-gen metadatagraph universe from the record
* overlay (no rebuild) and hands the provider `{ allowedIds, generation }`; the
* provider serves the vector leg, brainy composes. CI has no native provider, so this
* registers a faithful MOCK versioned vector index (it advertises visibility + records
* what it receives) to lock the routing + the seam. The JS index is NOT versioned, so
* the un-mocked path correctly falls through to materialization.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../src/index.js'
import { NounType } from '../../src/types/graphTypes.js'
import { createTestConfig } from '../helpers/test-factory.js'
/** A versioned vector provider that advertises visibility + records each search. */
function makeVersionedVectorMock(ranking: string[]) {
const calls: Array<{ generation?: bigint; allowedIds?: ReadonlySet<string> }> = []
const provider = {
// VersionedIndexProvider quartet (duck-typed by isVersionedIndexProvider).
generation: () => 0n,
isGenerationVisible: (_g: bigint) => true,
pin: (_g: bigint) => {},
release: (_g: bigint) => {},
// The at-gen vector leg: record the seam fields, return ranked allowed ids.
search: async (_vec: number[], _k: number, _filter: unknown, opts: any) => {
calls.push({ generation: opts?.generation, allowedIds: opts?.allowedIds })
const allowed = opts?.allowedIds as ReadonlySet<string> | undefined
const out: Array<[string, number]> = []
let distance = 0.1
for (const id of ranking) {
if (!allowed || allowed.has(id)) {
out.push([id, distance])
distance += 0.1
}
}
return out
},
// Unused VectorIndexProvider surface — no-op stubs so close()/flush() are safe.
addItem: async () => '',
removeItem: async () => true,
size: () => 0,
clear: () => {},
rebuild: async () => {},
flush: async () => 0,
getPersistMode: () => 'immediate' as const
}
return { provider, calls }
}
describe('#35 asOf filtered vector read defers to the versioned provider', () => {
let brain: Brainy
let a: string, b: string, c: string
beforeEach(async () => {
brain = new Brainy(createTestConfig())
await brain.init()
a = await brain.add({ type: NounType.Document, data: 'alpha machine learning', metadata: { team: 'x' } })
b = await brain.add({ type: NounType.Document, data: 'beta machine learning', metadata: { team: 'x' } })
c = await brain.add({ type: NounType.Document, data: 'gamma cooking', metadata: { team: 'y' } })
})
afterEach(async () => {
await brain.close()
})
it('routes to the provider with {allowedIds, generation} (no materialization) — at-gen universe is correct', async () => {
const db = brain.now() // pin the generation after a, b, c
// A later write advances the generation → `db` is now historical, and `later`
// (team x) did NOT exist at the pinned generation.
const later = await brain.add({ type: NounType.Document, data: 'delta machine learning', metadata: { team: 'x' } })
// Inject a versioned vector provider that can serve the pinned generation.
const mock = makeVersionedVectorMock([b, a]) // ranks b before a
;(brain as any).index = mock.provider
const results = await db.find({ query: 'machine learning', where: { team: 'x' } })
// Served natively — the provider was hit exactly once with the seam fields.
expect(mock.calls).toHaveLength(1)
expect(typeof mock.calls[0].generation).toBe('bigint')
// allowedIds = the at-gen `team: x` universe = {a, b}; NOT `later` (born after the pin).
const allowed = mock.calls[0].allowedIds!
expect(allowed.has(a)).toBe(true)
expect(allowed.has(b)).toBe(true)
expect(allowed.has(later)).toBe(false)
expect(allowed.has(c)).toBe(false) // team y, filtered out
// Results = the at-gen entities, in the provider's ranking (b before a).
expect(results.map((r) => r.id)).toEqual([b, a])
await db.release()
})
it('respects the page window on the provider-served path', async () => {
const db = brain.now()
await brain.add({ type: NounType.Document, data: 'epsilon', metadata: { team: 'z' } }) // advance gen
const mock = makeVersionedVectorMock([b, a])
;(brain as any).index = mock.provider
const page = await db.find({ query: 'machine learning', where: { team: 'x' }, limit: 1 })
expect(page.map((r) => r.id)).toEqual([b]) // top-1 by the provider ranking
await db.release()
})
})