feat(confidence-engine): v0.59a — correct Investigation revision provenance
Semantic revision tracking ensures every meaningful persisted Investigation change advances investigationRevision exactly once, while Report generation records (but does not advance) the current revision as generatedFromRevision for provenance integrity. Corrections: - updateFindingDisposition: add setInvestigationRevision(+1) for semantic transitions (eligible→not_relevant, restore) - updateFindingProposition: add no-op guard + setInvestigationRevision(+1) - onRestart/ContinueLaterBanner/reset button: add setInvestigationRevision(0) - onSituationGraphChange (Re-open seam): already had revision +1 in dirty impl Established behaviour preserved: - Re-open via reopenResolvedUnknown → onSituationGraphChange → revision +1 - Empty Done via handleDoneForNowPromotion → revision +1 - Report generation records generatedFromRevision, advances by 0 - Autosave passes revision but does not increment it - clearInvestigation() ownership intact Tests: targeted Vitest suite (17 tests) covering all provenance boundaries. Durable rule documented in current-handoff.md §v0.59a.
This commit is contained in:
@@ -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 ─── */
|
||||
|
||||
@@ -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 && (
|
||||
<ContinueLaterBanner onRestart={() => { clearInvestigation(); setStatus("idle"); setResult(null); setAnswer(""); setUpdateStatus("idle"); setCurrentUnderstanding(null); setFocusedContributions([]); setFindings([]); }} />
|
||||
<ContinueLaterBanner onRestart={() => { 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 }) {
|
||||
<button
|
||||
onClick={() => {
|
||||
clearInvestigation();
|
||||
setInvestigationRevision(0);
|
||||
setScenario("");
|
||||
setStatus("idle");
|
||||
setResult(null);
|
||||
|
||||
Reference in New Issue
Block a user