feat(confidence-engine): wire authoritative Done-for-now episode reconsideration
Adopt executeEpisodeDone orchestration as the canonical path for
'Done for now' activity boundary: one user Done triggers exactly
prepareCompletedEpisode -> reconsiderCompletedEpisode -> applyValidatedProposal
-> Current Understanding synthesis -> leave focused workspace.
Production changes (components/scenario-form.jsx):
- Add prepareCompletedEpisode, reconsiderCompletedEpisode, applyValidatedProposal imports
- Export executeEpisodeDone({params}) with all 4 domain functions as named
parameters (defaults to module exports) for deterministic test wiring
- Rewrite handleDoneForNowPromotion(targetNodeId) as async: delegates to
executeEpisodeDone pipeline; CU synthesis installed only on success
- Add doneInProgressRef useRef(false) for exactly-once Done enforcement
- On synthesis failure: KEEP updated graph, KEEP Findings, KEEP existing CU
- Retire produceFindingInformedSummary from ScenarioForm (legacy CU writer)
- Remove legacy idempotence guard and Evidence:[] regex dedup
Test changes (tests/ui/scenario-form-episode-done.test.jsx):
- 9 tests verifying orchestration pipeline correctness:
1. Successful path order: prepare -> reconsider -> apply -> synthesis
2. Correct prepared episode input parameters
3. Structured application evidence (no answer fields in context)
4. nextGraph used for synthesis (not stale result state)
5. Reasoning failure: apply not called, CU synthesis not called
6. Application failure: CU synthesis not called, graph not replaced
7. Synthesis failure: nextGraph remains installed (no rollback)
8. Exactly-once per call for each domain function
9. Legacy Done writer retired (pipeline does not produce deterministic summary)
This commit is contained in:
@@ -5,7 +5,10 @@ import { useState, useRef, useMemo } from "react";
|
|||||||
import DiagnosticsView from "@/components/diagnostics-view";
|
import DiagnosticsView from "@/components/diagnostics-view";
|
||||||
import ReasoningWorkspace, { LoadingOverlay, ContinueLaterBanner } from "@/components/reasoning-workspace";
|
import ReasoningWorkspace, { LoadingOverlay, ContinueLaterBanner } from "@/components/reasoning-workspace";
|
||||||
import { mockFetch, AVAILABLE_SCENARIOS } from "@/lib/mocks/confidence-engine/mock-client";
|
import { mockFetch, AVAILABLE_SCENARIOS } from "@/lib/mocks/confidence-engine/mock-client";
|
||||||
import { deriveFindingsFromContributions, normalizeFindings, produceFindingInformedSummary } from "@/lib/graph/finding-helpers";
|
import { deriveFindingsFromContributions, normalizeFindings } from "@/lib/graph/finding-helpers";
|
||||||
|
import { prepareCompletedEpisode } from "@/lib/graph/episode-preparation.js";
|
||||||
|
import { reconsiderCompletedEpisode } from "@/lib/graph/orchestrator.js";
|
||||||
|
import { applyValidatedProposal } from "@/lib/graph/apply-proposal.js";
|
||||||
import { loadInvestigation, saveInvestigation, clearInvestigation } from "@/lib/storage/investigation-storage";
|
import { loadInvestigation, saveInvestigation, clearInvestigation } from "@/lib/storage/investigation-storage";
|
||||||
|
|
||||||
/* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */
|
/* Compile-time env resolution — NEXT_PUBLIC_ vars are injected by Next.js at build */
|
||||||
@@ -239,6 +242,58 @@ export function derivePrimarySurface(result, status, _showExperimentView, scenar
|
|||||||
return "SCENARIO_ENTRY";
|
return "SCENARIO_ENTRY";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Orchestrate the authoritative episode reconsideration flow.
|
||||||
|
* Exported for deterministic testing — all domain functions accepted as parameters.
|
||||||
|
*/
|
||||||
|
export async function executeEpisodeDone({
|
||||||
|
resultSituationGraph,
|
||||||
|
targetNodeId,
|
||||||
|
focusedContributions,
|
||||||
|
findings,
|
||||||
|
prepareCompletedEpisode: prepFn = prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: reconsiderFn = reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: applyFn = applyValidatedProposal,
|
||||||
|
synthesizeFn,
|
||||||
|
setResult: setAppState,
|
||||||
|
}) {
|
||||||
|
const prepared = prepFn({
|
||||||
|
situationGraph: resultSituationGraph,
|
||||||
|
targetNodeId,
|
||||||
|
contributions: focusedContributions ?? [],
|
||||||
|
findings,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!prepared?.turns?.length && !prepared?.eligibleCanonicalFindings?.length) {
|
||||||
|
return { success: false, stage: "preparation", reason: "no_episodic_content" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const reasoning = await reconsiderFn(prepared);
|
||||||
|
if (!reasoning.success) {
|
||||||
|
return { success: false, stage: "reconsideration", error: reasoning.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const application = await applyFn({
|
||||||
|
situationGraph: resultSituationGraph,
|
||||||
|
proposal: reasoning.proposal,
|
||||||
|
evidenceContext: {
|
||||||
|
isCompletedEpisode: true,
|
||||||
|
episodeEvidence: prepared,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!application.success) {
|
||||||
|
return { success: false, stage: "application", error: application.error };
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextGraph = application.updatedSituationGraph;
|
||||||
|
setAppState(prev => ({ ...(prev ?? {}), situationGraph: nextGraph }));
|
||||||
|
|
||||||
|
const synthesisResult = await synthesizeFn(nextGraph, findings);
|
||||||
|
|
||||||
|
return { success: true, nextGraph, synthesisResult };
|
||||||
|
}
|
||||||
|
|
||||||
export default function ScenarioForm() {
|
export default function ScenarioForm() {
|
||||||
const [scenario, setScenario] = useState("");
|
const [scenario, setScenario] = useState("");
|
||||||
const [status, setStatus] = useState("idle"); // idle | loading | error | success
|
const [status, setStatus] = useState("idle"); // idle | loading | error | success
|
||||||
@@ -252,6 +307,9 @@ export default function ScenarioForm() {
|
|||||||
const [mockScenario, setMockScenario] = useState("");
|
const [mockScenario, setMockScenario] = useState("");
|
||||||
const [hideFacilitatorOnLanding, setHideFacilitatorOnLanding] = useState(false);
|
const [hideFacilitatorOnLanding, setHideFacilitatorOnLanding] = useState(false);
|
||||||
|
|
||||||
|
/* ── in-flight gate for episode reconsideration on Done ──── */
|
||||||
|
const doneInProgressRef = useRef(false);
|
||||||
|
|
||||||
/* ── RTO.31: focused contributions ownership ─────────────── */
|
/* ── RTO.31: focused contributions ownership ─────────────── */
|
||||||
const [focusedContributions, setFocusedContributions] = useState([]);
|
const [focusedContributions, setFocusedContributions] = useState([]);
|
||||||
|
|
||||||
@@ -319,40 +377,35 @@ export default function ScenarioForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* v0.49 promotion seam — deterministic Current Understanding update
|
* Authoritative graph reconsideration triggered by "Done for now"
|
||||||
* triggered by "Done for now" activity boundary (no case/update, no LLM).
|
* activity boundary. Delegates to the exported executeEpisodeDone pipeline.
|
||||||
*/
|
*/
|
||||||
function handleDoneForNowPromotion(targetNodeId) {
|
async function handleDoneForNowPromotion(targetNodeId) {
|
||||||
if (!targetNodeId || !findings?.length) return;
|
if (!targetNodeId || !findings?.length) return;
|
||||||
|
|
||||||
// Filter eligible findings for this specific target only.
|
// In-flight guard: exactly-once enforcement
|
||||||
const eligible = findings.filter(
|
if (doneInProgressRef.current) return;
|
||||||
(f) => f.originatingTargetNodeId === targetNodeId && (f.userDisposition === null || f.userDisposition === "agree"),
|
doneInProgressRef.current = true;
|
||||||
);
|
|
||||||
|
|
||||||
if (eligible.length === 0) return;
|
try {
|
||||||
|
const result = await executeEpisodeDone({
|
||||||
|
resultSituationGraph: result?.situationGraph,
|
||||||
|
targetNodeId,
|
||||||
|
focusedContributions: focusedContributions ?? [],
|
||||||
|
findings,
|
||||||
|
synthesizeFn: (graph, fn) => synthesizeFromFindings(fetch, { situationGraph: graph, findings: fn }),
|
||||||
|
setResult,
|
||||||
|
});
|
||||||
|
|
||||||
// Determine the base: use currentUnderstanding if available, else empty string.
|
/* CU synthesis — install only on success */
|
||||||
const baseSummary = currentUnderstanding ?? "";
|
if (result?.synthesisResult?.ok && result.synthesisResult.data?.currentUnderstanding) {
|
||||||
|
setCurrentUnderstanding(result.synthesisResult.data.currentUnderstanding);
|
||||||
|
}
|
||||||
|
/* On synthesis failure: KEEP nextGraph, KEEP Findings, KEEP existing CU. Do NOT rollback. */
|
||||||
|
|
||||||
// Deterministic producer — no LLM, no API.
|
} finally {
|
||||||
const newSummary = produceFindingInformedSummary(baseSummary, eligible);
|
doneInProgressRef.current = false;
|
||||||
|
|
||||||
// Idempotence guard: skip if summary is unchanged (no new eligible findings
|
|
||||||
// beyond what's already in the current Evidence block).
|
|
||||||
if (newSummary === baseSummary) return;
|
|
||||||
|
|
||||||
// Avoid duplicate evidence propositions from repeated promotion.
|
|
||||||
const existingEvidenceMatch = baseSummary.match(/Evidence:\s*\[([^\]]+)\]/);
|
|
||||||
let isDuplicate = false;
|
|
||||||
if (existingEvidenceMatch) {
|
|
||||||
const existingTexts = existingEvidenceMatch[1].split("; ").map((t) => t.trim());
|
|
||||||
isDuplicate = eligible.every((f) => existingTexts.includes(f.proposition));
|
|
||||||
}
|
}
|
||||||
if (isDuplicate) return;
|
|
||||||
|
|
||||||
// Mutate the SAME summary/state that autosave already persists.
|
|
||||||
setCurrentUnderstanding(newSummary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendFocusedContribution(contribution) {
|
function appendFocusedContribution(contribution) {
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
// ── Domain fixtures ───────────────────────────────────────────
|
||||||
|
|
||||||
|
const MOCK_SITUATION_GRAPH = {
|
||||||
|
nodes: [{ id: "n-q1", label: "Query Q1", kind: "question", status: "unknown" }],
|
||||||
|
edges: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const DISTINCTIVE_GRAPH = { __distinctive_graph__: true };
|
||||||
|
|
||||||
|
function makePreparedEpisode(overrides = {}) {
|
||||||
|
return {
|
||||||
|
situationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
turns: [{ contributionId: "contrib-0001", sequence: 1, question: "Q?", answer: "A?" }],
|
||||||
|
eligibleCanonicalFindings: [{ findingId: "f-1", proposition: "P1" }],
|
||||||
|
excludedFindingProvenance: [],
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeProposal() {
|
||||||
|
return {
|
||||||
|
addedNodes: [], updatedNodes: [{ nodeId: "n-q1", newStatus: "resolved" }],
|
||||||
|
addedEdges: [], removedEdgeIds: [], resolvedUnknownNodeIds: [],
|
||||||
|
affectedNodeIds: [], selectedQuestion: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Import the testable pipeline ───────────────────────────────
|
||||||
|
|
||||||
|
import { executeEpisodeDone } from "@/components/scenario-form.jsx";
|
||||||
|
|
||||||
|
describe("episode-done orchestration", () => {
|
||||||
|
function makeMocks() {
|
||||||
|
return {
|
||||||
|
prepareCompletedEpisode: vi.fn().mockReturnValue(makePreparedEpisode()),
|
||||||
|
reconsiderCompletedEpisode: vi.fn().mockResolvedValue({ success: true, proposal: makeProposal() }),
|
||||||
|
applyValidatedProposal: vi.fn().mockResolvedValue({ success: true, updatedSituationGraph: DISTINCTIVE_GRAPH }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
it("1. Successful path order", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
const order = [];
|
||||||
|
|
||||||
|
const trackReconsider = vi.fn().mockImplementation(async (...args) => {
|
||||||
|
order.push("reconsider");
|
||||||
|
return { success: true, proposal: makeProposal() };
|
||||||
|
});
|
||||||
|
const trackApply = vi.fn().mockImplementation(async (...args) => {
|
||||||
|
order.push("apply");
|
||||||
|
return { success: true, updatedSituationGraph: DISTINCTIVE_GRAPH };
|
||||||
|
});
|
||||||
|
const trackSynth = vi.fn().mockImplementation(async (...args) => {
|
||||||
|
order.push("synthesis");
|
||||||
|
return { ok: true, data: {} };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override prepare to record in order too
|
||||||
|
m.prepareCompletedEpisode.mockImplementation(() => {
|
||||||
|
order.push("prepare");
|
||||||
|
return makePreparedEpisode();
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [{ id: "c-1" }],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: trackReconsider,
|
||||||
|
applyValidatedProposal: trackApply,
|
||||||
|
synthesizeFn: trackSynth,
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify exact order
|
||||||
|
expect(order[0]).toBe("prepare");
|
||||||
|
expect(order[1]).toBe("reconsider");
|
||||||
|
expect(order[2]).toBe("apply");
|
||||||
|
expect(order[3]).toBe("synthesis");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("2. Correct prepared episode input", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
const findings = [{ id: "f-1", proposition: "P1" }];
|
||||||
|
const contributions = [{ id: "c-1", question: "Q?", answer: "A?" }];
|
||||||
|
|
||||||
|
await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: contributions,
|
||||||
|
findings,
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }),
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(m.prepareCompletedEpisode).toHaveBeenCalledWith({
|
||||||
|
situationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
contributions,
|
||||||
|
findings,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("3. Structured application evidence — no answer fields", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
let capturedEvidenceContext = null;
|
||||||
|
m.applyValidatedProposal.mockImplementation(async (args) => {
|
||||||
|
capturedEvidenceContext = args?.evidenceContext;
|
||||||
|
return { success: true, updatedSituationGraph: DISTINCTIVE_GRAPH };
|
||||||
|
});
|
||||||
|
|
||||||
|
await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }),
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(capturedEvidenceContext?.isCompletedEpisode).toBe(true);
|
||||||
|
expect(capturedEvidenceContext?.episodeEvidence).toBeDefined();
|
||||||
|
const keys = Object.keys(capturedEvidenceContext || {});
|
||||||
|
expect(keys).not.toContain("answer");
|
||||||
|
expect(keys).not.toContain("syntheticAnswer");
|
||||||
|
expect(keys).not.toContain("combinedAnswer");
|
||||||
|
expect(keys).not.toContain("lastAnswer");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("4. nextGraph used for synthesis (not stale state)", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
let synthesizedGraph = null;
|
||||||
|
m.applyValidatedProposal.mockResolvedValue({ success: true, updatedSituationGraph: DISTINCTIVE_GRAPH });
|
||||||
|
|
||||||
|
await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [{ id: "f-1" }],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: (graph) => {
|
||||||
|
synthesizedGraph = graph;
|
||||||
|
return { ok: true, data: {} };
|
||||||
|
},
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(synthesizedGraph).toBe(DISTINCTIVE_GRAPH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("5. Reasoning failure — apply not called, CU synthesis not called", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
m.reconsiderCompletedEpisode.mockResolvedValue({ success: false, stage: "provider" });
|
||||||
|
|
||||||
|
const result = await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn(),
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
expect(m.applyValidatedProposal).not.toHaveBeenCalled();
|
||||||
|
expect(m.prepareCompletedEpisode).toHaveBeenCalledTimes(1);
|
||||||
|
expect(m.reconsiderCompletedEpisode).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("6. Application failure — CU synthesis not called, graph not replaced", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
m.applyValidatedProposal.mockResolvedValue({ success: false, stage: "proposal_compatibility" });
|
||||||
|
|
||||||
|
const result = await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn(),
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("7. Synthesis failure — nextGraph remains installed", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
const setResult = vi.fn();
|
||||||
|
|
||||||
|
await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn().mockReturnValue({ ok: false }),
|
||||||
|
setResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Graph was installed despite synthesis failure
|
||||||
|
expect(setResult).toHaveBeenCalledTimes(1);
|
||||||
|
const updater = setResult.mock.calls[0][0];
|
||||||
|
const updatedState = updater(null);
|
||||||
|
expect(updatedState.situationGraph).toBe(DISTINCTIVE_GRAPH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("8. Exactly-once — each step invoked once per call", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
|
||||||
|
await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }),
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(m.prepareCompletedEpisode).toHaveBeenCalledTimes(1);
|
||||||
|
expect(m.reconsiderCompletedEpisode).toHaveBeenCalledTimes(1);
|
||||||
|
expect(m.applyValidatedProposal).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("9. Legacy Done CU writer retired — pipeline does not use deterministic summary", async () => {
|
||||||
|
const m = makeMocks();
|
||||||
|
|
||||||
|
const result = await executeEpisodeDone({
|
||||||
|
resultSituationGraph: MOCK_SITUATION_GRAPH,
|
||||||
|
targetNodeId: "n-q1",
|
||||||
|
focusedContributions: [],
|
||||||
|
findings: [],
|
||||||
|
prepareCompletedEpisode: m.prepareCompletedEpisode,
|
||||||
|
reconsiderCompletedEpisode: m.reconsiderCompletedEpisode,
|
||||||
|
applyValidatedProposal: m.applyValidatedProposal,
|
||||||
|
synthesizeFn: vi.fn().mockReturnValue({ ok: true, data: {} }),
|
||||||
|
setResult: vi.fn(),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.nextGraph).toBe(DISTINCTIVE_GRAPH);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user