Files
rohitg00--agentmemory/test/diagnostics.test.ts
T
wehub-resource-sync 979fb22d7c
CI / test (20, macos-latest) (push) Waiting to run
CI / test (20, ubuntu-latest) (push) Waiting to run
CI / test (22, macos-latest) (push) Waiting to run
CI / test (22, ubuntu-latest) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:01:18 +08:00

869 lines
29 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
vi.mock("../src/logger.js", () => ({
logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn() },
}));
import { registerDiagnosticsFunction } from "../src/functions/diagnostics.js";
import type {
Action,
ActionEdge,
DiagnosticCheck,
Lease,
Sentinel,
Sketch,
Signal,
Session,
Memory,
MeshPeer,
} from "../src/types.js";
import { KV } from "../src/state/schema.js";
function mockKV() {
const store = new Map<string, Map<string, unknown>>();
return {
get: async <T>(scope: string, key: string): Promise<T | null> => {
return (store.get(scope)?.get(key) as T) ?? null;
},
set: async <T>(scope: string, key: string, data: T): Promise<T> => {
if (!store.has(scope)) store.set(scope, new Map());
store.get(scope)!.set(key, data);
return data;
},
delete: async (scope: string, key: string): Promise<void> => {
store.get(scope)?.delete(key);
},
list: async <T>(scope: string): Promise<T[]> => {
const entries = store.get(scope);
return entries ? (Array.from(entries.values()) as T[]) : [];
},
};
}
function mockSdk() {
const functions = new Map<string, Function>();
return {
registerFunction: (idOrOpts: string | { id: string }, handler: Function) => {
const id = typeof idOrOpts === "string" ? idOrOpts : idOrOpts.id;
functions.set(id, handler);
},
registerTrigger: () => {},
trigger: async (idOrInput: string | { function_id: string; payload: unknown }, data?: unknown) => {
const id = typeof idOrInput === "string" ? idOrInput : idOrInput.function_id;
const payload = typeof idOrInput === "string" ? data : idOrInput.payload;
const fn = functions.get(id);
if (!fn) throw new Error(`No function: ${id}`);
return fn(payload);
},
};
}
function makeAction(overrides: Partial<Action> = {}): Action {
return {
id: `act_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
title: "Test action",
description: "",
status: "pending",
priority: 5,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
createdBy: "agent-1",
tags: [],
sourceObservationIds: [],
sourceMemoryIds: [],
...overrides,
};
}
function makeLease(overrides: Partial<Lease> = {}): Lease {
return {
id: `lease_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
actionId: "act_missing",
agentId: "agent-1",
acquiredAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 60_000).toISOString(),
status: "active",
...overrides,
};
}
function makeEdge(overrides: Partial<ActionEdge> = {}): ActionEdge {
return {
id: `ae_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
type: "requires",
sourceActionId: "src",
targetActionId: "tgt",
createdAt: new Date().toISOString(),
...overrides,
};
}
function makeSentinel(overrides: Partial<Sentinel> = {}): Sentinel {
return {
id: `sen_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
name: "Test sentinel",
type: "timer",
status: "watching",
config: {},
createdAt: new Date().toISOString(),
linkedActionIds: [],
...overrides,
};
}
function makeSketch(overrides: Partial<Sketch> = {}): Sketch {
return {
id: `sk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
title: "Test sketch",
description: "",
status: "active",
actionIds: [],
createdAt: new Date().toISOString(),
expiresAt: new Date(Date.now() + 60_000).toISOString(),
...overrides,
};
}
function makeSignal(overrides: Partial<Signal> = {}): Signal {
return {
id: `sig_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
from: "agent-1",
type: "info",
content: "test",
createdAt: new Date().toISOString(),
...overrides,
};
}
function makeSession(overrides: Partial<Session> = {}): Session {
return {
id: `ses_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
project: "test",
cwd: "/tmp",
startedAt: new Date().toISOString(),
status: "active",
observationCount: 0,
...overrides,
};
}
function makeMemory(overrides: Partial<Memory> = {}): Memory {
return {
id: `mem_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
type: "fact",
title: "Test memory",
content: "content",
concepts: [],
files: [],
sessionIds: [],
strength: 1,
version: 1,
isLatest: true,
...overrides,
};
}
function makePeer(overrides: Partial<MeshPeer> = {}): MeshPeer {
return {
id: `peer_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`,
url: "http://localhost:3111",
name: "Test peer",
status: "connected",
sharedScopes: [],
...overrides,
};
}
describe("Diagnostics Functions", () => {
let sdk: ReturnType<typeof mockSdk>;
let kv: ReturnType<typeof mockKV>;
beforeEach(() => {
sdk = mockSdk();
kv = mockKV();
registerDiagnosticsFunction(sdk as never, kv as never);
});
describe("mem::diagnose", () => {
it("empty system passes all checks", async () => {
const result = (await sdk.trigger("mem::diagnose", {})) as {
success: boolean;
checks: DiagnosticCheck[];
summary: { pass: number; warn: number; fail: number; fixable: number };
};
expect(result.success).toBe(true);
// 15 = 8 original (actions, leases, sentinels, sketches, signals,
// sessions, memories, mesh) + 6 added in #lesson-visibility
// (lessons, summaries, semantic, procedural, crystals, insights) +
// 1 added in #memory-project-scope (memory-project-coverage).
expect(result.summary.pass).toBe(15);
expect(result.summary.warn).toBe(0);
expect(result.summary.fail).toBe(0);
expect(result.summary.fixable).toBe(0);
expect(result.checks.every((c) => c.status === "pass")).toBe(true);
});
it("active action with no lease produces warn", async () => {
const action = makeAction({ status: "active" });
await kv.set(KV.actions, action.id, action);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["actions"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("active-no-lease:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("warn");
expect(check!.fixable).toBe(false);
});
it("blocked action with all deps done produces fail (fixable)", async () => {
const dep = makeAction({ status: "done" });
const blocked = makeAction({ status: "blocked" });
const edge = makeEdge({
sourceActionId: blocked.id,
targetActionId: dep.id,
type: "requires",
});
await kv.set(KV.actions, dep.id, dep);
await kv.set(KV.actions, blocked.id, blocked);
await kv.set(KV.actionEdges, edge.id, edge);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["actions"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("blocked-deps-done:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("pending action with unsatisfied deps produces fail (fixable)", async () => {
const dep = makeAction({ status: "active" });
const pending = makeAction({ status: "pending" });
const edge = makeEdge({
sourceActionId: pending.id,
targetActionId: dep.id,
type: "requires",
});
await kv.set(KV.actions, dep.id, dep);
await kv.set(KV.actions, pending.id, pending);
await kv.set(KV.actionEdges, edge.id, edge);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["actions"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("pending-unsatisfied-deps:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("expired active lease produces fail (fixable)", async () => {
const action = makeAction({ status: "active" });
const lease = makeLease({
actionId: action.id,
status: "active",
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.actions, action.id, action);
await kv.set(KV.leases, lease.id, lease);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["leases"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("expired-lease:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("orphaned lease (action gone) produces fail (fixable)", async () => {
const lease = makeLease({
actionId: "act_gone",
status: "active",
expiresAt: new Date(Date.now() + 60_000).toISOString(),
});
await kv.set(KV.leases, lease.id, lease);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["leases"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("orphaned-lease:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("expired watching sentinel produces fail (fixable)", async () => {
const sentinel = makeSentinel({
status: "watching",
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.sentinels, sentinel.id, sentinel);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["sentinels"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("expired-sentinel:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("sentinel referencing missing action produces warn", async () => {
const sentinel = makeSentinel({
linkedActionIds: ["act_nonexistent"],
});
await kv.set(KV.sentinels, sentinel.id, sentinel);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["sentinels"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("sentinel-missing-action:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("warn");
expect(check!.fixable).toBe(false);
});
it("expired active sketch produces fail (fixable)", async () => {
const sketch = makeSketch({
status: "active",
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.sketches, sketch.id, sketch);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["sketches"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("expired-sketch:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("expired signal produces fail (fixable)", async () => {
const signal = makeSignal({
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.signals, signal.id, signal);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["signals"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("expired-signal:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("active session older than 24h produces warn", async () => {
const session = makeSession({
status: "active",
startedAt: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(),
});
await kv.set(KV.sessions, session.id, session);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["sessions"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("abandoned-session:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("warn");
expect(check!.fixable).toBe(false);
});
it("memory with stale isLatest produces fail (fixable)", async () => {
const oldMemory = makeMemory({ isLatest: true });
const newMemory = makeMemory({ supersedes: [oldMemory.id] });
await kv.set(KV.memories, oldMemory.id, oldMemory);
await kv.set(KV.memories, newMemory.id, newMemory);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["memories"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("memory-stale-latest:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("fail");
expect(check!.fixable).toBe(true);
});
it("memory superseding non-existent produces warn", async () => {
const memory = makeMemory({ supersedes: ["mem_gone"] });
await kv.set(KV.memories, memory.id, memory);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["memories"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("memory-missing-supersedes:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("warn");
expect(check!.fixable).toBe(false);
});
it("stale mesh peer produces warn", async () => {
const peer = makePeer({
lastSyncAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
});
await kv.set(KV.mesh, peer.id, peer);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["mesh"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("stale-peer:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("warn");
expect(check!.fixable).toBe(false);
});
it("error mesh peer produces warn", async () => {
const peer = makePeer({ status: "error" });
await kv.set(KV.mesh, peer.id, peer);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["mesh"],
})) as { checks: DiagnosticCheck[] };
const check = result.checks.find((c) =>
c.name.startsWith("error-peer:"),
);
expect(check).toBeDefined();
expect(check!.status).toBe("warn");
expect(check!.fixable).toBe(false);
});
it("filters by categories", async () => {
const action = makeAction({ status: "active" });
await kv.set(KV.actions, action.id, action);
const signal = makeSignal({
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.signals, signal.id, signal);
const result = (await sdk.trigger("mem::diagnose", {
categories: ["signals"],
})) as { checks: DiagnosticCheck[] };
expect(result.checks.every((c) => c.category === "signals")).toBe(true);
expect(
result.checks.some((c) => c.category === "actions"),
).toBe(false);
});
});
describe("mem::heal", () => {
it("unblocks stuck blocked action", async () => {
const dep = makeAction({ status: "done" });
const blocked = makeAction({ status: "blocked", title: "Stuck task" });
const edge = makeEdge({
sourceActionId: blocked.id,
targetActionId: dep.id,
type: "requires",
});
await kv.set(KV.actions, dep.id, dep);
await kv.set(KV.actions, blocked.id, blocked);
await kv.set(KV.actionEdges, edge.id, edge);
const result = (await sdk.trigger("mem::heal", {
categories: ["actions"],
})) as { success: boolean; fixed: number; details: string[] };
expect(result.success).toBe(true);
expect(result.fixed).toBe(1);
expect(result.details[0]).toContain("Unblocked");
const updated = await kv.get<Action>(KV.actions, blocked.id);
expect(updated!.status).toBe("pending");
});
it("blocks pending action with unsatisfied deps", async () => {
const dep = makeAction({ status: "active" });
const pending = makeAction({
status: "pending",
title: "Should be blocked",
});
const edge = makeEdge({
sourceActionId: pending.id,
targetActionId: dep.id,
type: "requires",
});
await kv.set(KV.actions, dep.id, dep);
await kv.set(KV.actions, pending.id, pending);
await kv.set(KV.actionEdges, edge.id, edge);
const result = (await sdk.trigger("mem::heal", {
categories: ["actions"],
})) as { success: boolean; fixed: number; details: string[] };
expect(result.success).toBe(true);
expect(result.fixed).toBe(1);
expect(result.details[0]).toContain("Blocked");
const updated = await kv.get<Action>(KV.actions, pending.id);
expect(updated!.status).toBe("blocked");
});
it("expires stale lease and resets action", async () => {
const action = makeAction({
status: "active",
assignedTo: "agent-1",
});
const lease = makeLease({
actionId: action.id,
agentId: "agent-1",
status: "active",
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.actions, action.id, action);
await kv.set(KV.leases, lease.id, lease);
const result = (await sdk.trigger("mem::heal", {
categories: ["leases"],
})) as { success: boolean; fixed: number; details: string[] };
expect(result.success).toBe(true);
expect(result.fixed).toBe(1);
expect(result.details[0]).toContain("Expired lease");
const updatedLease = await kv.get<Lease>(KV.leases, lease.id);
expect(updatedLease!.status).toBe("expired");
const updatedAction = await kv.get<Action>(KV.actions, action.id);
expect(updatedAction!.status).toBe("pending");
expect(updatedAction!.assignedTo).toBeUndefined();
});
it("deletes orphaned lease", async () => {
const lease = makeLease({
actionId: "act_gone",
status: "released",
expiresAt: new Date(Date.now() + 60_000).toISOString(),
});
await kv.set(KV.leases, lease.id, lease);
const result = (await sdk.trigger("mem::heal", {
categories: ["leases"],
})) as { success: boolean; fixed: number; details: string[] };
expect(result.success).toBe(true);
expect(result.fixed).toBe(1);
expect(result.details[0]).toContain("Deleted orphaned lease");
const deleted = await kv.get<Lease>(KV.leases, lease.id);
expect(deleted).toBeNull();
});
it("expires stale sentinel", async () => {
const sentinel = makeSentinel({
status: "watching",
name: "Stale watcher",
expiresAt: new Date(Date.now() - 60_000).toISOString(),
});
await kv.set(KV.sentinels, sentinel.id, sentinel);
const result = (await sdk.trigger("mem::heal", {
categories: ["sentinels"],
})) as { success: boolean; fixed: number; details: string[] };
expect(result.success).toBe(true);
expect(result.fixed).toBe(1);
expect(result.details[0]).toContain("Expired sentinel");
const updated = await kv.get<Sentinel>(KV.sentinels, sentinel.id);
expect(updated!.status).toBe("expired");
});
it("dry run reports but does not fix", async () => {
const dep = makeAction({ status: "done" });
const blocked = makeAction({ status: "blocked", title: "Stuck task" });
const edge = makeEdge({
sourceActionId: blocked.id,
targetActionId: dep.id,
type: "requires",
});
await kv.set(KV.actions, dep.id, dep);
await kv.set(KV.actions, blocked.id, blocked);
await kv.set(KV.actionEdges, edge.id, edge);
const result = (await sdk.trigger("mem::heal", {
categories: ["actions"],
dryRun: true,
})) as { success: boolean; fixed: number; details: string[] };
expect(result.success).toBe(true);
expect(result.fixed).toBe(1);
expect(result.details[0]).toContain("[dry-run]");
const unchanged = await kv.get<Action>(KV.actions, blocked.id);
expect(unchanged!.status).toBe("blocked");
});
});
describe("per-store tally categories (#lesson-visibility)", () => {
it("lessons category: passes with valid live lessons + ignores tombstoned", async () => {
await kv.set(KV.lessons, "lsn_live", {
id: "lsn_live", content: "x", context: "", confidence: 0.8,
reinforcements: 0, source: "manual", sourceIds: [], tags: [],
createdAt: "", updatedAt: "", decayRate: 0.05,
});
await kv.set(KV.lessons, "lsn_tomb", {
id: "lsn_tomb", content: "x", context: "", confidence: 0.5,
reinforcements: 0, source: "manual", sourceIds: [], tags: [],
createdAt: "", updatedAt: "", decayRate: 0.05, deleted: true,
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["lessons"],
})) as { checks: DiagnosticCheck[] };
const ok = result.checks.find((c) => c.name === "lessons-ok");
expect(ok?.status).toBe("pass");
expect(ok?.message).toMatch(/All 1 lessons.*1 tombstoned/);
});
it("lessons category: warns on out-of-range confidence", async () => {
await kv.set(KV.lessons, "lsn_bad", {
id: "lsn_bad", content: "x", context: "", confidence: 1.5,
reinforcements: 0, source: "manual", sourceIds: [], tags: [],
createdAt: "", updatedAt: "", decayRate: 0.05,
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["lessons"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("lesson-bad-confidence:"));
expect(warn?.status).toBe("warn");
});
it("summaries category: warns on missing title", async () => {
await kv.set(KV.summaries, "ses_1", {
sessionId: "ses_1", project: "p", createdAt: "", title: "",
narrative: "n", keyDecisions: [], filesModified: [], concepts: [],
observationCount: 1,
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["summaries"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("summary-missing-title:"));
expect(warn?.status).toBe("warn");
});
it("procedural category: warns on empty steps", async () => {
await kv.set(KV.procedural, "proc_1", {
id: "proc_1", name: "noop", steps: [], triggerCondition: "x",
frequency: 1, sourceSessionIds: [], strength: 0.5,
createdAt: "", updatedAt: "",
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["procedural"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("procedural-empty-steps:"));
expect(warn?.status).toBe("warn");
});
it("crystals category: warns on empty narrative", async () => {
await kv.set(KV.crystals, "cry_1", {
id: "cry_1", narrative: "", keyOutcomes: [], filesAffected: [],
lessons: [], sourceActionIds: [], createdAt: "",
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["crystals"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("crystal-empty-narrative:"));
expect(warn?.status).toBe("warn");
});
it("insights category: warns on out-of-range confidence", async () => {
await kv.set(KV.insights, "ins_bad", {
id: "ins_bad", title: "t", content: "c", confidence: -0.1,
reinforcements: 0, sourceConceptCluster: [], sourceMemoryIds: [],
sourceLessonIds: [], sourceCrystalIds: [], tags: [],
createdAt: "", updatedAt: "", decayRate: 0.05,
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["insights"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("insight-bad-confidence:"));
expect(warn?.status).toBe("warn");
});
it("semantic category: warns on out-of-range confidence", async () => {
await kv.set(KV.semantic, "sem_bad", {
id: "sem_bad", fact: "f", confidence: 2.0, sourceSessionIds: [],
sourceMemoryIds: [], accessCount: 0, lastAccessedAt: "",
strength: 0, createdAt: "", updatedAt: "",
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["semantic"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("semantic-bad-confidence:"));
expect(warn?.status).toBe("warn");
});
it("categories filter accepts new categories and skips others", async () => {
const result = (await sdk.trigger("mem::diagnose", {
categories: ["lessons", "summaries"],
})) as { checks: DiagnosticCheck[] };
expect(result.checks.every((c) => c.category === "lessons" || c.category === "summaries")).toBe(true);
expect(result.checks.some((c) => c.category === "lessons")).toBe(true);
expect(result.checks.some((c) => c.category === "summaries")).toBe(true);
});
describe("defensive row-shape handling (CodeRabbit #473 review)", () => {
it("NaN/Infinity confidence on a lesson is flagged as warn, not silently passed", async () => {
await kv.set(KV.lessons, "lsn_nan", {
id: "lsn_nan", content: "x", context: "", confidence: NaN,
reinforcements: 0, source: "manual", sourceIds: [], tags: [],
createdAt: "", updatedAt: "", decayRate: 0.05,
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["lessons"],
})) as { checks: DiagnosticCheck[] };
const warn = result.checks.find((c) => c.name.startsWith("lesson-bad-confidence:"));
expect(warn?.status).toBe("warn");
});
it("non-string summary title doesn't throw — surfaces as warn", async () => {
await kv.set(KV.summaries, "ses_bad_title", {
sessionId: "ses_bad_title",
project: "p",
createdAt: "",
title: null as unknown as string, // simulate corrupted row
narrative: "n",
keyDecisions: [],
filesModified: [],
concepts: [],
observationCount: 1,
});
// The bug to guard against: the old code called .trim() unconditionally,
// which throws on null/number, which aborts the whole diagnose run and
// any later category check never executes. Verify diagnose completes
// AND surfaces the bad row.
const result = (await sdk.trigger("mem::diagnose", {
categories: ["summaries", "lessons"],
})) as { checks: DiagnosticCheck[]; success?: boolean };
expect(result.success).toBe(true);
const warn = result.checks.find((c) => c.name.startsWith("summary-missing-title:"));
expect(warn?.status).toBe("warn");
// Later category still ran:
expect(result.checks.some((c) => c.category === "lessons")).toBe(true);
});
it("non-string crystal narrative doesn't throw — surfaces as warn", async () => {
await kv.set(KV.crystals, "cry_bad", {
id: "cry_bad",
narrative: undefined as unknown as string,
keyOutcomes: [],
filesAffected: [],
lessons: [],
sourceActionIds: [],
createdAt: "",
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["crystals"],
})) as { checks: DiagnosticCheck[]; success?: boolean };
expect(result.success).toBe(true);
const warn = result.checks.find((c) => c.name.startsWith("crystal-empty-narrative:"));
expect(warn?.status).toBe("warn");
});
it("Infinity confidence on insight + semantic both flagged", async () => {
await kv.set(KV.insights, "ins_inf", {
id: "ins_inf",
title: "t",
content: "c",
confidence: Infinity,
reinforcements: 0,
sourceConceptCluster: [],
sourceMemoryIds: [],
sourceLessonIds: [],
sourceCrystalIds: [],
tags: [],
createdAt: "",
updatedAt: "",
decayRate: 0.05,
});
await kv.set(KV.semantic, "sem_nan", {
id: "sem_nan",
fact: "f",
confidence: NaN,
sourceSessionIds: [],
sourceMemoryIds: [],
accessCount: 0,
lastAccessedAt: "",
strength: 0,
createdAt: "",
updatedAt: "",
});
const result = (await sdk.trigger("mem::diagnose", {
categories: ["insights", "semantic"],
})) as { checks: DiagnosticCheck[] };
expect(result.checks.find((c) => c.name === "insight-bad-confidence:ins_inf")?.status).toBe("warn");
expect(result.checks.find((c) => c.name === "semantic-bad-confidence:sem_nan")?.status).toBe("warn");
});
});
});
});