AGPL-3.0-or-later · forever.

the path · 12

Failure routing — the paths a run takes when it breaks.

run itnika try 12-failure-routing

12-failure-routing.nika.yamlsource
# 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)#   - cleanup as a TASK on an `unwind` edge · `when:` + `timeout:` are the#     ordinary task fields, not a second grammar · cleanup errors are logged,#     never propagated · a producer that never STARTED unwinds nothing## 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: 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"  release_lock:    # Cleanup is a TASK on an `unwind` edge — `when:` and `timeout:` are the    # ordinary task fields, not a second grammar. It runs once `deploy` has    # STARTED, on every settle — and its errors are logged, never propagated:    # a cleanup can never turn a green run red. An unwind task may read the    # producer it unwinds (settled by definition) and nothing else.    after: { deploy: unwind }    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: trueoutputs:  # The settle the two arms routed on. Measured on the green run:  # `--output json` hands back {"deploy_status":"success"}.  deploy_status: ${{ tasks.deploy.status }}

nika-spec@2b3d6ac3e · sha256 8e19aed549838bd3