Ctrl K
ComunidadGuías…

test/mate-knowledge-migration.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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 });
  });
});