Skip to content

Pipeline validates, then cannot run: inferred dependencies are computed and discarded #465

Description

@jeremymanning

Summary

A pipeline whose step references an undeclared output of another step passes orchestrator validate and then fails at run time. The data-flow validator computes the dependency correctly, and the compiler discards it.

This came out of writing the four unknown-output integration cases requested in review.

Reproduction

id: uo
name: UO
steps:
  - id: make
    tool: filesystem
    action: write
    parameters:
      path: "./out_a.txt"
      content: "A ran"
  - id: use
    tool: filesystem
    action: write
    parameters:
      path: "./out_b.txt"
      content: "make.path={{ make.path }}"
$ orchestrator validate p.yaml
✓ p.yaml is valid
  pipeline: uo
  tasks: 2
    - make
    - use

$ orchestrator run p.yaml
PipelineExecutionError: Task 'use' failed and policy is 'fail':
Parameter 'content' of tool 'filesystem' still contains unresolved
template references: '{{ make.path }}'.

What each layer believes

The data-flow validator does infer the dependency:

DataFlowValidator().validate_pipeline_data_flow(...)
#   valid    : True
#   warnings : [('undefined_output', "Task 'make' does not declare its outputs, ...")]
#   graph    : {'make': set(), 'use': {'make'}}      <-- the edge is here

The compiled pipeline does not:

YAMLCompiler().compile(...)
#   make: dependencies=[]
#   use:  dependencies=[]                            <-- the edge is gone

So use is scheduled in the same level as make, make's result is not in
context yet, the render fails, and the step is stopped. The runtime behaves
correctly here (it fails closed rather than writing an empty value — #445);
the problem is that validation promised the pipeline was fine.

Second, smaller defect: the warning is invisible

The warning that hints at this never reaches stdout. It is emitted through the
logger, so orchestrator validate prints ✓ ... is valid and a script
capturing stdout sees nothing at all. A warning nobody can see is not a
warning.

The design fork

Two defensible resolutions, and they are not equivalent:

A. Infer the dependency and schedule on it. Makes the compiler agree with
the graph the validator already builds. Convenient, matches what an author
plainly meant, and no existing pipeline breaks. Risk: ordering becomes
implicit, and a cycle introduced by a template reference is now possible.

B. Refuse at validation. Require an explicit dependencies: entry when a
step references another step's output. Explicit, no hidden ordering. Risk: it
will reject pipelines that currently run, wherever the reference happens to be
in a later level anyway.

I recommend A, because the validator already computes the edge and using
it costs nothing, whereas B converts a class of working pipelines into errors.
But this changes scheduling, so it wants a decision rather than my assumption.

Not to do

Do not "fix" this by making the undeclared-output case an error. It is
deliberately a warning: when a task does not declare its outputs, the
validator has no basis for rejecting a field name, and asserting otherwise is
the false-positive class removed in #448/#450/#461.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions