the path · 12
Failure routing — the paths a run takes when it breaks.
run itnika try 12-failure-routing
12-failure-routing.nika.yaml
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## 12 · Failure routing — the paths a run takes when it breaks.## 03 drew control edges with `after:` and `success`/`skipped`. This lesson# adds the arm nobody rehearses: a task that runs ONLY when its producer# fails, and a cleanup hook in its full form. The file is a drill — flip# one flag and watch the failure path fire, offline, deterministically.## Demonstrates ·# - `after: { deploy: failure }` · the pass-set is strictly {failure} —# it admits neither `success` nor `cancelled` (the whatever-happens# word is `terminal`)# - a skipped arm is a VALUE · on the green run the failure arm settles# `cancelled`, which is dead-path elimination, not an error# - real WORK on the failure path · the sanctioned use — `retry:` covers# transient errors, `on_error: recover:` covers a fallback value, a# failure-edge task is for work (one-obvious-way/003 · /004)# - `on_finally:` in its FULL mini form · `when:` + `timeout:` + verb# (03 showed the minimal form) · cleanup errors are logged, never# propagated · a task that never STARTED runs no cleanup at all## Run · nika run examples/12-failure-routing.nika.yaml# the drill · nika run examples/12-failure-routing.nika.yaml --var drill=true# — the deploy fails ON PURPOSE, the run exits red, and out/incident.md# is the artifact that proves the failure arm ran.nika: v1workflow: id: failure-routingpermits: exec: false tools: ["nika:assert", "nika:log", "nika:write"] fs: write: ["out/incident.md", "out/release-note.md"]inputs: drill: type: bool default: false required: true description: "true = rehearse the failure path · the deploy fails on purpose"tasks: deploy: # The assert stands in for the risky step so the drill stays offline # and deterministic — in a real file this is the release command. When # `--var drill=true` the condition is false and the task settles # `failure`, which is exactly what the two arms below route on. invoke: tool: "nika:assert" args: condition: "${{ inputs.drill == false }}" message: "drill requested · the deploy step fails on purpose" on_finally: # The FULL mini-task form: `when:` + `timeout:` + exactly one verb — # the closed shape (03 showed the minimal one). It runs once the # parent has STARTED, on every settle — and its errors are logged, # never propagated: a cleanup can never turn a green run red. - when: ${{ tasks.deploy.status != "success" }} timeout: "10s" invoke: tool: "nika:log" args: level: warn message: "deploy settled ${{ tasks.deploy.status }} · lock released" publish: after: { deploy: success } invoke: tool: "nika:write" args: path: "out/release-note.md" content: "release went out · deploy green\n" create_dirs: true overwrite: true rollback: # Strictly the failure arm. On the green run this task settles # `cancelled` — its producer never entered the pass-set, so the path is # eliminated as a value, not reported as an error; the render says so # in one line: `⊘ rollback gate: an edge did not admit`. On the drill # run it does real work: the incident record IS the reason this is a # task and not a `recover:`. after: { deploy: failure } invoke: tool: "nika:write" args: path: "out/incident.md" content: "deploy failed · rollback engaged · see the trace for the settle order\n" create_dirs: true overwrite: truenika-spec@6ac29351a · sha256 c8fd3d4b38d9c262…