From 8526aa4b690d59445710f498b7f2764b27b73c1c Mon Sep 17 00:00:00 2001 From: robbond Date: Wed, 12 Aug 2026 10:41:02 +0100 Subject: [PATCH] tooling: supply anchored previous question in update-only mode --- .../reproduce-multi-turn-investigation.mjs | 5 +- ...e-multi-turn-investigation.harness.test.js | 51 ++++++++++++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/scripts/reproduce-multi-turn-investigation.mjs b/scripts/reproduce-multi-turn-investigation.mjs index 4ae0adb..eb4f1b5 100644 --- a/scripts/reproduce-multi-turn-investigation.mjs +++ b/scripts/reproduce-multi-turn-investigation.mjs @@ -239,7 +239,10 @@ async function runUpdateOnlyMode() { // Deep-copy so mutation doesn't corrupt the original fixture. const situationGraph = JSON.parse(JSON.stringify(initialGraph)); - let selectedQuestion = null; + + // Derive previousQuestion from the committed savings-realism anchor so that + // update-only mode can reach the production Update path without requiring Start. + let selectedQuestion = fixtureData.unresolved_question ?? savingsNode.label; // ── Exactly one Update through production route ──────── const updateNum = 1; diff --git a/tests/reproduce-multi-turn-investigation.harness.test.js b/tests/reproduce-multi-turn-investigation.harness.test.js index 41b26b7..f872785 100644 --- a/tests/reproduce-multi-turn-investigation.harness.test.js +++ b/tests/reproduce-multi-turn-investigation.harness.test.js @@ -651,15 +651,53 @@ describe("reproduce-multi-turn-investigation harness: one-shot semantics", () => expect(r.startCalls).toBe(0); expect(r.updateCalls).toBe(1); + // Direct assertion: exact fixture graph transmitted (57J.81) + const updateBody = r.capturedUpdateBody; + expect(updateBody.situationGraph).toBeDefined(); + const fixtureGraph = PRE_ANCHORED_FIXTURE.graph; + expect(JSON.parse(JSON.stringify(updateBody.situationGraph.nodes))).toEqual(fixtureGraph.nodes); + expect(JSON.parse(JSON.stringify(updateBody.situationGraph.edges))).toEqual(fixtureGraph.edges); + // Verify the captured graph matches what was sent const { nodes, edges } = r; - const fixtureGraph = PRE_ANCHORED_FIXTURE.graph; expect(nodes.length).toBe(fixtureGraph.nodes.length); expect(edges.length).toBe(fixtureGraph.edges.length); expect(nodes[0].id).toBe(fixtureGraph.nodes[0].id); expect(nodes[1].id).toBe(fixtureGraph.nodes[1].id); }); + it("57J.81 update-only previousQuestion is non-empty string and matches the committed savings-realism anchor", () => { + const r = runPreAnchoredSimulation(); + expect(r.startCalls).toBe(0); + expect(r.updateCalls).toBe(1); + + const prevQ = r.capturedUpdateBody.previousQuestion; + expect(typeof prevQ).toBe("string"); + expect(prevQ.length).toBeGreaterThan(0); + // Must correspond exactly to the anchored savings-realism uncertainty + expect(prevQ).toBe(PRE_ANCHORED_FIXTURE.unresolvedQuestion); + expect(prevQ).toBe("Are the projected office savings from relocation realistic?"); + }); + + it("57J.81 update-only exact ANSWER_2 sent as request answer", () => { + const customAnswer = "The projected savings are based on the current London lease and business rates."; + const r = runPreAnchoredSimulation({ answer: customAnswer }); + expect(r.startCalls).toBe(0); + expect(r.updateCalls).toBe(1); + + // Direct assertion of exact ANSWER_2 in request body + expect(r.capturedUpdateBody.answer).toBe(customAnswer); + }); + + it("57J.81 missing ANSWER_2 blocks before any HTTP call", () => { + const r = runPreAnchoredSimulationWithBlock(); + expect(r.startCalls).toBe(0); + expect(r.updateCalls).toBe(0); + expect(r.type).toBe("blocked_no_answer"); + expect(r.blockedMessage).toContain("missing ANSWER_2"); + expect(r.apiLog.length).toBe(0); + }); + it("pre-anchored update-only mode does not call Start", () => { const r = runPreAnchoredSimulation(); expect(r.startCalls).toBe(0); @@ -1061,7 +1099,14 @@ function runPreAnchoredSimulation(cfg) { // Pre-anchored: no Start call — graph is supplied directly. let graph = JSON.parse(JSON.stringify(initialGraph)); - let question = null; // will be set by the update response (or mock) + + // Derive previousQuestion from the committed savings-realism anchor + // (mirrors the harness fix in reproduce-multi-turn-investigation.mjs). + const unresolvedQuestion = PRE_ANCHORED_FIXTURE.unresolvedQuestion; + let question = unresolvedQuestion; // fixture always has this field for pre-anchored mode + + // Track the exact Update request body for direct assertions. + let capturedUpdateBody = null; const api = { post(path, body) { @@ -1073,6 +1118,7 @@ function runPreAnchoredSimulation(cfg) { if (path === "/api/cases/update") { apiLog.push({ step: "update", answer: body.answer }); + capturedUpdateBody = body; // capture for direct request-body assertions updateCalls++; const resp = typeof C.onResponseUpdate === "function" @@ -1161,6 +1207,7 @@ function runPreAnchoredSimulation(cfg) { selectedQuestion: sq && typeof sq === "object" ? { question: sq.question, nodeId: sq.nodeId } : null, graph: { nodes: uj.updatedSituationGraph?.nodes ?? [], edges: uj.updatedSituationGraph?.edges ?? [] }, }, + capturedUpdateBody, }; }