feat(confidence-engine): v0.60c identity-aware investigation storage
- loadInvestigation(id) selects by durable ID when provided, null for unknown - saveInvestigation(snapshot, id) persists under provider-chosen key derived from id - clearInvestigation(id) removes specific investigation by identity when provided - localStorage representation: confidence-engine-investigation:<durable-id> - Backward-compatible singleton path preserved for existing unmigrated callers - 6 new targeted tests proving two independently addressable Investigations
This commit is contained in:
@@ -4,6 +4,9 @@ const CANONICAL_KEY = "confidence-engine-investigation";
|
||||
const LEGACY_KEY = "confidence-engine-session";
|
||||
const SCHEMA_VERSION = 1;
|
||||
|
||||
// Multi-Investigation key prefix (v0.60c)
|
||||
const INVESTIGATION_PREFIX = "confidence-engine-investigation:";
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
function safeGet(storage, key) {
|
||||
@@ -20,16 +23,34 @@ function isPlainObject(value) {
|
||||
);
|
||||
}
|
||||
|
||||
// ── loadInvestigation ────────────────────────────────────────────────
|
||||
// ── loadInvestigation (identity-aware) ───────────────────────────────
|
||||
|
||||
/**
|
||||
* Returns the persisted investigation snapshot (normalised to current schema)
|
||||
* or null when no usable state exists.
|
||||
* Returns the persisted investigation snapshot keyed by durable id
|
||||
* (normalised to current schema), or null when no usable state exists.
|
||||
*
|
||||
* When called without an id argument, reads from the legacy singleton key
|
||||
* for backward-compatible consumers that have not yet migrated.
|
||||
*/
|
||||
export function loadInvestigation() {
|
||||
export function loadInvestigation(id) {
|
||||
const storage = _getTargetStorage();
|
||||
if (!storage) return null;
|
||||
|
||||
// — identity-aware contract: select by durable id —
|
||||
if (id != null) {
|
||||
const raw = safeGet(storage, `${INVESTIGATION_PREFIX}${id}`);
|
||||
if (raw === null) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (isPlainObject(parsed)) {
|
||||
if (!("schemaVersion" in parsed)) parsed.schemaVersion = SCHEMA_VERSION;
|
||||
return parsed;
|
||||
}
|
||||
} catch (_) { return null; }
|
||||
return null;
|
||||
}
|
||||
|
||||
// — backward-compatible singleton path (no id supplied) —
|
||||
// 1 – canonical key first
|
||||
let raw = safeGet(storage, CANONICAL_KEY);
|
||||
if (raw !== null) {
|
||||
@@ -64,36 +85,45 @@ export function loadInvestigation() {
|
||||
return parsed;
|
||||
}
|
||||
|
||||
// ── saveInvestigation ────────────────────────────────────────────────
|
||||
// ── saveInvestigation (identity-aware) ───────────────────────────────
|
||||
|
||||
/**
|
||||
* Persists the supplied snapshot to the canonical localStorage key.
|
||||
* Persists the supplied snapshot under its durable id key.
|
||||
* The caller's object is never mutated.
|
||||
*
|
||||
* When called without an id argument, writes to the legacy singleton key
|
||||
* for backward-compatible consumers that have not yet migrated.
|
||||
*/
|
||||
export function saveInvestigation(snapshot) {
|
||||
export function saveInvestigation(snapshot, id) {
|
||||
const storage = _getTargetStorage();
|
||||
if (!storage) return; // silently no-op in non-browser
|
||||
|
||||
try {
|
||||
const record = JSON.parse(JSON.stringify(snapshot));
|
||||
record.schemaVersion = SCHEMA_VERSION;
|
||||
_persist(storage, CANONICAL_KEY, JSON.stringify(record));
|
||||
const key = id != null ? `${INVESTIGATION_PREFIX}${id}` : CANONICAL_KEY;
|
||||
_persist(storage, key, JSON.stringify(record));
|
||||
} catch (_) { /* storage errors must not crash caller */ }
|
||||
}
|
||||
|
||||
// ── clearInvestigation ───────────────────────────────────────────────
|
||||
// ── clearInvestigation (identity-aware) ──────────────────────────────
|
||||
|
||||
/** Removes the canonical investigation key and the legacy session key. */
|
||||
export function clearInvestigation() {
|
||||
/**
|
||||
* Removes a specific investigation by durable id.
|
||||
* When called without an id, clears the legacy singleton keys only.
|
||||
*/
|
||||
export function clearInvestigation(id) {
|
||||
const storage = _getTargetStorage();
|
||||
if (!storage) return;
|
||||
try { storage.removeItem(CANONICAL_KEY); } catch (_) {}
|
||||
|
||||
// Also remove the legacy sessionStorage key so it cannot resurrect stale
|
||||
// state after loadInvestigation falls through from missing canonical key.
|
||||
const legacyStorage = _getLegacyStorage();
|
||||
if (!legacyStorage) return;
|
||||
try { legacyStorage.removeItem(LEGACY_KEY); } catch (_) {}
|
||||
if (id != null) {
|
||||
try { storage.removeItem(`${INVESTIGATION_PREFIX}${id}`); } catch (_) {}
|
||||
} else {
|
||||
try { storage.removeItem(CANONICAL_KEY); } catch (_) {}
|
||||
const legacyStorage = _getLegacyStorage();
|
||||
if (legacyStorage) {
|
||||
try { legacyStorage.removeItem(LEGACY_KEY); } catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── internals ────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user