AGPL-3.0-or-later · forever.

the skeleton register

human-gated-ship

Your intent sounds like: anything irreversible (deploy · send · publish) One of 10 skeletons the pack ships, green as-is under nika check. Fill the # SLOT: lines, repair from the fix lines, re-check. Machines read the catalog; the binary lists them with nika new '?'.

  • 10SLOT pointsthe only lines you edit
  • 4patterns lockedarrive correct, stay correct
  • 3tools grantedthe minimum for the job
  • 3a9bbde8sha-pinnedre-hashed on every test run

the plan

derived from the file, never drawn by hand
check_aexeccheck_bexec
gatesinvoke
humaninvoke
actexec
recordinvoke

the file, whole

conformance-gated upstream on every spec push
human-gated-ship.nika.yaml
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## TEMPLATE · human-gated-ship · gates in parallel → a human signs → ship · evidence always lands.##   [check_a]─┐                             ┌──▶[act]──▶[record]#   [check_b]─┴─▶[gates]──▶[human ⏸]──yes──┘    exec     nika:notify (always)#   exec ×2       assert     nika:prompt## Anything you cannot take back — a deploy, a send, a publish. The machine# proves the preconditions in parallel, an assert refuses to even ASK when a# gate is red, the human decides once with the full picture, and the evidence# lands whether the answer was yes, no, or the ship blew up.## Demonstrates ·#   - `nika:assert` before the question · never ask a human to rubber-stamp a#     red board#   - `after: <task>: success` · a STATE edge (ordering, no data) vs `with:` (a#     value edge) — two different kinds of arrow#   - `after: <task>: terminal` · the always-runs pattern · evidence on success,#     failure OR refusal#   - fail-closed defaults · unattended, the answer is NO## Needs · nothing to rehearse. To ship FOR REAL: TEAM_WEBHOOK_URL in the#   environment, real commands in the three `exec:` slots — then delete the#   `on_error:` on `record` so a lost audit trail is loud.## Run · nika run <file>                    # unattended · gates run, the answer defaults to NO#       Headless, `default: false` answers for you — delete that line and the#       run PAUSES instead (exit 4 · durable, not a failure) and prints its#       own resume line ·#       nika run <file> --resume <trace> --answer human=true · or falsenika: v1workflow:  id: human-gated-ship-template  # SLOT: kebab-case workflow id  # SLOT: one honest sentence — `nika new` matches intents against these words  description: "verify in parallel · human GO · act · record"permits:                            # SLOT: the blast radius · default-deny once present  exec: ["echo"]                    # SLOT: ONLY the programs the gates + act run (argv form)  tools: ["nika:assert", "nika:prompt", "nika:notify"]  # `host_from_self` below sanctions the FLOW (the secret may BE the URL) — it  # does not grant the capability to reach anyone. The host stays unknown at  # check, so it is judged at RUN against this list: name the webhook host here  # or the record is refused mid-run, after the ship already happened.  net: { http: ["hooks.slack.com"] }   # SLOT: the webhook host · nothing else may leavesecrets:  webhook:    source: env    key: TEAM_WEBHOOK_URL           # SLOT: where the record lands    egress:      - to: "nika:notify"        host_from_self: true        # the secret value IS the destination URLpolicy:                              # NEP-0014 · the gate runs under a NAMED endorsement mode  endorsement: solotasks:  # ── the verification wave · all checks run in parallel ──  check_a:    exec:      command: ["echo", "ok"]        # SLOT: gate 1 (argv form · injection-safe)      capture: structured  check_b:    exec:      command: ["echo", "ok"]        # SLOT: gate 2      capture: structured  gates:    with:      all_green: ${{ tasks.check_a.output.exit_code == 0 && tasks.check_b.output.exit_code == 0 }}   # two value edges · one boundary expression    invoke:      tool: "nika:assert"      args:        condition: "${{ with.all_green }}"        message: "A gate is RED: refusing to proceed"   # SLOT  human:    after:      gates: success              # state, no data · no question until the board is green    invoke:      # On a terminal this ASKS. Headless, it answers with `default:` below —      # the run completes, it does not pause (measured · act settles skipped).      # The durable pause belongs to a prompt with NO default: headless it      # PAUSES (exit 4 · not a failure) and prints its own resume line:      #   nika run <file> --resume <trace> --answer human=true · or false      tool: "nika:prompt"      args:        message: "All gates GREEN. Proceed?"   # SLOT: the decision, fully informed        # Fail CLOSED. The unattended answer to "should I do the irreversible        # thing" is no — `default: true` would hand CI a rubber stamp.        default: false  act:    with:      go: ${{ tasks.human.output }}   # the answer crosses as a value edge    when: ${{ with.go == true }}    exec:      command: ["echo", "shipped"]   # SLOT: the irreversible action (argv · program must be in permits.exec)      # default capture · a failing ship fails LOUDLY (NIKA-EXEC-001):      # never `capture: structured` on the irreversible step (exit codes      # would become data and a red ship would read as success)  record:    after:      act: terminal                 # the always-pattern · runs on success, failure, OR refusal    with:      acted: ${{ tasks.act.status }}   # observe the outcome · same pass-set as the after edge    invoke:      tool: "nika:notify"      args:        channel: webhook        target: "${{ secrets.webhook }}"        message: "Run finished · act=${{ with.acted }}"   # SLOT · success | failure | skipped        severity: info    on_error:      # Rehearsal only. With TEAM_WEBHOOK_URL unset the reference cannot resolve      # and this task fails NIKA-VAR-001 (measured) — which would make the      # skeleton red on a machine that has no webhook. Recovering keeps the      # scaffold runnable; DELETE this block for real use, because a ship whose      # audit trail silently vanished is exactly what this task exists to prevent.      recover: "REHEARSAL · no webhook configured · nothing was sent"outputs:  acted: ${{ tasks.act.status }}

sha256 3a9bbde8086cbe41. The copy above re-hashes to its pin on every test run (a copy is re-provable, never trusted). Source: human-gated-ship.nika.yaml in the spec pack · open it in the playground →

the patterns it locks

  • parallel gates
  • assert
  • `nika:prompt` GO
  • `on_finally` record

Scaffold it locally: nika new human-gated-ship my-flow.nika.yaml. Try the shape in the playground, or walk the showcase for the same patterns on real work. Read the spec →