the path · 11
The lift — the one door through the taint.
run itnika try 11-declassify-the-door
11-declassify-the-door.nika.yamlsource
# SPDX-License-Identifier: Apache-2.0# yaml-language-server: $schema=https://nika.sh/spec/v1/workflow.schema.json## 11 · The lift — the one door through the taint.## A permit-relevant argument fed by an UNTRUSTED value (`inputs.*` ·# `config.*` · anything fetched) is the parameterisation taint. When the# check can resolve the value and it ESCAPES the bound, it refuses with# `NIKA-AUTH-008` — and the reflex that refusal provokes (widen the# boundary until the message stops) is the wrong door. The right one is# authored: a `lift:` entry naming the **taint** law on the task, with a# reviewable `because:`# (spec/10-authority.md · "The only door is the authored one").## Demonstrates · every claim below was run against this exact file# - the wall · flip this file's `default:` to "../outside/secrets.csv"# and the check refuses ✖ NIKA-AUTH-008; the door below turns that# SAME variant check-green — the static question is deferred, not# answered# - never a bypass · that deferred variant still dies `NIKA-SEC-004` at# run, and `--var path=README.md` on THIS file dies the same way — the# boundary confines every value, door or no door# (spec/10-authority.md · "It is never a permit bypass")# - `lift:` · task-level · `law:` a closed enum (`taint` here) · `from:`# a dotted binding, required by `taint` and forbidden elsewhere ·# `because:` non-empty, the review record# - what the door yields on an ADMITTED value · the vouching lands in# the signed trace — one `lift` frame per entry, holding the# `because:`# - a wall with no door at all · a permit BOUND can never interpolate# (`NIKA-AUTH-007` · a bound is a literal you can read)## Run · nika run examples/11-declassify-the-door.nika.yaml# another vendor file · --var path=examples/fixtures/orders.csvnika: declassify-the-doorpermits: tools: ["nika:jq", "nika:read"] fs: # The bound is the DOMAIN the operator grants: any fixture csv, one # path segment, nothing nested. `*` never crosses `/` — a caller aiming # at `fixtures/deep/x.csv` or `../secrets.csv` is refused by the # boundary itself, before the taint question even comes up. read: ["examples/fixtures/*.csv"]inputs: path: type: string default: "examples/fixtures/inventory-q3.csv" required: true description: "WHICH vendor csv to load · must sit inside the fs.read bound"tasks: load: invoke: tool: "nika:read" args: path: "${{ inputs.path }}" # The door. `law:` names WHICH law this entry lifts; `from:` names the # exact tainted binding; `because:` is the sentence a reviewer holds # you to. What it buys, measured: a resolvable ESCAPING value stops # refusing at check (the question is handed to the run), and every run # writes one `lift` frame into the signed trace carrying this exact # sentence. What it does not buy, measured: the boundary — an # out-of-bound path dies `NIKA-SEC-004` at run with the door in place. lift: - law: taint from: inputs.path because: "the caller picks WHICH fixture csv, never an arbitrary path — the one-segment *.csv bound confines every value this input can take, and the run refuses anything outside it" shape: with: raw: ${{ tasks.load.output }} invoke: tool: "nika:jq" args: input: "${{ with.raw }}" expression: '{rows: (split("\n") | map(select(. != "")) | length - 1)}'outputs: summary: value: ${{ tasks.shape.output }} description: "How many data rows the vendor file carries (header excluded)"nika-spec@2b3d6ac3e · sha256 b18bdb82cab04c5e…