Warn when a node that runs no code names a device
A device on a node without source was accepted and silently ignored. The build now reports it as ignored_device, an advisory issue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WgBfkm4rCmtd1T4vHWjEPx
This commit is contained in:
@@ -174,6 +174,8 @@ class LoadedNode:
|
||||
#: Published, and with no body of its own — so what runs is the new-node
|
||||
#: template, which returns nothing at all.
|
||||
missing_source: bool = False
|
||||
#: The device this node names although its type never runs on a worker.
|
||||
ignored_device: str = ""
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -1041,6 +1043,8 @@ class FlowController:
|
||||
node_type = NODE_TYPES.get(node_def.type)
|
||||
if node_type is None:
|
||||
raise ValueError(f"Unknown node type '{node_def.type}'")
|
||||
if node_def.device and not node_type.has_source:
|
||||
entry.ignored_device = node_def.device
|
||||
|
||||
params = resolve_params(node_def.params)
|
||||
|
||||
@@ -1923,6 +1927,22 @@ def _collect_issues(
|
||||
for entry in loaded.values()
|
||||
if entry.missing_source
|
||||
]
|
||||
# Only code travels to a worker, so a device on any other node is a field
|
||||
# nothing reads. Saying so beats a setting that looks like it took.
|
||||
issues += [
|
||||
ValidationIssue(
|
||||
code="ignored_device",
|
||||
message=(
|
||||
f"Node '{entry.id.rpartition('.')[2]}' names device "
|
||||
f"'{entry.ignored_device}', but only a Function node runs on a "
|
||||
"worker. The setting is ignored."
|
||||
),
|
||||
flow=entry.flow,
|
||||
node=entry.id,
|
||||
)
|
||||
for entry in loaded.values()
|
||||
if entry.ignored_device
|
||||
]
|
||||
return issues
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ MANUAL_RUN_PREFIX = "manual-"
|
||||
# neither the brain graph nor the health summary treats them as a fault.
|
||||
# Lives here rather than beside the controller so `ValidationIssue` can carry
|
||||
# the distinction itself, and every reader gets it for free.
|
||||
ADVISORY_ISSUES = frozenset({"unauthenticated_hook"})
|
||||
ADVISORY_ISSUES = frozenset({"unauthenticated_hook", "ignored_device"})
|
||||
|
||||
|
||||
class ValidationIssue(BaseModel):
|
||||
@@ -61,6 +61,7 @@ class ValidationIssue(BaseModel):
|
||||
"self_loop_needs_initial",
|
||||
"missing_source",
|
||||
"node_unhealthy",
|
||||
"ignored_device",
|
||||
]
|
||||
message: str
|
||||
flow: str = ""
|
||||
|
||||
@@ -125,6 +125,29 @@ def test_a_published_node_with_no_body_is_reported(store: FlowStore):
|
||||
assert [i for i in controller.issues if i.code == "missing_source"] == []
|
||||
|
||||
|
||||
def test_a_device_on_a_node_without_code_is_reported(store: FlowStore):
|
||||
"""Only code travels to a worker, so the device would be ignored silently."""
|
||||
store.write_flow(
|
||||
FlowDef(
|
||||
name="heating",
|
||||
nodes=[
|
||||
NodeDef(
|
||||
id="relay",
|
||||
type="change",
|
||||
device="gpu",
|
||||
provides=[MessageSpec(name="temp")],
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
controller = FlowController(store)
|
||||
|
||||
asyncio.run(controller.reload())
|
||||
(issue,) = [i for i in controller.issues if i.code == "ignored_device"]
|
||||
assert issue.node == "heating.relay"
|
||||
assert issue.advisory
|
||||
|
||||
|
||||
def test_a_node_being_written_in_the_editor_is_not_reported(store: FlowStore):
|
||||
"""A draft legitimately has no published body yet — that is what a draft is."""
|
||||
store.write_draft(a_flow(), 0)
|
||||
|
||||
Reference in New Issue
Block a user