feat(confidence-engine): v0.60g1 list investigation summaries

Recover to clean v0.60f then implement only the storage listing contract.

- Add listInvestigations() to provider: enumerate by prefix, project lightweight summary (id, scenario, updatedAt, investigationRevision, reportExists, reportGeneratedFromRevision), sort by updatedAt desc
- Add application-facing wrapper in investigation-storage.js
- Add 8 deterministic tests covering all listing invariants (coexistence, correct IDs, lightweight projection, legacy exclusion, unrelated exclusion, independent update, ordering, malformed skip)
- Fix MockStorageMap WebStorage API compatibility (.length + .key(i))
- Portfolio NOT migrated — that is v0.60g2
This commit is contained in:
2026-09-04 06:45:26 +01:00
parent 4b55ad1eae
commit 7ba1771bcf
4 changed files with 298 additions and 21 deletions
+9 -1
View File
@@ -2,7 +2,7 @@
// Owns the canonical identity contract: snapshot.id is the sole save identity.
// Delegates to the concrete localStorage provider internally.
import { loadInvestigation as _load, saveInvestigation as _save, clearInvestigation as _clear } from "./providers/local-storage.js";
import { loadInvestigation as _load, saveInvestigation as _save, clearInvestigation as _clear, listInvestigations as _list } from "./providers/local-storage.js";
/**
* Canonical save contract: snapshot.id is the sole identity authority.
@@ -32,3 +32,11 @@ export function loadInvestigation(id) {
export function clearInvestigation(id) {
return _clear(id ?? undefined);
}
/**
* Lists all durable-ID Investigation records as lightweight summaries.
* Excludes legacy singleton, sessionStorage state, unrelated storage, malformed entries.
*/
export function listInvestigations() {
return _list();
}
+49
View File
@@ -126,6 +126,55 @@ export function clearInvestigation(id) {
}
}
// ── listInvestigations (durable-ID only) ───────────────────────
/**
* Enumerates all durable-ID Investigation records and returns lightweight summaries.
* Skips malformed entries; excludes legacy singleton, sessionStorage state, unrelated keys.
*/
export function listInvestigations() {
const storage = _getTargetStorage();
if (!storage) return [];
const summaries = [];
for (let i = 0; i < storage.length; i++) {
const key = storage.key(i);
if (!key || !key.startsWith(INVESTIGATION_PREFIX)) continue;
try {
const raw = safeGet(storage, key);
if (!raw) continue;
const record = JSON.parse(raw);
if (!isPlainObject(record)) continue;
if (!record.id) continue;
summaries.push({
id: record.id,
scenario: record.scenario ?? null,
updatedAt: record.updatedAt ?? null,
investigationRevision: record.investigationRevision ?? 0,
reportExists: !!record.investigationReport,
reportGeneratedFromRevision: record.investigationReport
? record.investigationReport.generatedFromRevision ?? null
: null,
});
} catch (_) {
// Malformed durable-ID entry — skip silently
}
}
summaries.sort((a, b) => {
const ta = a.updatedAt ?? "";
const tb = b.updatedAt ?? "";
if (ta > tb) return -1;
if (ta < tb) return 1;
return a.id > b.id ? -1 : 1;
});
return summaries;
}
// ── internals ────────────────────────────────────────────────────────
function _persist(storage, key, value) {