feat(confidence-engine): v0.60j preserve investigation on restart
This commit is contained in:
@@ -600,3 +600,218 @@ describe("investigation-storage v0.60g1 listing", () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// ── v0.60j — restartInvestigation semantic reset ─────────────────────
|
||||
|
||||
describe("investigation-storage v0.60j restart", () => {
|
||||
|
||||
function newId() {
|
||||
return `inv-${Math.random().toString(36).slice(2, 9)}`;
|
||||
}
|
||||
|
||||
async function getWrapper() {
|
||||
vi.resetModules();
|
||||
const m = await import("../../lib/storage/investigation-storage.js");
|
||||
return {
|
||||
restart: m.restartInvestigation,
|
||||
load: m.loadInvestigation,
|
||||
list: m.listInvestigations,
|
||||
save: m.saveInvestigation,
|
||||
};
|
||||
}
|
||||
|
||||
function makeRestartedSnapshot(overrides = {}) {
|
||||
return {
|
||||
id: overrides.id ?? newId(),
|
||||
scenario: "Test scenario text",
|
||||
situationGraph: { nodes: [{ id: "n1" }], edges: [] },
|
||||
selectedQuestion: { question: "Why?" },
|
||||
summary: "Previous understanding",
|
||||
focusedContributions: [{ evidence: "some evidence" }],
|
||||
findings: [{ id: "f1", proposition: "A finding" }],
|
||||
investigationReport: { understanding: "report text", generatedFromRevision: 5 },
|
||||
investigationRevision: 3,
|
||||
updatedAt: "2026-09-01T10:00:00.000Z",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
if (globalThis.window && globalThis.window.localStorage) {
|
||||
try { globalThis.window.localStorage.clear(); } catch(e) {}
|
||||
}
|
||||
});
|
||||
|
||||
// ── A — restart preserves identity ──────────────────────────────
|
||||
|
||||
it("restart preserves the Investigation durable id", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-a" });
|
||||
save(snap);
|
||||
restart("inv-a");
|
||||
|
||||
const loaded = load("inv-a");
|
||||
expect(loaded).not.toBeNull();
|
||||
expect(loaded.id).toBe("inv-a");
|
||||
});
|
||||
|
||||
// ── B — restart preserves container-required state ──────────────
|
||||
|
||||
it("restart preserves scenario framing", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-b" });
|
||||
save(snap);
|
||||
restart("inv-b");
|
||||
|
||||
const loaded = load("inv-b");
|
||||
expect(loaded.scenario).toBe("Test scenario text");
|
||||
});
|
||||
|
||||
it("restart preserves schemaVersion", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-c" });
|
||||
save(snap);
|
||||
restart("inv-c");
|
||||
|
||||
const loaded = load("inv-c");
|
||||
expect(loaded.schemaVersion).toBe(1);
|
||||
});
|
||||
|
||||
// ── C — restart removes previous reasoning/report state ────────
|
||||
|
||||
it("restart clears situationGraph", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-d" });
|
||||
save(snap);
|
||||
restart("inv-d");
|
||||
|
||||
const loaded = load("inv-d");
|
||||
expect(loaded.situationGraph).toBe(null);
|
||||
});
|
||||
|
||||
it("restart clears selectedQuestion", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-e" });
|
||||
save(snap);
|
||||
restart("inv-e");
|
||||
|
||||
const loaded = load("inv-e");
|
||||
expect(loaded.selectedQuestion).toBe(null);
|
||||
});
|
||||
|
||||
it("restart clears summary (current understanding)", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-f" });
|
||||
save(snap);
|
||||
restart("inv-f");
|
||||
|
||||
const loaded = load("inv-f");
|
||||
expect(loaded.summary).toBe(null);
|
||||
});
|
||||
|
||||
it("restart clears focusedContributions", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-g" });
|
||||
save(snap);
|
||||
restart("inv-g");
|
||||
|
||||
const loaded = load("inv-g");
|
||||
expect(loaded.focusedContributions).toEqual([]);
|
||||
});
|
||||
|
||||
it("restart clears findings", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-h" });
|
||||
save(snap);
|
||||
restart("inv-h");
|
||||
|
||||
const loaded = load("inv-h");
|
||||
expect(loaded.findings).toEqual([]);
|
||||
});
|
||||
|
||||
it("restart clears investigationReport (stale Report must not survive)", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-i" });
|
||||
save(snap);
|
||||
restart("inv-i");
|
||||
|
||||
const loaded = load("inv-i");
|
||||
expect(loaded.investigationReport).toBe(null);
|
||||
});
|
||||
|
||||
it("restart resets investigationRevision to 0", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
const snap = makeRestartedSnapshot({ id: "inv-j" });
|
||||
save(snap);
|
||||
restart("inv-j");
|
||||
|
||||
const loaded = load("inv-j");
|
||||
expect(loaded.investigationRevision).toBe(0);
|
||||
});
|
||||
|
||||
// ── D — restart is isolated ────────────────────────────────────
|
||||
|
||||
it("restart A does not affect B", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
save(makeRestartedSnapshot({ id: "inv-a" }));
|
||||
save(makeRestartedSnapshot({ id: "inv-b" }));
|
||||
|
||||
restart("inv-a");
|
||||
|
||||
const bLoaded = load("inv-b");
|
||||
expect(bLoaded.id).toBe("inv-b");
|
||||
expect(bLoaded.scenario).toBe("Test scenario text");
|
||||
expect(bLoaded.situationGraph).not.toBeNull(); // B untouched
|
||||
expect(bLoaded.findings.length).toBe(1); // B findings preserved
|
||||
});
|
||||
|
||||
// ── E — listing remains after restart ──────────────────────────
|
||||
|
||||
it("restart does not remove Investigation from listInvestigations", async () => {
|
||||
const { save, load, restart, list } = await getWrapper();
|
||||
|
||||
save(makeRestartedSnapshot({ id: "inv-k" }));
|
||||
const before = list();
|
||||
expect(before.length).toBe(1);
|
||||
|
||||
restart("inv-k");
|
||||
|
||||
const after = list();
|
||||
expect(after.length).toBe(1);
|
||||
expect(after[0].id).toBe("inv-k");
|
||||
});
|
||||
|
||||
// ── F — missing identity does not restart singleton ────────────
|
||||
|
||||
it("restart with undefined id is silently no-op (no singleton fallback)", async () => {
|
||||
const { save, load, restart } = await getWrapper();
|
||||
|
||||
// Set up a legacy singleton entry
|
||||
if (globalThis.window && globalThis.window.localStorage) {
|
||||
globalThis.window.localStorage.setItem(
|
||||
"confidence-engine-investigation",
|
||||
JSON.stringify(makeRestartedSnapshot({ id: "legacy-singleton" }))
|
||||
);
|
||||
}
|
||||
|
||||
restart(undefined); // should be no-op, not touch singleton
|
||||
|
||||
// Singleton entry still exists (unchanged)
|
||||
const raw = globalThis.window?.localStorage.getItem("confidence-engine-investigation");
|
||||
expect(raw).not.toBeNull();
|
||||
const parsed = JSON.parse(raw);
|
||||
expect(parsed.id).toBe("legacy-singleton");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user