Skip to content

Conditions and Actions

Typed conditions (when:) and typed actions (check: / step bodies) in pipelines.

Conditions (when:) are pre-conditions evaluated before a phase or step runs. They return a boolean: true = proceed, false = skip.

Actions are payloads that execute code. When used as check: (post-action), their success/failure determines whether the step passed or failed.

The pipeline system uses typed forms for both — a type: field dispatches to different evaluators. Workflow steps (inside command definitions) use a separate string-based when: form documented in commands/.

Pipeline steps (typed):
when: { type: builtin|shell|template, cmd: ..., expr: ... }
check: { type: shell|dwe|command|builtin, cmd: ..., with: ... }
check: auto # the logical inverse of when:
Workflow steps (string-based — separate, not covered here):
when: "dir-empty path" | "{{ ... }}" | "cmd: ..."
command: <id>

when: on a phase or pipeline step is a typed condition with three forms. It is evaluated before the phase/step runs; a falsy result skips it.

Builtin conditions test filesystem state using the predicate registry. Predicates are distinct from engine builtins (like service_configs_copy) — they live in a separate namespace and cannot be used in check: actions.

when:
type: builtin
cmd: "dir-empty services/main/src"

Available predicates (path is project-root-relative):

PredicateTrue when
dir-exists <path>path is an existing directory
dir-missing <path>path is missing or not a directory
dir-empty <path>path is missing or has no entries
dir-not-empty <path>path is a directory with at least one entry
file-exists <path>path is an existing regular file
file-missing <path>path is missing or not a regular file
generated-missing <svc> <field>the <field> value is absent from the generated-value store (.dwe/generated.yml), or the store file is missing

Unlike the other predicates, generated-missing takes two sub-arguments — a service name and a generated-field name — rather than a path. It reads the durable per-service store at .dwe/generated.yml and is used to gate a service’s secret-generation step so it runs only on the first deploy (when no value has been harvested yet). See services/fields.md for the generated: declaration and render/config.md for the harvest/replay flow.

Portability: Predicates are evaluated through hardcoded sh -c (not the project’s configured shell binary) to ensure POSIX portability and consistency regardless of the project’s shell choice.

Shell conditions execute a command and test its exit code: exit 0 = true, non-zero = false.

when:
type: shell
cmd: "test -f services/main/src/vendor/autoload.php"

Full shell semantics apply: pipes, redirection, operators, etc. Like predicates, shell conditions use hardcoded sh -c for portability.

Template conditions are evaluated at plan time using Go text/template syntax. They do not support check: in the same step (no side effects before execution).

when:
type: template
expr: "{{ .Services.second.Enabled }}"

Template conditions are purely for idempotency checks known at plan time:

- name: setup
when:
type: template
expr: "{{ not .Services.database.Enabled }}"
steps: []

The render context includes the full resolved project config, so you can reach any configuration value. See Templates for the template expression syntax and helper reference.

Actions are executable payloads — the same type: shell|dwe|command|builtin shape used in step bodies. When used as a check: post-action, the action’s success/failure determines the step’s success/failure.

- name: copy-configs
type: builtin
cmd: service_configs_copy
with:
service: main
mode: replace
check:
type: builtin
cmd: service_configs_check
with:
service: main

Actions support four executor types:

TypeExecutorExample
shellsh -ctype: shell, cmd: "test -f file.txt"
dweDWE CLItype: dwe, cmd: "docker up"
commandCommand registrytype: command, cmd: "services.main.migrate"
builtinEngine builtintype: builtin, cmd: "service_configs_check"

See deploy/conditions.md for the full action reference and the semantics of check: failures under continue_on_error.

Besides the mapping form, check: accepts one scalar: auto. It resolves to the logical inverse of the step’s own when:, for the common step whose “should I run” and “am I already done” are the same predicate read in opposite directions.

- name: clone-source
type: shell
cmd: "git clone ${vars.source.repo} services/backend/src"
when:
type: shell
cmd: "[ ! -e services/backend/src/.git ]"
check: auto # ≡ check: {type: builtin, cmd: shell, with: {cmd: "! ( [ ! -e … ] )"}}

It applies only to when: {type: shell}. The other two forms are load-time errors:

  • type: builtin — the two type: builtin namespaces below are disjoint. dir-empty is a predicate and has no counterpart in the action registry that check: draws from, so there is no action that could express “NOT dir-empty foo”. (Negating by swapping in the “paired opposite” predicate name would also be wrong at the edges — dir-empty and dir-not-empty are not complements for a missing directory.)
  • type: template — template conditions are evaluated at plan time and a false one removes the step entirely. Every step that survives to execution therefore had when == true, so its inverse is always false and the derived check would always fail.

check: auto without a when: is rejected as well — there is nothing to invert. The inversion is a logical negation of the rendered command (! (\n<cmd>\n)), never a textual edit of it.

See deploy/conditions.md for the resolution details (shell, working directory, timeout) and the journal/config-hash consequences.

The pipeline system has two separate type: builtin namespaces, disambiguated by YAML position:

  1. Predicates — used in when: type: builtin. Filesystem-state tests like dir-empty, file-exists.
  2. Engine builtins — used in step bodies and check: type: builtin. Executable actions like service_configs_copy, service_configs_check, message.

Example of the distinction:

phases:
- name: setup
when: # when: uses the PREDICATE registry
type: builtin
cmd: "dir-empty src"
steps:
- name: copy
type: builtin # step body uses the ENGINE BUILTIN registry
cmd: service_configs_copy
with:
service: main
- name: verify
check: # check: uses the ENGINE BUILTIN registry
type: builtin
cmd: service_configs_check
with:
service: main

dir-empty is not an engine builtin (not available as a step body or check). service_configs_copy is not a predicate (not available in when:).

Workflow conditions (string-based, separate)

Section titled “Workflow conditions (string-based, separate)”

Workflow steps use a separate string-based condition mini-language. The full grammar is documented in commands/; this section only sketches the surface for context.

# Workflow (string-based, separate system)
steps:
- command: services.main.migrate
when: "file-missing services/main/src/vendor/autoload.php"
- confirm: "Proceed?"
when: "{{ if .Params.confirm }}1{{ else }}0{{ end }}"
- command: cleanup
when: "cmd: test -d /tmp/workdir"

Workflow conditions are classified by leading prefix ({{ ... }} → template, cmd: ... → shell command, otherwise → predicate). See commands/ for the full workflow grammar.

  • deploy — pipeline when: and check: syntax with examples
  • lifecycle.md — lifecycle pipelines (same step/condition grammar as deploy)
  • commands/ — command definitions (separate system; workflows keep string-based when:)
  • Templates — Go template syntax, sprout helpers, render contexts