test/mate-knowledge-migration.test.js
Rama main · réplica de lectura del repositorio original.
var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("fs");
var os = require("os");
var path = require("path");
var migration = require("../lib/mate-knowledge-migration");
var knowledgeImport = require("../lib/knowledge-import");
// Durable sources per seeded Mate: memory-summary.md, architecture-notes.md,
// two session-digest lines, one user-observation line. Identity files excluded.
var DURABLE = 5;
function tmp(label) {
return fs.mkdtempSync(path.join(os.tmpdir(), "clay-mig-" + label + "-"));
}
function write(target, content) {
fs.mkdirSync(path.dirname(target), { recursive: true });
fs.writeFileSync(target, content);
}
function digest(topic) {
return JSON.stringify({ date: "2026-04-09", type: "session", topic: topic, my_position: "noted" });
}
// A realistic legacy Mate: identity artifacts alongside durable knowledge.
function seedMate(matesRoot, mateId, extras) {
var dir = path.join(matesRoot, mateId);
var knowledge = path.join(dir, "knowledge");
write(path.join(dir, "CLAUDE.md"), "# Identity\nYou are a systems thinker.\n");
write(path.join(dir, "mate.yaml"), "name: Arch\n");
write(path.join(knowledge, "base-template.md"), "# Base template\n");
write(path.join(knowledge, "identity-backup.md"), "# Identity backup\n");
write(path.join(knowledge, "identity-history.jsonl"), JSON.stringify({ at: 1, change: "created" }) + "\n");
write(path.join(knowledge, "sticky-notes.md"), "- [yellow] a sticky note\n");
write(path.join(knowledge, "memory-summary.md"), "## Summary\nWorked on storage.\n");
write(path.join(knowledge, "architecture-notes.md"), "# Architecture\nAppend-only wins.\n");
write(path.join(knowledge, "session-digests.jsonl"), digest("storage design") + "\n" + digest("auth review") + "\n");
write(path.join(knowledge, "user-observations.jsonl"), JSON.stringify({ date: "2026-04-01", note: "prefers terse output" }) + "\n");
var names = (extras && extras.names) || { mates: [{ id: mateId, name: "Arch" }] };
write(path.join(matesRoot, "mates.json"), JSON.stringify(names));
return { dir: dir, knowledge: knowledge };
}
function fixture(label) {
var home = tmp(label);
var matesHome = path.join(home, "mates");
var baseDir = path.join(home, "knowledge");
return { home: home, matesHome: matesHome, baseDir: baseDir };
}
function importerFor(f, scopeId) {
return knowledgeImport.createKnowledgeImporter({ scopeId: scopeId, baseDir: f.baseDir });
}
function scopeOf(f, mateId, userId) {
var root = userId ? path.join(f.matesHome, userId) : f.matesHome;
return migration.scopeIdFor(migration.ownerKeyForRoot(root, userId || null), mateId);
}
test("first import maps every durable source and excludes identity files", function () {
var f = fixture("first");
seedMate(f.matesHome, "mate_aaa");
var summary = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.scopes, 1);
assert.equal(summary.created, DURABLE, "2 markdown files + 2 digest lines + 1 observation");
assert.equal(summary.identitySkipped, 3, "base template, identity backup, identity history");
assert.equal(summary.excluded, 1, "sticky notes remain the separate attention layer");
assert.equal(summary.failed, 0);
assert.deepEqual(summary.errors, []);
var entries = importerFor(f, scopeOf(f, "mate_aaa")).entries();
var byKey = {};
for (var i = 0; i < entries.length; i++) byKey[entries[i].importKey] = entries[i];
assert.equal(Object.keys(byKey).length, DURABLE);
assert.equal(byKey["file:memory-summary.md"].kind, "memory-summary");
assert.equal(byKey["file:architecture-notes.md"].kind, "knowledge-file");
assert.match(byKey["file:architecture-notes.md"].content, /Append-only wins/);
// Identity and sticky notes never appear, under any key.
for (var k = 0; k < entries.length; k++) {
assert.doesNotMatch(entries[k].name || "", /identity|base-template|CLAUDE|sticky/i);
assert.doesNotMatch(entries[k].content, /systems thinker/);
assert.doesNotMatch(entries[k].content, /a sticky note/);
}
assert.equal(byKey["file:sticky-notes.md"], undefined);
// Journals are line-addressed with one record per line.
var digests = entries.filter(function (e) { return e.kind === "session-digest"; });
assert.equal(digests.length, 2);
assert.equal(entries.filter(function (e) { return e.kind === "user-observation"; }).length, 1);
// Provenance is sufficient to trace a record back to its exact source, and
// is relative: the scope already identifies the owner and the Mate.
var one = digests[0];
assert.equal(one.source.namespace, "mate-knowledge");
assert.equal(one.source.mateId, "mate_aaa");
assert.equal(one.source.mateName, "Arch");
assert.equal(one.source.fileName, "session-digests.jsonl");
assert.equal(one.source.relPath, "knowledge/session-digests.jsonl");
assert.equal(one.source.path, undefined, "no absolute path is persisted");
assert.equal(typeof one.source.lineIndex, "number");
assert.equal(one.source.sourceDate, "2026-04-09");
assert.ok(one.source.sourceModifiedAt > 0);
assert.equal(one.actor.type, "migration", "the migration is recorded as the actor");
for (var p = 0; p < entries.length; p++) {
assert.equal(JSON.stringify(entries[p].source).indexOf(f.home), -1, "no filesystem prefix leaks");
}
// File records keep the legacy file's own timestamp.
var stat = fs.statSync(path.join(f.matesHome, "mate_aaa", "knowledge", "architecture-notes.md"));
assert.equal(byKey["file:architecture-notes.md"].importedAt, Math.round(stat.mtimeMs));
});
test("re-running across restarts imports nothing and writes no records", function () {
var f = fixture("idempotent");
seedMate(f.matesHome, "mate_bbb");
var first = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(first.created, DURABLE);
var importer = importerFor(f, scopeOf(f, "mate_bbb"));
var recordsAfterFirst = importer.stats().records;
var second = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(second.created, 0);
assert.equal(second.revised, 0);
assert.equal(second.unchanged, DURABLE);
var third = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(third.created, 0);
assert.equal(third.unchanged, DURABLE);
assert.equal(importer.stats().records, recordsAfterFirst, "a no-op run appends nothing");
assert.equal(importer.entries().length, DURABLE);
assert.equal(importer.stats().duplicates, 0);
});
test("a deleted state file does not cause re-import; records are the authority", function () {
var f = fixture("state");
seedMate(f.matesHome, "mate_ccc");
migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
var state = migration.loadState(f.baseDir);
assert.equal(state.version, migration.MIGRATION_VERSION);
assert.ok(state.scopes[scopeOf(f, "mate_ccc")], "state records the completed scope");
fs.unlinkSync(migration.statePath(f.baseDir));
var afterDelete = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(afterDelete.created, 0, "import keys, not the marker, decide");
assert.equal(afterDelete.unchanged, DURABLE);
// A corrupt state file is equally harmless.
fs.writeFileSync(migration.statePath(f.baseDir), "{ not json");
var afterCorrupt = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(afterCorrupt.created, 0);
assert.equal(migration.loadState(f.baseDir).version, migration.MIGRATION_VERSION);
});
test("a changed file revises; a changed journal line is a new deterministic import", function () {
var f = fixture("changed");
var seeded = seedMate(f.matesHome, "mate_ddd");
migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
var importer = importerFor(f, scopeOf(f, "mate_ddd"));
// File-addressed: same key, new revision, previous revision retained.
write(path.join(seeded.knowledge, "architecture-notes.md"), "# Architecture\nAppend-only wins, with revisions.\n");
var changed = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(changed.revised, 1);
assert.equal(changed.created, 0);
assert.equal(changed.unchanged, DURABLE - 1);
var entries = importer.entries();
assert.equal(entries.length, DURABLE, "a revision does not add an entry");
var file = entries.filter(function (e) { return e.importKey === "file:architecture-notes.md"; })[0];
assert.equal(file.revisions, 2);
assert.match(file.content, /with revisions/);
// Line-addressed: an appended line is a new record, existing lines are not.
fs.appendFileSync(path.join(seeded.knowledge, "session-digests.jsonl"), digest("cache eviction") + "\n");
var appended = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(appended.created, 1);
assert.equal(appended.unchanged, DURABLE);
assert.equal(importer.entries().filter(function (e) { return e.kind === "session-digest"; }).length, 3);
// Rewriting an existing journal line is a new deterministic import for the
// new content, and the line that vanished is tombstoned rather than left
// active as a memory the user can no longer see in the legacy store.
var lines = fs.readFileSync(path.join(seeded.knowledge, "session-digests.jsonl"), "utf8").trim().split("\n");
lines[0] = digest("storage design, revised");
fs.writeFileSync(path.join(seeded.knowledge, "session-digests.jsonl"), lines.join("\n") + "\n");
var edited = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(edited.created, 1);
assert.equal(edited.revised, 0);
assert.equal(edited.deleted, 1, "the replaced line is tombstoned");
var finalDigests = importer.entries().filter(function (e) { return e.kind === "session-digest"; });
assert.equal(finalDigests.length, 3, "live digests match the legacy file exactly");
assert.equal(finalDigests.filter(function (e) { return /storage design"/.test(e.content); }).length, 0, "the removed line is no longer live");
assert.equal(finalDigests.filter(function (e) { return /storage design, revised/.test(e.content); }).length, 1);
var withDeleted = importer.entries({ includeDeleted: true }).filter(function (e) { return e.kind === "session-digest"; });
assert.equal(withDeleted.length, 4, "history is retained");
});
test("partial and corrupt journal lines are counted and skipped, never fatal", function () {
var f = fixture("corrupt");
var seeded = seedMate(f.matesHome, "mate_eee");
var journal = path.join(seeded.knowledge, "session-digests.jsonl");
fs.appendFileSync(journal, "this is not json\n");
fs.appendFileSync(journal, "[1,2,3]\n");
fs.appendFileSync(journal, digest("valid after damage") + "\n");
fs.appendFileSync(journal, "{\"date\":\"2026-05-01\",\"topic\":\"torn");
var summary = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.failed, 0, "malformed lines must not fail the run");
assert.equal(summary.malformedLines, 3, "two unparseable lines plus the torn trailing line");
assert.equal(summary.created, DURABLE + 1, "the valid line after the damage is still imported");
var digests = importerFor(f, scopeOf(f, "mate_eee")).entries().filter(function (e) { return e.kind === "session-digest"; });
assert.equal(digests.length, 3);
assert.equal(digests.filter(function (e) { return /valid after damage/.test(e.content); }).length, 1);
// An unreadable directory and an empty file are tolerated too.
var f2 = fixture("empty");
var seeded2 = seedMate(f2.matesHome, "mate_fff");
write(path.join(seeded2.knowledge, "blank.md"), " \n");
write(path.join(seeded2.knowledge, "notes.txt"), "unsupported extension");
var summary2 = migration.run({ matesHome: f2.matesHome, baseDir: f2.baseDir });
assert.equal(summary2.empty, 1);
assert.equal(summary2.unsupported, 1);
assert.equal(summary2.failed, 0);
});
test("each user's Mates land in an isolated scope", function () {
var f = fixture("isolation");
// Multi-user layout plus a legacy flat Mate in the same root.
seedMate(path.join(f.matesHome, "user-one"), "mate_shared_id");
seedMate(path.join(f.matesHome, "user-two"), "mate_shared_id");
seedMate(f.matesHome, "mate_flat");
var summary = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.scopes, 3);
var discovered = migration.discoverMates({ matesHome: f.matesHome });
var scopes = discovered.map(function (m) { return m.scopeId; });
assert.equal(new Set(scopes).size, 3, "identical Mate ids under different users do not collide");
assert.ok(scopes.indexOf(scopeOf(f, "mate_shared_id", "user-one")) !== -1);
assert.ok(scopes.indexOf(scopeOf(f, "mate_shared_id", "user-two")) !== -1);
var one = importerFor(f, scopeOf(f, "mate_shared_id", "user-one"));
var two = importerFor(f, scopeOf(f, "mate_shared_id", "user-two"));
assert.notEqual(one.filePath, two.filePath, "each scope has its own record file");
assert.equal(one.entries().length, DURABLE);
assert.equal(two.entries().length, DURABLE);
// No record from one user's scope refers to another user's Mate directory.
var oneEntries = one.entries();
for (var i = 0; i < oneEntries.length; i++) {
assert.equal(oneEntries[i].source.ownerKey, migration.ownerKeyForRoot(path.join(f.matesHome, "user-one")));
}
var twoEntries = two.entries();
for (var j = 0; j < twoEntries.length; j++) {
assert.notEqual(twoEntries[j].source.ownerKey, oneEntries[0].source.ownerKey);
}
var flat = importerFor(f, scopeOf(f, "mate_flat"));
assert.notEqual(flat.filePath, one.filePath);
assert.notEqual(flat.entries()[0].source.ownerKey, oneEntries[0].source.ownerKey);
// Under the real per-user layout the owner key stays readable and the user
// id is recovered for provenance. Path math only; nothing is created.
var configDir = require("../lib/config").CONFIG_DIR;
var realUserScope = migration.scopeForMateDir(path.join(configDir, "mates", "user-nine", "mate_zzz"));
assert.equal(realUserScope.ownerKey, "u-user-nine");
assert.equal(realUserScope.userId, "user-nine");
assert.equal(realUserScope.scopeId, "mate/u-user-nine/mate_zzz");
var realFlatScope = migration.scopeForMateDir(path.join(configDir, "mates", "mate_zzz"));
assert.equal(realFlatScope.userId, null);
assert.notEqual(realFlatScope.scopeId, realUserScope.scopeId);
});
test("overlapping runs are serialized by a stale-tolerant lock", function () {
var f = fixture("lock");
seedMate(f.matesHome, "mate_ggg");
// A live holder blocks the run entirely rather than importing concurrently.
var held = migration.acquireLock(f.baseDir);
assert.ok(held);
var blocked = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(blocked.skipped, "locked");
assert.equal(blocked.created, 0);
assert.equal(migration.acquireLock(f.baseDir), null, "the lock is exclusive");
migration.releaseLock(held);
var afterRelease = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(afterRelease.created, DURABLE);
// A lock left behind by a dead process is reclaimed.
fs.writeFileSync(migration.lockPath(f.baseDir), JSON.stringify({ pid: 999999999, startedAt: Date.now() }) + "\n");
var reclaimed = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.notEqual(reclaimed.skipped, "locked", "a dead holder does not block upgrades forever");
assert.equal(reclaimed.unchanged, DURABLE);
assert.equal(fs.existsSync(migration.lockPath(f.baseDir)), false, "the lock is released after a run");
});
test("a lock held by a live process is never stolen, however old it is", function () {
var f = fixture("live-lock");
seedMate(f.matesHome, "mate_live");
// A long-running daemon that is unambiguously alive. Age must not matter:
// stealing here would let two daemons import at once, and the first to
// finish would unlink the second's lock and leave the run unprotected.
var ancient = Date.now() - (100 * migration.LOCK_STALE_MS);
fs.mkdirSync(f.baseDir, { recursive: true });
fs.writeFileSync(migration.lockPath(f.baseDir), JSON.stringify({ pid: process.pid, startedAt: ancient }) + "\n");
assert.equal(migration.acquireLock(f.baseDir), null, "a live holder always wins");
var blocked = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(blocked.skipped, "locked");
assert.equal(blocked.created, 0, "no import happens behind a live holder");
// The live holder's lock file is still exactly as it was written.
var holder = JSON.parse(fs.readFileSync(migration.lockPath(f.baseDir), "utf8"));
assert.equal(holder.pid, process.pid);
assert.equal(holder.startedAt, ancient, "the live holder's lock was not overwritten");
// Only an unidentifiable holder may be reclaimed on age alone.
fs.writeFileSync(migration.lockPath(f.baseDir), JSON.stringify({ startedAt: Date.now() }) + "\n");
assert.equal(migration.acquireLock(f.baseDir), null, "a fresh anonymous lock still blocks");
fs.writeFileSync(migration.lockPath(f.baseDir), JSON.stringify({ startedAt: ancient }) + "\n");
var reclaimed = migration.acquireLock(f.baseDir);
assert.ok(reclaimed, "an abandoned anonymous lock is reclaimed once stale");
migration.releaseLock(reclaimed);
// A dead pid is reclaimed regardless of age.
fs.writeFileSync(migration.lockPath(f.baseDir), JSON.stringify({ pid: 999999999, startedAt: Date.now() }) + "\n");
var afterDead = migration.acquireLock(f.baseDir);
assert.ok(afterDead);
migration.releaseLock(afterDead);
});
test("a duplicate create is folded away even if a lock is bypassed", function () {
var f = fixture("duplicate");
var importer = importerFor(f, "mate/h-test/mate_hhh");
importer.importRecord({ importKey: "file:notes.md", kind: "knowledge-file", name: "notes.md", content: "first" });
// Simulate two daemons that both decided the key was absent.
var raw = fs.readFileSync(importer.filePath, "utf8").trim();
var forged = JSON.parse(raw);
forged.id = "forged-duplicate";
forged.rootId = "forged-duplicate";
fs.appendFileSync(importer.filePath, JSON.stringify(forged) + "\n");
var fresh = importerFor(f, "mate/h-test/mate_hhh");
assert.equal(fresh.entries().length, 1, "the projection never double-counts");
assert.equal(fresh.stats().duplicates, 1, "but the duplicate is visible for diagnosis");
assert.equal(fresh.importRecord({ importKey: "file:notes.md", kind: "knowledge-file", name: "notes.md", content: "first" }).action, "unchanged");
});
test("legacy files are never modified, moved, or deleted", function () {
var f = fixture("nondestructive");
var seeded = seedMate(f.matesHome, "mate_iii");
function snapshot() {
var out = {};
var walk = function (dir) {
var entries = fs.readdirSync(dir, { withFileTypes: true });
for (var i = 0; i < entries.length; i++) {
var full = path.join(dir, entries[i].name);
if (entries[i].isDirectory()) { walk(full); continue; }
var stat = fs.statSync(full);
out[full] = fs.readFileSync(full, "utf8") + "|" + stat.size + "|" + Math.round(stat.mtimeMs);
}
};
walk(f.matesHome);
return out;
}
var before = snapshot();
migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
var after = snapshot();
assert.deepEqual(Object.keys(after).sort(), Object.keys(before).sort(), "no legacy file added or removed");
assert.deepEqual(after, before, "no legacy file content or timestamp changed");
assert.ok(fs.existsSync(path.join(seeded.dir, "CLAUDE.md")), "identity file untouched");
assert.equal(fs.existsSync(path.join(f.matesHome, "mate_iii", "knowledge", "session-digests.jsonl.migrated")), false);
// Nothing is written under the Mate storage root at all.
assert.equal(fs.existsSync(path.join(f.matesHome, "migration-state.json")), false);
assert.ok(fs.existsSync(migration.statePath(f.baseDir)), "state lives with the new backend");
});
test("discovery ignores non-Mate directories and survives a corrupt registry", function () {
var f = fixture("discovery");
seedMate(f.matesHome, "mate_jjj", { names: "not-an-object" });
fs.mkdirSync(path.join(f.matesHome, "not-a-mate"), { recursive: true });
fs.writeFileSync(path.join(f.matesHome, "common-knowledge.json"), "[]");
fs.writeFileSync(path.join(f.matesHome, "mates.json"), "{ corrupt");
var discovered = migration.discoverMates({ matesHome: f.matesHome });
var ids = discovered.map(function (m) { return m.mateId; });
assert.ok(ids.indexOf("mate_jjj") !== -1);
assert.equal(ids.indexOf("not-a-mate"), -1);
assert.equal(ids.indexOf("common-knowledge.json"), -1);
assert.equal(discovered.filter(function (m) { return m.mateId === "mate_jjj"; })[0].mateName, null, "a corrupt registry costs only the name");
var summary = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.created, DURABLE);
assert.equal(summary.failed, 0);
// A missing Mate home is a clean no-op.
var empty = migration.run({ matesHome: path.join(f.home, "nothing-here"), baseDir: f.baseDir });
assert.equal(empty.scopes, 0);
assert.equal(empty.created, 0);
assert.equal(empty.failed, 0);
});
test("common and shared knowledge pointers are deliberately not migrated", function () {
var f = fixture("common");
seedMate(f.matesHome, "mate_kkk");
fs.writeFileSync(path.join(f.matesHome, "common-knowledge.json"),
JSON.stringify([{ name: "architecture-notes.md", mateId: "mate_kkk", mateName: "Arch", promotedAt: 1 }]));
migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
var entries = importerFor(f, scopeOf(f, "mate_kkk")).entries();
for (var i = 0; i < entries.length; i++) {
assert.notEqual(entries[i].kind, "common-knowledge");
assert.doesNotMatch(entries[i].importKey, /common/);
}
// The pointer file is left exactly as the existing read path expects it.
assert.equal(JSON.parse(fs.readFileSync(path.join(f.matesHome, "common-knowledge.json"), "utf8")).length, 1);
});
test("a large legacy source migrates losslessly and is reported honestly", function () {
var f = fixture("large");
var seeded = seedMate(f.matesHome, "mate_big");
var body = "";
for (var i = 0; i < 5000; i++) body += "# heading " + i + "\nsome \"quoted\" text with é and \\ backslash\n";
write(path.join(seeded.knowledge, "large-plan.md"), body);
// A single journal line larger than one record must survive too.
var fatLine = JSON.stringify({ date: "2026-06-01", topic: "fat", body: new Array(30000).join("z") });
fs.appendFileSync(path.join(seeded.knowledge, "session-digests.jsonl"), fatLine + "\n");
var summary = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.failed, 0, "a representable source is never reported as failed");
assert.deepEqual(summary.errors, []);
assert.equal(summary.created, DURABLE + 2);
assert.equal(summary.chunked, 2, "both oversized sources were chunked");
var importer = importerFor(f, scopeOf(f, "mate_big"));
var live = importer.entries();
var big = live.filter(function (e) { return e.name === "large-plan.md"; })[0];
assert.equal(big.chunked, true);
assert.equal(big.content, "", "no partial content is presented as the whole source");
var read = importer.readContent(big);
assert.equal(read.complete, true);
assert.equal(read.content, body, "byte-exact reassembly of a large file");
var fat = live.filter(function (e) { return e.chunked && e.kind === "session-digest"; })[0];
assert.equal(importer.readContent(fat).content, fatLine, "byte-exact reassembly of a large journal line");
// Chunk records are internal and never counted as knowledge.
assert.equal(live.length, DURABLE + 2);
assert.ok(importer.stats().chunks > 4);
// Re-running imports nothing and does not re-chunk.
var again = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(again.created, 0);
assert.equal(again.unchanged, DURABLE + 2);
assert.equal(again.failed, 0);
assert.equal(importer.readContent(importer.entries().filter(function (e) { return e.name === "large-plan.md"; })[0]).content, body);
});
test("a hostile large legacy source migrates losslessly and reports no failure", function () {
var f = fixture("hostile");
var seeded = seedMate(f.matesHome, "mate_hostile");
var NUL = String.fromCharCode(0);
var controls = "";
for (var c = 0; c < 32; c++) controls += String.fromCharCode(c);
var body = "";
for (var i = 0; i < 900; i++) {
body += "row " + i + " " + NUL + " ünïcode 漢字 🎉 " + controls + " \\ \" end\n";
}
write(path.join(seeded.knowledge, "hostile-plan.md"), body);
var fatLine = JSON.stringify({ date: "2026-08-01", topic: "n" + NUL + "ul", body: "漢字🎉" + new Array(20000).join("w") });
fs.appendFileSync(path.join(seeded.knowledge, "session-digests.jsonl"), fatLine + "\n");
var summary = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.failed, 0, "a representable source is never reported as failed");
assert.deepEqual(summary.errors, []);
assert.equal(summary.chunked, 2);
assert.equal(summary.created, DURABLE + 2);
var importer = importerFor(f, scopeOf(f, "mate_hostile"));
var live = importer.entries();
var big = live.filter(function (e) { return e.name === "hostile-plan.md"; })[0];
var read = importer.readContent(big);
assert.equal(read.complete, true);
assert.equal(read.content, body, "byte-exact reassembly with NUL and astral characters");
assert.equal(read.content.indexOf(NUL), body.indexOf(NUL));
var fat = live.filter(function (e) { return e.chunked && e.kind === "session-digest"; })[0];
var fatRead = importer.readContent(fat);
assert.equal(fatRead.complete, true);
assert.equal(fatRead.content, fatLine);
assert.equal(JSON.parse(fatRead.content).topic, "n" + NUL + "ul");
// Every stored source verifies against its own content hash.
var all = importer.entries({ includeChunks: true });
for (var j = 0; j < all.length; j++) {
assert.equal(importer.readContent(all[j]).complete, true, all[j].importKey);
}
var again = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(again.created, 0);
assert.equal(again.failed, 0);
assert.equal(again.unchanged, DURABLE + 2);
});
test("startup reconciles sources deleted while the daemon was stopped", function () {
var f = fixture("offline-delete");
var seeded = seedMate(f.matesHome, "mate_offline");
var first = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(first.created, DURABLE);
assert.equal(first.deleted, 0);
var importer = importerFor(f, scopeOf(f, "mate_offline"));
assert.equal(importer.entries().length, DURABLE);
// The daemon is not running: a knowledge file and a whole journal are
// removed by direct filesystem tooling, so no write path ever observes them.
fs.unlinkSync(path.join(seeded.knowledge, "architecture-notes.md"));
fs.unlinkSync(path.join(seeded.knowledge, "session-digests.jsonl"));
var restart = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(restart.deleted, 3, "one file record plus both digest lines");
assert.equal(restart.created, 0);
assert.equal(restart.unchanged, 2, "memory summary and observations are untouched");
assert.equal(restart.failed, 0);
var live = importer.entries();
assert.equal(live.length, 2);
assert.equal(live.filter(function (e) { return e.kind === "session-digest"; }).length, 0);
assert.equal(live.filter(function (e) { return e.name === "architecture-notes.md"; }).length, 0);
assert.equal(live.filter(function (e) { return e.kind === "memory-summary"; }).length, 1);
assert.equal(live.filter(function (e) { return e.kind === "user-observation"; }).length, 1);
// History is retained and the removal is attributed to the migration.
var all = importer.entries({ includeDeleted: true });
assert.equal(all.length, DURABLE);
var tombstoned = all.filter(function (e) { return e.name === "architecture-notes.md"; })[0];
assert.equal(tombstoned.deleted, true);
assert.equal(tombstoned.deletedBy.type, "migration");
assert.match(tombstoned.content, /Append-only wins/, "the last known content is still readable");
// A second startup is a no-op.
var before = importer.stats().records;
var second = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(second.deleted, 0);
assert.equal(second.created, 0);
assert.equal(second.unchanged, 2);
assert.equal(importer.stats().records, before, "an idempotent restart appends nothing");
// Restoring a source revives its record rather than duplicating it.
write(path.join(seeded.knowledge, "architecture-notes.md"), "# Architecture\nAppend-only wins.\n");
var restored = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(restored.revived, 1);
assert.equal(restored.created, 0);
assert.equal(importer.entries().length, 3);
assert.equal(importer.entries().filter(function (e) { return e.name === "architecture-notes.md"; }).length, 1);
});
test("an emptied Mate knowledge directory tombstones everything it had imported", function () {
var f = fixture("emptied");
var seeded = seedMate(f.matesHome, "mate_emptied");
assert.equal(migration.run({ matesHome: f.matesHome, baseDir: f.baseDir }).created, DURABLE);
var names = fs.readdirSync(seeded.knowledge);
for (var i = 0; i < names.length; i++) fs.unlinkSync(path.join(seeded.knowledge, names[i]));
var restart = migration.run({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(restart.deleted, DURABLE, "every imported source is tombstoned");
assert.equal(restart.failed, 0);
var importer = importerFor(f, scopeOf(f, "mate_emptied"));
assert.equal(importer.entries().length, 0);
assert.equal(importer.entries({ includeDeleted: true }).length, DURABLE, "history is retained");
assert.equal(migration.run({ matesHome: f.matesHome, baseDir: f.baseDir }).deleted, 0, "idempotent");
});
test("startup wrapper never throws and reports a locked run", function () {
var f = fixture("startup");
seedMate(f.matesHome, "mate_lll");
var summary = migration.runAtStartup({ matesHome: f.matesHome, baseDir: f.baseDir });
assert.equal(summary.created, DURABLE);
var held = migration.acquireLock(f.baseDir);
assert.equal(migration.runAtStartup({ matesHome: f.matesHome, baseDir: f.baseDir }).skipped, "locked");
migration.releaseLock(held);
// An unusable base directory is reported, not thrown.
var blocked = path.join(f.home, "blocked");
fs.writeFileSync(blocked, "not a directory");
assert.doesNotThrow(function () {
migration.runAtStartup({ matesHome: f.matesHome, baseDir: blocked });
});
});