end node is real

This commit is contained in:
lda
2026-05-25 02:04:29 +07:00 Verified
parent 2af0b3357f
commit 6ef2620602
15 changed files with 249 additions and 19 deletions
+30 -4
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
from wf_core.models.steps import (
ConditionNode,
EndNode,
ForeachNode,
InterruptNode,
NodeUse,
@@ -9,7 +10,7 @@ from wf_core.models.steps import (
SubgraphNode,
)
from wf_core.models.schemas import NodeDef
from wf_core.models.workflow import Edge, Workflow
from wf_core.models.workflow import Workflow
from wf_core.tokens import END
from wf_core.validation.issues import ValidationIssueCode, ValidationReport
from wf_core.validation.outcomes import declared_outcomes_for_step, reachable_node_ids
@@ -28,7 +29,7 @@ def validate_workflow(workflow: Workflow) -> ValidationReport:
node_defs = _collect_node_defs(workflow, report)
nodes_by_id = _validate_nodes(workflow, node_defs, report)
_validate_start(workflow, nodes_by_id, report)
outgoing = _validate_edges(workflow.edges, nodes_by_id, node_defs, report)
outgoing = _validate_edges(workflow, nodes_by_id, node_defs, report)
_validate_reachable_outcomes(workflow, nodes_by_id, node_defs, outgoing, report)
return report
@@ -73,6 +74,8 @@ def _validate_nodes(
validate_node_use(node, index, node_defs, workflow, report)
elif isinstance(node, SubgraphNode):
validate_subgraph_node(node, index, workflow, report)
elif isinstance(node, EndNode):
_validate_end_node(node, index, workflow, report)
elif isinstance(node, ConditionNode):
validate_condition_node(
node, index, report, state_root_fields, input_root_fields
@@ -94,6 +97,21 @@ def _validate_nodes(
return nodes_by_id
def _validate_end_node(
node: EndNode,
index: int,
workflow: Workflow,
report: ValidationReport,
) -> None:
"""Validate explicit workflow terminal outcomes."""
if node.outcome not in workflow.outcomes:
report.add(
ValidationIssueCode.UNDECLARED_WORKFLOW_OUTCOME,
f"nodes[{index}].outcome",
f"workflow outcome {node.outcome!r} is not declared",
)
def _validate_start(
workflow: Workflow,
nodes_by_id: dict[str, Step],
@@ -108,7 +126,7 @@ def _validate_start(
def _validate_edges(
edges: list[Edge],
workflow: Workflow,
nodes_by_id: dict[str, Step],
node_defs: dict[str, NodeDef],
report: ValidationReport,
@@ -116,7 +134,7 @@ def _validate_edges(
outgoing: dict[str, set[str]] = {}
edge_keys: set[tuple[str, str]] = set()
for index, edge in enumerate(edges):
for index, edge in enumerate(workflow.edges):
edge_key = (edge.from_, edge.outcome)
if edge_key in edge_keys:
report.add(
@@ -150,6 +168,14 @@ def _validate_edges(
f"edges[{index}].to",
f"unknown destination node {edge.to!r}",
)
if edge.to == END and "ok" not in workflow.outcomes:
# `__end__` is the legacy implicit end node for workflow outcome
# "ok". Explicit non-ok outcomes should use EndNode instead.
report.add(
ValidationIssueCode.UNDECLARED_WORKFLOW_OUTCOME,
f"edges[{index}].to",
"legacy __end__ requires workflow outcome 'ok' to be declared",
)
return outgoing
+1
View File
@@ -13,6 +13,7 @@ class ValidationIssueCode(StrEnum):
UNKNOWN_EDGE_DESTINATION = "unknown_edge_destination"
UNDECLARED_EDGE_OUTCOME = "undeclared_edge_outcome"
MISSING_OUTCOME_EDGE = "missing_outcome_edge"
UNDECLARED_WORKFLOW_OUTCOME = "undeclared_workflow_outcome"
UNKNOWN_NODE_DEF = "unknown_node_def"
INVALID_NODE_INPUT_FIELD = "invalid_node_input_field"
INVALID_SOURCE_PATH = "invalid_source_path"
+3 -1
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from wf_core.models.schemas import NodeDef
from wf_core.models.steps import InterruptNode, NodeUse, Step, SubgraphNode
from wf_core.models.steps import EndNode, InterruptNode, NodeUse, Step, SubgraphNode
from wf_core.models.workflow import Edge
from wf_core.tokens import END
@@ -21,6 +21,8 @@ def declared_outcomes_for_step(step: Step, node_defs: dict[str, NodeDef]) -> set
return outcomes
if step.type == "join":
return {"done"}
if isinstance(step, EndNode):
return set()
if isinstance(step, InterruptNode):
return set(step.outcomes)
return set()