// The vector contract suite against the store shape this module binds in tests: a conforming // in-memory VectorStore that keeps the tenant wall the real adapters guarantee (org filter on // every search and delete). The suite is the kit's own two-tenant contract; running it here pins // the module's metadata conventions (collection filters, subject deletion) to what every adapter // must provide, so a convention drift fails in this module's unit suite. import { newId, type TenantScope } from "@conusai-kit/core" import type { VectorMatch, VectorQuery, VectorRecord, VectorStore, VectorSubject } from "@conusai-kit/data" import { runVectorStoreContract } from "@conusai-kit/testing" import { describe, expect, it } from "vitest" /** The in-memory stand-in with the tenant wall the adapters implement. */ function fakeVectorStore(): { store: VectorStore; failures: () => unknown[] } { const rows = new Map() const keyOf = (orgId: string, id: string) => `${orgId}:${id}` const failures: unknown[] = [] const cosine = (a: readonly number[], b: readonly number[]) => { let dot = 0 let na = 0 let nb = 0 for (const [index, value] of a.entries()) { dot += value * (b[index] ?? 0) na += value * value nb += (b[index] ?? 0) ** 2 } return na === 0 || nb === 0 ? 0 : dot / Math.sqrt(na * nb) } return { failures: () => failures, store: { async upsert(scope: TenantScope, records: readonly VectorRecord[]) { for (const record of records) rows.set(keyOf(scope.orgId, record.id), { scope: scope.orgId, record }) }, async search(scope: TenantScope, query: VectorQuery): Promise { const out: VectorMatch[] = [] for (const row of rows.values()) { if (row.scope !== scope.orgId) continue const metadata = row.record.metadata ?? {} const filter = query.filter ?? {} if (!Object.entries(filter).every(([key, value]) => metadata[key] === value)) continue out.push({ id: row.record.id, score: cosine(query.vector, row.record.vector), subject: row.record.subject, metadata, }) } return out.sort((a, b) => b.score - a.score).slice(0, query.limit ?? 10) }, async deleteBySubject(scope: TenantScope, subject: VectorSubject) { for (const [key, row] of rows.entries()) { if ( row.scope === scope.orgId && row.record.subject.type === subject.type && row.record.subject.id === subject.id ) { rows.delete(key) } } }, }, } } const dimensions = 4 describe("the vector store the knowledge module relies on", () => { it("passes the kit's two-tenant vector contract", async () => { const { store } = fakeVectorStore() const result = await runVectorStoreContract(async () => ({ store, dimensions })) expect(result.failures.map((failure) => `${failure.check}: ${failure.error}`)).toEqual([]) expect(result.passed.length).toBeGreaterThan(0) }) }) // The collection-filter convention this module writes into every chunk's metadata: a search names // the collection and the wall holds. Pinning it here keeps the adapters' payload indexes useful. describe("the module's metadata conventions", () => { it("stores collection, filename, chunk and snippet so a cited result can be rendered", () => { const record: VectorRecord = { id: newId(), vector: [1, 0, 0, 0], subject: { type: "knowledge_document", id: newId() }, metadata: { collection: "contracts", filename: "a.txt", chunk: 2, snippet: "passage" }, } expect(Object.keys(record.metadata ?? {}).sort()).toEqual(["chunk", "collection", "filename", "snippet"]) }) })