import { createMemoryDataSource } from "@conusai-kit/data" import { DEFAULT_FILES_POLICY, type FilesDeps, files } from "@conusai-kit/files" import { type Knowledge, KnowledgeError, type KnowledgeSearchHit } from "@conusai-kit/platform" import { fakeObjectStore } from "@conusai-kit/testing" import { describe, expect, it } from "vitest" import { platformKnowledgeSearch } from "../adapters/platform-knowledge.js" import type { KnowledgeScope } from "../core/ports.js" const scope: KnowledgeScope = { level: "project", actor: { userId: "01900000-0000-7000-0000-000000000009" }, orgId: "01900000-0000-7000-0000-000000000001", workspaceId: "01900000-0000-7000-0000-000000000002", projectId: "01900000-0000-7000-0000-000000000003", } const otherScope: KnowledgeScope = { ...scope, projectId: "01900000-0000-7000-0000-000000000004", } const storageKey = `files/${scope.workspaceId}/${scope.projectId}/01900000-0000-7000-0000-000000000100` const otherStorageKey = `files/${otherScope.workspaceId}/${otherScope.projectId}/01900000-0000-7000-0000-000000000101` const pathname = `org/${scope.orgId}/${storageKey}` interface ClientStub { indexed: string[] removed: string[] searches: Array[0]> results: readonly KnowledgeSearchHit[] } function knowledgeClient(stub: ClientStub): Knowledge { return { readiness: async () => ({ state: "ready", reasons: [], checkedAt: new Date().toISOString() }), createCollection: async (input) => input, search: async (input) => { stub.searches.push(input) return stub.results }, index: async (input) => { stub.indexed.push(input.documentRef) return { jobId: "job-1" } }, job: async (jobId) => ({ jobId, status: "succeeded", progress: 1, generation: 1, error: null }), cancel: async () => undefined, remove: async (documentRef) => { stub.removed.push(documentRef) }, } } function stub(results: readonly KnowledgeSearchHit[] = []): ClientStub { return { indexed: [], removed: [], searches: [], results } } function makeFiles() { const data = createMemoryDataSource({ entities: [files] }) const deps: FilesDeps = { data, store: fakeObjectStore(), policy: DEFAULT_FILES_POLICY } return { data, deps } } async function seedFile( data: ReturnType, atScope: KnowledgeScope, key: string, filename = "contract.txt", ): Promise { const row = await data.forScope(atScope).of(files).insert({ status: "stored", storageKey: key, filename, contentType: "text/plain", size: 17, sha256: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", uploadedBy: atScope.actor.userId, storedAt: "2026-10-24T00:00:00.000Z", uploadId: null, declaredSize: null, partSize: null, deletingAt: null, }) return row.id } describe("the managed knowledge Blob reference", () => { it("indexes and removes by Blob pathname rather than opaque file id", async () => { const { data, deps } = makeFiles() const fileId = await seedFile(data, scope, storageKey) const client = stub() const service = platformKnowledgeSearch(knowledgeClient(client), deps, { environment: "preview" }) await service.index(scope, { collectionName: "contracts", fileId, filename: "contract.txt", bytes: new TextEncoder().encode("managed knowledge"), }) await service.remove(scope, { collectionName: "contracts", fileId }) expect(client.indexed).toEqual([pathname]) expect(client.removed).toEqual([pathname]) }) it("removes a deleted source by the key it had, whose files row is gone (F407)", async () => { const { data, deps } = makeFiles() const fileId = await seedFile(data, scope, storageKey) await data.forScope(scope).of(files).delete(fileId) const client = stub() const service = platformKnowledgeSearch(knowledgeClient(client), deps, { environment: "preview" }) // Without the key the file can no longer be found, so the removal refuses rather than guessing. await expect(service.remove(scope, { collectionName: "contracts", fileId })).rejects.toThrow() await service.remove(scope, { collectionName: "contracts", fileId, storageKey }) expect(client.removed).toEqual([pathname]) }) it("maps citations through scoped file rows and drops foreign or stale references", async () => { const { data, deps } = makeFiles() const fileId = await seedFile(data, scope, storageKey) await seedFile(data, otherScope, otherStorageKey, "other-project.txt") const client = stub([ { documentRef: pathname, snippet: "own snippet", score: 0.9, citations: [{ page: 1 }] }, { documentRef: `org/${scope.orgId}/${otherStorageKey}`, snippet: "other project snippet", score: 0.8, citations: [], }, { documentRef: `org/01900000-0000-7000-0000-000000000777/${storageKey}`, snippet: "other organization snippet", score: 0.7, citations: [], }, { documentRef: `org/${scope.orgId}/files/${scope.workspaceId}/${scope.projectId}/01900000-0000-7000-0000-000000000200`, snippet: "stale snippet", score: 0.6, citations: [], }, ]) const service = platformKnowledgeSearch(knowledgeClient(client), deps, { environment: "preview" }) const hits = await service.search(scope, { collectionName: "contracts", query: "contract", limit: 10, actorId: scope.actor.userId, }) expect(hits).toEqual([ { fileId, filename: "contract.txt", collectionName: "contracts", score: 0.9, chunk: 1, snippet: "own snippet", page: 1, }, ]) expect(client.searches).toHaveLength(1) expect(client.searches[0]).toMatchObject({ collection: "contracts", query: "contract", limit: 10 }) expect(client.searches[0]?.tenant).toMatch(/^[0-9a-f]{64}$/) expect(client.searches[0]).not.toHaveProperty("ownerRef") }) it("refuses a foreign file id before any managed index call", async () => { const { data, deps } = makeFiles() const foreignFileId = await seedFile(data, otherScope, otherStorageKey) const client = stub() const service = platformKnowledgeSearch(knowledgeClient(client), deps, { environment: "preview" }) await expect( service.index(scope, { collectionName: "contracts", fileId: foreignFileId, filename: "other-project.txt", bytes: new TextEncoder().encode("foreign content"), }), ).rejects.toThrow() expect(client.indexed).toEqual([]) }) }) describe("the managed service's collections", () => { it("creates the collection in the deployment's environment before it indexes: the service answers 404 otherwise", async () => { const { data, deps } = makeFiles() const fileId = await seedFile(data, scope, storageKey) const order: string[] = [] const client: Knowledge = { ...knowledgeClient(stub()), createCollection: async (input) => { order.push(`collection ${input.collection} in ${input.environment}`) return input }, index: async () => { order.push("index") return { jobId: "job-1" } }, } const input = { collectionName: "contracts", fileId, filename: "a.txt", bytes: new TextEncoder().encode("text") } await platformKnowledgeSearch(client, deps, { environment: "preview" }).index(scope, input) expect(order).toEqual(["collection contracts in preview", "index"]) order.length = 0 const missing = platformKnowledgeSearch(client, deps, { environment: undefined }) expect(await missing.readiness(scope)).toMatchObject({ state: "unavailable", refusal: "configuration_missing" }) await expect(missing.index(scope, input)).rejects.toMatchObject({ reason: "configuration_missing", permanent: "configuration_missing", }) expect(order).toEqual([]) }) it("answers no results for a collection nothing was indexed into yet", async () => { const client: Knowledge = { ...knowledgeClient(stub()), search: async () => { throw new KnowledgeError("unavailable_resource", "no such collection") }, } const hits = await platformKnowledgeSearch(client, makeFiles().deps, { environment: "preview" }).search(scope, { collectionName: "empty", query: "anything", limit: 5, actorId: scope.actor.userId, }) expect(hits).toEqual([]) }) }) describe("the managed service's refusals", () => { const answering = (over: Partial) => ({ ...knowledgeClient(stub()), ...over }) it.each([ ["plan_required", "disabled", "plan_required"], ["feature_disabled", "disabled", "feature_disabled"], ["forbidden", "unavailable", "scope_missing"], ["unauthenticated", "unavailable", "runtime_unavailable"], ["runtime_unavailable", "unavailable", "runtime_unavailable"], ["indexing", "indexing", "indexing"], ] as const)("readiness refused with %s is state %s and the typed refusal %s", async (reason, state, refusal) => { const client = answering({ readiness: async () => { throw new KnowledgeError(reason, "the service's own words") }, }) const answer = await platformKnowledgeSearch(client, makeFiles().deps, { environment: "preview" }).readiness(scope) expect(answer).toEqual({ state, reasons: [], refusal }) }) it("records a refusal waiting cannot fix on the document, and leaves a transient failure to the queue", async () => { const { data, deps } = makeFiles() const fileId = await seedFile(data, scope, storageKey) const bytes = new TextEncoder().encode("managed knowledge") const input = { collectionName: "contracts", fileId, filename: "contract.txt", bytes } const quota = platformKnowledgeSearch( answering({ job: async (jobId) => ({ jobId, status: "failed", progress: null, generation: null, error: "quota-exhausted" }), }), deps, { environment: "preview" }, ) await expect(quota.index(scope, input)).rejects.toMatchObject({ permanent: "quota_exhausted" }) const scope_missing = platformKnowledgeSearch( answering({ index: async () => { throw new KnowledgeError("forbidden", "the token lacks knowledge.collections:use") }, }), deps, { environment: "preview" }, ) await expect(scope_missing.index(scope, input)).rejects.toMatchObject({ permanent: "scope_missing" }) const down = platformKnowledgeSearch( answering({ index: async () => { throw new KnowledgeError("runtime_unavailable", "try later", true) }, }), deps, { environment: "preview" }, ) const transient = await down.index(scope, input).catch((error: unknown) => error) expect(transient).toBeInstanceOf(KnowledgeError) expect(transient).not.toHaveProperty("permanent") }) }) describe("bounded durable index polling", () => { it("stops a queued wait on cancellation without cancelling the durable platform job", async () => { const { data, deps } = makeFiles() const fileId = await seedFile(data, scope, storageKey) const controller = new AbortController() let polls = 0 let cancellations = 0 const client: Knowledge = { ...knowledgeClient(stub()), job: async (jobId) => { polls++ controller.abort() return { jobId, status: "queued", progress: null, generation: null, error: null } }, cancel: async () => { cancellations++ }, } await expect( platformKnowledgeSearch(client, deps, { environment: "preview", intervalMs: 1 }).index(scope, { collectionName: "contracts", fileId, filename: "a.txt", bytes: new Uint8Array(), signal: controller.signal, }), ).rejects.toBeDefined() expect(polls).toBe(1) expect(cancellations).toBe(0) }) })