Hold a node down for a minute before calling it down

A connector that misses one poll turned the health badge amber and the node
on the canvas red until it recovered. `LoadedNode.health_since` marks when the
status last actually changed — a rotating error detail under an unchanged
status does not move it, or a connector retrying with a different errno each
poll would never debounce — and `unhealthy_nodes` applies a 60 s floor to it.

The floor is on the surfacing, not on the transition: the stored health is the
truth the moment a node reports it, so a flow reading node health is never
told a stale story. Only going down is held back; recovery clears at once.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CL9zvnnvcp1mvA8o7impxk
This commit is contained in:
2026-09-07 07:54:10 +02:00
co-authored by Claude Opus 5
parent 39c231d6f7
commit 7b67eb5a8e
3 changed files with 105 additions and 4 deletions
+4 -1
View File
@@ -182,7 +182,10 @@ async def read_summary(request: Request, controller: FlowControllerDep) -> Any:
paused = set(controller.paused_flows())
entries = list(controller.loaded.values())
errored = [e for e in entries if e.status is NodeStatus.ERROR]
unhealthy = [e for e in entries if e.health == "down"]
# Not `e.health == "down"` directly: a node has to have been down for
# `HEALTH_FLOOR` before it counts, so a device that misses one poll does
# not turn this screen amber. Same source the canvas marks nodes from.
unhealthy = controller.unhealthy_nodes()
if quarantined:
problems.append(f"{len(quarantined)} flow(s) quarantined")
if errored: