var test = require("node:test"); var assert = require("node:assert/strict"); var fs = require("fs"); var os = require("os"); var path = require("path"); var attachService = require("../lib/mate-knowledge-service").attachMateKnowledgeService; var attachProjectMateKnowledge = require("../lib/project-mate-knowledge").attachProjectMateKnowledge; var attachKnowledge = require("../lib/project-knowledge").attachKnowledge; var knowledgeMcp = require("../lib/mate-knowledge-mcp-server"); var knowledgeSearch = require("../lib/knowledge-search"); var knowledgeImport = require("../lib/knowledge-import"); var mateSync = require("../lib/mate-knowledge-sync"); var createKnowledgeDbService = require("../lib/knowledge-db-service").createKnowledgeDbService; var MATE_TOOLS = ["list_knowledge", "search_knowledge", "read_knowledge", "read_knowledge_document", "create_knowledge_document", "update_knowledge_document", "list_knowledge_document_history", "restore_knowledge_document", "list_databases", "create_database", "read_database", "rename_database", "archive_database", "recover_database", "update_database_schema", "save_table_view", "query_database", "create_database_record", "read_database_record", "update_database_record", "archive_database_record", "recover_database_record", "retry_database_sync", "import_database_records", "read_database_import_progress"]; var CLAY_TOOLS = ["list_mate_knowledge", "search_mate_knowledge", "read_mate_knowledge"]; var databaseOperationSequence = 0; function databaseOperation(prefix) { databaseOperationSequence++; return prefix + "_" + Date.now() + "_" + databaseOperationSequence; } function tmp(label) { return fs.mkdtempSync(path.join(os.tmpdir(), "clay-mk-" + label + "-")); } function write(target, content) { fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, content); } function handle(status, sessions, databaseService) { var manager = { sessions: new Map() }; for (var i = 0; i < (sessions || []).length; i++) manager.sessions.set(sessions[i].localId, sessions[i]); return { getStatus: function () { return status; }, getSessionManager: function () { return manager; }, getKnowledgeDbService: function () { return databaseService || null; } }; } // Two users, each with two Mates, plus a plain Project Driver project. function workspace(opts) { var options = opts || {}; var home = tmp("ws"); var baseDir = path.join(home, "knowledge"); var roots = { alice: path.join(home, "mates", "alice"), bob: path.join(home, "mates", "bob"), }; function seed(owner, mateId, files) { var mateDir = path.join(roots[owner], mateId); var names = Object.keys(files); for (var i = 0; i < names.length; i++) write(path.join(mateDir, "knowledge", names[i]), files[names[i]]); // Identity must never reach Knowledge. write(path.join(mateDir, "CLAUDE.md"), "# Identity\nYou are " + mateId + ".\n"); write(path.join(mateDir, "knowledge", "identity-backup.md"), "# secret identity backup\n"); mateSync.reconcileMate({ mateDir: mateDir, baseDir: baseDir, actor: { type: "migration" } }); return mateDir; } var arch = seed("alice", "mate_arch", { "architecture.md": "# Architecture\nWe chose an append-only journal for durability and replay.\n", "session-digests.jsonl": JSON.stringify({ date: "2026-04-01", topic: "caching", summary: "decided to memoize the index" }) + "\n" + JSON.stringify({ date: "2026-04-02", topic: "재고 관리", summary: "재고 관리 시스템 설계를 논의했다" }) + "\n", }); var scribe = seed("alice", "mate_scribe", { "style-guide.md": "# Style\nShort sentences. No exclamation marks.\n", }); var clayDir = seed("alice", "mate_clay", { "coordination.md": "# Coordination\nClay routes work between Mates.\n", }); var bobMate = seed("bob", "mate_bobmate", { "private.md": "# Bob private\nBob's confidential roadmap for the acquisition.\n", }); var sessions = { arch: { localId: 1, cliSessionId: "cli-arch", ownerId: "alice" }, scribe: { localId: 2, cliSessionId: "cli-scribe", ownerId: "alice" }, clay: { localId: 3, cliSessionId: "cli-clay", ownerId: "alice" }, bob: { localId: 4, cliSessionId: "cli-bob", ownerId: "bob" }, driver: { localId: 5, cliSessionId: "cli-driver", ownerId: "alice" }, }; var projects = new Map(); projects.set("mate-arch", handle({ slug: "mate-arch", path: arch, projectOwnerId: "alice", isMate: true, mateId: "mate_arch" }, [sessions.arch], createKnowledgeDbService({ projectDir: arch, authorizeTextReference: function () { return false; } }))); projects.set("mate-scribe", handle({ slug: "mate-scribe", path: scribe, projectOwnerId: "alice", isMate: true, mateId: "mate_scribe" }, [sessions.scribe], createKnowledgeDbService({ projectDir: scribe, authorizeTextReference: function () { return false; } }))); projects.set("mate-clay", handle({ slug: "mate-clay", path: clayDir, projectOwnerId: "alice", isMate: true, mateId: "mate_clay" }, [sessions.clay], createKnowledgeDbService({ projectDir: clayDir, authorizeTextReference: function () { return false; } }))); projects.set("mate-bob", handle({ slug: "mate-bob", path: bobMate, projectOwnerId: "bob", isMate: true, mateId: "mate_bobmate" }, [sessions.bob], createKnowledgeDbService({ projectDir: bobMate, authorizeTextReference: function () { return false; } }))); projects.set("app", handle({ slug: "app", path: "/srv/app", projectOwnerId: "alice" }, [sessions.driver])); var registry = { mate_arch: { id: "mate_arch", name: "Arch", createdBy: "alice", builtinKey: null, dir: arch }, mate_scribe: { id: "mate_scribe", name: "Scribe", createdBy: "alice", builtinKey: null, dir: scribe }, mate_clay: { id: "mate_clay", name: "Clay", createdBy: "alice", builtinKey: options.clayKey === undefined ? "clay" : options.clayKey, dir: clayDir }, mate_bobmate: { id: "mate_bobmate", name: "BobMate", createdBy: "bob", builtinKey: null, dir: bobMate }, }; var service = attachService({ baseDir: baseDir, getProjects: function () { return projects; }, isMultiUser: function () { return options.multiUser !== false; }, resolveMate: function (userId, mateId) { var mate = registry[mateId]; if (!mate) return null; if (options.multiUser !== false && mate.createdBy !== userId) return null; return mate; }, listMates: function (userId) { var out = []; var keys = Object.keys(registry); for (var i = 0; i < keys.length; i++) { if (registry[keys[i]].createdBy !== userId) continue; out.push(registry[keys[i]]); } return out; }, }); function attach(slug, mateId, owner) { return attachProjectMateKnowledge({ service: service, sm: projects.get(slug).getSessionManager(), projectSlug: slug, getProjectOwnerId: function () { return owner; }, isMate: mateId !== null, mateId: mateId, }); } return { home: home, baseDir: baseDir, service: service, projects: projects, sessions: sessions, registry: registry, dirs: { arch: arch, scribe: scribe, clay: clayDir, bob: bobMate }, arch: attach("mate-arch", "mate_arch", "alice"), scribe: attach("mate-scribe", "mate_scribe", "alice"), clay: attach("mate-clay", "mate_clay", "alice"), bob: attach("mate-bob", "mate_bobmate", "bob"), driver: attach("app", null, "alice"), }; } function call(attached, session, name, args) { var defs = attached.getToolDefs(session); for (var i = 0; i < defs.length; i++) { if (defs[i].name === name) return defs[i].handler(args || {}); } throw new Error("tool not advertised: " + name); } async function json(promise) { var result = await promise; if (result.isError) throw new Error(result.content[0].text); return JSON.parse(result.content[0].text); } // --- Search engine ------------------------------------------------------ test("ranking uses the shared BM25 engine and handles CJK bigrams", function () { var items = [ { id: "a", name: "architecture.md", body: "append only journal durability replay" }, { id: "b", name: "notes.md", body: "a passing mention of journal" }, { id: "c", name: "styles.md", body: "unrelated prose about typography" }, { id: "d", name: "korean.md", body: "재고 관리 시스템 설계를 논의했다" }, ]; function project(item) { return { id: item.id, fields: [{ text: item.name, weight: 3 }, { text: item.body, weight: 1 }], meta: item }; } var ranked = knowledgeSearch.rank(items, "journal durability", project, 10); assert.equal(ranked[0].id, "a", "the denser match outranks the passing mention"); assert.ok(ranked.length >= 2); assert.ok(ranked[0].score > ranked[1].score, "BM25 produces distinct scores, not a flat count"); assert.equal(ranked.filter(function (r) { return r.id === "c"; }).length, 0, "non-matching documents score nothing"); // Field weighting is by controlled repetition, so a title hit outranks a body hit. var byTitle = knowledgeSearch.rank(items, "architecture", project, 10); assert.equal(byTitle[0].id, "a"); // CJK: no spaces between terms, matched through the shared tokenizer's bigrams. var cjk = knowledgeSearch.rank(items, "재고 관리", project, 10); assert.equal(cjk[0].id, "d"); assert.ok(knowledgeSearch.tokenize("재고 관리").length > 2, "CJK input produces bigram tokens"); assert.deepEqual(knowledgeSearch.rank(items, " ", project, 10), [], "an empty query ranks nothing"); // Snippets centre on the matched term. var snippet = knowledgeSearch.snippet("aaa bbb ccc durability ddd eee", "durability", 40); assert.ok(snippet.indexOf("durability") !== -1); }); // --- Ordinary Mate isolation ------------------------------------------- test("an ordinary Mate sees only its own Knowledge and cannot express another scope", async function () { var w = workspace(); var defs = w.arch.getToolDefs(w.sessions.arch); assert.deepEqual(defs.map(function (d) { return d.name; }), MATE_TOOLS); // No tool accepts an identity, owner, or scope argument. for (var i = 0; i < defs.length; i++) { var keys = Object.keys(defs[i].inputSchema); assert.equal(keys.indexOf("mateId"), -1, defs[i].name); assert.equal(keys.indexOf("ownerId"), -1, defs[i].name); assert.equal(keys.indexOf("userId"), -1, defs[i].name); assert.equal(keys.indexOf("scopeId"), -1, defs[i].name); assert.equal(keys.indexOf("projectSlug"), -1, defs[i].name); } var listed = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(listed.total, 3, "one markdown file and two digest lines"); for (var j = 0; j < listed.entries.length; j++) { assert.equal(listed.entries[j].mateId, undefined, "a Mate is never told which Mate it is reading"); assert.equal(listed.entries[j].mateName, undefined); assert.doesNotMatch(listed.entries[j].name || "", /identity/i); } // Forged arguments cannot widen the scope or reveal another Mate. var forged = await json(call(w.arch, w.sessions.arch, "list_knowledge", { mateId: "mate_scribe", ownerId: "bob", userId: "bob", scopeId: "mate/u-bob/mate_bobmate", projectSlug: "mate-bob", })); assert.equal(forged.total, 3, "forged arguments are ignored entirely"); var serialized = JSON.stringify(forged); assert.equal(serialized.indexOf("Style"), -1); assert.equal(serialized.indexOf("Bob"), -1); assert.equal(serialized.indexOf("mate_scribe"), -1); var found = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "exclamation marks" })); assert.equal(found.total, 0, "another Mate's content is not searchable"); // Clay's cross-Mate tools are not advertised to an ordinary Mate. for (var c = 0; c < CLAY_TOOLS.length; c++) { assert.equal(defs.filter(function (d) { return d.name === CLAY_TOOLS[c]; }).length, 0); } assert.throws(function () { call(w.arch, w.sessions.arch, "list_mate_knowledge", {}); }, /not advertised/); }); test("an ordinary Mate cannot read another Mate's record by its opaque ref", async function () { var w = workspace(); var scribeList = await json(call(w.scribe, w.sessions.scribe, "list_knowledge", {})); var foreignRef = scribeList.entries[0].ref; assert.match(foreignRef, /^know:/); var denied = await call(w.arch, w.sessions.arch, "read_knowledge", { ref: foreignRef }); assert.equal(denied.isError, true, "a ref from another scope simply does not resolve"); assert.match(denied.content[0].text, /not found/i); var own = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); var mine = await json(call(w.arch, w.sessions.arch, "read_knowledge", { ref: own.entries[0].ref })); assert.ok(mine.content.length > 0); assert.equal(mine.preview, undefined, "read returns the record, not a preview"); var badRef = await call(w.arch, w.sessions.arch, "read_knowledge", { ref: "know:../../etc/passwd" }); assert.equal(badRef.isError, true); }); test("session-bound text Knowledge tools guard revisions, history, sync, paths, and Mate ownership", async function (t) { var w = workspace(); var captured = w.arch.getToolDefs(w.sessions.arch).filter(function (tool) { return tool.name === "read_knowledge_document"; })[0]; var unknownId = await call(w.arch, w.sessions.arch, "read_knowledge_document", { documentId: "missing-document-id", name: "architecture.md" }); assert.equal(unknownId.isError, true); var created = await json(call(w.arch, w.sessions.arch, "create_knowledge_document", { name: "areas/agent-notes.md", content: "# Agent notes\nInitial narrative.\n", })); assert.match(created.documentId, /^[a-f0-9-]{20,50}$/); assert.ok(created.revision); var read = await json(call(w.arch, w.sessions.arch, "read_knowledge_document", { documentId: created.documentId })); assert.equal(read.content, "# Agent notes\nInitial narrative.\n"); assert.equal(read.name, "areas/agent-notes.md"); assert.equal(read.revision, created.revision); var updated = await json(call(w.arch, w.sessions.arch, "update_knowledge_document", { documentId: created.documentId, content: "# Agent notes\nUpdated narrative with searchable marker.\n", expectedRevision: created.revision, })); assert.notEqual(updated.revision, created.revision); var stale = await call(w.arch, w.sessions.arch, "update_knowledge_document", { documentId: created.documentId, content: "stale", expectedRevision: created.revision, }); assert.equal(stale.isError, true); assert.match(stale.content[0].text, /changed since it was opened|revision/i); var history = await json(call(w.arch, w.sessions.arch, "list_knowledge_document_history", { documentId: created.documentId })); assert.equal(history.versions.length, 1); var restored = await json(call(w.arch, w.sessions.arch, "restore_knowledge_document", { documentId: created.documentId, version: history.versions[0].version, expectedRevision: updated.revision, })); assert.ok(restored.revision); var restoredRead = await json(call(w.arch, w.sessions.arch, "read_knowledge_document", { name: "areas/agent-notes.md" })); assert.equal(restoredRead.content, "# Agent notes\nInitial narrative.\n"); var found = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "searchable marker" })); assert.equal(found.total, 0, "restore removes the revised text from the search projection"); var updatedAgain = await json(call(w.arch, w.sessions.arch, "update_knowledge_document", { name: "areas/agent-notes.md", content: "# Agent notes\nSearchable marker survives restart.\n", expectedRevision: restoredRead.revision, })); assert.ok(updatedAgain.indexSyncPending === false); var searched = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "survives restart" })); assert.equal(searched.total, 1); var restartedService = attachService({ baseDir: w.baseDir, getProjects: function () { return w.projects; }, isMultiUser: function () { return true; }, resolveMate: function (userId, mateId) { var mate = w.registry[mateId]; return mate && mate.createdBy === userId ? mate : null; }, listMates: function (userId) { return Object.keys(w.registry).map(function (key) { return w.registry[key]; }).filter(function (mate) { return mate.createdBy === userId; }); }, }); var restarted = attachProjectMateKnowledge({ service: restartedService, sm: w.projects.get("mate-arch").getSessionManager(), projectSlug: "mate-arch", getProjectOwnerId: function () { return "alice"; }, isMate: true, mateId: "mate_arch", }); var restartRead = await json(call(restarted, w.sessions.arch, "read_knowledge_document", { documentId: created.documentId })); assert.equal(restartRead.content, "# Agent notes\nSearchable marker survives restart.\n"); var restartSearch = await json(call(restarted, w.sessions.arch, "search_knowledge", { query: "survives restart" })); assert.equal(restartSearch.total, 1); var foreign = await call(w.scribe, w.sessions.scribe, "read_knowledge_document", { documentId: created.documentId }); assert.equal(foreign.isError, true); var bob = await call(w.bob, w.sessions.bob, "read_knowledge_document", { documentId: created.documentId }); assert.equal(bob.isError, true); assert.deepEqual(w.driver.getToolDefs(w.sessions.driver), []); w.sessions.arch.ownerId = "bob"; var mutableOwner = await captured.handler({ documentId: created.documentId }); assert.equal(mutableOwner.isError, true, "a captured handler rechecks the live session owner"); w.sessions.arch.ownerId = "alice"; var outside = path.join(w.home, "outside"); fs.mkdirSync(outside); fs.symlinkSync(outside, path.join(w.dirs.arch, "knowledge", "linked")); var linked = await call(w.arch, w.sessions.arch, "create_knowledge_document", { name: "linked/escape.md", content: "nope" }); assert.equal(linked.isError, true); assert.equal(fs.existsSync(path.join(outside, "escape.md")), false); }); test("text tools and the Knowledge workbench share ids, history, conflicts, and restart state", async function (t) { var w = workspace(); var sent = []; var mates = { buildMateCtx: function () { return {}; }, isPromoted: function () { return false; }, getCommonKnowledgeForMate: function () { return []; }, readCommonKnowledgeFile: function () { throw new Error("no shared file"); }, }; function attachUi() { return attachKnowledge({ cwd: w.dirs.arch, isMate: true, knowledgeBaseDir: w.baseDir, sendTo: function (_ws, message) { sent.push(message); }, matesModule: mates, getProjectOwnerId: function () { return "alice"; }, }); } var ui = attachUi(); function uiMessage(message) { sent.length = 0; ui.handleKnowledgeMessage({ _clayUser: { id: "alice" } }, message); return sent.filter(function (entry) { return entry.type; })[0]; } var uiCreated = uiMessage({ type: "knowledge_save", requestId: "ui-create", name: "interop.md", content: "UI version" }); assert.ok(uiCreated.revision); var agentRead = await json(call(w.arch, w.sessions.arch, "read_knowledge_document", { name: "interop.md" })); assert.equal(agentRead.documentId, uiCreated.id); assert.equal(agentRead.revision, uiCreated.revision); var agentUpdate = await json(call(w.arch, w.sessions.arch, "update_knowledge_document", { documentId: agentRead.documentId, content: "Agent version", expectedRevision: agentRead.revision, })); var uiStale = uiMessage({ type: "knowledge_save", requestId: "ui-stale", name: "interop.md", content: "lost", expectedRevision: uiCreated.revision }); assert.equal(uiStale.type, "knowledge_error"); assert.equal(uiStale.code, "STALE"); assert.equal(fs.readFileSync(path.join(w.dirs.arch, "knowledge", "interop.md"), "utf8"), "Agent version"); var uiRead = uiMessage({ type: "knowledge_read", requestId: "ui-read", documentId: agentUpdate.documentId, name: "interop.md" }); assert.equal(uiRead.content, "Agent version"); var uiHistory = uiMessage({ type: "knowledge_history", requestId: "ui-history", documentId: agentUpdate.documentId }); assert.ok(uiHistory.versions.length >= 1); ui = attachUi(); var afterRestart = uiMessage({ type: "knowledge_read", requestId: "ui-restart", documentId: agentUpdate.documentId, name: "interop.md" }); assert.equal(afterRestart.revision, agentUpdate.revision); }); test("text Knowledge tools surface a committed write when projection synchronization fails", async function (t) { var w = workspace(); var original = mateSync.syncMateSource; mateSync.syncMateSource = function () { throw new Error("fixture projection unavailable"); }; t.after(function () { mateSync.syncMateSource = original; }); var saved = await json(call(w.arch, w.sessions.arch, "create_knowledge_document", { name: "offline-agent.md", content: "Canonical text" })); assert.equal(saved.indexSyncPending, true); assert.match(saved.syncError, /projection unavailable/); assert.equal(fs.readFileSync(path.join(w.dirs.arch, "knowledge", "offline-agent.md"), "utf8"), "Canonical text"); }); test("session-bound native Knowledge tools create and operate only the owning Mate database", async function () { var w = workspace(); var createOperation = databaseOperation("create-cases"); var recordOperation = databaseOperation("create-record"); var created = await json(call(w.arch, w.sessions.arch, "create_database", { name: "Cases", operationId: createOperation })); assert.match(created.database.id, /^db_/); var database = created.database; var title = database.schema[0].id; var discovered = await json(call(w.arch, w.sessions.arch, "list_databases", {})); assert.equal(discovered.databases[0].id, database.id); var record = await json(call(w.arch, w.sessions.arch, "create_database_record", { databaseId: database.id, expectedDatabaseRevision: database.revision, operationId: recordOperation, values: (function () { var values = {}; values[title] = "Alpha"; return values; })() })); var queried = await json(call(w.arch, w.sessions.arch, "query_database", { databaseId: database.id })); assert.equal(queried.records[0].id, record.record.id); var foreign = await call(w.scribe, w.sessions.scribe, "read_database", { databaseId: database.id }); assert.equal(foreign.isError, true); assert.match(foreign.content[0].text, /not found/i); var duplicate = await json(call(w.arch, w.sessions.arch, "create_database_record", { databaseId: database.id, expectedDatabaseRevision: database.revision, operationId: recordOperation, values: (function () { var values = {}; values[title] = "Alpha"; return values; })() })); assert.equal(duplicate.record.id, record.record.id); var retried = await json(call(w.arch, w.sessions.arch, "retry_database_sync", { databaseId: database.id })); assert.equal(retried.database.id, database.id); }); test("static and stale-session descriptors fail closed", async function () { var w = workspace(); var adapter = { createToolServer: function (definition) { return definition; } }; var staticServer = w.arch.createMcpServer(adapter, null); assert.equal(staticServer.name, "clay-knowledge"); assert.equal(staticServer.tools.length, MATE_TOOLS.length); for (var i = 0; i < staticServer.tools.length; i++) { assert.equal((await staticServer.tools[i].handler({})).isError, true, staticServer.tools[i].name); } // An impostor object with a matching localId is not the live session. var impostor = { localId: 1, cliSessionId: "cli-arch", ownerId: "alice" }; assert.deepEqual(w.arch.getToolDefs(impostor), []); assert.equal((await w.arch.createMcpServer(adapter, impostor).tools[0].handler({})).isError, true); // A session that was live at bind time but has since been dropped. w.projects.get("mate-arch").getSessionManager().sessions.delete(1); assert.deepEqual(w.arch.getToolDefs(w.sessions.arch), []); assert.equal(w.arch.getSystemPrompt(w.sessions.arch), ""); }); // --- Clay cross-Mate ---------------------------------------------------- test("authoritative Clay reads across the same user's Mates and never across users", async function () { var w = workspace(); var defs = w.clay.getToolDefs(w.sessions.clay); assert.deepEqual(defs.map(function (d) { return d.name; }), CLAY_TOOLS); for (var i = 0; i < defs.length; i++) { assert.equal(MATE_TOOLS.indexOf(defs[i].name), -1, "no tool name is advertised by both sets"); } assert.equal(Object.keys(defs[0].inputSchema).indexOf("mateId") !== -1, true, "list accepts an explicit mateId"); assert.equal(Object.keys(defs[2].inputSchema).indexOf("mateId"), -1, "read resolves an opaque ref, not a mateId"); var all = await json(call(w.clay, w.sessions.clay, "list_mate_knowledge", { limit: 50 })); var mateIds = {}; for (var j = 0; j < all.entries.length; j++) { assert.ok(all.entries[j].mateId, "Clay sees which Mate owns each record"); mateIds[all.entries[j].mateId] = true; } assert.ok(mateIds.mate_arch && mateIds.mate_scribe, "Clay spans the user's Mates"); assert.equal(mateIds.mate_bobmate, undefined, "another user's Mate is never included"); assert.equal(JSON.stringify(all).indexOf("acquisition"), -1, "no cross-user content leaks"); var scoped = await json(call(w.clay, w.sessions.clay, "list_mate_knowledge", { mateId: "mate_scribe" })); assert.equal(scoped.entries.length, 1); assert.equal(scoped.entries[0].mateName, "Scribe"); var hits = await json(call(w.clay, w.sessions.clay, "search_mate_knowledge", { query: "exclamation marks" })); assert.equal(hits.total, 1); assert.equal(hits.results[0].mateId, "mate_scribe"); assert.ok(hits.results[0].score > 0); assert.ok(hits.results[0].snippet.indexOf("exclamation") !== -1); var read = await json(call(w.clay, w.sessions.clay, "read_mate_knowledge", { ref: hits.results[0].ref })); assert.match(read.content, /No exclamation marks/); assert.equal(read.mateId, "mate_scribe"); // Naming another user's Mate is refused, and its refs do not resolve. var crossUser = await call(w.clay, w.sessions.clay, "list_mate_knowledge", { mateId: "mate_bobmate" }); assert.equal(crossUser.isError, true); assert.match(crossUser.content[0].text, /not found/i); var bobList = await json(call(w.bob, w.sessions.bob, "list_knowledge", {})); var bobRef = bobList.entries[0].ref; var stolen = await call(w.clay, w.sessions.clay, "read_mate_knowledge", { ref: bobRef }); assert.equal(stolen.isError, true, "a ref resolves only inside the authorized user's Mate scopes"); }); test("a Mate that is not authoritative builtin Clay gets only its own scope", async function () { // Same Mate id and project, but the registry says it is not builtin Clay. var notClay = workspace({ clayKey: null }); var defs = notClay.clay.getToolDefs(notClay.sessions.clay); assert.deepEqual(defs.map(function (d) { return d.name; }), MATE_TOOLS); var listed = await json(call(notClay.clay, notClay.sessions.clay, "list_knowledge", {})); assert.equal(listed.total, 1, "only its own record"); assert.equal(listed.entries[0].mateId, undefined); // A non-host builtin is equally not Clay. var otherBuiltin = workspace({ clayKey: "researcher" }); assert.deepEqual(otherBuiltin.clay.getToolDefs(otherBuiltin.sessions.clay).map(function (d) { return d.name; }), MATE_TOOLS); }); // --- Project Driver ----------------------------------------------------- test("a non-Mate Project Driver session gets no Mate Knowledge at all", function () { var w = workspace(); var adapter = { createToolServer: function (definition) { return definition; } }; assert.deepEqual(w.driver.getToolDefs(w.sessions.driver), []); assert.deepEqual(w.driver.getToolDefs(null), []); assert.equal(w.driver.createMcpServer(adapter, w.sessions.driver), null); assert.equal(w.driver.createMcpServer(adapter, null), null, "no descriptor is advertised either"); assert.deepEqual(w.driver.getBridgeTools(w.sessions.driver, function () { return {}; }), []); assert.equal(w.driver.getSystemPrompt(w.sessions.driver), ""); }); // --- Degraded content --------------------------------------------------- test("an unreadable record is refused on read and excluded from ranking", async function () { var w = workspace(); var scope = mateSync.scopeForMateDir(w.dirs.arch); var importer = knowledgeImport.createKnowledgeImporter({ scopeId: scope.scopeId, baseDir: w.baseDir }); // A chunked record whose chunk is then tombstoned behind the reader's back. var big = new Array(30000).join("z"); write(path.join(w.dirs.arch, "knowledge", "big.md"), big); mateSync.syncMateSource({ mateDir: w.dirs.arch, fileName: "big.md", baseDir: w.baseDir, actor: { type: "user" } }); var before = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(before.total, 4); assert.equal(before.degraded, 0); importer.removeRecord(knowledgeImport.chunkKey("file:big.md", 1), { actor: { type: "system" } }); var after = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(after.total, 3, "the damaged record is excluded, not half-listed"); assert.equal(after.degraded, 1, "and the exclusion is reported, not silent"); var searched = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "zzz" })); assert.equal(searched.degraded, 1); assert.equal(searched.results.filter(function (r) { return r.name === "big.md"; }).length, 0); }); // --- Bounds and MCP wiring --------------------------------------------- test("list, search, and read limits are enforced server-side", async function () { var w = workspace(); var lines = []; for (var i = 0; i < 80; i++) lines.push(JSON.stringify({ date: "2026-05-01", topic: "topic " + i, summary: "bounded corpus entry " + i })); write(path.join(w.dirs.arch, "knowledge", "session-digests.jsonl"), lines.join("\n") + "\n"); mateSync.syncMateSource({ mateDir: w.dirs.arch, fileName: "session-digests.jsonl", baseDir: w.baseDir, actor: { type: "agent" } }); var huge = await json(call(w.arch, w.sessions.arch, "list_knowledge", { limit: 9999 })); assert.equal(huge.entries.length, 50, "the page size is clamped, not honoured verbatim"); assert.ok(huge.total > 50); assert.ok(huge.nextCursor); var next = await json(call(w.arch, w.sessions.arch, "list_knowledge", { limit: 50, cursor: huge.nextCursor })); assert.notEqual(next.entries[0].ref, huge.entries[0].ref); var defaulted = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(defaulted.entries.length, 20, "the default page size is bounded too"); var searched = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "bounded corpus", limit: 9999 })); assert.equal(searched.results.length, 50); assert.equal((await call(w.arch, w.sessions.arch, "search_knowledge", { query: " " })).isError, true); // A read is clamped, not refused. var readAll = await json(call(w.arch, w.sessions.arch, "read_knowledge", { ref: huge.entries[0].ref, maxChars: 9999999 })); assert.ok(readAll.content.length <= 40000); }); test("a large record reads back completely through continuable slices", async function () { var w = workspace(); var body = ""; for (var i = 0; i < 3000; i++) body += "paragraph " + i + " with ünïcode 漢字 🎉 content\n"; assert.ok(body.length > 60000); write(path.join(w.dirs.scribe, "knowledge", "huge.md"), body); var listed = await json(call(w.scribe, w.sessions.scribe, "list_knowledge", {})); var ref = listed.entries.filter(function (e) { return e.name === "huge.md"; })[0].ref; var first = await json(call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: ref })); assert.equal(first.offset, 0); assert.equal(first.totalChars, body.length); assert.equal(first.complete, false, "one response did not carry the whole record"); assert.ok(first.nextOffset > 0); assert.equal(first.content.length, first.nextOffset, "the default slice is bounded"); assert.equal(first.preview, undefined); // Page to the end and reassemble byte-exactly. var assembled = first.content; var offset = first.nextOffset; var pages = 1; while (offset !== null && pages < 200) { var next = await json(call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: ref, offset: offset, maxChars: 20000 })); assert.equal(next.offset, offset); assert.equal(next.totalChars, body.length); assert.equal(next.complete, false, "a continuation is never reported as complete"); assembled += next.content; offset = next.nextOffset; pages++; } assert.equal(offset, null, "paging terminates"); assert.equal(assembled, body, "the slices reassemble exactly"); assert.ok(pages > 2); // A small record is complete in one call. var small = listed.entries.filter(function (e) { return e.name === "style-guide.md"; })[0]; var whole = await json(call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: small.ref })); assert.equal(whole.complete, true); assert.equal(whole.nextOffset, null); assert.equal(whole.offset, 0); assert.match(whole.content, /No exclamation marks/); // Bounds are clamped server-side and a bad offset is refused. var clamped = await json(call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: ref, maxChars: 9999999 })); assert.equal(clamped.content.length, 40000); var negative = await json(call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: ref, offset: -5, maxChars: -1 })); assert.equal(negative.offset, 0); assert.equal(negative.content.length, 1, "maxChars is clamped to at least one character"); var past = await call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: ref, offset: body.length + 10 }); assert.equal(past.isError, true); assert.match(past.content[0].text, /beyond the end/); // No slice ever ends inside a surrogate pair. for (var probe = 0; probe < 6; probe++) { var at = 8000 + probe; var sliced = await json(call(w.scribe, w.sessions.scribe, "read_knowledge", { ref: ref, offset: 0, maxChars: at })); var last = sliced.content.charCodeAt(sliced.content.length - 1); assert.equal(last >= 0xd800 && last <= 0xdbff, false, "a slice never ends on a lone high surrogate"); } // Clay's cross-Mate read is continuable in the same way. var clayHits = await json(call(w.clay, w.sessions.clay, "search_mate_knowledge", { query: "paragraph" })); var clayRead = await json(call(w.clay, w.sessions.clay, "read_mate_knowledge", { ref: clayHits.results[0].ref, maxChars: 100 })); assert.equal(clayRead.complete, false); assert.equal(clayRead.nextOffset, 100); assert.equal(clayRead.totalChars, body.length); assert.equal(clayRead.mateId, "mate_scribe"); }); test("bridge advertising mirrors the adapter path without duplicates", async function () { var w = workspace(); var normalize = function () { return { type: "object", properties: {} }; }; var mateBridge = w.arch.getBridgeTools(w.sessions.arch, normalize); assert.equal(mateBridge.length, MATE_TOOLS.length); var seen = {}; for (var i = 0; i < mateBridge.length; i++) { assert.equal(mateBridge[i].server, "clay-knowledge"); assert.equal(seen[mateBridge[i].name], undefined, "no duplicate advertised tool"); seen[mateBridge[i].name] = true; } assert.deepEqual(Object.keys(seen), MATE_TOOLS); assert.deepEqual(w.arch.getBridgeTools(null, normalize), []); var clayBridge = w.clay.getBridgeTools(w.sessions.clay, normalize); assert.deepEqual(clayBridge.map(function (t) { return t.name; }), CLAY_TOOLS); var listed = JSON.parse((await w.arch.callBridgeTool(w.sessions.arch, "list_knowledge", {})).content[0].text); assert.equal(listed.total, 3); await assert.rejects(function () { return w.arch.callBridgeTool(w.sessions.arch, "list_mate_knowledge", {}); }, /not found/); await assert.rejects(function () { return w.arch.callBridgeTool(null, "list_knowledge", {}); }, /valid Mate session/); var dynamic = w.arch.getDynamicToolDefs(w.sessions.arch); for (var d = 0; d < dynamic.length; d++) { assert.equal(dynamic[d].permissionName, "mcp__clay-knowledge__" + dynamic[d].name); } }); test("system prompt guidance is scoped and carries no record dump", function () { var w = workspace(); var matePrompt = w.arch.getSystemPrompt(w.sessions.arch); assert.match(matePrompt, /durable personal and expertise context you own/); assert.match(matePrompt, /project Logs are a separate, unrelated surface/); assert.match(matePrompt, /Do not enumerate or restate your Knowledge/); assert.equal(matePrompt.indexOf("append-only journal"), -1, "no record content is dumped into the prompt"); assert.equal(matePrompt.indexOf("Scribe"), -1, "no other Mate is named"); var clayPrompt = w.clay.getSystemPrompt(w.sessions.clay); assert.match(clayPrompt, /Mates belonging to the current user/); assert.match(clayPrompt, /cite the owning Mate/); assert.equal(clayPrompt.indexOf("exclamation"), -1); assert.notEqual(clayPrompt, matePrompt); }); test("the two MCP tool sets are disjoint and unbound defs fail closed", async function () { var mate = knowledgeMcp.getToolDefs(null, false); var clay = knowledgeMcp.getToolDefs(null, true); assert.deepEqual(mate.map(function (d) { return d.name; }), MATE_TOOLS); assert.deepEqual(clay.map(function (d) { return d.name; }), CLAY_TOOLS); for (var i = 0; i < mate.length; i++) { assert.ok(mate[i].description.indexOf(knowledgeMcp.MATE_CONTRACT) === 0); assert.equal((await mate[i].handler({})).isError, true); } for (var j = 0; j < clay.length; j++) { assert.ok(clay[j].description.indexOf(knowledgeMcp.CLAY_CONTRACT) === 0); assert.equal((await clay[j].handler({})).isError, true); } assert.equal(knowledgeMcp.createMcpServer(null, null, false), null); }); // --- Tail search beyond the old truncation limit ------------------------ test("a match far beyond 20k characters is still found, including CJK", async function () { var w = workspace(); // Filler that must not match, then the only occurrence of the needles well // past the old 20000-character field truncation limit. var filler = ""; for (var i = 0; i < 4000; i++) filler += "routine filler paragraph number " + i + " about nothing in particular\n"; assert.ok(filler.length > 60000); var body = filler + "\nThe hexokinase pathway note lives here.\n재고 관리 시스템 설계를 논의했다\n"; var needleAt = body.indexOf("hexokinase"); assert.ok(needleAt > 60000, "the needle sits far past the old truncation point"); write(path.join(w.dirs.arch, "knowledge", "long-notes.md"), body); var hits = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "hexokinase" })); assert.equal(hits.total, 1, "a tail-only match is findable"); assert.equal(hits.results[0].name, "long-notes.md"); assert.ok(hits.results[0].score > 0); assert.ok(hits.results[0].snippet.indexOf("hexokinase") !== -1, "the snippet centres on the tail match"); // CJK tail content routes through the shared tokenizer's bigrams. var cjk = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "재고 관리" })); var names = cjk.results.map(function (r) { return r.name; }); assert.ok(names.indexOf("long-notes.md") !== -1, "a CJK tail match is findable"); // One item yields one result, however many segments it was indexed as. assert.equal(hits.results.filter(function (r) { return r.name === "long-notes.md"; }).length, 1); // Clay finds the same tail content across the user's Mates. var clayHits = await json(call(w.clay, w.sessions.clay, "search_mate_knowledge", { query: "hexokinase" })); assert.equal(clayHits.total, 1); assert.equal(clayHits.results[0].mateId, "mate_arch"); }); test("segmentation preserves anchor weighting and collapses to best segment", function () { var tail = ""; for (var i = 0; i < 4000; i++) tail += "filler line " + i + "\n"; var items = [ { id: "titled", name: "durability.md", body: "unrelated body" }, { id: "tail", name: "misc.md", body: tail + "\ndurability appears only at the very end\n" }, { id: "none", name: "other.md", body: "nothing relevant" }, ]; function project(item) { return { id: item.id, fields: [{ text: item.name, weight: 3 }, { text: item.body, weight: 1 }], meta: item }; } var segments = knowledgeSearch.segmentTexts(project(items[1]).fields); assert.ok(segments.length > 1, "a long body is segmented, not truncated"); for (var s = 0; s < segments.length; s++) { assert.ok(segments[s].indexOf("misc.md") !== -1, "the name anchors every segment"); assert.ok(segments[s].split("misc.md").length - 1 >= 3, "anchor weighting is identical in each segment"); } var ranked = knowledgeSearch.rank(items, "durability", project, 10); var ids = ranked.map(function (r) { return r.id; }); assert.ok(ids.indexOf("tail") !== -1, "the tail match is ranked at all"); assert.equal(ids.indexOf("none"), -1); assert.equal(ids.filter(function (id) { return id === "tail"; }).length, 1, "segments collapse to one result"); assert.equal(ids[0], "titled", "a name hit still outranks a body-tail hit"); // Deterministic and stable across repeated runs. assert.deepEqual(knowledgeSearch.rank(items, "durability", project, 10).map(function (r) { return r.id; }), ids); assert.equal(knowledgeSearch.rank(items, "durability", project, 1).length, 1, "maxResults applies after collapsing"); }); // --- Direct filesystem divergence -------------------------------------- test("a direct filesystem edit is visible on the next tool call", async function () { var w = workspace(); var before = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(before.total, 3); assert.equal((await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "phosphorylase" }))).total, 0); // Written straight to disk, exactly as a Bash tool call would. write(path.join(w.dirs.arch, "knowledge", "bash-written.md"), "# Added out of band\nphosphorylase kinetics summary\n"); var after = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(after.total, 4, "the new file is visible without a daemon restart"); var found = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "phosphorylase" })); assert.equal(found.total, 1); var read = await json(call(w.arch, w.sessions.arch, "read_knowledge", { ref: found.results[0].ref })); assert.match(read.content, /phosphorylase kinetics/); // An edit to an existing file is picked up too. write(path.join(w.dirs.arch, "knowledge", "bash-written.md"), "# Added out of band\nrevised kinetics summary\n"); assert.equal((await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "phosphorylase" }))).total, 0); assert.equal((await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "revised kinetics" }))).total, 1); // A deletion is reflected immediately, not left as a stale live record. fs.unlinkSync(path.join(w.dirs.arch, "knowledge", "bash-written.md")); var afterDelete = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(afterDelete.total, 3); assert.equal(afterDelete.entries.filter(function (e) { return e.name === "bash-written.md"; }).length, 0); var gone = await call(w.arch, w.sessions.arch, "read_knowledge", { ref: found.results[0].ref }); assert.equal(gone.isError, true, "a deleted record no longer resolves"); // Repeated no-op calls append nothing to the record store. var scope = mateSync.scopeForMateDir(w.dirs.arch); var importer = knowledgeImport.createKnowledgeImporter({ scopeId: scope.scopeId, baseDir: w.baseDir }); var records = importer.stats().records; await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "architecture" })); await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(importer.stats().records, records, "an idempotent refresh appends nothing"); }); test("reconciliation never reaches outside the authorized scopes", async function () { var w = workspace(); var bobScope = mateSync.scopeForMateDir(w.dirs.bob); var bobImporter = knowledgeImport.createKnowledgeImporter({ scopeId: bobScope.scopeId, baseDir: w.baseDir }); var bobBefore = bobImporter.stats().records; // Bob writes directly to disk. Alice's Mates and Clay must not touch it. write(path.join(w.dirs.bob, "knowledge", "bob-secret.md"), "# Secret\nacquisition target shortlist\n"); await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); await json(call(w.clay, w.sessions.clay, "list_mate_knowledge", { limit: 50 })); await json(call(w.clay, w.sessions.clay, "search_mate_knowledge", { query: "acquisition" })); assert.equal(bobImporter.stats().records, bobBefore, "another user's scope is never reconciled"); var clayView = await json(call(w.clay, w.sessions.clay, "search_mate_knowledge", { query: "acquisition" })); assert.equal(clayView.total, 0, "and its content is never visible"); // Bob's own session does see it, through his own authorized scope. var bobView = await json(call(w.bob, w.sessions.bob, "search_knowledge", { query: "shortlist" })); assert.equal(bobView.total, 1); assert.equal(bobView.results[0].name, "bob-secret.md"); assert.ok(bobImporter.stats().records > bobBefore, "reconciled only by its own owner"); }); // --- Captured handler identity ----------------------------------------- test("a captured tool handler stops working when its exact session goes away", async function () { var w = workspace(); var defs = w.arch.getToolDefs(w.sessions.arch); var listTool = defs[0]; assert.equal((await json(listTool.handler({}))).total, 3, "valid while the session is live"); // The exact session object is dropped from the manager after the handler // was created. The captured handler must not keep working. w.projects.get("mate-arch").getSessionManager().sessions.delete(1); var afterDrop = await listTool.handler({}); assert.equal(afterDrop.isError, true, "identity is re-derived on every call"); assert.match(afterDrop.content[0].text, /no longer valid/); // Restoring the exact same object restores the capability. w.projects.get("mate-arch").getSessionManager().sessions.set(1, w.sessions.arch); assert.equal((await json(listTool.handler({}))).total, 3); // A registry change that would alter the tool set also invalidates. var clayDefs = w.clay.getToolDefs(w.sessions.clay); assert.equal((await json(clayDefs[0].handler({ limit: 50 }))).entries.length > 0, true); w.registry.mate_clay.builtinKey = null; var demoted = await clayDefs[0].handler({ limit: 50 }); assert.equal(demoted.isError, true, "a Mate that is no longer Clay loses the cross-Mate view"); }); // --- Fail-closed refresh ------------------------------------------------- // Reconciliation reports a failure for exactly one source, leaving its previous // record in place. That record must not be served. function breakSource(mateDir, fileName, options) { var settings = options || {}; var real = mateSync.reconcileMate; mateSync.reconcileMate = function (opts) { var summary = real(opts); // Only the named Mate fails, so the blast radius of a failure is exactly // what the test is asserting about. if (opts && opts.mateDir === mateDir) { summary.failed++; summary.errors.push(settings.unattributable ? { scopeId: "x", message: "something went wrong" } : { scopeId: "x", file: fileName, message: "Unreadable source" }); } return summary; }; return function () { mateSync.reconcileMate = real; }; } test("a source that fails to reconcile is withheld, not served from stale content", async function () { var w = workspace(); var before = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(before.total, 3); var stale = before.entries.filter(function (e) { return e.name === "architecture.md"; })[0]; assert.ok(stale, "the record exists while reconciliation succeeds"); assert.equal((await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "append-only journal" }))).total, 1); var restore = breakSource(w.dirs.arch, "architecture.md"); try { var listed = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(listed.entries.filter(function (e) { return e.name === "architecture.md"; }).length, 0, "the failed source is absent, not stale"); assert.equal(listed.total, 2, "unaffected sources remain when attribution is exact"); assert.equal(listed.degraded, 1, "and the exclusion is reported"); assert.equal(listed.degradedScopes, 0); var searched = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "append-only journal" })); assert.equal(searched.total, 0, "stale content is not searchable either"); assert.equal(searched.degraded, 1); var read = await call(w.arch, w.sessions.arch, "read_knowledge", { ref: stale.ref }); assert.equal(read.isError, true, "the previous content is refused, not returned"); assert.match(read.content[0].text, /could not be brought up to date/); // The digest lines from an unaffected source still read normally. var other = listed.entries.filter(function (e) { return e.kind === "session-digest"; })[0]; assert.ok(other); assert.equal((await json(call(w.arch, w.sessions.arch, "read_knowledge", { ref: other.ref }))).complete, true); } finally { restore(); } // Once reconciliation succeeds again, the source comes back. var after = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(after.total, 3); assert.equal(after.degraded, 0); assert.equal((await json(call(w.arch, w.sessions.arch, "read_knowledge", { ref: stale.ref }))).complete, true); }); test("an unattributable refresh failure withholds the whole scope for that call", async function () { var w = workspace(); var before = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); var anyRef = before.entries[0].ref; var restore = breakSource(w.dirs.arch, null, { unattributable: true }); try { var listed = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); assert.equal(listed.total, 0, "no record is served when the stale one cannot be identified"); assert.equal(listed.degradedScopes, 1); assert.ok(listed.degraded > 0); var searched = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "journal" })); assert.equal(searched.total, 0); assert.equal(searched.degradedScopes, 1); var read = await call(w.arch, w.sessions.arch, "read_knowledge", { ref: anyRef }); assert.equal(read.isError, true); assert.match(read.content[0].text, /withheld/); // Clay spanning several Mates loses only the failing scope. var clayView = await json(call(w.clay, w.sessions.clay, "list_mate_knowledge", { limit: 50 })); assert.equal(clayView.entries.filter(function (e) { return e.mateId === "mate_arch"; }).length, 0); assert.ok(clayView.entries.filter(function (e) { return e.mateId === "mate_scribe"; }).length > 0, "other Mates are unaffected"); assert.equal(clayView.degradedScopes, 1); } finally { restore(); } assert.equal((await json(call(w.arch, w.sessions.arch, "list_knowledge", {}))).total, before.total); }); // --- Honest search coverage --------------------------------------------- test("search reports incomplete index coverage instead of implying completeness", async function () { var w = workspace(); var small = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "journal" })); assert.equal(small.incompleteCoverage, 0, "an ordinary corpus is fully covered"); assert.equal(small.coveredCharsPerRecord, knowledgeSearch.MAX_INDEXED_CHARS); // A record whose searchable text runs past the indexed bound. var unit = "filler segment text abcdef\n"; var body = ""; while (body.length < knowledgeSearch.MAX_INDEXED_CHARS + 40000) body += unit; var needle = "pyrophosphatase"; body += "\n" + needle + " appears only past the indexed bound\n"; assert.ok(body.indexOf(needle) > knowledgeSearch.MAX_INDEXED_CHARS); write(path.join(w.dirs.arch, "knowledge", "enormous.md"), body); var hits = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: "filler segment" })); assert.equal(hits.incompleteCoverage, 1, "the partially indexed record is counted"); assert.equal(hits.coveredCharsPerRecord, knowledgeSearch.MAX_INDEXED_CHARS); assert.ok(hits.results.length > 0); // The tail term cannot be ranked, and the response says coverage was partial // rather than implying the term is absent. var tail = await json(call(w.arch, w.sessions.arch, "search_knowledge", { query: needle })); assert.equal(tail.total, 0); assert.equal(tail.incompleteCoverage, 1, "an empty result is qualified, not silent"); // Ordinary Mate results still expose no other Mate identity. assert.equal(JSON.stringify(hits).indexOf("mate_"), -1); assert.equal(JSON.stringify(tail).indexOf("mate_"), -1); for (var i = 0; i < hits.results.length; i++) { assert.equal(hits.results[i].mateId, undefined); assert.equal(hits.results[i].mateName, undefined); } // Clay sees the same honest accounting, with owner attribution. var clayHits = await json(call(w.clay, w.sessions.clay, "search_mate_knowledge", { query: "filler segment" })); assert.equal(clayHits.incompleteCoverage, 1); assert.equal(clayHits.results[0].mateId, "mate_arch"); // The full record is still readable in full, even though it is not fully indexed. var listed = await json(call(w.arch, w.sessions.arch, "list_knowledge", {})); var ref = listed.entries.filter(function (e) { return e.name === "enormous.md"; })[0].ref; var read = await json(call(w.arch, w.sessions.arch, "read_knowledge", { ref: ref, maxChars: 40000 })); assert.equal(read.totalChars, body.length); assert.equal(read.complete, false); }); test("coverage accounting is deterministic and matches what is indexed", function () { var short = [{ text: "title.md", weight: 3 }, { text: "small body", weight: 1 }]; var shortCoverage = knowledgeSearch.coverage(short); assert.equal(shortCoverage.complete, true); assert.equal(shortCoverage.indexedChars, shortCoverage.totalChars); var body = new Array(knowledgeSearch.MAX_INDEXED_CHARS + 50000).join("a"); var long = [{ text: "title.md", weight: 3 }, { text: body, weight: 1 }]; var longCoverage = knowledgeSearch.coverage(long); assert.equal(longCoverage.complete, false); assert.equal(longCoverage.totalChars, "title.md".length + body.length); assert.equal(longCoverage.indexedChars, "title.md".length + knowledgeSearch.MAX_INDEXED_CHARS); assert.equal(knowledgeSearch.segmentTexts(long).length, knowledgeSearch.MAX_SEGMENTS_PER_ITEM, "the segment cap is respected"); assert.deepEqual(knowledgeSearch.coverage(long), longCoverage, "repeated accounting is identical"); // Exactly at the bound is complete. var exact = [{ text: new Array(knowledgeSearch.MAX_INDEXED_CHARS + 1).join("b"), weight: 1 }]; assert.equal(knowledgeSearch.coverage(exact).complete, true); });