Ctrl K
ComunidadGuías…

specs/007-value-resolution-steps.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Spec: Value Resolution Steps

## Status

Implemented.

## Scope

This spec defines `${steps.step_id.outputs.name}` references used by value resolution.

Common reference syntax, supported fields, and string insertion are defined by [Spec 003: Value Resolution and Field Evaluation](003-value-resolution.md).

Resolution timing is defined by [Spec 003: Value Resolution and Field Evaluation](003-value-resolution.md).

Step identity is defined by [Spec 009: Step Reference](009-step-reference.md).

Step output declaration and publication are defined by [Spec 012: Step Outputs](012-step-outputs.md).

This spec does not define dependency resolution mechanics, step execution, or output file parsing; it only defines dependency constraints for fields that reference step outputs.

## Goal

Later fields can reference outputs published by completed steps.

## Motivation

Step outputs are produced during execution, so they are not available when the workflow file is loaded.
A step-output reference must therefore describe both a static relationship between steps and a runtime lookup after the producing step completes.

This spec keeps those rules in one place.
References use step ids.
A reference resolves only when the producing step is ordered before the consuming step.
If the value is unavailable, Dagu preserves the original reference text.
Explicit inspection surfaces report a passive notice for that preserved reference.

## Behavior

### Reference Form

- An unescaped `${steps.step_id.outputs.name}` reads `name` from a completed step output.

- The reference form is exact.
- It has exactly four path segments: `steps`, `step_id`, `outputs`, and `name`.
- Nested output paths such as `${steps.build.outputs.metadata.tag}` are unsupported braced text.

- `step_id` must identify an existing step `id`.

- `outputs` is a literal path segment.

- `name` must identify an output value published by the referenced step.

- When the referenced step declares an output contract, `name` must be declared by that contract.

- `step_id` and `name` must match `^[A-Za-z][A-Za-z0-9_]*$`.

- Escaped `\${steps.step_id.outputs.name}` is ordinary string content under Spec 003.

- Escaped step-output-looking text must not be resolved.

- Escaped step-output-looking text must not produce a passive notice.

- Unsupported braced text such as `${step.outputs.name}`, `${steps.foo.bar}`, `${steps.build.outputs.metadata.tag}`, `${steps.build-step.outputs.image}`, `${steps.1build.outputs.image}`, `${build.output.image}`, and `${foo.steps.outputs.name}` is outside this spec.

- Unsupported braced text is preserved silently by Spec 003.

### Dependency Rules

- A step output reference can resolve only when the owning step depends directly or transitively on the producing step, through `depends` or through an inferred dependency.

- A step output reference to the owning step cannot resolve.

- A field without an owning step cannot resolve step outputs unless another spec explicitly allows that field to wait for step completion.

- Handler fields do not have step-output lookup scope for this spec.

### Runtime Lookup

- A step output reference may resolve only after the referenced step completes and publishes the output.

- A step-owned field may resolve a step output reference only when the referenced output is available before the owning step starts.

- Step output values are inserted into string fields according to Spec 003 string insertion rules.

- Step output references read the named outputs a step publishes, whatever mechanism publishes them.

- A step publishes named outputs through its top-level `outputs` contract and `DAGU_OUTPUT_FILE`, through `output_schema`, through object-form `output`, through `stdout.outputs`, or through an action that publishes named outputs.

- A failed attempt publishes no outputs, so its references stay unresolved.

- Step output references do not read string-form `output: VAR`, which captures a variable rather than a named output, nor stdout, stderr, logs, artifacts, or nested output paths.

### Foreach Body Scope

Spec 018 adds `foreach.steps` body scopes.

Rules:

- A `foreach` body step can reference top-level step outputs when the top-level
  producer is ordered before the owning `foreach` step.
- A `foreach` body step can reference an earlier body step output from the same
  item body.
- Body step output references never cross item bodies.
- A top-level step after a `foreach` step cannot reference body step outputs
  directly.
- A top-level step after a `foreach` step must consume the `foreach` aggregate
  output when it needs values collected from body steps.
- `foreach.collect` expressions resolve after a successful item body with item
  scope and that item body's step-output scope available.

### Inferred Dependencies

A supported step output reference is an unambiguous ordering signal: without
the dependency the reference can only preserve as text. Dagu therefore adds
the dependency.

Rules:

- A supported `${steps.<step_id>.outputs.<name>}` reference in a value-resolution
  field adds an inferred dependency from the producing step to the top-level
  step that owns the field.
- Only the reference forms this spec defines as supported create an inferred
  dependency. Escaped text, unsupported braced text, `${env.<name>}`,
  `$NAME`, and string-form `output: VAR` variables create none.
- Only fields with step-output lookup scope create an inferred dependency. A
  field whose notice reason would be `namespace_unavailable` creates none.
  `handler_on` steps have no position in the step graph and create none.
- A template executor body is rendered as written and creates none. Other
  template executor fields, such as `with.data`, create one.
- A reference inside `foreach.steps` to a top-level step attaches the
  dependency to the owning top-level `foreach` step. A reference inside
  `foreach.steps` to another body step of the same body creates no top-level
  dependency.
- Item-scoped `foreach.key` and `foreach.collect` fields create none.
- Explicit `depends` and inferred dependencies are unioned. An explicit empty
  `depends` sequence does not suppress inferred dependencies.
- A reference to a producer the owning step already depends on, directly or
  transitively, adds nothing.
- A reference to an unknown step id or to the owning step adds nothing and
  keeps its passive notice.
- The combined graph must be acyclic. An inferred dependency that would close a
  cycle is a load error that names the producing step and the owning step.
- In a chain DAG, chain order already satisfies every inferred dependency that
  points to an earlier step. A reference to a later step is the cycle case.
- Inferred dependencies order execution. Behavior that `depends` enables
  beyond ordering, such as chat message inheritance between LLM steps, still
  requires an explicit `depends` entry.
- Inferred dependencies are stored on the step separately from `depends`.
  `dagu validate` reports each one as
  `inferred: <producer> -> <consumer> (<field path>)`, the API carries them in
  a field separate from `depends`, and the UI graph draws them with a distinct
  line style.

### Validation

- An unresolved supported step-output reference in a value-resolution field must preserve the original reference text.

- An unresolved supported step-output reference is not a validation error by itself.

- Explicit inspection surfaces must report a passive notice for each preserved supported step-output reference.

- Each passive notice must identify the owning field path, the original reference text, and the reason.

- Reason values are:

| Reason | Meaning |
| --- | --- |
| `unknown_step_id` | The referenced step id does not identify a step. |
| `unknown_output_name` | The referenced step is known, but the output name is not declared by an available output contract. |
| `self_reference` | The owning step references its own output. |
| `namespace_unavailable` | The owning field has no step-output lookup scope in the current phase. |

- An unknown `steps.<step_id>` reference must use reason `unknown_step_id`.

- An unknown `steps.<step_id>.outputs.<name>` reference must use reason `unknown_output_name` when the referenced step is known, publishes a statically known output contract, and the referenced output name is not in it.

- A step whose published output names are known only during a run has no statically known contract, so a reference to it must not use reason `unknown_output_name`. A sub-DAG step and an action step resolved from a manifest publish such names.

- A step output reference to the owning step must use reason `self_reference`.

- A step output reference in a field with no step-output lookup scope must use reason `namespace_unavailable`.
- A `namespace_unavailable` step-output notice is a defect because the owning
  field cannot resolve a step output during a run.

- An unavailable step output value must preserve the original reference text before the owning field is used.

- For step-owned fields, runtime value-resolution misses must preserve before the owning step starts.

- When more than one notice reason could apply, Dagu reports the first matching reason in this order:

  1. `namespace_unavailable`
  2. `unknown_step_id`
  3. `self_reference`
  4. `unknown_output_name`

- Escaped step-output-looking text must not produce a passive notice.

- Unsupported braced text must not produce a passive notice.

## Examples

Valid step output reference:

```yaml
steps:
  - id: build
    run: |
      printf 'image=v1.2.3\n' >> "$DAGU_OUTPUT_FILE"
    outputs:
      - name: image

  - id: deploy
    depends: build
    env:
      IMAGE: ${steps.build.outputs.image}
    run: ./deploy.sh "$IMAGE"
```

Inferred dependency:

```yaml
steps:
  - id: build
    run: |
      printf 'image=v1.2.3\n' >> "$DAGU_OUTPUT_FILE"
    outputs:
      - name: image

  - id: deploy
    run: echo ${steps.build.outputs.image}
```

`deploy` runs after `build` and the reference resolves to `v1.2.3`.
`dagu validate` reports `inferred: build -> deploy (steps[1].run)`.

Inferred dependency cycle:

```yaml
steps:
  - id: a
    run: echo ${steps.b.outputs.y}
    outputs:
      - name: x
  - id: b
    run: echo ${steps.a.outputs.x}
    outputs:
      - name: y
```

Loading fails with an error naming `a` and `b`.

Literal embedded code:

```yaml
steps:
  - id: script
    run: |
      node - <<'JS'
      console.log('\${steps.build.outputs.image}')
      JS
```

Dagu passes `${steps.build.outputs.image}` to the later JavaScript interpreter as code text.
The JavaScript single-quoted string then treats it as literal text.