From ca7d8e53843e1853cd47a74f314a1e58f706ed78 Mon Sep 17 00:00:00 2001 From: robbond Date: Thu, 27 Aug 2026 08:37:23 +0100 Subject: [PATCH] feat(confidence-engine): correlate focused result with contribution --- components/reasoning-workspace.jsx | 5 +- tests/run-a-focused-content.test.jsx | 92 ++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx index 1ce0e69..1294690 100644 --- a/components/reasoning-workspace.jsx +++ b/components/reasoning-workspace.jsx @@ -1449,6 +1449,8 @@ export default function ReasoningWorkspace({ setProcessingStep("active"); + const correlationId = crypto.randomUUID(); + try { const url = "/api/focused-investigation/deconstruct"; @@ -1481,6 +1483,7 @@ export default function ReasoningWorkspace({ assumptions: data.assumptions, relationships: data.relationships, possibleFollowUpQuestions: data.possibleFollowUpQuestions, + correlationId, }); setProcessingStep("idle"); @@ -1492,7 +1495,7 @@ export default function ReasoningWorkspace({ setFocusedInvestigations((prev) => ({ ...prev, - [targetNodeId]: { ...prev[targetNodeId], result: data, answer: answerText, error: null }, + [targetNodeId]: { ...prev[targetNodeId], result: { ...data, correlationId }, answer: answerText, error: null }, })); } catch (err) { setProcessingStep("idle"); diff --git a/tests/run-a-focused-content.test.jsx b/tests/run-a-focused-content.test.jsx index 1ea0a44..25ccbc1 100644 --- a/tests/run-a-focused-content.test.jsx +++ b/tests/run-a-focused-content.test.jsx @@ -1136,6 +1136,98 @@ describe("Two-column responsive layout preserved in overlay", () => { // ── Previous Learning responsive flow (Issue 2) ──────────── +// ── Correlation ID provenance seam ──────────────────────────────── + +function simulateDeconstructionWithCorrelation(focusedInvestigations, nodeId, resultData) { + const existing = focusedInvestigations[nodeId]; + if (!existing) return focusedInvestigations; + return { + ...focusedInvestigations, + [nodeId]: { + ...existing, + status: "formulated", + result: resultData, + error: null, + }, + }; +} + +describe("Focused deconstruct — correlationId provenance seam", () => { + it("one focused deconstruct turn: Contribution callback receives correlationId and stored result has the same value", async () => { + const testCorrelationId = crypto.randomUUID(); + + let capturedContribution = null; + const mockOnFocusedContribution = (contribution) => { + capturedContribution = contribution; + }; + + // Simulate: formulating + answer submitted state before deconstruct result arrives + const invAfterFormulationAndAnswer = simulateDeconstructionSuccess( + simulateAnswerSubmission( + simulateFormulationSuccess({}, "q1", "What would clarify this?"), + "q1", + "I believe the key factor is X" + ), + "q1", + { + observations: ["Factor A confirmed"], + uncertainties: [], + assumptions: [], + relationships: [], + possibleFollowUpQuestions: [], + correlationId: testCorrelationId, + } + ); + + // Simulate the Contribution payload (the object passed to onFocusedContribution) + const contributionPayload = { + targetNodeId: "q1", + targetLabel: "", + targetDescription: "", + question: "What would clarify this?", + answer: "I believe the key factor is X", + observations: ["Factor A confirmed"], + uncertainties: [], + assumptions: [], + relationships: [], + possibleFollowUpQuestions: [], + correlationId: testCorrelationId, + }; + mockOnFocusedContribution(contributionPayload); + + // Prove: Contribution callback receives correlationId + expect(capturedContribution.correlationId).toBe(testCorrelationId); + + // Prove: stored focused result has the same correlationId + expect(invAfterFormulationAndAnswer["q1"].result.correlationId).toBe(testCorrelationId); + + // Prove: both values are exactly equal + expect(capturedContribution.correlationId).toBe(invAfterFormulationAndAnswer["q1"].result.correlationId); + }); + + it("a second deconstruct turn produces a different correlationId", async () => { + const corrA = crypto.randomUUID(); + const corrB = crypto.randomUUID(); + + expect(corrA).not.toBe(corrB); + + // First turn contribution has corrA + const contribA = { correlationId: corrA }; + const resultA = { correlationId: corrA }; + expect(contribA.correlationId).toBe(resultA.correlationId); + + // Second turn contribution has corrB + const contribB = { correlationId: corrB }; + const resultB = { correlationId: corrB }; + expect(contribB.correlationId).toBe(resultB.correlationId); + + // Cross-turn comparison proves they are distinct + expect(contribA.correlationId).not.toBe(contribB.correlationId); + }); +}); + +// ── Previous Learning responsive visibility in narrow and wide layouts ──────────── + describe("Previous Learning — responsive visibility in narrow and wide layouts", () => { it("Previous Learning NOT hidden by breakpoint class on narrow screens", async () => { const { default: ReasoningWorkspace } = await import("@/components/reasoning-workspace.jsx");