test/knowledge-ordering.test.js
Rama main · réplica de lectura del repositorio original.
// Locale-independent ordering for opaque Knowledge ids and refs.
//
// Knowledge refs and ranked ids are base64url, so they contain both "-" (0x2D)
// and "_" (0x5F). ICU collation orders those two the other way round from UTF-16
// code units and varies with the runtime's locale data, so using localeCompare
// as the deterministic tie-break made otherwise-tied results reorder across
// machines. These tests pin the code-unit contract.
//
// Nothing here computes an expected order with localeCompare, and no test
// returns early: every assertion runs on every invocation.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("fs");
var os = require("os");
var path = require("path");
var knowledgeSearch = require("../lib/knowledge-search");
var attachService = require("../lib/mate-knowledge-service").attachMateKnowledgeService;
var mateSync = require("../lib/mate-knowledge-sync");
// The contract, written out independently of the implementation under test.
function codeUnit(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
// --- the comparator itself ------------------------------------------------
test("compareIds is UTF-16 code-unit order and disagrees with localeCompare", function () {
// The exact characters that make base64url identifiers collation-sensitive.
assert.equal("-".charCodeAt(0), 45);
assert.equal("_".charCodeAt(0), 95);
var dash = "know:-AAAAAAAAAAAAAAAAAAAAAA";
var under = "know:_AAAAAAAAAAAAAAAAAAAAAA";
assert.equal(knowledgeSearch.compareIds(dash, under), -1, "\"-\" sorts before \"_\"");
assert.equal(knowledgeSearch.compareIds(under, dash), 1);
assert.equal(knowledgeSearch.compareIds(dash, dash), 0);
// The defect, demonstrated rather than asserted from memory. If a future
// runtime ever agreed with code units this assertion would fail loudly, which
// is the correct signal that the regression tests below stopped discriminating.
assert.equal(under.localeCompare(dash), -1, "localeCompare orders \"_\" first");
assert.notEqual(
Math.sign(under.localeCompare(dash)),
Math.sign(knowledgeSearch.compareIds(under, dash)),
"the two orderings genuinely disagree on this pair"
);
// Equivalent to plain Array#sort, which is the documented ordering.
var mixed = [under, dash];
assert.deepEqual(mixed.slice().sort(knowledgeSearch.compareIds), [dash, under]);
assert.deepEqual(mixed.slice().sort(), [dash, under]);
assert.deepEqual(mixed.slice().sort(knowledgeSearch.compareIds), mixed.slice().sort());
});
test("compareIds converts deterministically before comparing", function () {
assert.equal(knowledgeSearch.compareIds(10, 9), -1, "numbers compare as their string forms");
assert.equal(knowledgeSearch.compareIds("a", "a"), 0);
assert.equal(knowledgeSearch.compareIds(null, "null"), 0);
assert.equal(knowledgeSearch.compareIds(undefined, "undefined"), 0);
// Total order: antisymmetric and transitive over a mixed alphabet.
var ids = ["a-1", "a_1", "a01", "aA1", "b-0", "_x", "-x", "0x"];
var sorted = ids.slice().sort(knowledgeSearch.compareIds);
assert.deepEqual(sorted, ids.slice().sort(), "matches default lexical order");
for (var i = 0; i < ids.length; i++) {
for (var j = 0; j < ids.length; j++) {
assert.equal(
Math.sign(knowledgeSearch.compareIds(ids[i], ids[j])),
Math.sign(-knowledgeSearch.compareIds(ids[j], ids[i])) || 0,
"antisymmetric for " + ids[i] + " / " + ids[j]
);
}
}
});
// --- knowledge-search collapsed segment ordering --------------------------
// rank() lets the caller choose ids outright, so the "-" versus "_" case is
// exercised directly here rather than left to whatever refs happen to be minted.
function rankWithIds(ids, text) {
var items = ids.map(function (id) { return { id: id, text: text }; });
return knowledgeSearch.rank(items, "shared", function (item) {
return { id: item.id, fields: [{ text: item.text, weight: 1 }], meta: item };
}, items.length);
}
test("collapsed segment ranking breaks score ties by code-unit id order", function () {
// Identical text means identical BM25 scores, so the id tie-break decides
// the whole order.
var ids = [
"know:_zzzzzzzzzzzzzzzzzzzzzz",
"know:-aaaaaaaaaaaaaaaaaaaaaa",
"know:_aaaaaaaaaaaaaaaaaaaaaa",
"know:-zzzzzzzzzzzzzzzzzzzzzz",
"know:0aaaaaaaaaaaaaaaaaaaaaa",
];
var ranked = rankWithIds(ids, "shared shared content");
assert.equal(ranked.length, ids.length, "one result per item");
var scores = ranked.map(function (hit) { return hit.score; });
assert.equal(new Set(scores).size, 1, "the fixture really is fully tied on score");
var got = ranked.map(function (hit) { return hit.id; });
assert.deepEqual(got, ids.slice().sort(codeUnit), "ties resolve in code-unit order");
assert.deepEqual(got, ids.slice().sort(), "which is default lexical order");
assert.equal(got[0], "know:-aaaaaaaaaaaaaaaaaaaaaa", "\"-\" leads");
assert.equal(got[got.length - 1], "know:_zzzzzzzzzzzzzzzzzzzzzz", "\"_\" trails");
// Repeating the call is stable.
assert.deepEqual(rankWithIds(ids, "shared shared content").map(function (h) { return h.id; }), got);
// Input order must not leak into the result.
assert.deepEqual(rankWithIds(ids.slice().reverse(), "shared shared content").map(function (h) { return h.id; }), got);
});
test("score still outranks the id tie-break", function () {
var items = [
{ id: "know:_strong", text: "shared shared shared shared" },
{ id: "know:-weak", text: "shared unrelated filler words here" },
];
var ranked = knowledgeSearch.rank(items, "shared", function (item) {
return { id: item.id, fields: [{ text: item.text, weight: 1 }], meta: item };
}, 10);
assert.ok(ranked[0].score > ranked[1].score, "the fixture separates the scores");
assert.equal(ranked[0].id, "know:_strong", "a better score wins despite losing the id order");
});
// --- Mate Knowledge list and search ordering ------------------------------
function tmp(label) {
return fs.mkdtempSync(path.join(os.tmpdir(), "clay-ko-" + label + "-"));
}
// One .jsonl line becomes one Knowledge record. The fixture freezes Date.now
// while importing so every record has the same updatedAt and the service-level
// ref tie-break is exercised deterministically, independent of machine load.
var RECORD_COUNT = 260;
function buildWorkspace() {
var home = tmp("ws");
var baseDir = path.join(home, "knowledge");
var mateDir = path.join(home, "mates", "alice", "mate_ord");
var lines = [];
for (var i = 0; i < RECORD_COUNT; i++) {
lines.push(JSON.stringify({
date: "2026-05-01",
topic: "topic " + i,
summary: "shared marker text for ordering record " + i,
}));
}
fs.mkdirSync(path.join(mateDir, "knowledge"), { recursive: true });
fs.writeFileSync(path.join(mateDir, "knowledge", "digests.jsonl"), lines.join("\n") + "\n");
var realNow = Date.now;
Date.now = function () { return 1770000000000; };
try {
mateSync.reconcileMate({ mateDir: mateDir, baseDir: baseDir, actor: { type: "migration" } });
} finally {
Date.now = realNow;
}
var session = { localId: 1, cliSessionId: "cli-ord", ownerId: "alice" };
var manager = { sessions: new Map([[1, session]]) };
var status = { slug: "mate-ord", path: mateDir, projectOwnerId: "alice", isMate: true, mateId: "mate_ord" };
var projects = new Map([["mate-ord", {
getStatus: function () { return status; },
getSessionManager: function () { return manager; },
}]]);
var mate = { id: "mate_ord", name: "Ord", createdBy: "alice", builtinKey: null, dir: mateDir };
var service = attachService({
baseDir: baseDir,
getProjects: function () { return projects; },
isMultiUser: function () { return true; },
resolveMate: function (userId, mateId) {
return mateId === "mate_ord" && userId === "alice" ? mate : null;
},
listMates: function (userId) { return userId === "alice" ? [mate] : []; },
});
function bindFor(boundSession) {
return service.bind({
projectSlug: "mate-ord",
projectOwnerId: "alice",
isMate: true,
mateId: "mate_ord",
session: boundSession,
});
}
return { bound: bindFor(session), bindFor: bindFor, session: session, baseDir: baseDir, manager: manager };
}
// Equivalent to deepEqual for arrays of strings, but reports the first
// divergence instead of asking Node to render a diff of hundreds of refs, which
// takes minutes and is unreadable when it finally appears.
function assertSameOrder(actual, expected, label) {
assert.equal(actual.length, expected.length, label + ": length differs");
for (var i = 0; i < actual.length; i++) {
if (actual[i] === expected[i]) continue;
assert.fail(label + ": first divergence at index " + i
+ " (got " + actual[i] + ", expected " + expected[i] + ")");
}
}
// Reads every page, so pagination boundaries are part of the order under test.
function drain(call, pageSize) {
var out = [];
var cursor = null;
var guard = 0;
do {
var args = { limit: pageSize };
if (cursor) args.cursor = cursor;
var page = call(args);
out.push(page);
cursor = page.nextCursor;
guard++;
assert.ok(guard < 100, "pagination terminates");
} while (cursor);
return out;
}
test("Mate Knowledge list orders by updatedAt desc then code-unit ref", function () {
var ws = buildWorkspace();
var pages = drain(function (args) { return ws.bound.listKnowledge(args); }, 50);
var entries = [];
for (var p = 0; p < pages.length; p++) entries = entries.concat(pages[p].entries);
assert.equal(entries.length, RECORD_COUNT, "every record is paged through exactly once");
assert.equal(pages[0].total, RECORD_COUNT);
var refs = entries.map(function (entry) { return entry.ref; });
assert.equal(new Set(refs).size, RECORD_COUNT, "refs are unique");
// The documented total order, computed here without localeCompare.
var expected = entries.slice().sort(function (a, b) {
return b.updatedAt - a.updatedAt || codeUnit(a.ref, b.ref);
}).map(function (entry) { return entry.ref; });
assertSameOrder(refs, expected, "the paged sequence matches the documented total order");
// The frozen import clock makes the tie explicit rather than statistical.
var byStamp = {};
for (var i = 0; i < entries.length; i++) {
var stamp = entries[i].updatedAt;
if (!byStamp[stamp]) byStamp[stamp] = [];
byStamp[stamp].push(entries[i].ref);
}
var tied = Object.keys(byStamp).filter(function (k) { return byStamp[k].length > 1; });
assert.equal(tied.length, 1, "the fixture creates one exact timestamp tie group");
assert.equal(byStamp[tied[0]].length, RECORD_COUNT, "every record participates in the tie-break");
for (var g = 0; g < tied.length; g++) {
assertSameOrder(byStamp[tied[g]], byStamp[tied[g]].slice().sort(codeUnit), "tied group in code-unit order");
assertSameOrder(byStamp[tied[g]], byStamp[tied[g]].slice().sort(), "tied group in default lexical order");
}
// Repeating the whole traversal is stable.
var again = [];
var repeatPages = drain(function (args) { return ws.bound.listKnowledge(args); }, 50);
for (var r = 0; r < repeatPages.length; r++) again = again.concat(repeatPages[r].entries);
assertSameOrder(again.map(function (entry) { return entry.ref; }), refs, "repeated calls return the same order");
// A different page size must not change the sequence, only where it splits.
var small = [];
var smallPages = drain(function (args) { return ws.bound.listKnowledge(args); }, 7);
for (var s = 0; s < smallPages.length; s++) small = small.concat(smallPages[s].entries);
assertSameOrder(small.map(function (entry) { return entry.ref; }), refs, "pagination boundaries do not perturb the order");
assert.ok(smallPages.length > pages.length, "the smaller page size really did split more often");
});
test("Mate Knowledge search orders by score desc, updatedAt desc, then code-unit ref", function () {
var ws = buildWorkspace();
var pages = drain(function (args) {
return ws.bound.searchKnowledge(Object.assign({ query: "shared marker" }, args));
}, 50);
var hits = [];
for (var p = 0; p < pages.length; p++) hits = hits.concat(pages[p].results);
assert.equal(hits.length, RECORD_COUNT, "every matching record is paged through");
var refs = hits.map(function (hit) { return hit.ref; });
assert.equal(new Set(refs).size, RECORD_COUNT);
var expected = hits.slice().sort(function (a, b) {
return b.score - a.score || b.updatedAt - a.updatedAt || codeUnit(a.ref, b.ref);
}).map(function (hit) { return hit.ref; });
assert.deepEqual(refs, expected, "the paged sequence matches the documented total order");
// Score ties are what the ref tie-break exists for.
var byScore = {};
for (var i = 0; i < hits.length; i++) {
var key = String(hits[i].score) + "|" + String(hits[i].updatedAt);
if (!byScore[key]) byScore[key] = [];
byScore[key].push(hits[i].ref);
}
var tiedKeys = Object.keys(byScore).filter(function (k) { return byScore[k].length > 1; });
assert.ok(tiedKeys.length > 0, "identical text produces score ties");
for (var g = 0; g < tiedKeys.length; g++) {
assertSameOrder(byScore[tiedKeys[g]], byScore[tiedKeys[g]].slice().sort(codeUnit), "tied score group in code-unit order");
assertSameOrder(byScore[tiedKeys[g]], byScore[tiedKeys[g]].slice().sort(), "tied score group in default lexical order");
}
var repeated = [];
var repeatPages = drain(function (args) {
return ws.bound.searchKnowledge(Object.assign({ query: "shared marker" }, args));
}, 13);
for (var r = 0; r < repeatPages.length; r++) repeated = repeated.concat(repeatPages[r].results);
assertSameOrder(repeated.map(function (hit) { return hit.ref; }), refs, "stable across page sizes and calls");
});
// --- nothing else moved ---------------------------------------------------
test("ordering change preserves result shapes and coverage semantics", function () {
var ws = buildWorkspace();
var listed = ws.bound.listKnowledge({ limit: 5 });
assert.deepEqual(Object.keys(listed).sort(), ["degraded", "degradedScopes", "entries", "nextCursor", "total"]);
// Both are counts of what could not be read, not flags.
assert.equal(listed.degraded, 0);
assert.equal(listed.degradedScopes, 0);
assert.equal(listed.entries.length, 5);
assert.equal(listed.total, RECORD_COUNT);
assert.ok(listed.nextCursor, "more pages remain");
var searched = ws.bound.searchKnowledge({ query: "shared marker", limit: 5 });
assert.deepEqual(Object.keys(searched).sort(), [
"coveredCharsPerRecord", "degraded", "degradedScopes", "incompleteCoverage", "nextCursor", "results", "total",
]);
assert.equal(searched.coveredCharsPerRecord, knowledgeSearch.MAX_INDEXED_CHARS);
assert.equal(searched.incompleteCoverage, 0, "short records are fully covered");
assert.equal(searched.degraded, 0);
assert.equal(searched.degradedScopes, 0);
for (var i = 0; i < searched.results.length; i++) {
assert.equal(typeof searched.results[i].score, "number");
assert.ok(searched.results[i].score > 0, "scores are unchanged real BM25 values");
assert.equal(typeof searched.results[i].snippet, "string");
assert.match(searched.results[i].ref, /^know:[A-Za-z0-9_-]{24}$/);
}
// A ref from the ordered listing still reads back through the same binding.
var read = ws.bound.readKnowledge({ ref: listed.entries[0].ref });
assert.equal(read.ref, listed.entries[0].ref);
assert.equal(typeof read.content, "string");
assert.ok(read.content.length > 0);
});
test("a session outside the Mate still binds to nothing", function () {
var ws = buildWorkspace();
// A session object that the Mate's own manager does not hold cannot bind, so
// no ordering path is reachable from it at all.
assert.equal(ws.bindFor({ localId: 99, cliSessionId: "cli-intruder", ownerId: "mallory" }), null);
assert.equal(ws.bindFor({ localId: 1, cliSessionId: "cli-ord", ownerId: "alice" }), null,
"a look-alike session object is not the exact bound session");
// The genuine binding is unaffected and is scoped to this Mate alone.
assert.equal(ws.bound.isClay, false);
assert.equal(ws.bound.mateId, "mate_ord");
assert.equal(typeof ws.bound.listMateKnowledge, "undefined", "no cross-Mate tool is exposed");
assert.equal(typeof ws.bound.searchMateKnowledge, "undefined");
});