v0.109.2 — the nine-key release (installable)
This is the first 0.109 tag that published binaries. v0.109.0 named the language change and shipped no assets. v0.109.1 fixed the fossil gates and died on Linux (NIKA-1710). Install this one.
A workflow written for 0.108.0 will not nika check on 0.109.2. The envelope is nine keys: nika · model · inputs · const · secrets · permits · run · tasks · outputs. Identity is nika: <kebab-id>. The description is a # comment above it.
0.109.0 - 2026-08-18
The nine-key release. The envelope shrinks from fourteen keys to nine and
a workflow written for 0.108.0 will not check on 0.109.0 · this is the flag-day
the 0.106.0 front page announced as possible, and it lands whole. The identity
moves onto nika: itself (nika: <id> · a kebab-case name · the workflow:
block, its id: and its description: are gone), the value authorities are
exactly three (inputs · const · secrets · config: died with the block),
types: · policy: · assert: leave the envelope, and the task body loses
its second grammars: cleanup is a real task on an unwind edge (on_finally:
is dead · graph_format: 3 carries the finally node), output: is spelled
extract:, declassify: and inert: merge into one door (lift:),
fail_workflow is gone (on_error is recover or skip), the two fan-out
knobs live INSIDE for_each:, and group: arrives (fan-in · NIKA-DAG-008).
Two P0 close at the surface users install: an expression sees only its INPUT
(the ambient env leaves the jaq function set at the three seams, with a
pinned inventory of natives that reddens if a future jaq adds one), and a
third-party receipt can no longer write the operator's clipboard (every field
rides escape_tty). The refusal a 0.108.0 file meets first now TEACHES where
each retired key's role went, instead of unknown field.
⚠️ Migration
1 · The envelope · fourteen keys become nine (LOT 2 · ADR-113 · #909 and the sweep of 2026-08-11/13)
The live envelope is nika · model · inputs · const · secrets ·
permits · run · tasks · outputs. Every other top-level key refuses,
and the refusal names the destination:
| Dead form | Write instead | The teaching |
|---|---|---|
nika: v1 + workflow: { id, description } |
nika: <id> (kebab-case) · the description as a # comment above it |
the identity IS the envelope key · prose is demoted, never dropped |
top-level description: |
a # comment above nika: |
shipped twice (bare · inside workflow:) · both dead |
config: |
an inputs: entry with required: false and a default: |
a deployment-supplied value is an input with a default · authorities are exactly three |
types: (NIKA-TYPE-002 retired) |
the verb's schema: (structured output) · a task's returns: |
a shape rides its consumer · the ten primitives stay lowercase (spec 09) |
policy: |
permits: (fs · net · exec · tools) · secrets: · the unconditional laws (spec 10 · NIKA-SEC-015 net-before-exec) |
a vocabulary is not a policy · what survived is the boundary |
assert: |
nothing in the file · nika trace verify (spec 15) |
obligations are proven on the sealed trace |
2 · The task body · one grammar per thing
| Dead form | Write instead |
|---|---|
on_finally: mini-tasks |
a task of its own · after: { <parent>: unwind } (a finally node · graph_format: 3) · every graph judge governs it because it walks wf.tasks |
output: |
extract: (same shape) |
declassify: list · inert: string |
lift: (the law is a parameter of one door · spec 10 §the authored doors) |
on_error: { fail_workflow: true } |
nothing · the default IS the failure · on_error is recover or skip |
task-level max_parallel: · fail_fast: |
inside the block · for_each: { items: …, max_parallel: N, fail_fast: false } |
depends_on: |
with: bindings (the binding IS the edge) · after: { x: success } for control (NIKA-PARSE-024 · unchanged since W2) |
graph_format: 2 pins (*.graph.json goldens) |
regenerate · nika inspect --format json · never edit a projection by hand |
3 · What nika check --fix migrates · and what it does not
The rungs are idempotent and equivalence-or-stop · r1-identity (NEW ·
nika: v1 + workflow: {id, description} · the block, the pre-W1 scalar
workflow: <id>, the one-line flow form, a bare top-level description: ·
become nika: <id> with the prose demoted to a # comment ABOVE it, never
dropped · it STOPS, never guesses, when nika: already names something else,
when the block carries a foreign key, when the id is not kebab-case, or when
there is no id at all) · w1-map (a tasks: sequence becomes the map ·
atomic or nothing) · w2-flow (depends_on + body tasks.* reads become
with: bindings and after: predicates) · d1-split (the pre-0.103 string
command:) · esplit (vars: → inputs: / const: · classify-not-rename)
· predicates (succeeded → success). One --fix runs them all in one
loop, so a 0.108.0 file whose only sins are the identity and the tasks list
heals to green in one command. Every round is a transaction: a repair whose
text no longer parses is rolled back and reported, and the file is written
once, from committed text only.
Still hand migrations in this release · the on_finally: restructuring
(cleanup becomes its own task on an unwind edge), output: → extract:,
declassify:/inert: → lift:, config: → inputs: (a classification),
and the for_each re-nesting · the refusal teaches each destination at the
point of refusal and --fix leaves the file untouched rather than write a
document its own checker would reject. Measured on the pack that ships inside
this binary: 0.108.0 passes 0/40 of these examples · 0.109.0 passes 40/40 ·
every existing file outside this repo sits on the 0.108.0 side of that line
until it is migrated.
4 · Two P0, closed where users install
- An expression sees only its input (#959). A
nika:jqexpression could
read the ambient environment (env.PATH) under an ABSENT permits block
whilecheckprinted « the body is pure compute so nothing escapes ». The
retained natives leave the jaq function set at the three seams from ONE
list (nika_cap), and a pinned inventory of jaq natives reddens if a
future jaq adds one.checknow refuses the escape at the binary users
install. - A third-party receipt wrote the operator's clipboard (#958). Three
fields of a proof receipt (assert·level·task) reached the
terminal withoutescape_ttywhile the helper existed in the same file ·
a foreign evidence pack could emit OSC52. Every receipt field rides the
escape · proven by mutation before publication.
5 · The two trains (RELEASING §0)
stable is the newest tag · what brew · the Registry · nika-action and the
starters install. next is main, at <next>.0-dev between tags · a real
semver prerelease · nika --version and every trace say which one they are.
Stable consumers move only when a tag they can install exists.
What this patch adds on top of the 0.109 language
The second refusal, one layer down. v0.109.1 fixed the fossil envelope in the release gates and died on both Linux builders one leg later: [consent-run] exit=3 want=4 — since #889 (0.109.0) a workflow that declares permits: refuses to START on a host with no sandbox backend (NIKA-1710), and a GitHub Linux runner has no bubblewrap. The macOS builders, where seatbelt exists, passed both gates confined. This patch is the same tree plus the second fix.
Fixed
- The Linux release builders install bubblewrap before the gates run (release.yml). The Diamond CI tests-leg recipe (apt bubblewrap · detach ubuntu-24.04's AppArmor bwrap profile · keep unprivileged userns open) now runs on the two Linux builders, so the funnel e2e and the trust battery run CONFINED there exactly as they do on macOS — never a waiver.
Install
brew install supernovae-st/tap/nika # macOS · Linux
curl -LsSf https://nika.sh/install.sh | sh # script install
docker run --rm ghcr.io/supernovae-st/nika:0.109.2 --versionTarballs below: macOS arm64 / x64 · Linux x64 / arm64, plus SHA256SUMS.
Verify: three independent proofs
# 1 · checksum
sha256sum -c SHA256SUMS --ignore-missing # macOS: shasum -a 256 -c
# 2 · attestation
gh attestation verify nika-<platform>-0.109.2.tar.gz --repo supernovae-st/nika
# 3 · SLSA
slsa-verifier verify-artifact nika-<platform>-0.109.2.tar.gz \
--provenance-path multiple.intoto.jsonl \
--source-uri github.com/supernovae-st/nika --source-tag v0.109.2Provenance
Built from tag v0.109.2 by release.yml. Timeline: https://nika.sh/timeline
The language notes for v0.109.0 and v0.109.1 (no assets) live on those GitHub releases.
Full Changelog: v0.108.0...v0.109.2