From 260247c27d27625e9875371810193d011cb3a160 Mon Sep 17 00:00:00 2001 From: Lindy Xu Date: Mon, 24 Aug 2026 13:46:14 +0800 Subject: [PATCH] test: add result-level ANN plan validation above k threshold Follow-up from #259 review: the existing SQL-text assertion only verifies the query contains CROSS JOIN, which would pass even if the planner silently reordered. This test seeds 200 real memories across two container tags (above the k=128 ANN threshold), runs a tagged search, and asserts that: - the closest target-tag memory ranks first (similarity > 0.9) - no other-container-tag memories leak into results - the result count respects the limit This catches future planner/driver regressions at the behavior level rather than SQL string matching. --- tests/turso-vector-search.test.ts | 67 +++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/turso-vector-search.test.ts b/tests/turso-vector-search.test.ts index bbcda1c..51e2b7c 100644 --- a/tests/turso-vector-search.test.ts +++ b/tests/turso-vector-search.test.ts @@ -100,4 +100,71 @@ describe("turso vector search", () => { } expect(observedSql[1]).toContain("m.container_tag = ?"); }); + + it("returns correct tagged memories from ANN above the k threshold (result-level plan check)", async () => { + baseDir = mkdtempSync(join(tmpdir(), "turso-vector-ann-")); + + const { CONFIG } = await import("../src/config.js"); + CONFIG.storagePath = baseDir; + + const { tursoConnectionManager } = await import("../src/services/turso/connection-manager.js"); + const { tursoShardManager } = await import("../src/services/turso/shard-manager.js"); + const { tursoVectorSearch } = await import("../src/services/turso/vector-search.js"); + + const dims = CONFIG.embeddingDimensions; + const scopeHash = "a1b2c3d4e5f67890"; + const targetTag = `opencode_project_${scopeHash}`; + const otherTag = "opencode_project_0000000000000000"; + + const shard = await tursoShardManager.createShard("project", scopeHash, 0); + const db = await tursoConnectionManager.getConnection(shard.dbPath); + + // Insert 200 memories across two container tags, well above the k=128 + // threshold for container-tagged ANN searches. The target-tag memory at + // index 0 has the query vector itself (similarity 1.0), so it must rank + // first; other-tag memories must never appear in results. + const now = Date.now(); + for (let i = 0; i < 200; i++) { + const vec = new Float32Array(dims); + // Spread signal across dimensions so vectors are distinguishable but + // the index-0 vector is closest to the query (also vec[0]=1). + vec[i % dims] = 1; + if (i > 0) vec[0] = 0.001; + + const tag = i < 150 ? targetTag : otherTag; + await tursoVectorSearch.insertVector(db, { + id: `mem_ann_${i}`, + content: `Memory ${i}`, + vector: vec, + containerTag: tag, + tags: "", + createdAt: now - (200 - i), + updatedAt: now, + }); + } + + // Query vector matches mem_ann_0 (vec[0]=1, no other dimensions set). + const queryVector = new Float32Array(dims); + queryVector[0] = 1; + + const results = await tursoVectorSearch.searchInShard( + shard, + queryVector, + targetTag, + 10, + "turso" + ); + + expect(results.length).toBeGreaterThan(0); + // The closest target-tag memory must rank first. + expect(results[0]?.id).toBe("mem_ann_0"); + expect(results[0]?.similarity).toBeGreaterThan(0.9); + // No other-container-tag memories (indices 150-199) should leak through. + for (const r of results) { + const idx = Number(r.id.replace("mem_ann_", "")); + expect(idx).toBeLessThan(150); + } + // Results should be limited to the requested limit. + expect(results.length).toBeLessThanOrEqual(10); + }); });