diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 63ecd33..ff74810 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -178,6 +178,72 @@ Reconstruct CU when canonical meaning or eligible evidence set changes — NOT w - The `≤5` processing bound observed during development was an experimental apparatus constraint, NOT a product requirement. Six Open Questions surfacing is legitimate product output, not a formulation defect. - Evidence discipline: what proves useful in live experimentation must be captured at provenance level, not as test diary entries that become operational constraints. +## v0.60 — Multi-Investigation Storage Contract Decision + +Completed on branch `feature/multi-investigation-storage-v0.60`. Bounded architecture/contract decision task following v0.60 audit. Zero production changes. Documentation-only decision checkpoint. + +### 1. Contract ownership + +**Decision:** `lib/storage/investigation-storage.js` is the application-facing persistence boundary. It defines the contract whose semantics (not just signatures) are immutable across provider implementations. Consumers import only from this module. + +`lib/storage/providers/local-storage.js` remains one concrete provider implementation behind the contract, not an application import target. + +**Invariant:** No consumer imports `providers/local-storage` directly. + +### 2. Durable Investigation identity + +**Decision:** Canonical Investigation carries a durable `id` field. + +| Property | Value | +|---|---| +| ID allocation owner | Application (not storage contract) | +| Allocation timing | On create new, before any persistence call | +| Mutable after creation | NO | +| Route `[id]` represents same identity | YES — direct round-trip: app sets → route reads → storage uses | + +### 3. Minimum contract operations + +Five semantic operations. No speculative additions. + +| # | Operation | Input | Output | Product use | +|---|---|---|---|---| +| 1 | `listInvestigations()` | — | `InvestigationSummary[]` (lightweight) | Portfolio renders index/list | +| 2 | `loadInvestigation(id)` | durable ID string | `Investigation \| null` | Investigation/Report page hydration | +| 3 | `createEmptyInvestigation()` | — | `{ id: string }` | "+ Create new investigation" | +| 4 | `saveInvestigation(id, investigation)` | ID + full snapshot | void | Autosave, Report generation, all writes | +| 5 | `restartInvestigation(id)` | durable Investigation ID | void (reasoning state cleared) | "Restart this investigation" | + +**Explicitly rejected:** search, pagination, sorting, sync, merge, transactions, history, report versions, sharing, permissions, accounts. No existing product behaviour requires them. + +### 4. Portfolio listing returns lightweight metadata + +`listInvestigations()` returns summaries, not full snapshots. Minimum information: +- `id` — navigation target +- `scenario` (or title framing) — card text +- `updatedAt` — freshness display +- `investigationRevision` — revision metadata +- Report existence and `generatedFromRevision` — "Current" / "Update available" indicator + +Full Investigation loads separately via `loadInvestigation(id)` on navigation. + +### 5. Restart = reset reasoning state, NOT remove + +Restart clears reasoning state (findings, questions, report) within the Investigation but preserves the Investigation's durable identity and container. The Investigation remains visible in Portfolio listing as a named item with cleared state. + +A separate "Delete investigation" UI is not required by current MVP. Contract-level `removeInvestigation(id)` is acceptable for future use but has no product owner yet. + +### 6. Create new investigation flow + +User clicks "+ Create new investigation" → app allocates durable ID (crypto.randomUUID or equivalent, library choice deferred) → persist an empty Investigation under that ID immediately → navigate to `/investigations/{id}` → existing Investigations untouched. Empty Investigation is persisted on creation so it appears in Portfolio; user can return later or abandon with container remaining visible. + +### 7. localStorage ≠ contract + +The storage contract accepts/returns domain-level Investigation objects keyed by `id`. How each provider maps those to its storage mechanism is implementation detail. Contract does not dictate key scheme, row schema, or collection structure. + +### 8. Synchronization separate + +Storage contract = persistence only. Not synchronization. No sync-specific fields (syncStatus, remoteId, dirtyFlags, lastSyncedAt) belong in the Investigation shape during this increment. Decisions here do not prevent future sync — the Investigation object carries its own durable ID sufficient for identity resolution. + ## Next restart point Consult `docs/design-evolution/README.md` for progressive loading of product reasoning and provenance chronology; load the relevant chapter only when a specific historical question requires it.