Skip to main content
Version: v5.6.1

The step family

Included from Basic tier

A step is one node of a run's execution tree. Two families share the shape: change steps (JSON:API type step, owned by a change) and workflow steps (one JSON:API type per stored STI class — the snake-cased class name, so workflow_noop_step, workflow_change_step, workflow_wait_step, workflow_workflow_step). This page covers all of them. Note that wait is not a JSON:API type: wait semantics surface as the step_type attribute on step and as the workflow_wait_step type on workflow steps.

The v2 OpenAPI document declares this resource's schema; the resource-field coverage gate holds this page to it.

Endpoints​

RouteServes
GET /api/stepsChange steps, created_at desc then id desc. Default and max limit 1000; 206 + meta.partial_response when trimmed.
GET /api/steps/{id}Show. Default include=pre_properties_versions,post_properties_versions,properties.
POST /api/steps/{id}/approve / /continue / /rejectThe wait/approval gates (below).
GET /api/steps/{id}/log_lines, /artefacts (alias /artifacts), /converged_properties, /converged_settingsPer-step surfaces; the converged documents resolve to the step's change.
GET /api/workflow_stepsWorkflow steps, ordered (parent_id, step_sequence) — the model default scope. Default and max limit 1000.
GET /api/workflow_steps/{id}, /log_lines, and the same /approve//continue//reject gatesThe workflow-step twins.

Both lists accept filter[<attribute>_eq] over the serialized attributes plus one scope filter: filter[waiting_for_user]=<username> — steps whose raw status is waiting and that either store no pending approval requirements or name the user (or one of their LDAP groups) in a requirement's user_names/ldap_groups, matched case-insensitively. Sparse fieldsets (fields[step]=…) are honoured on both lists and shows.

step attributes (change steps)​

AttributeTypeMeaning / origin / null semantics
approved_byarrayAudited approve transitions ({username, message, date}, second-precision date) from the step's audit:steps:approve events. [] when none.
rejected_byarrayAs above, for audit:steps:reject. [] when none.
continued_byarrayAs above, for audit:steps:continue. [] when none.
requires_approval_fromarray/nullStored approval requirements, each merged with its first matching approved_by response (present once requirements exist; null until matched) and, when matched, a rejected_by response. null when the step needs no approval.
actionstringThe action this step runs (colon-namespaced).
change_idstringThe owning change's id (also the change relationship).
child_execution_strategystring/nullsequential/parallel child ordering; null/empty when the step has no strategy.
full_pathstringThe step's run-graph identity (what skip_steps entries match against). "" when unset.
ignore_failurebooleanA failure of this step does not fail the parent.
input_argumentsarray/objectThe step's input arguments, stored verbatim.
state_timingobjectPer-state timing ranges.
step_sequenceintegerPosition under its parent.
step_typestringWait semantics flag — see the vocabulary below.
descriptionstring/nullStep description; null when unset.
step_namestringDisplay name.
skip_requestedbooleanTrue when the owning change's skip_steps mask matches this step's full_path (case-folded exact, glob, or path-prefix match; Ruby extglob {a,b} alternates are a recorded gap).
created_at / updated_atstringRow timestamps.
started_at / finished_atstring/nullExecution window; null until reached.
status_codestringThe serialized step status (the upstream SQL function public.step_serialized_status_code) — vocabulary below.
outputsobjectThe step's published outputs, key → string value; absent (not null) when the step published none or the database predates the step_outputs table. Sensitive values always serialize as the redaction placeholder ******. Workflow steps carry the same attribute holding the nested run's rolled-up outputs (root-scope union, tree order, last writer wins, the implicit exit_status included), persisted on success and on a plain nested failure.
condition_verdictsarrayThe step's recorded when: evaluations, oldest first: {expression, result, operands, attempt, evaluated_at, error?} — operand values matching the run's mask set record as ******. Absent for unconditional steps. A retry's re-evaluation appends on the retried run's step.
estate_readsarrayThe estate() snapshots resolved at the step's entry: {path, versions, assets?, environments?} per referenced node, versions keyed by layer (project, environment, …) with each property document's integer version — the staleness audit. When the step's expressions enumerate, assets/environments list each RBAC-readable child as {path, version?}. The resolved values stay on the step row and never serialize in list payloads. Absent when the step reads no estate paths. Workflow steps carry the same two attributes.
retry_attemptsarrayThe step's recorded automatic in-place retries (rider), oldest first: {attempt, failed_at, message?, next_attempt_at, policy_source?, nested_run_id?} — policy_source names the level that supplied the policy (step, or the settings-ladder layer: asset/environment/project/global), next_attempt_at the backoff deadline the re-dispatch honoured. Absent when the step never auto-retried. Workflow steps carry the same attribute.
error_handledbooleantrue on a failure-path step (when: error()/always()) that executed on its failed scope. Absent otherwise. Workflow steps carry the same attribute.
last_error_messagestringThe reason recorded when the step last entered error or system_error, verbatim: a workflow step's refused nested-change creation (an action whose declarative file is invalid — the file's own validation error; an unknown or missing required input — the same text POST /api/changes would answer with), a lost runner, a timeout, a failed output roll-up. What error.message binds in an on_error: handler. Absent while the step has never failed (so a step that failed and was then retried in place keeps its last reason). The v2 Step carries it as last_error_message ("" when absent); a workflow step carries it in its attribute document. opschain runs show prints it under the failed step.
fan_outarrayThe step's recorded for_each: expansions, oldest first: {expression, as?, count, cap, cap_source, evaluated_at} — cap_source names the settings-ladder level that supplied the cap (default when none did). A retry's re-entry appends a fresh evaluation. The expansion children are ordinary steps of the tree; a template step's dynamic name: renders into each child's step_name at expansion, with the authored expression preserved on the step's graph document as name_expression; a fan-out stage's own dynamic name: renders onto each stage copy the same way, the copy's name_expression then carrying the stage's authored expression. Absent for ordinary steps. Workflow steps carry the same attribute.

Show-only meta: property_sources (where each converged property value came from), present only when the source supplies it — unseedable from the ports store pre-Stage-0 (recorded gap).

RelationshipTarget typeNotes
propertiespropertiesThe step-owned properties document.
childrenstepChild steps, step_sequence order.
pre_properties_versions / post_properties_versionsproperties_versionThe properties-version pins taken before/after execution, level order.
changechangeThe owning change.

Links: self, parent (/api/changes/{change_id}), log_lines, and change — the owning change's node-path URL, or /api/workflow_runs/{id} when the owning run resolves as a workflow run rather than a change.

Status vocabulary (both families)​

Raw states (mirroring the upstream StateMachine::AvailableStates):

pending, queued, running, waiting, success, skipped, error, cancelled, aborted, system_error

system_error is an infrastructure failure rather than the action's own verdict — a runner lost mid-step (runner lost: lease expired without a result, see opschain docs fleet-queue) lands here; the owning change rolls up to error and the step is retried by hand.

Serialized pseudo-statuses (runstate.go SerializedStatus, mirrored by the SQL functions): a retried step awaiting its re-run reads pending; a waiting step with approval requirements reads waiting_for_approval; an aborted, approval-gated, rejected step reads rejected; otherwise the raw state.

step_type vocabulary​

  • Postgres-backed reads emit standard or wait_step (wait_step when the action's last colon segment starts with opschain_wait_step_ — the opschain-core wait-action predicate).
  • Ports-store-backed reads (the Go engine's own graph) emit the graph node's kind: standard, wait or approval.

A waiting wait step routinely has children (it acquires them on entry to waiting), so "no children" never identifies a wait step — the status functions carry an explicit wait-step disjunct for exactly this reason.

Transition gates​

POST /api/steps/{id}/approve|continue|reject (and the /api/workflow_steps/{id}/… twins). Body attributes: message (audited, max 1000 characters — 422 Audit message too long - cannot exceed 1000 characters beyond it) and input_arguments. Authorisation: read on the run plus execute on the step's action path (<node path>/actions/<action>), 404-on-deny. Refusals:

  • 403 You are not permitted to <gate> this step — a continue against a waiting step that requires approval, or an approval gate the actor may not pass.
  • 422 Cannot <gate> step because it is in the "<status>" state — the step is not in a gateable state.

Success answers 200 with the re-read step document. Outcomes land in approved_by/rejected_by/continued_by and the status moves accordingly (reject on an approval gate drives the pseudo-status rejected).

Workflow step types​

Workflow steps serialize per stored STI class; the wire type is the snake-cased class name. Attributes common to every type:

AttributeTypeMeaning
lock_versionintegerOptimistic-lock counter of the row.
created_bystringThe run creator's username.
namestringThe step's name in the workflow definition.
step_sequenceintegerPosition under its parent.
status_codestringSerialized step status: the raw vocabulary above, with the approval pseudo-statuses applied exactly as for change steps — an approval-gated waiting step reads waiting_for_approval, an approval-gated aborted one reads rejected.
skip_requestedbooleanAlways false on this read surface (the workflow skip mask is not evaluated per step here — recorded gap).
created_at / updated_atstringRow timestamps.
started_at / finished_atstring/nullExecution window; null until reached.
child_execution_strategystring/nullChild ordering; null when none.

Per-type extras:

  • workflow_change_step — adds action (string/null) and property_overrides (object, {} when none); relationships change (the spawned change), change_parent (the target node, typed by its node kind) and action_template_version.
  • workflow_workflow_step — adds code (the child workflow's code, string/null); relationships child_workflow_version and child_workflow_run.
  • workflow_noop_step — no extra attributes beyond the common set.
  • workflow_wait_step — adds the audited transition responses change steps carry: approved_by, rejected_by and continued_by (arrays of {username, message, date}, [] when none), read from the step's audit events so the actor who answered the gate is on the record. An approval-gated step additionally carries requires_approval_from: the stored requirement object (the same shape the run's step_tree node carries — e.g. {"user_names": ["alice"]}), absent when the step needs no approval. Unlike the change-step attribute, matched responses are not merged into it (upstream serializes the raw column here too) — read them from the three response arrays, and the gate outcome from the serialized status (rejected).

Common relationships: workflow_run, children (each child ref typed by its own STI wire type). Links: log_lines (/api/workflow_steps/{id}/log_lines) and — when the owning run resolves — self (…/runs/{run}/steps/{id}), parent and workflow_run.

  • opschain docs resources/change — the owning change run.
  • opschain docs resources/workflow_run — the owning workflow run.
  • opschain docs resources/properties — the documents and version pins steps carry.
  • opschain docs fleet-queue — how a ready step is dispatched to a runner.