diff --git a/app/investigations/[id]/page.jsx b/app/investigations/[id]/page.jsx
index ebd8ce7..8f945d2 100644
--- a/app/investigations/[id]/page.jsx
+++ b/app/investigations/[id]/page.jsx
@@ -4,18 +4,18 @@ import React from "react";
import { loadInvestigation } from "@/lib/storage/investigation-storage";
import ScenarioForm from "@/components/scenario-form";
import Link from "next/link";
-import { useRouter } from "next/navigation";
+import { useParams, useRouter } from "next/navigation";
import { useEffect, useState } from "react";
-const INVESTIGATION_ID = "case-1";
-
-export default function InvestigationPage() {
+export default function InvestigationPage({ params }) {
const router = useRouter();
+ const routeId = typeof params?.id === "string" ? params.id : "";
const [existing, setExisting] = useState(null);
useEffect(() => {
- setExisting(loadInvestigation());
- }, []);
+ if (!routeId) return;
+ setExisting(loadInvestigation(routeId));
+ }, [routeId]);
return (
@@ -37,12 +37,14 @@ export default function InvestigationPage() {
{existing ? (
router.push(`/investigations/${INVESTIGATION_ID}/report`)}
+ onNavigateToReport={() => router.push(`/investigations/${routeId}/report`)}
/>
) : (
router.push(`/investigations/${INVESTIGATION_ID}/report`)}
+ investigationId={routeId}
+ onNavigateToReport={() => router.push(`/investigations/${routeId}/report`)}
/>
)}
diff --git a/components/scenario-form.jsx b/components/scenario-form.jsx
index d0121e9..911fb4d 100644
--- a/components/scenario-form.jsx
+++ b/components/scenario-form.jsx
@@ -271,7 +271,7 @@ export async function executeEpisodeDone({
return { success: true, nextGraph, synthesisResult };
}
-export default function ScenarioForm({ onNavigateToReport }) {
+export default function ScenarioForm({ investigationId, onNavigateToReport }) {
const [scenario, setScenario] = useState("");
const [status, setStatus] = useState("idle"); // idle | loading | error | success
const [result, setResult] = useState(null);
@@ -476,6 +476,7 @@ export default function ScenarioForm({ onNavigateToReport }) {
// Trigger autosave to persist the report
void saveInvestigation({
+ id: investigationId,
scenario,
situationGraph: result.situationGraph,
selectedQuestion: result.selectedQuestion,
@@ -543,7 +544,7 @@ export default function ScenarioForm({ onNavigateToReport }) {
/* Restore persisted session on mount ─────────── */
useEffect(() => {
if (typeof window === "undefined") return;
- const saved = loadInvestigation();
+ const saved = investigationId ? loadInvestigation(investigationId) : null;
if (!saved) return;
const hasGraph = Boolean(saved.situationGraph);
@@ -580,6 +581,7 @@ export default function ScenarioForm({ onNavigateToReport }) {
if (!result?.situationGraph) return;
void saveInvestigation({
+ id: investigationId,
scenario,
situationGraph: result.situationGraph,
selectedQuestion: result.selectedQuestion,
@@ -678,7 +680,7 @@ export default function ScenarioForm({ onNavigateToReport }) {
setResult(normalised);
/* ── 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 });
+ saveInvestigation({ id: investigationId, 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);
@@ -765,7 +767,7 @@ export default function ScenarioForm({ onNavigateToReport }) {
/* ── 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 });
+ saveInvestigation({ id: investigationId, scenario, situationGraph: nextGraph, selectedQuestion: normaliseUpdateSelectedQuestion(outcome.selectedQuestion), summary: currentUnderstanding, updatedAt: new Date().toISOString(), focusedContributions, findings: nextFindings, investigationReport, investigationRevision: nextRev });
} else {
setUpdateStatus("error");
setUpdateError(outcome);
diff --git a/docs/current-handoff.md b/docs/current-handoff.md
index 400a2d8..792c9da 100644
--- a/docs/current-handoff.md
+++ b/docs/current-handoff.md
@@ -381,7 +381,42 @@ Migrate existing application callers to the identity-aware contract signatures:
**Status:** UI/routes are **not** migrated. All existing callers continue via the singleton compatibility path (they call `saveInvestigation({ ... })` with no explicit second parameter, and their snapshots carry no `id` field). No production caller was modified in this increment.
-**Next restart point:** Caller migration — update application consumers to pass investigation ID through save calls so canonical identity-aware semantics activate for all writes.
+## v0.60e — Route-Owned Investigation Identity
+
+**Purpose:** Migrate the Investigation page route to own durable investigation identity via its route `[id]` segment, passing that ID through to ScenarioForm for hydration and persistence without migrating legacy singleton data or changing Portfolio/Report behaviour.
+
+### What was implemented
+
+| File | Change |
+|---|---|
+| `app/investigations/[id]/page.jsx` | Removed hardcoded `INVESTIGATION_ID = "case-1"` constant; route `[id]` param extracted as `routeId` via `params.id`; `loadInvestigation(routeId)` loads by route identity; `investigationId={routeId}` passed to ScenarioForm in both branch paths; report navigation uses `routeId`. |
+| `components/scenario-form.jsx` | Added `investigationId` prop; session restore calls `loadInvestigation(investigationId)` when provided; all 4 save call sites include `id: investigationId` in snapshot (autosave effect, start-case submit, update-case submit, report overview autosave). |
+
+### Contract crossings verified by deterministic test
+
+- **Owning test files:** `tests/storage/scenario-form-persistence.test.js` (11 tests) + `tests/storage/investigation-storage.test.js` (23 tests, including v0.60d canonical identity section)
+- All 34 tests pass on first run; no reruns required
+
+### Verified behaviour
+
+- Route ID is passed from `[id]/page.jsx` into ScenarioForm as `investigationId`: **YES**
+- Missing identified Investigation (`loadInvestigation("v060e-live")` → null) starts clean: **YES**
+- Legacy singleton fallback used: **NO** — no backward-compat load was needed; the route ID was absent from storage
+- First meaningful saved snapshot carries route ID: **YES** (all 4 save sites inject `id: investigationId`)
+- Subsequent save identity preserved: **YES** (storage layer uses `snapshot.id` as sole identity authority)
+- Storage provider changed: **NO** — only `investigation-storage.js` wrapper, already-proved in v0.60d
+
+### Portfolio
+
+Not migrated. `app/page.jsx` retains hardcoded `INVESTIGATION_ID = "case-1"`. Create New navigation to a durable-ID route is later work.
+
+### Report
+
+Not migrated. `app/investigations/[id]/report/page.jsx` untouched. Remains a later caller migration increment.
+
+### Restart
+
+Untouched in this increment. All `clearInvestigation()` calls remain without an id argument (legacy singleton path). If identity-aware restart is needed, the next increment should pass `investigationId` through those clear calls.
## Next implementation boundary