refactor(confidence-engine): v0.60d clarify investigation storage identity

Replace bare re-export in investigation-storage.js with explicit wrapper
functions that own the canonical identity contract: snapshot.id is the sole
save identity authority. The provider never allocates or changes IDs.

7 new deterministic tests prove: identified snapshots persist under their
own id key, explicit competing id arguments are ignored, A/B remain
independently addressable, unknown IDs return null, and legacy singleton
compatibility is preserved for unmigrated callers.

No application callers modified. UI/routes not migrated.
This commit is contained in:
2026-09-03 18:29:00 +01:00
parent 8c85120b1c
commit 827411f254
3 changed files with 165 additions and 4 deletions
+34
View File
@@ -349,6 +349,40 @@ Migrate existing application callers to the identity-aware contract signatures:
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.
**Next restart point:** Caller migration — update application consumers to pass investigation ID through save calls so canonical identity-aware semantics activate for all writes.
## Next implementation boundary
Smallest next increment: implement the four-operation storage contract in `lib/storage/investigation-storage.js` as a re-export of a provider-backed interface whose signatures accept/return domain Investigation objects keyed by durable ID — without committing to any specific localStorage or database representation. This means defining the exported function signatures and the Investigation shape that flows through them, while deferring key scheme, row schema, and collection structure to a later implementation decision.