Live wall: truth strip keyed to the pipeline, not node heartbeats (#232) #252

Merged
logan merged 2 commits from feat/232-truth-strip into main 2026-10-05 02:47:01 -04:00
Showing only changes of commit 824b0f6f5a - Show all commits
+13 -5
View File
@@ -182,14 +182,21 @@ export function derivePipelineHealth(
// ── Radio ────────────────────────────────────────────────────────────────
const newestCallAt = maxDate(calls.map((c) => ms(c.started_at)));
const anyNodeUp = nodes.some((n) => n.status !== "offline");
// Every configured node offline is not "Listening" — nothing can be heard.
const allNodesOffline = nodes.length > 0 && !anyNodeUp;
const radioDry = anyNodeUp && (!newestCallAt || now - newestCallAt.getTime() > absenceMs);
const radio: PipelineStage = {
key: "radio", label: "Radio", lastAt: newestCallAt,
state: nodes.length === 0 ? "unknown" : radioDry ? "degraded" : "ok",
detail: newestCallAt ? `last call heard ${agoText(newestCallAt, now)}` : "no calls in the recent window",
state: nodes.length === 0 ? "unknown" : radioDry || allNodesOffline ? "degraded" : "ok",
detail: allNodesOffline
? `all ${nodes.length} node${nodes.length === 1 ? "" : "s"} offline`
: newestCallAt ? `last call heard ${agoText(newestCallAt, now)}` : "no calls in the recent window",
};
// Bounded to configured nodes so a call from a since-removed node can't push
// the KPI past N/N.
const knownNodes = new Set(nodes.map((n) => n.node_id));
const nodesHearing = new Set(
calls.filter((c) => now - ms(c.started_at) <= staleMs).map((c) => c.node_id),
calls.filter((c) => now - ms(c.started_at) <= staleMs && knownNodes.has(c.node_id)).map((c) => c.node_id),
).size;
// ── STT: radio heard, nothing transcribed ────────────────────────────────
@@ -225,7 +232,7 @@ export function derivePipelineHealth(
state: aiDry || aiTiers.length ? "degraded" : reachable === false && !newestAiAt ? "unknown" : "ok",
detail: aiDry
? `transcribed, no incident written since ${newestAiAt ? clockTime(newestAiAt) : clockTime(new Date(oldestUncorrelated))}`
: `last incident write ${agoText(newestAiAt, now)}${reachable === false ? " · /health/ai unreachable" : ""}`,
: `${newestAiAt ? `last incident write ${agoText(newestAiAt, now)}` : "no incident writes in the recent window"}${reachable === false ? " · /health/ai unreachable" : ""}`,
};
// ── Context feeds (chip only — a dead camera feed doesn't blind the pipeline) ──
@@ -244,7 +251,8 @@ export function derivePipelineHealth(
// ── Verdict ─────────────────────────────────────────────────────────────
const parts: string[] = [];
if (radio.state === "degraded") parts.push(`nodes checking in, no radio heard since ${newestCallAt ? clockTime(newestCallAt) : "—"}`);
if (allNodesOffline) parts.push(`${radio.detail}${newestCallAt ? `, last call heard ${clockTime(newestCallAt)}` : ""}`);
else if (radioDry) parts.push(`nodes checking in, no radio heard since ${newestCallAt ? clockTime(newestCallAt) : "—"}`);
if (sttDry) parts.push(stt.detail);
if (aiDry) parts.push(ai_.detail);
for (const t of degradedTiers) parts.push(tierText(t.tier, t.state));