109 lines
4.7 KiB
TypeScript
109 lines
4.7 KiB
TypeScript
|
|
/**
|
||
|
|
* @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 metadata∩graph 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()
|
||
|
|
})
|
||
|
|
})
|