/** * GOLDEN FIXTURES (owner ruling, 2026-10-10): real user data, verbatim, * read through the PRODUCTION read paths. tests/fixtures/golden-data/ is a * real data dir (see its README); this suite copies it to a temp dir, points * the stores at it via the LH_DATA_DIR seam (set BEFORE any config-loading * import — every app-module import is dynamic), and asserts: * * 1. every record parses and passes the contract folds the app itself * runs (phases, pending questions/problems, plan tree, summaries); * 2. the LLM wire flattening (toLlmMessages) turns each full record into * a legal provider message list — the strongest property a record has; * 3. the graph derivation builds from the real ledgers end-to-end; * 4. marker COVERAGE: every custom-marker kind of the current contract * appears in real data here (a new marker without a fixture update * fails this suite); * 5. reads are READ-ONLY: after everything above, the copy is * byte-identical to the fixture source. * * THE FIXTURE LAW: an intentional format change updates the fixtures in the * same commit; an unintentional one fails here. */ import assert from "node:assert/strict"; import fs from "node:fs"; import * as os from "node:os"; import * as path from "node:path"; import { fileURLToPath } from "node:url"; import test from "node:test"; const HERE = path.dirname(fileURLToPath(import.meta.url)); const GOLDEN = path.join(HERE, "fixtures", "golden-data"); // ── the seam, BEFORE any config-loading import ────────────────────────────── const TMP = fs.mkdtempSync(path.join(os.tmpdir(), "lh-golden-")); fs.cpSync(GOLDEN, TMP, { recursive: true }); process.env.LH_DATA_DIR = TMP; const { chatStore } = await import("../src/store/chats.ts"); const { graphStore } = await import("../src/store/graph.ts"); const { jobStore } = await import("../src/store/jobs.ts"); const { settingsStore } = await import("../src/store/settings.ts"); const { readRenderSettings } = await import("../src/store/render-settings.ts"); const { getGraph, deriveView } = await import("../src/core/graph.ts"); const { sanitizeRenderSettings } = await import("../src/core/render-settings.ts"); const { toLlmMessages } = await import("../src/core/native-messages.ts"); const { phaseAt3Lh } = await import("../src/core/phases.ts"); const { danglingQuestionOf, activeProblemOf } = await import("../src/core/native-messages.ts"); const TEACH = "chat_mv27rrra_feff23b6"; const PRACTICE = "chat_mv293wv5_10656952"; function snapshotTree(dir: string): Map { const out = new Map(); for (const name of fs.readdirSync(dir)) { const p = path.join(dir, name); const st = fs.statSync(p); if (st.isDirectory()) for (const [k, v] of snapshotTree(p)) out.set(`${name}/${k}`, v); else out.set(name, fs.readFileSync(p)); } return out; } const BEFORE = snapshotTree(TMP); test.after(() => { fs.rmSync(TMP, { recursive: true, force: true }); }); // ── chats: records, folds, and the wire ───────────────────────────────────── test("chats: both fixture records parse through the production read path", () => { const list = chatStore.listChats(); const ids = new Set(list.map((c) => c.id)); assert.ok(ids.has(TEACH), "the teach master record lists"); assert.ok(ids.has(PRACTICE), "the practice lifecycle record lists"); for (const id of [TEACH, PRACTICE]) { const r = chatStore.getChat(id); assert.ok(r, `${id} reads`); assert.ok(Array.isArray(r!.messages) && r!.messages.length > 0); // the folds the app itself runs per render/turn: assert.equal(typeof phaseAt3Lh(r!.messages), "string", "the phase fold runs clean"); assert.equal(danglingQuestionOf(r!.messages), null, "no dangling question tool call"); activeProblemOf(r!.messages); // must not throw } }); test("chats: the LLM wire flattening turns each full record into a legal provider message list", () => { for (const id of [TEACH, PRACTICE]) { const r = chatStore.getChat(id)!; const wire = toLlmMessages(r.messages as never); assert.ok(wire.length > 0); for (const m of wire) { assert.ok(["system", "user", "assistant", "toolResult"].includes(m.role), `legal role ${m.role} in ${id}`); // every toolResult answers a call that appeared BEFORE it if (m.role === "toolResult") { const t = m as { toolCallId: string }; assert.ok( wire.some((w) => w.role === "assistant" && JSON.stringify(w).includes(t.toolCallId)), `toolResult ${t.toolCallId} has its calling assistant message`, ); } } // no assistant toolCall left unanswered (the dangling-call synthesis covers aborts) const answered = new Set(wire.filter((w) => w.role === "toolResult").map((w) => (w as { toolCallId: string }).toolCallId)); for (const m of wire) { if (m.role !== "assistant") continue; for (const b of (m as { content: Array<{ type: string; id?: string }> }).content) { if (b.type === "toolCall" && b.id) assert.ok(answered.has(b.id), `call ${b.id} is answered on the wire`); } } } }); test("markers: the fixture pair covers EVERY custom-marker kind of the current contract", () => { // The coverage law: a new customType added to the contract without real // fixture data fails here — extend the fixtures in the same commit. const EXPECTED = [ "graph-context", "probe-handoff", "subagent-steer", "subagent-abort", "plan-block", "practice-thinking", "practice-next", "problem-dismissed", "compaction", ] as const; const seen = new Set(); for (const id of [TEACH, PRACTICE]) { for (const m of chatStore.getChat(id)!.messages) { if ((m as { role?: string }).role === "custom") seen.add((m as { customType: string }).customType); } } for (const kind of EXPECTED) assert.ok(seen.has(kind), `marker ${kind} appears in real fixture data`); }); test("tools: the core tool surface appears in real fixture data", () => { // A living inventory, not an exhaustive pin: teach tools in the teach // record, practice tools in the practice record. New TOOLS don't fail // this (they fail their own suites) — the pinned CORE set must simply // stay representable in real records. const teachTools = new Set(); const practiceTools = new Set(); for (const m of chatStore.getChat(TEACH)!.messages) { for (const b of (m as { content?: Array<{ type?: string; name?: string }> }).content ?? []) { if (b.type === "toolCall" && b.name) teachTools.add(b.name); } } for (const m of chatStore.getChat(PRACTICE)!.messages) { for (const b of (m as { content?: Array<{ type?: string; name?: string }> }).content ?? []) { if (b.type === "toolCall" && b.name) practiceTools.add(b.name); } } for (const t of ["quiz", "ask_user_question", "plan_init", "probe_done", "recall", "explain", "research", "demo", "visual", "pdf"]) { assert.ok(teachTools.has(t), `teach tool ${t} exercised in the fixture record`); } for (const t of ["problem", "conclude"]) { assert.ok(practiceTools.has(t), `practice tool ${t} exercised in the fixture record`); } }); // ── the graph: ledgers through the full derivation ────────────────────────── test("graph: the real ledgers parse through their shape guards", () => { assert.ok(graphStore.readNodes().length >= 250, "nodes parse"); assert.ok(graphStore.readChains().length >= 150, "chains parse"); assert.ok(graphStore.readCheckpoints().length >= 5, "session checkpoints parse"); assert.ok(graphStore.readCoverage().length >= 90, "coverage entries parse"); assert.ok(graphStore.readSources().length >= 1, "imported-notes sources parse"); }); test("graph: getGraph + deriveView build the snapshot end-to-end", () => { const derived = getGraph(graphStore); assert.ok(derived.nodes.length >= 250, "merged nodes"); assert.ok(derived.chains.length >= 150, "chains survive derivation"); const view = deriveView(derived); assert.ok(view.nodes.length >= 250, "the view carries every proposition"); // chains are the stored truth; the view renders each as an implication // node with premise→implication→conclusion arrows (the derived image), // and contracts each to direct premise→conclusion LOGICAL edges. assert.ok(view.impLinks.length >= 90, "every unique chain renders into the view (lines merged by premises+conclusion)"); assert.ok(view.edges.length >= view.impLinks.length, "premise/conclusion arrows drawn"); assert.ok(view.logicalEdges.length > 0, "logical edge contraction runs"); }); test("graph: render-settings sanitize and positions parse", () => { const raw = readRenderSettings(); assert.ok(raw, "render-settings.json reads"); const clean = sanitizeRenderSettings(raw); assert.ok(clean.nodeSize > 0 && typeof clean.labelZoom === "number", "sanitizes to concrete knobs"); const positionsFile = JSON.parse(fs.readFileSync(path.join(TMP, "graph", "positions.json"), "utf8")) as { positions: Record; }; assert.ok(Object.keys(positionsFile.positions).length >= 300, "positions parse"); }); // ── jobs + settings ───────────────────────────────────────────────────────── test("jobs: the kind × status matrix parses through the job store", () => { const kinds = new Set(); const statuses = new Set(); for (const f of fs.readdirSync(path.join(GOLDEN, "jobs"))) { const id = f.replace(/\.json$/, ""); const j = jobStore.getJob(id); assert.ok(j, `${id} reads through the job store`); assert.ok(["research", "demo", "visual", "pdf"].includes(j!.kind), `${id}: known kind`); assert.ok(["done", "error"].includes(j!.status), `${id}: terminal status`); kinds.add(j!.kind); statuses.add(j!.status); } assert.deepEqual([...kinds].sort(), ["demo", "pdf", "research", "visual"], "every job kind is fixture-d"); assert.deepEqual([...statuses].sort(), ["done", "error"], "both terminal statuses are fixture-d"); }); test("settings: the stamped record parses", () => { const s = settingsStore.readSettings(); assert.ok(s, "settings record reads"); assert.equal(s!.version, 1, "settings carry their version stamp"); assert.ok(s!.models && typeof s!.models === "object", "model slots present"); }); // ── read-only: readers never write ────────────────────────────────────────── test("read-only: after every read above, the data dir is byte-identical to the fixture source", () => { const after = snapshotTree(TMP); assert.deepEqual([...after.keys()].sort(), [...BEFORE.keys()].sort(), "no files added or removed"); for (const [name, buf] of after) { assert.ok(BEFORE.has(name), `${name} existed before`); assert.deepEqual(buf, BEFORE.get(name), `${name} unchanged by reads`); } });