Health: a flow that cannot run says so, and the brain marks which neurons

A dependency loop is flagged on the canvas and was invisible everywhere else:
/observability/summary answered "ok" with an empty problems list while the
published flow could not run at all. It now reports the flows validation
blocks, and the brain graph carries the reason on each neuron the issue names
so the view built to find broken wiring can show it.

Node errors stay counted once, as the nodes that failed to load, and an
advisory like an unauthenticated webhook marks nothing — it is worth saying,
but the flow still runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XC2jX6Hdj7pxGGKzBTrbqB
This commit is contained in:
2026-08-17 14:39:07 +02:00
co-authored by Claude Opus 5
parent 3b5241bb9a
commit a1265450df
7 changed files with 143 additions and 2 deletions
+18 -1
View File
@@ -17,7 +17,7 @@ from sqlalchemy import func
from sqlmodel import col, select
from app.api.deps import FlowControllerDep, SessionDep, get_current_user
from app.flow.controller import NodeStatus
from app.flow.controller import ADVISORY_ISSUES, NodeStatus
from app.models import EngineEvent, FlowRun, MetricBucket
router = APIRouter(
@@ -128,6 +128,22 @@ async def read_summary(
if errored:
problems.append(f"{len(errored)} node(s) failed to load")
# What the canvas flags on a flow — a dependency loop, an input nothing
# feeds — stops that flow running just as surely as a node that will not
# load, and until now this screen was the one place it did not show.
# `node_error` is left out: those are the nodes already counted above.
invalid = sorted(
{
issue.flow
for issue in controller.issues
if issue.flow
and issue.code != "node_error"
and issue.code not in ADVISORY_ISSUES
}
)
if invalid:
problems.append(f"{len(invalid)} flow(s) cannot run: {', '.join(invalid)}")
statement = (
select(func.count())
.select_from(EngineEvent)
@@ -146,6 +162,7 @@ async def read_summary(
),
"paused": len(paused),
"quarantined": len(quarantined),
"invalid": len(invalid),
},
nodes={"total": len(entries), "error": len(errored)},
queue=queue,