deskpaw-memory/test/continuity.test.js

129 lines
6.8 KiB
JavaScript

import assert from "node:assert/strict";
import { DatabaseSync } from "node:sqlite";
import { test } from "node:test";
import { ContinuityStore } from "../src/continuity.js";
import { reciprocalRankFusion } from "../src/rank-fusion.js";
const owner = "device:test";
function event(eventId = "e1", overrides = {}) {
return { event_id: eventId, subject_id: "task:reimburse", task_id: "reimburse", kind: "task_completed",
source: "runtime", observed_at: Date.now(), text: "十月报销的发票已经整理到报销区,尚未提交审批。", ...overrides };
}
function setup(t) {
const db = new DatabaseSync(":memory:");
const store = new ContinuityStore(db);
t.after(() => db.close());
return store;
}
function ingest(store, events) { return store.ingest({ version: 1, owner, events }); }
function proposal(job, content = "十月报销发票已整理,尚未提交审批。") {
return { job_id: job.job_id, token: job.token, episodes: [{ subject_id: job.events[0].subject_id, content,
cues: ["每月报销整理发票"], source_refs: job.events.map((item) => item.event_id) }], candidates: [], discarded_event_ids: [] };
}
test("idempotent ingest rejects conflicting IDs and rolls back the whole envelope", (t) => {
const store = setup(t);
const first = event();
assert.equal(ingest(store, [first]).stored, 1);
assert.equal(ingest(store, [first]).stored, 0);
assert.throws(() => ingest(store, [event("e2"), { ...first, text: "changed" }]), /collision/);
assert.equal(store.db.prepare("SELECT count(*) AS n FROM continuity_events").get().n, 1);
assert.throws(() => ingest(store, [event("forged", { source: "user" })]), /provenance/);
});
test("leases recover the same batch, reject stale workers, commit atomically and replay receipts", (t) => {
const store = setup(t);
ingest(store, [event()]);
const now = Date.now();
const job = store.claim({ owner }, now);
assert.equal(store.claim({ owner }, now + 1), null);
const restored = new ContinuityStore(store.db).claim({ owner }, now + 120_001);
assert.equal(job.job_id, restored.job_id);
assert.notEqual(job.token, restored.token);
assert.throws(() => store.commit(proposal(job), now + 120_002), /Stale/);
const receipt = store.commit(proposal(restored), now + 120_002);
assert.equal(receipt.consumed, 1);
assert.deepEqual(store.commit(proposal(restored), now + 999_999), receipt);
assert.equal(store.claim({ owner }, now + 1_000_000), null);
});
test("missing or invented evidence never consumes an observation batch", (t) => {
const store = setup(t);
ingest(store, [event("e1"), event("e2", { text: "另一项没有引用的独立结果" })]);
const job = store.claim({ owner });
const draft = proposal(job);
draft.episodes[0].source_refs = ["e1"];
assert.throws(() => store.commit(draft), /all source events/);
draft.episodes[0].source_refs = ["invented"];
assert.throws(() => store.commit(draft), /outside/);
assert.equal(store.db.prepare("SELECT count(*) AS n FROM continuity_records").get().n, 0);
assert.equal(store.db.prepare("SELECT sum(consumed) AS n FROM continuity_events").get().n, 0);
assert.equal(store.fail(job).released, true);
assert.equal(store.claim({ owner }), null);
});
test("background proposals cannot promote executor text to preferences or change another subject", (t) => {
const store = setup(t);
ingest(store, [event()]);
const job = store.claim({ owner });
const draft = proposal(job);
draft.candidates = [{ ...draft.episodes[0], kind: "preference", user_quote: "报销" }];
assert.throws(() => store.commit(draft), /exact user quote/);
draft.candidates = [];
draft.episodes[0].subject_id = "project:other";
assert.throws(() => store.commit(draft), /no evidence/);
});
test("explicit corrections remain sourced candidates, not automatically active global rules", (t) => {
const store = setup(t);
ingest(store, [event("correction", { source: "user", kind: "user_message", text: "报销前先让我确认发票清单。" })]);
const job = store.claim({ owner }, Date.now() + 31_000);
store.commit({ job_id: job.job_id, token: job.token, episodes: [], candidates: [{ subject_id: "task:reimburse",
kind: "correction", content: "报销前须确认发票清单", user_quote: "报销前先让我确认发票清单。",
source_refs: ["correction"], cues: ["报销确认"] }], discarded_event_ids: [] }, Date.now() + 31_001);
const record = store.recall({ owner, query: "报销确认" }).hits[0];
assert.equal(record.status, "candidate");
assert.deepEqual(record.source_refs, ["correction"]);
});
test("Chinese recall finds dormant software beyond a recent-history window and respects owner scope", (t) => {
const store = setup(t);
ingest(store, [event("software", { subject_id: "asset:billing", text: "账单软件升级后导出发票失败,需要回滚模板。" })]);
store.commit(proposal(store.claim({ owner }), "账单软件导出发票故障:模板字段变更,回滚后恢复。"));
for (let i = 0; i < 110; i++) {
ingest(store, [event(`noise${i}`, { subject_id: `task:noise${i}` })]);
store.commit(proposal(store.claim({ owner }), `天气查看任务编号${i}`));
}
const recalled = store.recall({ owner, query: "账单软件导出发票又失败" });
assert.match(recalled.hits[0].content, /模板字段/);
assert.equal(recalled.hits[0].subject_id, "asset:billing");
assert.deepEqual(store.recall({ owner: "device:other", query: "账单软件" }).hits, []);
assert.deepEqual(store.evidence({ owner: "device:other", event_ids: ["software"] }), []);
assert.equal(store.evidence({ owner, event_ids: ["software"] })[0].source, "runtime");
});
test("secrets are redacted before the model, and job size is bounded in UTF-8 bytes", (t) => {
const store = setup(t);
ingest(store, [event("secret", { text: "密码=do-not-retain" }), ...Array.from({ length: 19 }, (_, i) => event(`long${i}`, { text: "中文观察".repeat(2000) }))]);
const job = store.claim({ owner });
assert.ok(!JSON.stringify(job).includes("do-not-retain"));
assert.ok(Buffer.byteLength(JSON.stringify(job.events)) < 12_000);
assert.ok(job.events.length < 20);
});
test("RRF fuses channels deterministically without rewarding duplicates within one channel", () => {
const ranked = reciprocalRankFusion([["b", "a"], ["a", "c"]]);
assert.equal(ranked[0].id, "a");
assert.equal(reciprocalRankFusion([["a", "a"]])[0].score, 1 / 61);
});
test("repeated observations collapse for the model but retain every evidence reference", (t) => {
const store = setup(t);
ingest(store, [event("one"), event("two")]);
const job = store.claim({ owner });
assert.equal(job.events.length, 1);
assert.equal(job.events[0].repeat_count, 2);
assert.equal(store.commit(proposal(job)).consumed, 2);
assert.deepEqual(store.recall({ owner, query: "报销" }).hits[0].source_refs, ["one", "two"]);
});