Keep the engine's own history, and a screen that reads it

A second bus subscriber folds executions, errors, timings and queue lag
into per-minute rollups, keeps failures with their traceback and an audit
trail of who published what, and records one row per cascade — manual runs
and previews included, under an id of their own that writes no idempotency
markers. Read back through /observability/*, which always answers 200 so a
degraded engine still renders its own health screen.

Also fixes two things found on the way: node-health alerts read `status`
where the engine publishes `health`, so a device dropping never alerted
anyone, and the Redis queue reported `parked: 0` whatever was held.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MeiWk3Yq12n2pTvnQWYvt
This commit is contained in:
2026-08-16 22:29:32 +02:00
co-authored by Claude Fable 5
parent f300c43f3a
commit af3ba51571
30 changed files with 2610 additions and 22 deletions
+261
View File
@@ -379,6 +379,38 @@ export const DashboardsPublicSchema = {
title: 'DashboardsPublic'
} as const;
export const DeadLetterSchema = {
properties: {
id: {
type: 'string',
title: 'Id'
},
ts: {
type: 'number',
title: 'Ts'
},
flow: {
type: 'string',
title: 'Flow'
},
node: {
type: 'string',
title: 'Node'
},
cause: {
type: 'string',
title: 'Cause'
},
reason: {
type: 'string',
title: 'Reason'
}
},
type: 'object',
required: ['id', 'ts', 'flow', 'node', 'cause', 'reason'],
title: 'DeadLetter'
} as const;
export const EndpointSchema = {
properties: {
kind: {
@@ -426,6 +458,43 @@ these so a value never appears to come from nowhere — or worse, appears to
come from whichever node happens to be drawn as a producer.`
} as const;
export const EventRowSchema = {
properties: {
id: {
type: 'integer',
title: 'Id'
},
ts: {
type: 'string',
format: 'date-time',
title: 'Ts'
},
type: {
type: 'string',
title: 'Type'
},
flow: {
type: 'string',
title: 'Flow'
},
node: {
type: 'string',
title: 'Node'
},
detail: {
type: 'string',
title: 'Detail'
},
actor: {
type: 'string',
title: 'Actor'
}
},
type: 'object',
required: ['id', 'ts', 'type', 'flow', 'node', 'detail', 'actor'],
title: 'EventRow'
} as const;
export const FlowDef_InputSchema = {
properties: {
name: {
@@ -597,6 +666,56 @@ export const FlowInput_OutputSchema = {
description: 'A message the flow starts with rather than computes.'
} as const;
export const FlowRollupSchema = {
properties: {
flow: {
type: 'string',
title: 'Flow'
},
executions: {
type: 'integer',
title: 'Executions'
},
errors: {
type: 'integer',
title: 'Errors'
},
messages: {
type: 'integer',
title: 'Messages'
},
avg_ms: {
type: 'number',
title: 'Avg Ms'
},
avg_lag_ms: {
type: 'number',
title: 'Avg Lag Ms'
},
spark: {
items: {
type: 'integer'
},
type: 'array',
title: 'Spark'
},
last_error_ts: {
anyOf: [
{
type: 'number'
},
{
type: 'null'
}
],
title: 'Last Error Ts'
}
},
type: 'object',
required: ['flow', 'executions', 'errors', 'messages', 'avg_ms', 'avg_lag_ms', 'spark'],
title: 'FlowRollup'
} as const;
export const FlowStatePublicSchema = {
properties: {
values: {
@@ -698,6 +817,55 @@ export const HTTPValidationErrorSchema = {
title: 'HTTPValidationError'
} as const;
export const HealthSummarySchema = {
properties: {
status: {
type: 'string',
title: 'Status'
},
problems: {
items: {
type: 'string'
},
type: 'array',
title: 'Problems'
},
flows: {
additionalProperties: {
type: 'integer'
},
type: 'object',
title: 'Flows'
},
nodes: {
additionalProperties: {
type: 'integer'
},
type: 'object',
title: 'Nodes'
},
queue: {
additionalProperties: true,
type: 'object',
title: 'Queue'
},
loop_lag: {
additionalProperties: {
type: 'number'
},
type: 'object',
title: 'Loop Lag'
},
failures_24h: {
type: 'integer',
title: 'Failures 24H'
}
},
type: 'object',
required: ['status', 'problems', 'flows', 'nodes', 'queue', 'loop_lag', 'failures_24h'],
title: 'HealthSummary'
} as const;
export const HistoryPointSchema = {
properties: {
ts: {
@@ -1630,6 +1798,63 @@ export const RunRequestSchema = {
title: 'RunRequest'
} as const;
export const RunRowSchema = {
properties: {
id: {
type: 'string',
title: 'Id'
},
flow: {
type: 'string',
title: 'Flow'
},
source: {
type: 'string',
title: 'Source'
},
status: {
type: 'string',
title: 'Status'
},
started_at: {
type: 'string',
format: 'date-time',
title: 'Started At'
},
finished_at: {
anyOf: [
{
type: 'string',
format: 'date-time'
},
{
type: 'null'
}
],
title: 'Finished At'
},
nodes: {
type: 'integer',
title: 'Nodes'
},
errors: {
type: 'integer',
title: 'Errors'
},
duration_ms: {
type: 'number',
title: 'Duration Ms'
},
deliveries: {
type: 'integer',
title: 'Deliveries'
}
},
type: 'object',
required: ['id', 'flow', 'source', 'status', 'started_at', 'nodes', 'errors', 'duration_ms', 'deliveries'],
title: 'RunRow'
} as const;
export const SecretNamesSchema = {
properties: {
data: {
@@ -1711,6 +1936,42 @@ export const SectionDef_OutputSchema = {
description: 'A grid of widgets under a heading.'
} as const;
export const SeriesPointSchema = {
properties: {
ts: {
type: 'number',
title: 'Ts'
},
executions: {
type: 'integer',
title: 'Executions'
},
errors: {
type: 'integer',
title: 'Errors'
},
messages: {
type: 'integer',
title: 'Messages'
},
avg_ms: {
type: 'number',
title: 'Avg Ms'
},
max_ms: {
type: 'number',
title: 'Max Ms'
},
avg_lag_ms: {
type: 'number',
title: 'Avg Lag Ms'
}
},
type: 'object',
required: ['ts', 'executions', 'errors', 'messages', 'avg_ms', 'max_ms', 'avg_lag_ms'],
title: 'SeriesPoint'
} as const;
export const ShareRequestSchema = {
properties: {
lib_name: {