Every persisted projection artifact (metadata field indexes + column segments, HNSW node records, graph adjacency LSM trees) now carries a stamp asserting 'this state reflects every committed generation ≤ W, atomically' — written LAST in each owner's flush (stamp-after-data: a crash between data and stamp = unstamped = rescan, never trust). At load, each owner computes the three-way verdict: stamped==committed → adopt (zero work) · behind → catchup (gap reported) · above/unstamped → RESCAN, loudly. Legacy artifacts re-derive once, then are stamped forever. Shared law in projectionWatermark.ts (the aggregation verdict machinery, generalized); vector artifacts carry model dimensions. Verdicts are computed and exposed (watermark()/watermarkVerdict()/watermarkGap()); rebuild triggers unchanged — acting on 'catchup' is the fold train. Pins: 22 unit (7 metadata · 8 hnsw · 7 graph, incl. spy-order stamp-after-data) + the end-to-end reopen-adopts pin.
50 lines
2.1 KiB
TypeScript
50 lines
2.1 KiB
TypeScript
/**
|
|
* @module tests/integration/watermark-adopt-reopen
|
|
* @description End-to-end LC1 watermark adoption: a clean flush+close stamps
|
|
* every projection at the committed generation; the reopen verdicts all read
|
|
* 'adopt' — a same-version reopen owes ZERO rebuild work, provably, via the
|
|
* stamps rather than via absence of complaint.
|
|
*/
|
|
import { describe, it, expect, afterEach } from 'vitest'
|
|
import { mkdtempSync, rmSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { Brainy } from '../../src/index.js'
|
|
import { NounType } from '../../src/types/graphTypes.js'
|
|
|
|
const dirs: string[] = []
|
|
const brains: Brainy[] = []
|
|
afterEach(async () => {
|
|
for (const b of brains.splice(0)) await b.close().catch(() => {})
|
|
for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true })
|
|
})
|
|
|
|
describe('watermark stamps ride the flush fan-out', () => {
|
|
it('flush stamps all three projections at the committed generation; reopen adopts', async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'brainy-wm-'))
|
|
dirs.push(dir)
|
|
let brain = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false })
|
|
await brain.init()
|
|
brains.push(brain)
|
|
await brain.add({ data: 'stamped row', type: NounType.Document, metadata: { k: 1 } })
|
|
await brain.flush()
|
|
|
|
const committed = (brain as unknown as {
|
|
storage: { committedGeneration(): number }
|
|
}).storage.committedGeneration()
|
|
const mi = (brain as unknown as { metadataIndex: { watermark(): number | null } }).metadataIndex
|
|
expect(mi.watermark(), 'metadata stamp = committed').toBe(committed)
|
|
await brain.close()
|
|
brains.pop()
|
|
|
|
brain = new Brainy({ storage: { type: 'filesystem', path: dir }, requireSubtype: false })
|
|
await brain.init()
|
|
brains.push(brain)
|
|
const mi2 = (brain as unknown as {
|
|
metadataIndex: { watermarkVerdict(): string | null }
|
|
}).metadataIndex
|
|
expect(mi2.watermarkVerdict(), 'clean reopen adopts').toBe('adopt')
|
|
// And the brain serves.
|
|
expect((await brain.find({ where: { k: 1 }, limit: 5 })).length).toBe(1)
|
|
}, 60000)
|
|
})
|