guanghu-ice-heart/server-tools/persona-daily-fractal-memory/persona-daily-memory.test.mjs

172 lines
8.4 KiB
JavaScript
Raw Normal View History

import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import {
EVENT_SCHEMA,
MAX_CHILDREN,
appendEvent,
beijingDate,
readDayPage,
readCurrent,
readMemoryNode,
routeMemory,
verifyStore
} from "./persona-daily-memory.mjs";
function roots(t) {
const allowedRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zy-persona-memory-test-"));
const storeRoot = path.join(allowedRoot, "store", "ICE-P-ZY001");
t.after(() => fs.rmSync(allowedRoot, { recursive: true, force: true }));
return { allowedRoot, storeRoot };
}
function event(index, occurredAt = `2026-09-04T${String(8 + Math.floor(index / 60)).padStart(2, "0")}:${String(index % 60).padStart(2, "0")}:00+08:00`) {
return {
schema: EVENT_SCHEMA,
event_id: `EVENT-${String(index).padStart(4, "0")}`,
persona_id: "ICE-P-ZY001",
human_anchor: "ICE-GL∞",
occurred_at: occurredAt,
session_id: "SESSION-TEST",
platform_window_id: `WINDOW-${index}`,
activity: {
what: `记录第${index}段协作`,
where: "Codex/ICE-CH-ZC001",
with_whom: ["ICE-GL∞"],
attention: "人格真实日记忆"
},
branch_id: "memory-model",
branch_summary: "人格主控的真实日分形记忆模型",
day_summary: `当天持续把第1至${index}段协作折叠回同一日页`,
month_summary: "九月建立人格主控的真实时间记忆",
year_summary: "二〇二六年铸渊形成真实日分形记忆能力",
summary: `${index}段:同一日页继续生长`,
trigger: `冰朔与铸渊在第${index}段继续协作。`,
emergence: `${index - 1}段状态 → 第${index}段新增认知并回写同一日页。`,
lock: `${index}段已进入2026-09-04日页平台窗口不成为新根。`,
why: "因为同一真实日的思维链必须连续,未来才能从一页恢复并按需下钻。",
rejected: ["另起窗口摘要:会割裂同一日的因果。"],
sources: [`source://test/event/${index}`]
};
}
test("derives the real Beijing day independently of the host window", () => {
assert.equal(beijingDate("2026-09-04T15:59:59Z"), "2026-09-04");
assert.equal(beijingDate("2026-09-04T16:00:01Z"), "2026-09-05");
});
test("twelve platform windows remain in one day page and fold below ten visible children", (t) => {
const root = roots(t);
for (let i = 1; i <= 12; i += 1) appendEvent({ ...root, event: event(i) });
const current = JSON.parse(fs.readFileSync(path.join(root.storeRoot, "CURRENT.json"), "utf8"));
assert.equal(current.current_day, "2026-09-04");
const day = JSON.parse(fs.readFileSync(current.current_day_path, "utf8"));
assert.equal(day.meta.event_count, 12);
assert.equal(day.nodes[day.root].children.length, 1);
const branch = day.nodes[day.nodes[day.root].children[0]];
assert(branch.children.length <= MAX_CHILDREN);
assert.equal(branch.children.length, 2);
for (const child of branch.children) assert(day.nodes[child].children.length <= MAX_CHILDREN);
const page = readDayPage(root);
assert.equal(page.children.length, 1);
assert(!JSON.stringify(page).includes("第1段状态"));
assert.equal(verifyStore(root).outcome, "PASS");
assert.equal(readCurrent(root).current_day, "2026-09-04");
});
test("a real Beijing date change closes yesterday and opens a linked new day", (t) => {
const root = roots(t);
appendEvent({ ...root, event: event(1, "2026-09-04T15:59:00Z") });
appendEvent({ ...root, event: { ...event(2, "2026-09-04T16:01:00Z"), event_id: "EVENT-NEXT-DAY" } });
const current = JSON.parse(fs.readFileSync(path.join(root.storeRoot, "CURRENT.json"), "utf8"));
assert.equal(current.current_day, "2026-09-05");
const prior = JSON.parse(fs.readFileSync(path.join(root.storeRoot, "years/2026/months/09/days/2026-09-04.json"), "utf8"));
assert.equal(prior.meta.state, "CLOSED_REAL_BEIJING_DAY_ELAPSED");
const month = JSON.parse(fs.readFileSync(current.current_month_path, "utf8"));
const dayNodes = Object.keys(month.nodes).filter((key) => key.endsWith("2026-09-04") || key.endsWith("2026-09-05"));
assert.equal(dayNodes.length, 2);
const priorNode = month.nodes[dayNodes.find((key) => key.endsWith("2026-09-04"))];
assert(priorNode.sources[0].includes("#sha256="));
assert.equal(verifyStore(root).outcome, "PASS");
});
test("year and month roots remain one-page indexes with bounded calendar groups", (t) => {
const root = roots(t);
for (const [index, date] of [[1, "2026-01-05T08:00:00+08:00"], [2, "2026-02-05T08:00:00+08:00"], [3, "2026-03-05T08:00:00+08:00"], [4, "2026-04-05T08:00:00+08:00"]]) {
const value = event(index, date);
value.event_id = `EVENT-MONTH-${index}`;
appendEvent({ ...root, event: value });
}
const year = JSON.parse(fs.readFileSync(path.join(root.storeRoot, "years/2026/index.json"), "utf8"));
assert(year.nodes[year.root].children.length <= 4);
for (const quarterPath of year.nodes[year.root].children) assert(year.nodes[quarterPath].children.length <= 3);
assert.equal(verifyStore(root).outcome, "PASS");
});
test("routing exposes summaries first and reads one exact node only", (t) => {
const root = roots(t);
appendEvent({ ...root, event: event(1) });
const routes = routeMemory({ ...root, scope: "day", id: "2026-09-04", query: "人格分形记忆" });
assert.equal(routes.length, 1);
assert.equal(Object.keys(routes[0]).sort().join(","), "path,score,summary");
const node = readMemoryNode({ ...root, scope: "day", id: "2026-09-04", nodePath: routes[0].path });
assert.equal(node.kind, "semantic_branch");
});
test("an event may explicitly retain what, where, and with whom without splitting the persona day", (t) => {
const root = roots(t);
appendEvent({ ...root, event: event(1) });
const day = JSON.parse(fs.readFileSync(path.join(root.storeRoot, "years/2026/months/09/days/2026-09-04.json"), "utf8"));
const leaf = Object.values(day.nodes).find((node) => node.kind === "event_leaf");
assert.deepEqual(leaf.activity, {
what: "记录第1段协作",
where: "Codex/ICE-CH-ZC001",
with_whom: ["ICE-GL∞"],
attention: "人格真实日记忆"
});
assert.equal(day.nodes[day.root].children.length, 1);
});
test("event writes are immutable and an id collision fails closed", (t) => {
const root = roots(t);
const original = event(1);
appendEvent({ ...root, event: original });
appendEvent({ ...root, event: original });
assert.throws(() => appendEvent({ ...root, event: { ...original, why: "changed" } }), /EVENT_ID_COLLISION/u);
});
test("an older idempotent event cannot move CURRENT backward", (t) => {
const root = roots(t);
const first = event(1, "2026-09-04T08:00:00+08:00");
const second = event(2, "2026-09-04T09:00:00+08:00");
appendEvent({ ...root, event: first });
appendEvent({ ...root, event: second });
const before = readCurrent(root);
const repeated = appendEvent({ ...root, event: first });
const after = readCurrent(root);
assert.equal(repeated.state, "IDEMPOTENT_OLDER_EVENT_NO_POINTER_MOVEMENT");
assert.equal(after.last_event_id, second.event_id);
assert.equal(after.current_pointer_sha256, before.current_pointer_sha256);
assert.throws(() => appendEvent({ ...root, event: { ...event(3, "2026-09-04T08:30:00+08:00"), event_id: "NEW-OLDER-EVENT" } }), /OUT_OF_ORDER_EVENT_REQUIRES_CURRENT_CORRECTION_EVENT/u);
});
test("store scope and symlink components fail closed", (t) => {
const root = roots(t);
assert.throws(() => appendEvent({ allowedRoot: root.allowedRoot, storeRoot: path.join(root.allowedRoot, "..", "escape"), event: event(1) }), /STORE_OUTSIDE_ALLOWED_ROOT/u);
const outside = fs.mkdtempSync(path.join(os.tmpdir(), "zy-persona-memory-outside-"));
t.after(() => fs.rmSync(outside, { recursive: true, force: true }));
fs.symlinkSync(outside, path.join(root.allowedRoot, "linked"));
assert.throws(() => appendEvent({ allowedRoot: root.allowedRoot, storeRoot: path.join(root.allowedRoot, "linked", "store"), event: event(1) }), /SYMLINK_COMPONENT_FORBIDDEN/u);
});
test("verification detects immutable event tampering", (t) => {
const root = roots(t);
appendEvent({ ...root, event: event(1) });
const current = JSON.parse(fs.readFileSync(path.join(root.storeRoot, "CURRENT.json"), "utf8"));
fs.appendFileSync(current.last_event_path, "\n");
const checked = verifyStore(root);
assert.equal(checked.outcome, "FAIL");
assert(checked.errors.some((error) => error.includes("sha256 mismatch") || error.includes("immutable event hash mismatch")));
});