From 642a969b1813767ee0225a7e2353c0478ea807f6 Mon Sep 17 00:00:00 2001 From: robbond Date: Mon, 7 Sep 2026 07:51:08 +0100 Subject: [PATCH] refactor(confidence-engine): compact current-handoff to operational snapshot; archive v0.61 experiment history to ch19 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reduce docs/current-handoff.md from 2003 → 185 lines (90% reduction) - Move all initial-decomposition v0.61 experiment narrative to docs/archive/experiments/vol-1-chapters/ch19/initial-decomposition-v0.61.md - Update design-evolution/README.md with ch19 Era 8 entry - Add CURRENT MVP DIRECTION section (frozen; OpenAI investigation is next question) - Fix stale branch reference in current-project-state.md - Update Return-to-Work Summary to reflect v0.61 completion - Update Verification Marker for v0.60/v0.61 status --- .../ch19/initial-decomposition-v0.61.md | 159 ++ docs/current-handoff.md | 2042 +---------------- docs/current-project-state.md | 17 +- docs/design-evolution/README.md | 6 + 4 files changed, 287 insertions(+), 1937 deletions(-) create mode 100644 docs/archive/experiments/vol-1-chapters/ch19/initial-decomposition-v0.61.md diff --git a/docs/archive/experiments/vol-1-chapters/ch19/initial-decomposition-v0.61.md b/docs/archive/experiments/vol-1-chapters/ch19/initial-decomposition-v0.61.md new file mode 100644 index 0000000..191949d --- /dev/null +++ b/docs/archive/experiments/vol-1-chapters/ch19/initial-decomposition-v0.61.md @@ -0,0 +1,159 @@ +# Ch19 — Initial Decomposition Hardening (v0.61) + +**Status: COMPLETE (frozen)** +**Branch:** `feature/initial-decomposition-v0.61` +**Final HEAD:** `5878ce4` experiment(confidence-engine): add reconstruction-only helper flag + +## v0.61 Objective + +How stable is the initial semantic decomposition of the same fixed scenario across repeated runs using the same model, prompt version and production API path? + +## Fixed manufacturing scenario (used throughout) + +``` +I run a small manufacturing business. Customer complaints have risen by 35% over the last six months, while production volume increased by 40%. + +Most complaints mention late delivery or minor product defects, but our complaint categories changed when we introduced a new CRM tagging system three months ago. During the same period we also changed one supplier and introduced a weekend production shift. + +I am deciding whether to spend about £120,000 on automated quality inspection now or wait until we understand whether there is actually a quality problem. + +I do not yet know the complaint rate per unit, whether defect rates differ by shift or supplier, or whether the new tagging system changed what gets counted as a complaint. +``` + +## v0.61.3 — Matched Provider Reconstruction Evidence Checkpoint + +**Status:** DOCUMENTATION-ONLY CHECKPOINT +**Purpose:** Record six matched reconstruction-comparison observations (Qwen + Terra) against the canonical manufacturing fixture before closing out. No live model calls. No production changes. + +### Production prompt / schema frozen +- **Prompt:** `reconstruct-v0.5` (production default) +- **Rule:** Rule 5a +- **Salience check:** semantic-preservation +- **Schema:** canonical reconstruction schema +- **Path:** reconstruction-only experiment path + +### Qwen / Ollama evidence (3 matched observations) + +| Channel | Finding | +|---|---| +| A — complaints +35%, production +40%, complaint-rate-per-unit uncertainty | semantically stable | +| B — CRM tagging / complaint-count comparability uncertainty | semantically stable | +| C — late-delivery versus minor-product-defect distinction | meaning preserved, representation varied | +| D1 — supplier-related defect uncertainty | meaning preserved, but supplier and shift frequently recombined | +| D2 — weekend-shift-related defect uncertainty | meaning preserved, but supplier and shift frequently recombined | +| E — intervention-fit dependency | core dependency preserved, graph topology varied | + +**Important observed pattern:** compound supplier/shift unknown appeared in all three Qwen runs. + +### Terra / OpenAI evidence (3 matched observations) + +| Channel | Finding | +|---|---| +| A — complaints +35%, production +40% | stable | +| B — CRM comparability | stable | +| C — late-delivery vs defects | semantically stable and separately represented in all three | +| D1 — supplier-related defect uncertainty | stable | +| D2 — weekend-shift-related defect uncertainty | stable | +| E — intervention-fit dependency | core dependency stable | + +All three Terra runs: supplier transition distinct=YES, weekend-shift distinct=YES, compound supplier/shift=NO, unsupported expansion=NO, causal strengthening=NO. + +### Cross-provider conclusion + +> Exact graph topology is not stable for either provider and should not be treated as a reconstruction invariant. The more important invariant is preservation and independent recoverability of supplied distinctions and uncertainties. + +> Terra showed stronger stability in preserving independently investigable C and D1/D2 distinctions as separate graph structures. Qwen preserved their meaning but more frequently compressed them into combined representations. + +## v0.61 Experiment 5 — Repeated Same-Input Initial Decomposition Stability + +**Status:** INCOMPLETE — five runs required; obtained four successful plus one validation failure. + +### Execution +- **Route:** POST /api/cases/start +- **Requests made:** 5 (4 successful, 1 validation failure) +- **Model:** `qwen-claude:latest` +- **Prompt version:** v0.2 + +### Structure Range + +| | Nodes | Unknowns | Assumptions | +|---|---|---|---| +| Run 1 | 16 | 3 | 3 | +| Run 2 | 20 | 7 | 2 | +| Run 3 | 21 | 3 | 3 | +| Run 4 | 14 | 3 | 2 | + +Unknown count: 3–7. Assumption count: 2–3. Total node count: 14–21. + +### Semantic Channel Frequency (Runs 1–4) + +``` +A Normalisation: 4/4 present, 0/4 partial, 0/4 absent +B CRM comparability: 4/4 present, 0/4 partial, 0/4 absent +C Delivery vs defects: 1/4 present, 3/4 partial, 0/4 absent +D Supplier/shift/source: 4/4 present, 0/4 partial, 0/4 absent +E Intervention fit: 1/4 present, 0/4 partial, 3/4 absent +``` + +### Key findings + +- Run 2 introduced speculative subdivisions not grounded in source text (customer segment variation, QC team capability shifts, responsibility distribution impact, batch-size/equipment utilization correlation). +- No run contained steering language, unsupported causal claims, premature arithmetic conclusions, or action recommendations. +- The £120k intervention was represented neutrally in all runs. + +### Conclusion + +Initial decomposition represents core factual uncertainty channels (normalisation, CRM comparability, supplier/shift ambiguity) to varying degrees. Primary instability: intervention-fit mapping and speculative subdivision risk. Graph topology varies materially (14–21 nodes). + +## Focused-deconstruction plumbing fix (historical) + +**Root cause:** focused route passed full provider envelope `{ response, providerApiPath, providerExecution }` into `validateFocusedDeconstructSchema()`. Validator expected semantic fields at top level but they lived on `wrapper.response`, not the wrapper. All six fields appeared absent → structured-output 502. + +**Fix:** Route extracts `const deconstruction = wrapper.response` and passes inner object to validation and serialization. Provider diagnostics preserved but do not interfere with semantic fields. + +**Tests:** `tests/focused-deconstruct-boundary.test.js` — 48 focused-investigation-boundary tests pass on first run. Previous two pre-fix semantic runs remain invalid as semantic evidence. + +### Post-fix repeatability (3 controlled Qwen/Ollama runs) + +| Metric | Result | +|---|---| +| Qwen/Ollama calls | 3 | +| HTTP 200 | 3/3 | +| schema valid | 3/3 | +| supplier evidence preservation | 3/3 PASS | +| weekend-shift uncertainty preservation | 3/3 PASS | +| epistemic separation | 3/3 PASS | +| unsupported inference | 0/3 | +| steering | 0/3 | + +**Product interpretation:** For the fixed compound supplier/weekend-shift case, Qwen's initial compression did not prevent focused-investigation stage from repeatedly recovering the epistemic distinction once substantive user evidence was supplied. This supports tolerating some initial representation compression when meaning survives downstream. + +This is **not yet generalised to a production invariant.** + +## v0.61 — Closed experiment boundaries + +| Boundary | Status | +|---|---| +| Repeated-same-input decomposition experiments | FROZEN | +| Qwen/Terra reconstruction comparison | FROZEN | +| Initial prompt refinement | FROZEN | +| Supplier/weekend-shift decomposition experiments | FROZEN | +| Relationship-preservation experiments | FROZEN | +| Causal-fidelity experiments | FROZEN | + +## v0.4 initial reconstruction — implementation status + +**Status:** COMPLETE — deterministic tests pass, one live smoke accepted +**Prompt:** `prompts/reconstruct-v0.4.md` (replaced by v0.5 in production default) +**Tests:** 89/89 passed on first run, zero reruns + +Key principles proven: provenance-preserving decomposition, relationship preservation, explicit stop boundary, interpretation separation. All documented and verified. + +## Git + +- **Documentation commit:** `docs(confidence-engine): record focused deconstruction repeatability` +- **Working tree:** clean after this session's commit + +--- + +*This chapter records the initial-decomposition v0.61 experiment line. The line is frozen for the current MVP stage. See `docs/current-handoff.md` §CURRENT MVP DIRECTION.* diff --git a/docs/current-handoff.md b/docs/current-handoff.md index 305b5a6..168ff53 100644 --- a/docs/current-handoff.md +++ b/docs/current-handoff.md @@ -3,205 +3,96 @@ > **Role:** Concise operational snapshot for resuming work today. Not a historical diary. > The design evolution archive index at `docs/design-evolution/README.md` provides progressive loading of experiment history; load the relevant chapter only when a specific historical question requires it. +## CURRENT MVP DIRECTION + +Initial-decomposition hardening is frozen for the current MVP stage. + +Do not resume: +- repeated-same-input reconstruction experiments +- Qwen/Terra reconstruction comparison +- initial prompt refinement +- supplier/weekend-shift decomposition experiments +- relationship-preservation experiments +- causal-fidelity experiments + +unless new end-to-end user-flow evidence reopens one of those boundaries. + +**Immediate next evidence question:** + +Can the real browser UI run an entire investigation using OpenAI/Terra for every LLM stage, without any stage silently reverting to the default Ollama/Qwen provider? + +If YES, the next live experiment is one timed/costed OpenAI UI investigation measuring: +- user-visible latency +- OpenAI call count +- token usage where available +- approximate cost per investigation + ## Repository checkpoint -- **Branch:** `feature/investigation-report-v0.55` -- **HEAD:** `37a9a12` — route design evolution provenance through archive index -- **Working tree:** will be clean after v0.59a commit +- **Branch:** `feature/initial-decomposition-v0.61` +- **HEAD:** `5878ce4` — experiment(confidence-engine): add reconstruction-only helper flag +- **Working tree:** clean after this session's commit -## v0.5 initial reconstruction relationship contract +## Initial reconstruction — current status -- v0.5 first-class initial reconstruction relationship contract implemented. -- Reconstruction relationships use explicit `fromId`/`toId` references. -- Declared relationships project deterministically to existing SituationGraph edges. -- Invalid relationship references are skipped rather than inferred or repaired. -- SituationGraph schema unchanged; v0.5 is the production default. -- Targeted deterministic suite passed; live v0.5 semantic/reliability validation remains pending. -- 502 structured-output reliability remains a separate unresolved issue. +**Semantically stable enough for current MVP stage.** Exact graph topology is not stable and is not treated as an invariant. Trust-critical meaning must remain stable. Some compression is acceptable when meaning survives downstream. Missing meaning cannot be faithfully recovered downstream. Causal hypotheses must remain visibly provisional. -## Initial reconstruction observability +Current production default: `reconstruct-v0.5` prompt + canonical reconstruction schema + Zod validation via `z.toJSONSchema()`. -- Successful `/api/cases/start` responses now expose the validated initial `reconstruction`, the exact object used by `buildInitialGraph()`. -- Semantic experiments can compare `reconstruction.relationships` directly with projected `e-rel-*` SituationGraph edges; this is observability only and does not change reasoning semantics. -- Owning deterministic tests pass. Ten broader orchestrator failures were observed and classified as unrelated to this increment, but were not proven pre-existing because non-mutating baseline execution was blocked by the existing Vitest apparatus. -- No live validation has been rerun against this observability boundary. Next restart point: exactly one production-default `/api/cases/start` call using the fixed manufacturing scenario, comparing reconstruction relationship E directly with its projected graph edge. +The `/api/cases/start` route returns validated initial reconstruction, situation graph, and selected question. Observability seam exposes the exact object used by `buildInitialGraph()` for comparison. -## v0.5 relationship direction contract +**Frozen:** initial decomposition, prompt refinement, Qwen/Terra comparison — see CURRENT MVP DIRECTION above. -- Directly observable v0.5 live validation proved first-class relationship declaration and deterministic projection work end-to-end, while exposing one `may_cause` directional inconsistency between endpoints and description. -- v0.5 now defines relationships literally as `fromId → relationship → toId`: directional semantics are explicit for `depends_on`, `causes`, `may_cause`, `supports`, and `weakens`; `compares_with` may use either endpoint order. -- The read-back rule requires relationship type, endpoint direction, and description to agree. Provenance and interpretation-separation rules remain unchanged. -- Deterministic tests prove the instruction exists, not that the model obeys it. No live call has tested this refinement; next restart point is live directional-integrity validation before broader repeatability testing. +## Focused investigation — current status -## Unexplained-transition projection coverage +Focused deconstruction plumbing fixes are complete: +- Schema mismatch resolved (focused route now supplies its own `focusedDeconstructJsonSchema`) +- Provider envelope no longer leaks into validator (inner `.response` unwrapped correctly) +- All 48 focused-investigation-boundary tests pass on first run -- Starting checkpoint: `e183914`. The latest one-call v0.5 validation emitted two reconstruction relationships: one directionally clean relationship projected, while the other (`r1`) was semantically/directionally incoherent and lost because it referenced an `unexplainedTransition` without graph projection. -- `unexplainedTransitions` now project as unresolved transition nodes and register their source IDs for existing relationship projection; the relationship architecture itself was not redesigned. -- The semantic coherence of live `r1` remains a separate unresolved reasoning issue; intervention-fit E was absent in that run and remains unresolved. No live validation has occurred after this deterministic fix. +Focused deconstruction receives only: +- `centralStatement` +- `targetLabel` +- `targetDescription` +- `question` +- `answer` -## Intervention-fit dependency preservation +Full SituationGraph / original scenario / previous findings are **not** supplied to that route. This is intentional epistemic separation. -- First-class relationship generation and deterministic projection now work end-to-end; the latest live run projected all three declared relationships correctly. -- The remaining semantic weakness was substitution: narrower decision prerequisites were preserved, but the full supplied intervention-fit dependency was not. v0.5 now requires preservation of the user's actual dependency level; narrower evidence prerequisites may coexist but may not replace that higher-order contingency. -- No schema, builder, or orchestrator change was required. Deterministic tests prove this instruction exists only; live compliance remains untested. Next restart point: one live intervention-fit validation, followed by bounded repeatability if it passes. +Repeatability: supplier/weekend-shift epistemic separation repeated 3/3 on the fixed case after plumbing fix. Previous pre-fix semantic runs remain invalid (contaminated by provider-envelope misuse + wrong transport schema). -## Reconstruction validation failure observability +## Canonical experiment apparatus — currently valid -- The latest one-call intervention-fit validation returned HTTP 502: a real structured-output product failure, so intervention-fit semantic compliance was not evaluable. -- Previous diagnostics flattened the failure to `reconstruction: Required` and truncated raw evidence. Failure responses now retain exact validation issue paths, codes, messages, available native metadata, and complete diagnostic raw response at the existing validation boundary. -- Reasoning prompt, schema acceptance, and provider behaviour remain unchanged; no live calls occurred in this increment. Next restart point: one production-default manufacturing call—evaluate E if it succeeds, or use the new evidence to diagnose the exact structured-output defect if it fails. +``` +scripts/start-case-experiment-helper.cjs — canonical helper (tsx runtime) +tsx runtime — alias-capable experiment execution +--file — file-input mode (JSON fixture) +--reconstruction-only — stop after initial reconstruction, no question generation +import-only proof — prove startCase() import chain resolves without live calls +OpenAI reconstruction experiment seam — START_CASE_EXPERIMENT_PROVIDER=openai (experiment-only) +focused-deconstruction schema fix — plumbing verified, 48/48 tests pass +``` -## Relationship endpoint reference integrity - -- The latest valid live run semantically preserved intervention-fit E in relationship `r5` with correct direction, but `r5.fromId` referenced a semantic-unit ID never emitted, so the non-inferential builder correctly skipped projection. -- This is a generation-time relationship-reference integrity defect. v0.5 now requires semantic units before relationships and exact reuse of emitted endpoint IDs; legitimate missing endpoint concepts must be materialised first under existing provenance rules. -- The builder remains unchanged. Live compliance remains untested; next restart point is one production-default relationship-reference-integrity validation. - -## Case-start error logging - -- The latest live validation was blocked by an HTTP 500 route-level exception with generic client output. `/api/cases/start` now writes structured Next.js server-console diagnostics for every route-owned error response: 4xx at warning level and 5xx/502 at error level. -- Caught exceptions log their actual exception, message, and stack while the client response remains generic; successful responses are not logged. No reasoning, status, or response semantics changed. -- Next restart point: one production-default manufacturing call while observing the Next.js dev-server terminal. - -## Chat capability detection - -- Direct POST `/api/chat` was proven supported. The false-negative cause was the capability probe using model `dummy-check`, which conflated model availability with endpoint capability. -- The probe now uses the configured model; live behaviour after this fix remains untested. - -## Failed provider-path observability - -- The detector fix remains unverified live; a subsequent HTTP 500 left the actually attempted provider path unobservable. -- Case-start failure diagnostics now preserve `providerApiPath` only when the provider reports an endpoint actually attempted during that request. The next step remains one production call. - -## Successful provider-path observability - -- The 502 live run at `dabd9e2` was PATH NOT OBSERVABLE ON FAILURE: provider path survived provider errors but not successful-provider/later-validation failures. -- Successful provider results now retain their factual attempted path through later reconstruction-validation failure diagnostics. This corrects only that observability seam; the next unknown remains one real `/api/cases/start` call establishing `/api/chat` versus `/api/generate`. - -## Zod 4 migration - -- Zod upgraded from 3.25.76 to 4.5.4. Reconstruction and direct Zod-owner suites pass, and native `z.toJSONSchema()` converts the production reconstruction schema with required closed enum constraints. -- Zod-3-specific error-message assertions are version-independent while retaining semantic validation evidence. Production schema semantics are unchanged and no third-party JSON-schema conversion dependency was added. -- Reconstruction `/api/chat` now sends `z.toJSONSchema(reconstructionV2Schema)` rather than generic `format: "json"`; the same canonical Zod contract constrains generation and remains the post-hoc validator. -- The deterministic provider-boundary test passes 2/2 under Node. A direct configured Ollama/Qwen `/api/chat` call accepted and obeyed the full schema in one call; `/api/generate`, chat detection, fallback, prompt, temperature, retries, and schema semantics are unchanged. -- Production repeatability remains untested after this change. Next restart point: a small repeated `/api/cases/start` stability observation using the fixed manufacturing scenario. - -## Reconstruction fallback diagnostics - -- Schema-constrained reconstruction `/api/chat` remains active. A later manual production 502 came through `/api/generate`; its unconstrained response omitted required transition confidence fields and failed Zod validation. -- Provider execution diagnostics now distinguish chat skipped due capability state, chat attempted and failed before generate fallback, and successful chat without fallback. They are deterministically verified through `/api/cases/start`; endpoint selection and fallback behaviour remain unchanged. -- Next restart point: one observation-only fixed-scenario production call to identify why `/api/generate` is reached. - -## Chat capability body disposal - -- A fresh-process debugger observation proved capability `/api/chat` returned HTTP 200, then invalid `res.body?.consume()` threw and the catch incorrectly cached `_chatSupported = false`. -- This skipped schema-constrained reconstruction `/api/chat` and used unconstrained `/api/generate`. Response-body disposal now uses Fetch-compatible consumption without changing capability or fallback semantics. -- Next boundary: one fresh-process fixed-scenario production observation. - -## Reconstruction chat timeout - -- Capability detection/body disposal was fixed at `7070342`. Fresh runtime evidence then proved the real schema-constrained `/api/chat` request was attempted but Confidence Engine aborted it after 60 seconds while Qwen was still generating. -- Fallback `/api/generate` completed but produced structurally invalid reconstruction. The real reconstruction `/api/chat` timeout is now 300 seconds, matching `/api/generate`; fallback, schema, prompt, and reasoning semantics are unchanged. -- Next boundary: one fresh-process fixed-scenario production observation. - -## Rule 5a outcome-category baseline - -- Five consecutive post-provider-fix production runs completed without 500/502, materially improving structural provider/schema stability. -- C — the late-delivery versus product-defect distinction — remains the clearest repeated semantic weakness. Rule 5a is now isolated as the first semantic experiment variable; unrelated prompt formatting changes were removed from the experimental diff. -- No live calls occurred in this increment. Next experiment may add a separate pre-output semantic preservation self-check on top of this clean baseline. - -## Outcome-preservation salience variant - -- `5c9f94c` established the clean Rule 5a baseline. C — the late-delivery versus product-defect distinction — remains the targeted semantic weakness. -- This increment adds only a final semantic-preservation self-check, testing instruction salience rather than missing semantic specification. No live model calls were made. -- Next boundary: one manual fixed-scenario Postman production run, scoring C first while checking A/B/D1/D2/E and unsupported expansion or causal strengthening for regressions. - -## OpenAI reconstruction-provider experiment apparatus - -- Structural Ollama/provider issues were previously fixed; current semantic comparison now warrants testing model-compliance variance. -- An experiment-only OpenAI Responses API provider apparatus exists for `gpt-5.6-terra`, using the same current v0.5 prompt and canonical reconstruction schema. Production provider selection remains Ollama/Qwen. -- No live OpenAI calls have occurred. Next boundary: a bounded live semantic comparison, not production migration. - -## Reconstruction provider injection seam - -- OpenAI provider apparatus exists at `860ee6f`; the canonical helper was previously blocked because `startCase()` and `analyseScenario()` created providers internally. -- An optional reconstruction-provider injection seam now carries an experiment provider through the same production analysis path. Production callers still default to configured Ollama/Qwen through `getProvider()`. -- No live calls occurred. Next boundary: exactly one live OpenAI fixed-scenario run through the canonical helper; this is not a production provider migration. - -## Canonical helper alias-capable runtime - -- Plain Node could not resolve repository `@/` aliases, and no plain-Node alias runtime existed. `tsx` is now a dev-only experiment runtime; the canonical command is `npm run experiment:start-case`. -- Its import-only proof succeeded with `startCaseResolved: true`: the real `startCase()` dependency chain resolves and no provider call occurs. Production runtime and the Ollama/Qwen default remain unchanged. -- Next boundary: exactly one live GPT-5.6 Terra reconstruction through this canonical helper. - -## OpenAI CLI experiment selection - -- The canonical helper now exposes experiment-only OpenAI selection through `START_CASE_EXPERIMENT_PROVIDER=openai`, reusing the existing OpenAI provider constructor and provider-injection seam. Its default behavior is unchanged. -- Import-only mode remains inert, production provider selection remains Ollama/Qwen, and no live calls occurred. Next boundary: exactly one live OpenAI call using the fixed manufacturing scenario. - -## OpenAI strict reconstruction transport apparatus - -- The first real OpenAI request reached Responses API but failed before inference with `invalid_json_schema`; the first reported incompatibility was optional `unexplainedTransitions[].entity` absent from `required`. -- OpenAI strict transport projection now derives structure from canonical Zod-generated JSON Schema and canonical input optionality from the Zod contract, including `.optional().default(...)`. Canonically omittable fields are required-but-nullable for transport; null placeholders are omitted only for those fields before canonical Zod validation, while genuinely required fields remain non-nullable. -- `reconstructionV2Schema` and Ollama behavior remain unchanged. `START_CASE_EXPERIMENT_PROVIDER=openai` is experiment-only selection; default behavior remains Ollama/Qwen. Deterministic closeout passed with zero live calls; next boundary is exactly one live GPT-5.6 Terra reconstruction. - -## Reconstruction-only OpenAI observation seam - -- The first Terra observation was blocked before execution because normal `startCase()` would continue into Ollama-backed question generation. No Terra inference occurred. -- An experiment-only reconstruction-only seam now returns after initial reconstruction and graph construction, before `determineGraphBackedQuestion`. Normal production/default `startCase()` behavior remains unchanged; explicit OpenAI helper selection guarantees one selected reconstruction provider call and zero downstream question-generation calls. -- Zero live calls occurred while implementing and verifying this seam. Next boundary: exactly one live Terra reconstruction with no retry. - -## OpenAI Responses text extraction - -- The first reconstruction-only Terra request reached inference successfully, but native-fetch parsing relied on SDK-only `output_text` and could not extract the raw response. The provider now reads documented `output[].content[].output_text` parts in response order while retaining the convenience-property path. -- No tool-call assumption was introduced; canonical schema, prompt, transport compatibility, and reasoning remain unchanged. Zero live calls occurred during this correction; next boundary is exactly one live Terra reconstruction with no retry and no Ollama call. - -## Focused-deconstruction structured-output transport - -- Confirmed mismatch: the focused route requested and validated its six-field deconstruction contract while Ollama `/api/chat` was constrained to the initial reconstruction schema. -- The focused route now supplies `focusedDeconstructJsonSchema`; `generateReconstruction()` accepts it as an optional chat-format argument, while initial reconstruction callers retain the default `reconstructionJsonSchema`. -- Provider tests prove default and alternate schema transport. Focused route tests model the real provider wrapper, preserve `wrapper.response` unwrapping, and verify the schema argument without module/mock-state contamination. -- Previous failed live focused-deconstruction observations remain invalid semantic evidence. Zero live calls occurred during implementation and apparatus correction. Next boundary: exactly one substantive compound-answer live observation through the corrected production route. +**No other experimental apparatus is currently valid.** Obsolete setups have been removed or superseded. ## Current product architecture -Three distinct routes, not a single page: +Three distinct routes: ``` / → Portfolio (notebook index) -/investigations/case-1 → Investigation (working case/pages) -/investigations/case-1/report → Investigation Report (readable derived summary) +/investigations/{id} → Investigation (working case/pages) +/investigations/{id}/report → Investigation Report (derived summary) ``` -**Portfolio = investigator notebook index.** Shows the single canonical persisted investigation card with actions: *View report*, *Continue investigation*, *Restart investigation*. Below the card: *+ Create new investigation* (portfolio-level, not inside the card). +**Portfolio:** investigation collection with actions per card (View report, Continue investigation, Restart). "+ Create new investigation" allocates durable ID via `crypto.randomUUID()` + navigates. -**Investigation = working case.** Contains `ScenarioForm` + `ReasoningWorkspace`. Handles graph reasoning, focused investigation turns, Done/Re-open semantics, Current Understanding synthesis. No Report presentation — that is owned by the dedicated Report route. +**Investigation:** `ScenarioForm` + `ReasoningWorkspace`. Handles focused turns, Done/Re-open semantics, Current Understanding synthesis. -**Report = derived artefact.** Renders persisted `investigationReport` snapshot. Generation is on-demand, triggered by the Report page itself (not ReasoningWorkspace). Exactly one `/api/cases/overview` call on first visit; zero calls on subsequent visits. The Report is not canonical reasoning evidence — it is a derived summary for review/export/use. +**Report:** rendered persisted `investigationReport` snapshot. On-demand generation (one `/api/cases/overview` on first visit; zero on subsequent). Not canonical reasoning evidence — derived artefact. -## Current working product journey +## Evidence discipline invariants -``` -Portfolio (/) - → "Continue investigation" - → Investigation page (/investigations/case-1) - → Focused question asked → user answers → Done for now - → Current Understanding synthesizes - → If zero Open Questions: "Review current understanding" appears - OR → "Review current understanding" - → Report page (/investigations/case-1/report) - → Generates via /api/cases/overview (once) - → Persists investigationReport - → Subsequent visits render persisted snapshot (zero calls) -``` - -### Restart flow - -Portfolio card *Restart investigation* → confirmation dialog (title: "Restart this investigation?") → destructive second confirmation → `clearInvestigation()` (canonical localStorage clear seam). No direct storage-key manipulation. - -## Current reasoning / ownership invariants - -**Evidence discipline:** ``` RAW USER EVIDENCE ≠ MODEL-DERIVED CONTRIBUTION SEMANTICS @@ -211,1793 +102,84 @@ RAW USER EVIDENCE ≠ TURN CONTEXT / PROVENANCE ``` -**Finding dispositions:** `null` (eligible working premise), `agree` (user-endorsed), `not_relevant` (excluded from eligible reasoning, provenance retained), corrected Finding. User disposition never directly mutates authoritative graph state. +**Finding dispositions:** `null` (eligible working premise), `agree` (user-endorsed), `not_relevant` (excluded from eligible reasoning, provenance retained). User disposition never directly mutates authoritative graph state. -**Evidence distinctions preserved by the persistence layer.** Contributions preserve: question/context, verbatim answer, model observations, uncertainties, assumptions, relationships, follow-ups, target/provenance, sequence/order. Findings preserve: canonical proposition, sourceObservation, contributionId, originatingTargetNodeId, userDisposition. +## Persistence -### Focused investigation presentation ownership (v0.52) +- **Owner:** `lib/storage/providers/local-storage.js` (`saveInvestigation` / `loadInvestigation`) +- **Key prefix:** `confidence-engine-investigation:` +- **Storage contract:** `lib/storage/investigation-storage.js` (application-facing boundary) +- **Identity:** durable `id` allocated by application, not storage +- **First persistence:** when user produces meaningful state (scenario submitted), not on create-click +- **Restart:** preserves container/id/scenario; clears reasoning/report state -`FocusedQuestionBody` derives a thread-local subset (`targetNodeId || originatingTargetNodeId`) for every presentation surface. Previously answered content does NOT bleed from one question to another. Verified manually and by targeted Vitest. +## MVP boundaries -### Empty Done + Re-open semantics (v0.53) - -- Empty Done is valid: parks/resolves the question locally, does NOT invoke episode processing, does NOT produce `no_episodic_content` 400. -- Re-open returns the question to Open Questions and removes from `doneForNowIds`. -- Older stale development localStorage states (pre-v0.53 shape) may be discarded during current dev phase. No migration required. - -### v0.59a — Investigation revision provenance rule - -- Investigation has a semantic revision (`investigationRevision`). -- Meaningful persisted Investigation changes advance it by exactly 1. -- Persistence activity itself (autosave, save) does NOT advance revision. -- Report generation records `generatedFromRevision: investigationRevision`. -- Report generation advances revision by 0. -- Equal revisions mean Report reflects current Investigation. -- Different revisions mean the Investigation has changed since Report generation. -- Existing Report remains available (not invalidated). -- Report update/regeneration remains manual (user-triggered). -- Restart clears Investigation + Report via `clearInvestigation()` + `setInvestigationRevision(0)`. -- Report history/comparison remains deferred beyond MVP. - -### v0.59b — Report freshness UI on the Report page - -- Report page shows `Current` (matching revisions) or `Update available` (differing revisions). -- Manual `Update report` action on Report page triggers regeneration via `/api/cases/overview`. -- Duplicate prevention guard during in-flight update. -- No automatic regeneration. -- Explanation copy: "The investigation has changed since this report was generated." - -### v0.59c — Report freshness state surfaced on the Portfolio - -- Portfolio surfaces `Current` / `Update available` alongside existing View report link. -- Derives freshness solely from revision provenance (`investigationReport.generatedFromRevision === investigationRevision`). -- Zero model calls during Portfolio render. -- No mutation of Investigation. -- No `Update report` action on the Portfolio — manual Report updating remains owned by the Report page. -- If no Report, neither freshness state is shown. - -**Semantic transitions that advance revision:** -``` -Episode Done (with content) → +1 -Re-open (resolved → unknown) → +1 -Finding proposition correction → +1 -Eligible → not_relevant → +1 -not_relevant → eligible (restore) → +1 -First meaningful change → =1 (from initial 0) -``` - -**Transitions that do NOT advance revision:** -``` -Autosave → 0 -Report generation → 0 -Hydration → 0 (reads value) -Empty proposition correction (no-op) → 0 -null → null disposition (no-op) → 0 -``` - -### Zero Open Questions milestone (v0.51) - -When all unknowns are resolved and clarified questions exist: "You've now worked through all of the questions we surfaced. Would you like to see an overview of what we understand so far?" with "Review current understanding" button. This occupies the former Open Questions position. The invitation is a milestone, not a readiness/completion judgement. - -### Current Understanding refresh invariants - -Reconstruct CU when canonical meaning or eligible evidence set changes — NOT when investigation/question status changes alone. Re-open ≠ change what we understand; Finding correction / Not Relevant / completed episode = change what we understand. - -## Persistence and Report ownership - -- **Canonical persistence owner:** `lib/storage/providers/local-storage.js` (`saveInvestigation` / `loadInvestigation`). All routes read from the same snapshot. -- **Report generation owner:** Report page only (NOT ReasoningWorkspace, NOT Investigation page). -- **localStorage key:** `confidence-engine-investigation` (single canonical key — multi-investigation not yet implemented). -- **Temporary identity:** `case-1`. True multi-investigation persistence/identity is future work. -- **Portfolio client hydration:** Portfolio page uses `'use client'` — initial pre-hydration empty state ≠ absence of persisted data. Always wait for hydrated semantic controls before classifying state. - -## MVP boundaries (v0.59b) - -**Implemented in MVP:** -- visible Report freshness state (`Current` / `Update available`) -- explanation that the Investigation has changed since Report generation -- manual `Update report` action (user-triggered) -- no automatic regeneration -- duplicate prevention guard during in-flight update +**Implemented:** +- Multi-investigation via localStorage with durable IDs +- Report freshness (`Current` / `Update available`) derived from revision comparison +- Manual `Update report` action +- Confirmation-gated restart +- No automatic regeneration +- Duplicate prevention guard **Deferred beyond MVP:** -- Report history -- retaining multiple Reports -- Report comparison -- modelling/preview comparison between Investigation revisions +- Search, tag, archive, group within Portfolio +- Export/copy Reports to external document +- Report history / comparison +- Multi-provider production routing (OpenAI/Terra comparison is frozen) -## Current development / verification constraints +## Current development constraints - Canonical dev server at `http://localhost:3000`. Never start/stop/restart/probe it. If unavailable → BLOCKED and stop. -- Playwright MCP: use **Run Playwright code** with semantic locators for known controls. Snapshot refs (`[ref=...]`) are observational only. +- Playwright MCP: use **Run Playwright code** with semantic locators. Snapshot refs are observational only. - For async/hydration states: use `waitFor({ state: 'visible', timeout })` — not arbitrary sleeps. - If a prescribed semantic locator cannot find its control → STOP. No fallback to CSS/XPath/DOM traversal. -- Live freeze: once Playwright verification begins, no production file edits until evidence is captured and classified. -- **Tests are instruments, not product truth.** At first deterministic failure: classify PRODUCT vs APPARATUS failure, then stop. Do not enter test-harness repair loops. -- Mocked boundary ownership: if a lower-layer function is mocked, test the value crossing the seam — do not require the mock to reproduce real implementation. +- Live freeze: once Playwright verification begins, no production file edits until evidence is captured. +- **Tests are instruments, not product truth.** At first deterministic failure: classify PRODUCT vs APPARATUS, then stop. -## Current limitations / genuinely open boundaries +## Current genuinely open boundaries **Not yet implemented:** - - Multi-investigation portfolio (search/tag/archive/group) -- Durable investigation identities beyond `case-1` -- Export/copy of Reports to Jira or external document -- Portfolio expansion beyond one canonical investigation -- Report history / comparison +- Export/copy of Reports to external document +- Production multi-provider routing (Ollama/Qwen default remains) -**Known boundaries:** +**Known limitations:** +- Current Understanding and Investigation Report are architecturally distinct artefacts. Report interpretations remain explicit, not evidence. +- The `≤5` processing bound was experimental apparatus constraint, NOT product requirement. -- Current Understanding and Investigation Report are architecturally distinct artefacts. Plausible interpretations in the Report remain explicitly interpretive, not evidence. -- 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. +## HISTORICAL — initial-decomposition v0.61 status -## v0.60 — Multi-Investigation Storage Contract Decision +Initial decomposition experiments (v0.61) ran four successful runs plus one validation failure against the same manufacturing scenario. Key findings: -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. +- **Consistently preserved across all runs:** normalisation uncertainty, CRM comparability, supplier/shift ambiguity +- **Variable:** late-delivery vs defect distinction (1 PRESENT, 3 PARTIAL) +- **Rarely surfaced:** intervention-fit uncertainty (1 PRESENT, 3 ABSENT) +- **Run 2 introduced speculative subdivisions** not grounded in source text +- No steering language, unsupported causality, or action recommendations in any run +- Graph topology varies materially (14–21 nodes) across runs — this is expected, not a defect -### 1. Contract ownership +Qwen/Terra matched evidence showed Terra with stronger stability on C and D1/D2 distinctions; Qwen preserved meaning but compressed more frequently. Both providers vary graph topology while preserving core dependencies. -**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 — resolved in v0.60b - -Four semantic operations. `createEmptyInvestigation()` does **not** belong to the storage contract (resolved below). - -| # | 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 | `saveInvestigation(investigation)` | full snapshot (with `id` already present) | void | Autosave, Report generation, all writes | -| 4 | `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. - ---- - -### 3b. Investigation creation ownership — v0.60b resolution - -**Contract correction decision.** The v0.60a contract contained a contradiction: Section 2 stated "ID allocation owner: Application" while Section 3 listed `createEmptyInvestigation()` as a storage operation producing `{ id: string }`. These are incompatible models (application-owned identity vs. storage-owned creation). They cannot both be true. - -**Evidence from current product:** - -1. **Create new behaviour today:** Portfolio's "+ Create new investigation" is a `` — a static navigation link with zero ID allocation and zero storage call. It navigates to the Investigation route which calls `loadInvestigation()`, receives null (no persisted data), and shows an empty ScenarioForm state. No empty Investigation is persisted on click. - -2. **First meaningful persistence event:** `scenario-form.jsx` line 681 — after `submitScenarioForStartCase` returns successfully. The user has selected a scenario, submitted it, and the application receives a structured result. Only then does `saveInvestigation()` persist with real data (scenario + situationGraph + investigationRevision: 1). This is a domain-driven persistence boundary, not an identity-driven one. - -3. **No product requirement for empty-persisted Investigations:** There is no current product behaviour that creates, displays, or expects empty persisted Investigations in Portfolio. The concept of persisting an abandoned start as a visible Portfolio item was asserted in v0.60a without product evidence. - -4. **No product requirement for storage to allocate identity:** Current code uses hardcoded `INVESTIGATION_ID = "case-1"` in application code with no storage involvement in ID generation or allocation. - -**Decision: MODEL A wins — application-owned identity, deferred persistence.** - -- **ID allocation owner:** Application layer (not storage contract). -- **When allocated:** On user click of "+ Create new investigation", before navigation. -- **First persistence:** When the user produces meaningful Investigation state (scenario submitted) and `saveInvestigation(investigation)` is called — not on create-new click. -- **Empty abandoned Investigation persisted:** NO. No durable Investigation exists until meaningful state triggers save. -- **Storage allocates identity:** NO. - -`createEmptyInvestigation()` does **not** belong in the storage contract. It never did — it was inconsistent with Section 2's "Application (not storage)" decision. The creation flow lives entirely at the application layer: app allocates durable ID → navigates to `/investigations/{id}` → user produces state → `saveInvestigation(investigation)` persists. - -**Create new investigation flow (corrected):** - -1. User clicks "+ Create new investigation" on Portfolio. -2. Application allocates a durable immutable ID (library/algorithm deferred). -3. Application navigates to `/investigations/{id}`. -4. User fills out ScenarioForm → submits → first meaningful `saveInvestigation(investigation)` persists the Investigation under its already-known ID. The Investigation becomes listable in Portfolio at that point. - -**Investigation properties (unchanged from v0.60a):** - -- Carries durable id: YES -- ID immutable: YES -- ID exists before first persistence: YES - -**Restart decision retained:** YES — identity preserved, reasoning/report state cleared.** - -### 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" → application allocates durable immutable ID (library/algorithm deferred) → navigates to `/investigations/{id}` → Investigation page loads, `loadInvestigation(id)` returns null (no persisted state yet) → ScenarioForm renders in empty-start mode → user fills scenario and submits → first meaningful state is captured → `saveInvestigation(investigation)` persists the full snapshot under its already-known ID → Investigation becomes listable in Portfolio. No empty Investigation is persisted at creation time. - -### 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. - -## v0.60c — Identity-Aware Investigation Storage Implementation - -**First production implementation increment of v0.60.** Bounded capability: two independently addressable Investigations can be persisted and loaded through the storage contract. - -### What was implemented - -- `loadInvestigation(id)` — accepts optional durable ID; selects by that identity when provided; returns `null` for unknown IDs -- `saveInvestigation(investigation, id)` — accepts optional durable ID; persists under provider-chosen key derived from `id`; does NOT allocate or replace the supplied ID -- `clearInvestigation(id)` — accepts optional durable ID; removes specific investigation by identity when provided - -### Representation chosen (internal to localStorage provider) - -Each Investigation is stored as a separate top-level localStorage key: -``` -confidence-engine-investigation: -``` -e.g. `confidence-engine-investigation:inv-abc123` - -This was the smallest sufficient representation because: -- Direct key lookup provides O(1) per-investigation access without needing an index -- No generic repository layer required — each key is independently addressable by its durable ID -- The two-Investigation proof requires independent storage and retrieval, which this achieves with zero indexes or aggregation - -Representation is **not exposed** to callers — the contract does not reveal localStorage key structure. - -### Two-Investigation proof results (deterministic test) - -| Invariant | Result | -|---|---| -| saveInvestigation(A) persists A under A.id | ✅ PASS | -| saveInvestigation(B) persists B independently under B.id | ✅ PASS | -| saving B does not overwrite A | ✅ PASS | -| loadInvestigation(A.id) returns A | ✅ PASS | -| loadInvestigation(B.id) returns B | ✅ PASS | -| loadInvestigation(unknownId) returns null | ✅ PASS | -| saveInvestigation() does not invent/change supplied ID | ✅ PASS | - -### Singleton compatibility seam - -- Existing consumers call `loadInvestigation()` and `clearInvestigation()` **without** an id argument — these continue via the legacy singleton path (canonical key + sessionStorage fallback) -- No caller was refactored in this increment -- The compatibility seam is: functions accept optional second parameter; when absent, behaviour matches pre-v0.60c singleton semantics -- `case-1` is **NOT** introduced as canonical identity — it exists only in existing consumer route URLs and legacy data - -### Legacy migration - -- No legacy singleton → new durable ID migration policy was invented -- Existing `case-1` localStorage data continues to be served by the backward-compatible path -- Migration of existing consumers to the new identity-aware calls is deferred to a later increment - -### Production files changed - -| File | Purpose | -|---|---| -| `lib/storage/providers/local-storage.js` | Identity-aware storage contract implementation | -| `tests/storage/investigation-storage.test.js` | 6 new targeted tests for v0.60c invariants | - -### Next restart point - -Migrate existing application callers to the identity-aware contract signatures: -1. `[id]/page.jsx` — pass route `[id]` to `loadInvestigation(id)` -2. `[id]/report/page.jsx` — pass route `[id]` to `loadInvestigation(id)` and `saveInvestigation(..., id)` -3. `scenario-form.jsx` — pass investigation ID through save calls -4. `app/page.jsx` — migrate Portfolio to use `listInvestigations()` (next increment) - -## v0.60d — Canonical Save Identity Contract - -**Problem:** `investigation-storage.js` was a bare re-export (`export { ... } from "./providers/local-storage.js"`). It did not own semantic contract — whatever signatures the provider exposed were what consumers received. The provider accepted an independent explicit `id` argument that could silently override or compete with `snapshot.id`. - -**Decision:** `investigation-storage.js` now owns the canonical identity contract. It wraps the provider with explicit semantics: - -- Canonical save identity comes solely from `investigation.id`; -- `investigation-storage.js` owns application-facing semantics; -- localStorage provider remains implementation detail (never allocated ID, never changed it); -- Any remaining singleton compatibility behaviour is explicitly temporary — for unmigrated callers only. - -**Implementation:** - -| Module | Role | -|---|---| -| `lib/storage/investigation-storage.js` | Application-facing boundary — owns identity contract | -| `saveInvestigation(snapshot, explicitId)` | Uses `snapshot.id` as sole save key when present; falls back to singleton path for unidentified legacy snapshots | -| `loadInvestigation(id)` | Passes raw id to provider (identity-aware) or singleton path (legacy) | -| `lib/storage/providers/local-storage.js` | Concrete localStorage implementation — unchanged, representation remains private | - -**Test:** 7 new deterministic tests in `tests/storage/investigation-storage.test.js` under describe block "v0.60d canonical identity" prove: - -1. Identified snapshot persists under its own id key (not CANONICAL_KEY) -2. Load by same id round-trips correctly -3. Provider does not invent or replace the supplied ID -4. Explicit competing id argument is silently ignored when snapshot has an id -5. Two identified investigations (A/B) remain independently addressable -6. Unknown ID returns null -7. Unidentified legacy snapshots still fall back to CANONICAL_KEY - -**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. - -## 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. - -## v0.60f — Allocate Investigation Identity on Create New - -**Purpose:** Replace the Portfolio's "+ Create new investigation" static link (`/investigations/case-1`) with an application-owned durable ID allocation that navigates to `/investigations/{id}` without persisting any empty Investigation. - -### What was implemented - -| File | Change | -|---|---| -| `app/page.jsx` | Changed "+ Create new investigation" from `` to a `