project workflow state -> output
This commit is contained in:
@@ -20,6 +20,7 @@ from wf_core.validation.steps import (
|
||||
validate_interrupt_node,
|
||||
validate_node_use,
|
||||
validate_subgraph_node,
|
||||
validate_workflow_output_bindings,
|
||||
)
|
||||
|
||||
|
||||
@@ -27,6 +28,7 @@ def validate_workflow(workflow: Workflow) -> ValidationReport:
|
||||
report = ValidationReport()
|
||||
|
||||
node_defs = _collect_node_defs(workflow, report)
|
||||
validate_workflow_output_bindings(workflow.output, workflow, report)
|
||||
nodes_by_id = _validate_nodes(workflow, node_defs, report)
|
||||
_validate_start(workflow, nodes_by_id, report)
|
||||
outgoing = _validate_edges(workflow, nodes_by_id, node_defs, report)
|
||||
|
||||
@@ -18,6 +18,7 @@ class ValidationIssueCode(StrEnum):
|
||||
INVALID_NODE_INPUT_FIELD = "invalid_node_input_field"
|
||||
INVALID_SOURCE_PATH = "invalid_source_path"
|
||||
INVALID_NODE_OUTPUT_FIELD = "invalid_node_output_field"
|
||||
INVALID_WORKFLOW_OUTPUT_FIELD = "invalid_workflow_output_field"
|
||||
INVALID_DESTINATION_PATH = "invalid_destination_path"
|
||||
EMPTY_CONDITION_ARGS = "empty_condition_args"
|
||||
INVALID_CONDITION_PATH = "invalid_condition_path"
|
||||
|
||||
@@ -16,6 +16,7 @@ from wf_core.models.steps import (
|
||||
ForeachNode,
|
||||
InputBinding,
|
||||
InputPathBinding,
|
||||
InputValueBinding,
|
||||
InterruptNode,
|
||||
NodeUse,
|
||||
OutputBinding,
|
||||
@@ -153,6 +154,56 @@ def _validate_boundary_bindings(
|
||||
)
|
||||
|
||||
|
||||
def validate_workflow_output_bindings(
|
||||
bindings: list[InputBinding],
|
||||
workflow: Workflow,
|
||||
report: ValidationReport,
|
||||
) -> None:
|
||||
"""Validate final workflow output projection bindings.
|
||||
|
||||
Root output bindings reuse input-binding shape: graph paths flow into a
|
||||
local output payload that is later validated against `output_schema`.
|
||||
"""
|
||||
output_fields = set(workflow.output_schema.properties)
|
||||
state_root_fields = workflow.state_schema.root_fields()
|
||||
input_root_fields = set(workflow.input_schema.properties)
|
||||
output_targets = []
|
||||
for output_index, binding in enumerate(bindings):
|
||||
output_targets.append(binding.target)
|
||||
destination_root = _local_root(binding.target)
|
||||
if destination_root is None or (
|
||||
destination_root != "." and destination_root not in output_fields
|
||||
):
|
||||
report.add(
|
||||
ValidationIssueCode.INVALID_WORKFLOW_OUTPUT_FIELD,
|
||||
f"output[{output_index}].target",
|
||||
"destination field is not declared in workflow output schema",
|
||||
)
|
||||
if isinstance(binding, InputPathBinding) and not is_valid_source_path(
|
||||
binding.path,
|
||||
state_root_fields,
|
||||
input_root_fields,
|
||||
allow_context=True,
|
||||
):
|
||||
report.add(
|
||||
ValidationIssueCode.INVALID_SOURCE_PATH,
|
||||
f"output[{output_index}].path",
|
||||
"source path must start with input., state., or context. and reference a declared root field when applicable",
|
||||
)
|
||||
elif not isinstance(binding, (InputPathBinding, InputValueBinding)):
|
||||
report.add(
|
||||
ValidationIssueCode.INVALID_WORKFLOW_OUTPUT_FIELD,
|
||||
f"output[{output_index}]",
|
||||
"unsupported workflow output binding",
|
||||
)
|
||||
if has_overlapping_paths(output_targets):
|
||||
report.add(
|
||||
ValidationIssueCode.INVALID_WORKFLOW_OUTPUT_FIELD,
|
||||
"output",
|
||||
"workflow output has overlapping output payload paths",
|
||||
)
|
||||
|
||||
|
||||
def _local_root(path: str | LocalPath) -> str | None:
|
||||
try:
|
||||
parts = split_local_path(path)
|
||||
|
||||
Reference in New Issue
Block a user