91 lines
3.6 KiB
TypeScript
91 lines
3.6 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { MindDB, FrameStore, SessionStore } from '@waggle/core';
|
|
import { runVectorBackfill } from '../../src/local/vector-backfill.js';
|
|
import { MockEmbedder } from '../../../hive-mind-core/tests/mind/helpers/mock-embedder.js';
|
|
import type { EmbeddingProviderInstance } from '@waggle/core';
|
|
|
|
/**
|
|
* D1 follow-up — one-time vector repair + chunk backfill per mind.
|
|
* Mock-fingerprint repair (re-embed noise vectors) + rechunkAllFrames,
|
|
* idempotent via meta flag, skip-and-retry while the embedder is mock.
|
|
*/
|
|
|
|
/** Wrap MockEmbedder as a provider instance reporting a REAL active provider. */
|
|
function realProvider(): EmbeddingProviderInstance {
|
|
const m = new MockEmbedder();
|
|
return Object.assign(m, {
|
|
getActiveProvider: () => 'ollama',
|
|
getStatus: () => ({ activeProvider: 'ollama', modelName: 'test-model' }),
|
|
}) as unknown as EmbeddingProviderInstance;
|
|
}
|
|
|
|
function mockProvider(): EmbeddingProviderInstance {
|
|
const m = new MockEmbedder();
|
|
return Object.assign(m, {
|
|
getActiveProvider: () => 'mock',
|
|
getStatus: () => ({ activeProvider: 'mock', modelName: 'deterministic-mock' }),
|
|
}) as unknown as EmbeddingProviderInstance;
|
|
}
|
|
|
|
describe('runVectorBackfill', () => {
|
|
let db: MindDB;
|
|
let gopId: string;
|
|
|
|
beforeEach(() => {
|
|
db = new MindDB(':memory:');
|
|
const frames = new FrameStore(db);
|
|
gopId = new SessionStore(db).create().gop_id;
|
|
frames.createIFrame(gopId, 'a frame about quarterly planning details', 'normal', 'system');
|
|
frames.createIFrame(gopId, 'another frame with sailing trip notes and logistics', 'normal', 'system');
|
|
});
|
|
|
|
afterEach(() => db.close());
|
|
|
|
it('skips (without setting the flag) while the embedder is mock — retries later', async () => {
|
|
const r1 = await runVectorBackfill(db, mockProvider());
|
|
expect(r1.skipped).toBe('no_real_embedder');
|
|
// a later run with a real provider DOES the work
|
|
const r2 = await runVectorBackfill(db, realProvider());
|
|
expect(r2.skipped).toBeNull();
|
|
expect(r2.chunksCreated).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('backfills chunks once and is a flagged no-op afterwards', async () => {
|
|
const r1 = await runVectorBackfill(db, realProvider());
|
|
expect(r1.skipped).toBeNull();
|
|
expect(r1.chunksCreated).toBe(2);
|
|
expect(r1.vectorsRepaired).toBe(false); // no mock fingerprint on a fresh mind
|
|
|
|
const r2 = await runVectorBackfill(db, realProvider());
|
|
expect(r2.skipped).toBe('already_done');
|
|
expect(r2.chunksCreated).toBe(0);
|
|
});
|
|
|
|
it('repairs mock-fingerprinted vectors: recreates vec tables and re-embeds all frames', async () => {
|
|
const raw = db.getDatabase();
|
|
// simulate the D1 probe finding: vectors written under the mock fingerprint
|
|
raw.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('embedding_provider', 'mock')").run();
|
|
raw.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('embedding_dim', '1024')").run();
|
|
|
|
const r = await runVectorBackfill(db, realProvider());
|
|
expect(r.skipped).toBeNull();
|
|
expect(r.vectorsRepaired).toBe(true);
|
|
expect(r.framesReembedded).toBe(2);
|
|
expect(r.chunksCreated).toBe(2);
|
|
const vec = raw.prepare('SELECT COUNT(*) AS n FROM memory_frames_vec').get() as { n: number };
|
|
expect(vec.n).toBe(2);
|
|
});
|
|
|
|
it('marks an empty mind done without doing work', async () => {
|
|
const empty = new MindDB(':memory:');
|
|
try {
|
|
const r = await runVectorBackfill(empty, realProvider());
|
|
expect(r.skipped).toBe('empty_mind');
|
|
const again = await runVectorBackfill(empty, realProvider());
|
|
expect(again.skipped).toBe('already_done');
|
|
} finally {
|
|
empty.close();
|
|
}
|
|
});
|
|
});
|