Skip to content

v0.114.0

Choose a tag to compare

@github-actions github-actions released this 23 Aug 13:54
· 84 commits to main since this release

What

Remote execution as a loopback door. Default nika serve stays the
resident ARM firer. --bind + --workflows + --token-file opens
authenticated HTTP: jobs, SSE, OpenAPI. The worker runs the POST-time
snapshot, not live files. Cancel, artifacts and /v1/run stay 404.
The workflow envelope is unchanged since v0.113.0. The project file's
retired nika: v1 tag now refuses. Pre-1.0 real semver puts this at a
minor bump.

Added

  • nika serve --bind is authenticated loopback HTTP. Pair it with
    --workflows and --token-file (credential bytes never enter argv).
    Default nika serve is still the resident ARM firer. The verb is on
    nika --help. Listen line prints the bound address, including port 0.
    Job cancel and artifacts stay 404 until those authorities exist.
  • Job events project over SSE at GET /v1/jobs/{id}/events. Frames
    are {sequence,kind,status} only. GET /v1/openapi.json is the live
    route table; cancel, artifacts and /v1/run are omitted.
  • nika doctor names the HTTP door when a token file is present.
    Owner-only mode is OK; group/world-readable is a fail with the umask
    077 fix. The row never claims TLS — that is the reverse proxy.
    Silent when the cwd has no .nika/serve.token (the door is opt-in).
    systemd and Caddy examples live in docs/ops/.

Changed

  • nika check names the ancestor nika.yaml spend cap. nika run
    already filled --max-cost-usd from that file; check printed « no
    total ceiling » and advised adding the flag, on a tree that already
    had one (issue 1050). The COST line still prices the workflow; a
    BUDGET footnote (and a presence-gated run_budget object on
    --json) now says the number and nika.yaml:line. --max-cost-usd
    still wins. run --dry-run and --help stay silent — this is the
    surface that contradicted the file.
  • A piped nika try / nika run no longer waits on the macOS keychain
    after a green card.
    The card printed, then SecKeychainFindGenericPassword
    blocked the main thread: a pipe cannot complete an ACL prompt. The
    keychain is skipped when stderr is not a TTY (file/env custody stay);
    a disabled journal (nika try · --no-trace-file) does not consult it
    at all.
  • The README says the trace inherits what the run read. A nika:read
    of a file puts those bytes into .nika/traces/ in the clear — correct
    for a replayable journal, unnamed until an auditor derived it (issue
    1047). The receipt paragraph now says hash-chained is not confidential.
  • nika spec --canon says the error-code count is the floor. An agent
    read count: 103 plus two NIKA-BUILTIN-* rows as the builtin-code
    family. The spec now carries a scope: sibling (nika-spec 282); this pin
    vendors it. Per-builtin and per-provider codes still live in nika explain.
  • BREAKING — the project file names itself (nika.yaml). nika: carried a
    frozen v1 tag while a workflow's nika: names the file: one key, two
    grammars, and every surface reading both had to know a special rule. It now
    carries the project's kebab-case name, the same grammar the workflow
    envelope gives its own nika:. The reasoning is this repo's own, written
    when the workflow envelope was nuked — a field with one legal value is not a
    version, so nothing is traded away, and the project gains a name it never
    had. The retired tag refuses here and nowhere else: a pre-nuke workflow
    carried a workflow: block beside its nika: v1 and still refuses on that
    key, so v1 stays free to be an ordinary workflow name; a pre-nuke project
    file had no companion key, so without a refusal the same bytes would quietly
    stop meaning « schema v1 » and start meaning « a project named v1 ». Only a
    whole marker qualifies — vault, v2ray and v1-migration stay names.
    Migration is one line: nika: v1nika: <your-project-name>.

Fixed

  • HTTP cancel now reaches the blocking worker. Timeout and SIGTERM
    no longer mark interrupted while effects continue. Queued jobs persist
    their workflow name and are re-enqueued on the next incarnation.
  • POST captures the execution world. A symlink or rewrite after 202
    cannot run bytes the client never admitted. GET names execution_id
    and trace_id after readmit. The service sandbox is the workflow root.

Added

  • nika check names the exact builtin when a utility is 1:1. sleep,
    date, uuidgen, the digest family, yq, grep/rg/ag and find
    used to be silent — or, for date/sha256sum, a family catalogue.
    Rule native-first/006 answers with one builtin and its argument shape
    (nika:wait · duration:) so the author types the next line, not a
    menu. It sits at the foot of the ladder and never steals a more specific
    family. echo stays silent on purpose: it is the universal placeholder,
    not a builtin.
  • Tool-result spill in the agent: loop (opt-in seam). Past 16 KiB a
    tool result's full text leaves the conversation for the blob store —
    the content hash IS the locator — and the model keeps a 2 KiB preview
    plus the pointer, so the context window stops re-paying bytes it cannot
    use. Nothing is discarded, and a store refusal keeps the full text:
    the spill is an optimization of the model's reading, never a gate on
    the data. Seat it with AgentVerb::with_spill; without the seam the
    loop is byte-unchanged.
  • nika check judges a project file as a project. It applied the nine-key
    WORKFLOW envelope to every document, so a project file came back
    NIKA-PARSE-002 missing required envelope field: tasks — on a file
    nika init --project-file had just written — and following that finding's
    own advice converted a correct project file into a broken workflow. The
    discriminant is the spec's, normative and covering every document
    (01-envelope §The type discriminant): a tasks: key means WORKFLOW, its
    absence means PROJECT. Deliberately not the filename, so it still holds for
    a registry blob, an HTTP body or a fence pasted into a chat. A project now
    audits in its own vocabulary — exit 0 clean, 2 on a finding — and says what
    it governs rather than a count it never stated.
  • nika check notes a file whose name and filename have drifted apart.
    Copy foo.nika.yaml to bar.nika.yaml, forget the header, and every trace
    and journal event keeps saying foo. It is a NOTE and never a finding:
    divergence is usually deliberate (an ordering prefix such as 01-hello is
    stripped before comparing), and the exit code is untouched. The filename is
    a location git mv may change; the name is an identity that rides traces.

Fixed

  • nika check's audited line names the declared blast radius. The
    default card said permits declared; the grants themselves lived
    behind --infer-permits and --json (persona 4). Cost was already
    on the card. The cell now lists the exec / tools / fs / net / env
    grants (an explicit empty block is {}, absent is still none).
  • A recovered run is no longer a green tick at a glance. --quiet
    and the shareable card titled / on a run that repaired a
    task (task_recovered then Ok). Exit 0 is still correct — recovered
    is a success cause. The headline and the card title now carry the
    warn mark; the storyboard already named N recovered (persona 14).
    trace ls still says completed (no new TraceState this cut).
  • --infer-permits no longer pastes a host-file grant. A
    nika:read of /etc/passwd (or ~/.ssh/…) under an absent
    permits: block printed fs.read: ["/etc/passwd"]; applying that
    block greened check and run (persona 7). G-09 already withheld the
    shovel on a declared boundary; the AUTH-006 companion and the
    inferred YAML still handed it. Escaping paths (absolute · home ·
    ..-climb) stay a note. The printed repair is the tool conjunct
    only.
  • nika check no longer panics on a decorative verb glyph. Copying
    ⛨permits: / ◇infer: from nika.sh into a file, then running the
    advertised nika check, dumped annotate-snippets (byte index N is not a char boundary) with no NIKA code and no --fix hint. The
    snippet painter widened a point span by one BYTE into a 3-byte glyph;
    it now snaps to a char boundary and widens by one CHAR. MCP already
    taught did you mean permits? — the CLI default path matches that
    calm.
  • nika try 10-compose-pipeline stages the child it invokes. The
    rehearsal room used to hold only the parent; check then died
    NIKA-COMP-001 on ./10-compose-child.nika.yaml (e2e S2 17/18 on
    81c1138). Fixture materialize already carried examples/fixtures/
    ingredients; a relative workflow: "./….nika.yaml" pack sibling is
    the same class.
  • for_each over a constant that is not an array is refused at check, not
    at dispatch.
    const: { items: "x" } with for_each: ${{ const.items }}
    audited clean, then died at the run with NIKA-VAR-006 — a linter's answer,
    not a verifier's, and exactly the gap ADR-092 exists to close. The static
    lane already caught a typed non-array var but exempted every untyped one,
    on the rationale that « a --var override could pass an array ». That rule
    is inputs-only: --var sets an inputs: value and refuses unknown keys, and
    spec 01 §const is normative — a constant is « immutable across the run and
    never caller-supplied ». An untyped constant's literal IS its run value, so
    a non-array can never become one. Untyped entries are legal in const:
    alone, arrays and typed declarations are untouched, and all 59 shipped
    templates and examples still audit clean.
  • nika explain answers the token nika check printed in [brackets],
    including a hint.
    A HINT row put jq-as-map (or native-first/006)
    in the same slot as NIKA-PARSE-019, so the next gesture was
    nika explain jq-as-map and the answer was unknown code. A finding
    carries code; a hint carries kind — real in the data, invisible
    in the render. Explain now resolves the printed identity (the kind,
    or the numbered native-first rule) and teaches the class; MCP
    nika_explain speaks the same text.
  • nika check names the line of a PARSE refusal. A CONFORM finding
    already carried a rustc-grade frame (path:line:col + caret). A PARSE
    finding — NIKA-PARSE-017 duplicate key, NIKA-PARSE-005 unknown
    field, any span the parser held — printed the code and left the author
    to find the site. Duplicate keys are the worst of that class: the
    message says "a" appears twice, so grepping a: returns both, and
    neither is wrong on its own. The colliding key's span was in the
    loader's hand and discarded (span: None). It now rides the same
    frame CONFORM uses, under the same PARSE ✗ first line.
  • nika arm no longer refuses a project that sets its retention or its
    provenance floor.
    Two readers parse nika.yaml — the project reader
    (ceiling · traces.keep · registry.floor · arm) and the cadence
    grammar. The grammar refused traces: and registry: by name as
    « round 2 » keys with a remedy that was false (« retention stays with
    the env vars ») while the project reader had just accepted them and the
    retention ladder and the provenance gate consume them — the project
    starter's own traces: line made nika arm exit 2. Measured
    2026-08-18. The grammar now admits the project's other rungs OPAQUE and
    judges nothing about them (they are judged where they are owned); the
    cadence domain's own deferred keys (signature: · budget:) stay
    refused by name. A test derived from the project reader's closed key
    set pins the parity: every key it admits, nika arm reads green.

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.114.0 --version

Tarballs below: macOS arm64 / x64 · Linux x64 / arm64, plus SHA256SUMS.

Verify: three independent proofs

# 1 · checksum: the bytes you hold are the bytes CI hashed
sha256sum -c SHA256SUMS --ignore-missing     # macOS: shasum -a 256 -c

# 2 · attestation: GitHub-signed build provenance for this exact artifact
gh attestation verify nika-<platform>-0.114.0.tar.gz --repo supernovae-st/nika

# 3 · SLSA provenance: the intoto asset, verifiable offline
slsa-verifier verify-artifact nika-<platform>-0.114.0.tar.gz \
  --provenance-path multiple.intoto.jsonl \
  --source-uri github.com/supernovae-st/nika --source-tag v0.114.0

Provenance

Built from tag v0.114.0 by release.yml
on GitHub-hosted runners. Provenance is published twice: GitHub's native
build attestation (proof 2) and the SLSA generator's multiple.intoto.jsonl
release asset (proof 3). The release itself is a claim on the
machine-verified timeline: https://nika.sh/timeline

What's Changed

Full Changelog: v0.113.0...v0.114.0