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"]); });