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
+36
View File
@@ -12,6 +12,7 @@ import pytest
from app.flow.controller import FlowController
from app.flow.messages import DType, MessageSpec
from app.flow.nodes import MqttNode
from app.flow.pipeline import ValidationIssue
from app.flow.schemas import FlowDef, NodeDef
from app.flow.store import FlowStore
@@ -80,3 +81,38 @@ def test_a_credential_never_reaches_the_key():
key = MqttNode.instance_key({**BROKER, "password": {"$secret": "broker_pw"}})
assert key == "mosquitto:1883/sensors/temp"
def test_a_neuron_carries_what_stops_it_running(controller: FlowController):
# Validation runs on a build, so the graph on its own knows nothing yet.
assert all(node.issue is None for node in controller.brain_graph().nodes)
controller.issues = [
ValidationIssue(
code="cycle",
message="These nodes depend on each other in a loop",
flow="house",
nodes=["house.scale", "house.sensor"],
)
]
graph = controller.brain_graph()
# Both named nodes are marked, the merged neuron among them, and the flow
# that has nothing wrong with it is left alone.
assert {node.id: node.issue is not None for node in graph.nodes} == {
"house.scale": True,
"mqtt:mosquitto:1883/sensors/temp": True,
}
def test_an_advisory_issue_leaves_the_graph_clean(controller: FlowController):
controller.issues = [
ValidationIssue(
code="unauthenticated_hook",
message="Webhook 'hook' has no secret",
flow="house",
node="house.scale",
)
]
assert all(node.issue is None for node in controller.brain_graph().nodes)