import { describe, test } from "node:test"; import assert from "node:assert/strict"; import { proposeDiff, applyDiff, type Candidate } from "../src/consolidate.js"; import { mintEntityId } from "../src/id.js"; import { parseProfile, serializeProfile } from "../src/profile.js"; import type { ProfileDoc, ProfileFact } from "../src/types.js"; const TODAY = "2026-06-23"; const observedId = (key: string) => mintEntityId({ kind: "observed", key }); const fact = (over: Partial): ProfileFact => ({ id: "000000000000", value: "v", first_seen: "2026-01-01", last_seen: TODAY, evidence_count: 1, provenance: "published", status: "active", ...over, }); const doc = (staticF: ProfileFact[], dynamicF: ProfileFact[]): ProfileDoc => ({ schemaVersion: 1, static: staticF, dynamic: dynamicF, }); const cand = (over: Partial): Candidate => ({ key: "k", value: "v", provenance: "published", source: "manual", observed_date: TODAY, ...over, }); describe("proposeDiff classification (SC1a–g)", () => { test("SC1a — add: a published candidate with no match becomes a new dynamic fact", () => { const diff = proposeDiff({ current: doc([], []), candidates: [cand({ key: "topic", value: "AI safety" })], today: TODAY }); assert.equal(diff.additions.length, 1); assert.equal(diff.additions[0].value, "AI safety"); assert.equal(diff.additions[0].id, observedId("topic")); assert.equal(diff.additions[0].evidence_count, 1); assert.equal(diff.additions[0].status, "active"); }); test("SC1b — reject: an ai-draft candidate produces an empty diff", () => { const diff = proposeDiff({ current: doc([], []), candidates: [cand({ provenance: "ai-draft" })], today: TODAY }); assert.deepEqual(diff.additions, []); assert.deepEqual(diff.evidenceBumps, []); assert.deepEqual(diff.promotions, []); assert.deepEqual(diff.conflicts, []); }); test("SC1c — evidence-bump: same key+value bumps the existing fact, no addition", () => { const id = observedId("topic"); const cur = doc([], [fact({ id, value: "AI safety", evidence_count: 1 })]); const diff = proposeDiff({ current: cur, candidates: [cand({ key: "topic", value: "AI safety" })], today: TODAY }); assert.equal(diff.additions.length, 0); assert.equal(diff.evidenceBumps.length, 1); assert.deepEqual(diff.evidenceBumps[0], { id, newCount: 2, last_seen: TODAY }); }); test("SC1d — promote: a dynamic fact reaching N=3 is promoted", () => { const id = observedId("topic"); const cur = doc([], [fact({ id, value: "AI safety", evidence_count: 2 })]); const diff = proposeDiff({ current: cur, candidates: [cand({ key: "topic", value: "AI safety" })], today: TODAY }); assert.equal(diff.evidenceBumps[0].newCount, 3); assert.deepEqual(diff.promotions, [{ id }]); }); test("SC1d — no promote below N", () => { const id = observedId("topic"); const cur = doc([], [fact({ id, value: "x", evidence_count: 1 })]); const diff = proposeDiff({ current: cur, candidates: [cand({ key: "topic", value: "x" })], today: TODAY }); assert.deepEqual(diff.promotions, []); }); test("SC1e — conflict: different value keeps BOTH with DISTINCT ids, old fact untouched", () => { const primaryId = observedId("role"); const cur = doc([], [fact({ id: primaryId, value: "advisor", evidence_count: 2 })]); const diff = proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect" })], today: TODAY }); assert.equal(diff.conflicts.length, 1); assert.equal(diff.conflicts[0].primaryId, primaryId); assert.equal(diff.conflicts[0].primaryValue, "advisor"); assert.notEqual(diff.conflicts[0].altId, primaryId, "alt id must differ from primary id (no duplicate-id corruption)"); // the alt fact is added; the old fact is NOT bumped assert.equal(diff.additions.length, 1); assert.equal(diff.additions[0].value, "architect"); assert.equal(diff.additions[0].id, diff.conflicts[0].altId); assert.deepEqual(diff.evidenceBumps, []); }); test("SC1f — decay: a dynamic fact older than 90d is flagged; a static fact is decay-exempt", () => { const old = "2026-03-01"; // ~114 days before TODAY const dyn = fact({ id: observedId("stale-topic"), value: "old", last_seen: old }); const stat = fact({ id: observedId("settled"), value: "stable", last_seen: old }); const cur = doc([stat], [dyn]); const diff = proposeDiff({ current: cur, candidates: [], today: TODAY }); assert.equal(diff.staleFlags.length, 1, "only the dynamic stale fact is flagged"); assert.equal(diff.staleFlags[0].id, dyn.id); assert.ok(diff.staleFlags[0].daysStale > 90); }); test("SC1f — a fresh dynamic fact is not flagged", () => { const cur = doc([], [fact({ id: observedId("fresh"), last_seen: "2026-06-20" })]); const diff = proposeDiff({ current: cur, candidates: [], today: TODAY }); assert.deepEqual(diff.staleFlags, []); }); test("SC1g — folded immutable: a candidate overlapping a profile-field seed adds a new observed fact, seed untouched", () => { const foldedId = mintEntityId({ kind: "profile-field", key: "role" }); const folded = fact({ id: foldedId, value: "advisor", provenance: "human" }); const cur = doc([folded], []); const before = serializeProfile(cur); const diff = proposeDiff({ current: cur, candidates: [cand({ key: "role", value: "architect" })], today: TODAY }); // candidate keys to observed:role (≠ profile-field:role) → no match → ADD, no conflict on the folded seed assert.equal(diff.additions.length, 1); assert.equal(diff.additions[0].id, observedId("role")); assert.equal(diff.conflicts.length, 0); assert.equal(serializeProfile(cur), before, "input doc not mutated; folded seed untouched"); }); }); describe("proposeDiff purity + applyDiff round-trip (SC2, SC3, SC4)", () => { test("SC2 — proposeDiff does not mutate its inputs", () => { const cur = doc([], [fact({ id: observedId("topic"), value: "x" })]); const candidates = [cand({ key: "topic", value: "x" }), cand({ key: "new", value: "y" })]; const snapCur = JSON.stringify(cur); const snapCand = JSON.stringify(candidates); proposeDiff({ current: cur, candidates, today: TODAY }); assert.equal(JSON.stringify(cur), snapCur, "current unchanged"); assert.equal(JSON.stringify(candidates), snapCand, "candidates unchanged"); }); test("SC3 — applyDiff(current, proposeDiff(...)) round-trips through parse/serialize", () => { const cur = doc([fact({ id: mintEntityId({ kind: "profile-field", key: "name" }), value: "KTG", provenance: "human" })], [fact({ id: observedId("topic"), value: "AI safety", evidence_count: 2 })]); const candidates = [ cand({ key: "topic", value: "AI safety" }), // bump→promote cand({ key: "new-topic", value: "governance" }), // add cand({ key: "topic", value: "alignment" }), // conflict on topic → alt ]; const diff = proposeDiff({ current: cur, candidates, today: TODAY }); const next = applyDiff(cur, diff); const round = parseProfile(serializeProfile(next)); assert.deepEqual(round, next, "applied doc round-trips exactly"); // every fact id is unique (no duplicate-id corruption) const ids = [...round.static, ...round.dynamic].map((f) => f.id); assert.equal(new Set(ids).size, ids.length, "all fact ids unique"); }); test("SC4 — idempotent: re-running propose→apply with the same candidates adds no duplicate facts", () => { const cur = doc([], []); const candidates = [cand({ key: "topic", value: "AI safety" })]; const once = applyDiff(cur, proposeDiff({ current: cur, candidates, today: TODAY })); const twice = applyDiff(once, proposeDiff({ current: once, candidates, today: TODAY })); const factsOnce = [...once.static, ...once.dynamic].length; const factsTwice = [...twice.static, ...twice.dynamic].length; assert.equal(factsTwice, factsOnce, "no duplicate fact on re-run (bump only)"); }); });