diff --git a/app/investigations/[id]/report/page.jsx b/app/investigations/[id]/report/page.jsx index ac90bff..093b217 100644 --- a/app/investigations/[id]/report/page.jsx +++ b/app/investigations/[id]/report/page.jsx @@ -48,10 +48,12 @@ export default function ReportPage() { const data = await res.json(); if (data.success) { - setExisting((prev) => { - const updated = { ...prev, investigationReport: { understanding: data.understanding, plausibleInterpretations: data.plausibleInterpretations, hasPlausibleInterpretations: true } }; - saveInvestigation(updated); - return updated; + /* ── v0.59a — provenance: record generation revision (does NOT change Investigation revision) ── */ + const rev = existing?.investigationRevision ?? 0; + const reportData = { understanding: data.understanding, plausibleInterpretations: data.plausibleInterpretations, hasPlausibleInterpretations: true, generatedFromRevision: rev }; + setExisting((p) => { + saveInvestigation({ ...p, investigationReport: reportData }); + return { ...p, investigationReport: reportData }; }); } else { setGenerationError(true); diff --git a/components/reasoning-workspace.jsx b/components/reasoning-workspace.jsx index a27a569..9d4a0e7 100644 --- a/components/reasoning-workspace.jsx +++ b/components/reasoning-workspace.jsx @@ -1329,6 +1329,8 @@ export default function ReasoningWorkspace({ onImmediateGraphChange, /* ── canonical graph-replacement seam (future Re-open) ── */ onSituationGraphChange, + /* ── v0.59a — provenance revision ──────────────────── */ + investigationRevision, /* ── test init seam (no effect → immediate state) ───────── */ initialPostAnalyseStatus, /* ── v0.54b — investigation overview transient state ─── */ diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx index 54a3c80..d0121e9 100644 --- a/components/scenario-form.jsx +++ b/components/scenario-form.jsx @@ -292,6 +292,9 @@ export default function ScenarioForm({ onNavigateToReport }) { /* ── v0.55 — persisted investigation report (derived artefact) ── */ const [investigationReport, setInvestigationReport] = useState(null); + /* ── v0.59a — provenance: Investigation revision tracking ── */ + const [investigationRevision, setInvestigationRevision] = useState(0); + /* ── in-flight gate for episode reconsideration on Done ──── */ const doneInProgressRef = useRef(false); @@ -326,6 +329,9 @@ export default function ScenarioForm({ onNavigateToReport }) { if (!notRelevantTransition && !restoreTransition) return; + /* ── v0.59a — provenance: eligible evidence set changed ── */ + setInvestigationRevision((prev) => (prev ?? 0) + 1); + const currentGraph = result?.situationGraph; if (!currentGraph) return; @@ -345,8 +351,15 @@ export default function ScenarioForm({ onNavigateToReport }) { f.id === findingId ? { ...f, proposition: newProposition, userDisposition: null } : f, ); + /* ── v0.59a — provenance: no-op guard ── */ + const prevFinding = (findings ?? []).find((f) => f.id === findingId); + if (prevFinding?.proposition === newProposition) return; // no semantic change + setFindings(() => nextFindings); + /* ── v0.59a — provenance: corrected Finding changes evidence ── */ + setInvestigationRevision((prev) => (prev ?? 0) + 1); + // ── Synthesis trigger: corrected Finding → one reconstruction ── const currentGraph = result?.situationGraph; if (!currentGraph) return; @@ -408,6 +421,10 @@ export default function ScenarioForm({ onNavigateToReport }) { setResult, }); + /* ── v0.59a — provenance: episode done is meaningful evidence change ── */ + const nextRev = (investigationRevision ?? 0) + 1; + setInvestigationRevision(nextRev); + /* CU synthesis — install only on success */ if (doneResult?.synthesisResult?.ok && doneResult.synthesisResult.data?.currentUnderstanding) { setCurrentUnderstanding(doneResult.synthesisResult.data.currentUnderstanding); @@ -448,10 +465,12 @@ export default function ScenarioForm({ onNavigateToReport }) { setOverviewState(res); // Persist as a derived artefact of this investigation + const rev = investigationRevision ?? 0; const report = { understanding: res.understanding, plausibleInterpretations: hasPlausibleInput ? res.plausibleInterpretations ?? "" : "", hasPlausibleInterpretations: hasPlausibleInput, + generatedFromRevision: rev, }; setInvestigationReport(report); @@ -465,6 +484,7 @@ export default function ScenarioForm({ onNavigateToReport }) { focusedContributions, findings, investigationReport: report, + investigationRevision: rev, }); } // On failure: do not clear existing CU, do not block further attempts @@ -539,6 +559,9 @@ export default function ScenarioForm({ onNavigateToReport }) { setInvestigationReport(saved.investigationReport); } + /* ── v0.59a — hydrate provenance revision ─────────── */ + setInvestigationRevision(saved.investigationRevision ?? 0); + // Partial sessions (present but no graph) must NOT suppress the // scenario-entry form. Only promote to success when there is actual // investigation data to render. @@ -565,6 +588,7 @@ export default function ScenarioForm({ onNavigateToReport }) { focusedContributions, findings, investigationReport, + investigationRevision, }); }, [ scenario, @@ -574,6 +598,7 @@ export default function ScenarioForm({ onNavigateToReport }) { focusedContributions, findings, investigationReport, + investigationRevision, ]); /* Restore facilitator dismiss preference (Experiment 05) ─── */ @@ -651,7 +676,9 @@ export default function ScenarioForm({ onNavigateToReport }) { setCurrentUnderstanding(data.summary ?? null); const normalised = normaliseStartResult(data); setResult(normalised); - saveInvestigation({ scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [], investigationReport }); + /* ── v0.59a — provenance: first meaningful change sets revision to 1 ── */ + setInvestigationRevision(1); + saveInvestigation({ scenario, situationGraph: normalised.situationGraph, selectedQuestion: normalised.selectedQuestion, summary: data.summary ?? null, updatedAt: new Date().toISOString(), focusedContributions, findings: [], investigationReport, investigationRevision: 1 }); } else { setStatus("error"); setCurrentUnderstanding(data.summary ?? null); @@ -735,7 +762,10 @@ export default function ScenarioForm({ onNavigateToReport }) { setAnswer(""); // Persist after successful update turn — include explicit next state - saveInvestigation({ scenario, situationGraph: nextGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: currentUnderstanding, updatedAt: new Date().toISOString(), focusedContributions, findings: nextFindings, investigationReport }); + /* ── v0.59a — provenance: meaningful change advances revision ── */ + const nextRev = (investigationRevision ?? 0) + 1; + setInvestigationRevision(nextRev); + saveInvestigation({ scenario, situationGraph: nextGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: currentUnderstanding, updatedAt: new Date().toISOString(), focusedContributions, findings: nextFindings, investigationReport, investigationRevision: nextRev }); } else { setUpdateStatus("error"); setUpdateError(outcome); @@ -907,10 +937,16 @@ export default function ScenarioForm({ onNavigateToReport }) { onSummaryUpdate={handleDoneForNowPromotion} /* ── immediate graph transition (Done acknowledged before async) ── */ onImmediateGraphChange={(nextGraph) => setResult((prev) => ({ ...(prev ?? {}), situationGraph: nextGraph }))} - /* ── canonical graph-replacement seam (future Re-open) ── */ - onSituationGraphChange={(nextGraph) => setResult((prev) => ({ ...(prev ?? {}), situationGraph: nextGraph }))} + /* ── v0.59a — provenance tracking ─────────────────── */ + investigationRevision={investigationRevision} + onSituationGraphChange={(nextGraph) => { + const nextRev = (investigationRevision ?? 0) + 1; + setInvestigationRevision(nextRev); + setResult((prev) => ({ ...(prev ?? {}), situationGraph: nextGraph })); + }} onRestart={() => { clearInvestigation(); + setInvestigationRevision(0); setStatus("idle"); setResult(null); setAnswer(""); @@ -930,7 +966,7 @@ export default function ScenarioForm({ onNavigateToReport }) { {/* ── Continue later banner when session was restored ── */} {status === "success" && result?.updatedAt && ( - { clearInvestigation(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); setFocusedContributions([]); setFindings([]); }} /> + { clearInvestigation(); setInvestigationRevision(0); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); setFocusedContributions([]); setFindings([]); }} /> )} {/* Reset button after successful analysis */} @@ -939,6 +975,7 @@ export default function ScenarioForm({ onNavigateToReport }) {