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
@@ -333,3 +333,101 @@ describe("investigation-storage v0.60c identity-aware", () => {
});
});
// ── v0.60d — canonical save identity contract (snapshot.id is sole authority) ─
describe("investigation-storage v0.60d canonical identity", () => {
function newId() {
return `inv-${Math.random().toString(36).slice(2, 9)}`;
}
// Import the application-facing wrapper (not the bare provider)
async function getWrapper() {
vi.resetModules();
const m = await import("../../lib/storage/investigation-storage.js");
return { load: m.loadInvestigation, save: m.saveInvestigation, clear: m.clearInvestigation };
}
beforeEach(() => {
globalThis.window.localStorage.clear();
});
// ── 1. saveInvestigation({ id: "inv-a", ... }) persists under inv-a ──
it("save with snapshot.id persists under that id key", async () => {
const { save } = await getWrapper();
const snap = makeSnapshot({ id: newId(), title: "Alpha" });
save(snap);
const raw = globalThis.window.localStorage.getItem(`confidence-engine-investigation:${snap.id}`);
expect(raw).not.toBeNull();
});
// ── 2. loadInvestigation("inv-a") returns inv-a ───────────────
it("save with snapshot.id and load by same id round-trips", async () => {
const { load, save } = await getWrapper();
const snap = makeSnapshot({ id: newId(), title: "Alpha" });
save(snap);
const loaded = load(snap.id);
expect(loaded).not.toBeNull();
expect(loaded.id).toBe(snap.id);
expect(loaded.title).toBe("Alpha");
});
// ── 3. provider does not allocate/change the ID ────────────────
it("saveInvestigation does not invent or replace snapshot.id", async () => {
const { save } = await getWrapper();
const snap = makeSnapshot({ id: "inv-audit-99", title: "Audit" });
save(snap);
const raw = globalThis.window.localStorage.getItem(`confidence-engine-investigation:${snap.id}`);
expect(JSON.parse(raw).id).toBe("inv-audit-99");
});
// ── 4. canonical save has no second competing identity authority ─
it("explicit id argument is ignored when snapshot has an id", async () => {
const { load, save } = await getWrapper();
const snap = makeSnapshot({ id: newId(), title: "Alpha" });
const fakeSecondId = "fake-override";
save(snap, fakeSecondId);
// Should NOT appear under fakeSecondId — identity comes from snapshot.id only
const fakeRaw = globalThis.window.localStorage.getItem(`confidence-engine-investigation:${fakeSecondId}`);
expect(fakeRaw).toBeNull();
});
// ── 5. A and B remain independently addressable ────────────────
it("save two identified investigations are independent", async () => {
const { load, save } = await getWrapper();
const idA = newId();
const idB = newId();
const snapA = makeSnapshot({ id: idA, title: "Alpha" });
const snapB = makeSnapshot({ id: idB, title: "Beta" });
save(snapA);
save(snapB);
expect((await load(idA)).id).toBe(idA);
expect((await load(idA)).title).toBe("Alpha");
expect((await load(idB)).id).toBe(idB);
expect((await load(idB)).title).toBe("Beta");
});
// ── 6. unknown ID remains null ────────────────────────────────
it("loadInvestigation(unknownId) returns null", async () => {
const { load } = await getWrapper();
expect(await load("non-existent-id-xyz")).toBeNull();
});
// ── 7. legacy singleton compatibility preserved ───────────────
it("unidentified snapshot falls back to CANONICAL_KEY for legacy callers", async () => {
const { load, save } = await getWrapper();
save(makeSnapshot({ legacy: true })); // no id on snapshot
const loaded = load(); // no id argument
expect(loaded).not.toBeNull();
expect(loaded.legacy).toBe(true);
});
});