nika-action

August 7, 2026 · View on GitHub

Nika

nika-action · plan with receipts

ci

Static pre-flight for Nika workflows in CI: when a PR touches a .nika.yaml, this action posts what the change would do before anyone runs it: the nika check verdict, an honest cost floor, the models and secrets it needs, its egress statics, and the DAG (rendered natively by GitHub):

nika check: clean · flows/report.nika.yaml · 3 task(s) · 3 wave(s) 💰 cost floor ≥ $0.12 · ⚠ 1 unpriced task: never rendered as $0 🔐 requires: models: ollama/qwen3.5:4b · secrets: OPENAI_API_KEY 🌊 schedule: 3 wave(s), max width 1 🗺 DAG (collapsible mermaid)

This is the same audit you get locally · a broken DAG is a named finding with its fix, before any token is spent:

nika check catches a missing depends_on and a typo'd task reference with the exact three-line fix, then the same audit passes clean

name: nika
on:
  pull_request:
    paths: ['**.nika.yaml']   # fire only when a workflow file changes
permissions:
  contents: read
  pull-requests: write        # the sticky comment: drop it and the body
                              # lands in the step summary instead
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: supernovae-st/nika-action@704e664ddeeee8513f953b75e00238d0ab7dc0ac # v1.0.11
        with:
          workflow: flows/report.nika.yaml
          mode: check           # or: test (offline mock golden lane)

The pin is the full commit SHA with the release in a comment: the form Dependabot and Renovate bump for you. @v1 also works and tracks the latest v1.x.y.

No workflow yet? nika new chain flows/report.nika.yaml scaffolds one (authoring in 10 minutes)

What it does, and what it refuses to

lanewhat runssecrets
check (default)nika check --json + nika inspect: static analysis, nothing executesnone
test+ nika test against <file>.golden.json: the mock provider, offline, deterministicnone
runnot provided. Executing workflows (which can carry exec: shell steps) under a CI token is a decision this action refuses to make for you. Run lanes belong in your own workflow, behind your own review, never on fork-origin eventsnone

Security posture (the boring contract)

  • The install is verified: the release tarball is checked against the release's published SHA256SUMS before extraction. No bare curl | tar.
  • Zero secrets by default: both lanes are static/offline. The action never reads provider keys.
  • Fork PRs: the default GITHUB_TOKEN on a fork pull_request is read-only: the comment degrades to the step summary automatically. Never wire this (or anything) via pull_request_target + a checkout of the PR head: a .nika.yaml can declare exec: steps by design, so "run the PR's file under a privileged token" is code execution with your secrets. Same-repo PRs get the sticky comment; forks get the summary.
  • One comment, forever: the comment is upserted by a hidden per-file marker: re-pushes edit it in place, never spam the thread.
  • Pin this action by commit SHA: demonstrated in the usage block above, and what we do to nika itself inside. Full posture: SECURITY.md.

Honesty semantics (why "receipts")

  • The cost figure is a floor, not a total: spend ≥ floor. Rendered as ≥ $X, always.
  • Unpriced is never $0: a task with no list rate renders as unpriced with its reason verbatim (NoTokenLimit, uncataloged model, …). A model the engine cannot price does not become free by omission.
  • The budget bound is stated, not hidden: --max-cost-usd (in your own run lanes) stops new admissions. The worst-case overshoot is one full wave: spend ≤ floor_checked + W · c_max, where W is the max wave width the comment prints. Tighten with max_parallel: when the budget is strict.
  • Unknown report_version → the comment renders the stable subset and says so. This action never guesses at fields it does not know.

Inputs

inputdefaultnotes
workflowrequiredpath to the .nika.yaml (one file; matrix over paths for more)
modecheckcheck | test
commenttruesticky PR comment (needs pull-requests: write)
engine-version0.108.0the nika release to install (checksum-verified)
native-strictfalsefail while native-first hints remain
github-tokengithub.tokenoverride for the comment upsert

Outputs

check-exit (0 clean · 2 findings) · clean (true/false: chainable in if:) · cost-floor (static floor in USD · empty when unavailable, never a fake 0) · cost-unbounded (true when unpriced/unbounded tasks exist: a 0.0 floor with this true is NOT free; consume the pair, never the bare number) · comment-file (rendered markdown path).

More than one workflow file

workflow takes one path; fan out with a matrix: one sticky comment per file, upserted independently:

jobs:
  check:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        flow: [flows/report.nika.yaml, flows/triage.nika.yaml]
    steps:
      - uses: actions/checkout@v4
      - uses: supernovae-st/nika-action@v1
        with:
          workflow: ${{ matrix.flow }}

FAQ

Does this run my workflow? No: and there is deliberately no mode that does. check is static; test runs the offline mock provider. Execution belongs in your own steps, behind your own review.

Why did the comment land in the step summary instead? Fork PR: the default token is read-only there. That's the designed degradation, not a bug; never grant pull_request_target to force the comment.

The cost line says unpriced: is that an error? No: a task without a list rate renders as unpriced with its reason, because a model the engine cannot price does not become free by omission.

License

Apache-2.0: the adoption side of the Nika license split. The engine this action downloads stays AGPL-3.0-or-later; invoking it as a subprocess imposes nothing on your repository.

🦋 SuperNovae Studio · Paris

The city · where this repo sits

📜 nika-spec ──── the civil code · the law tables, the corpus, the exam
    │ sync-pack: byte-gated mirror        │ projectors: drift-gated
    ▼                                     ▼
⚙️ nika ───────── the engine + the catalog (the yellow pages)
    │ the release train                  🖥️ nika.sh · 📖 nika-docs
    ▼                                     the showroom · the manual
📦 homebrew-tap · npm · Docker ── the docks
🔌 nika-client · 🎨 nika-vscode · 🤖 nika-plugins · ⚡ gh-nika ── the doors
🏭 nika-action · 🧪 nika-actions-starter ── the CI district   ◀── you are here
🏪 nika-registry ── the market · 🏛 nika-estate ── the land registry

This building · THE GATE IN CI · runs the check on every pull request and posts the verdict, the cost floor, the permits and the DAG as one sticky comment.

Root · neither · this building runs the ENGINE's released binary and reports what it says. It decides nothing about the language and nothing about the engine · nothing authoritative is typed here.

Consumes · the engine's released binary (pinned by version, checksum-verified) · the workflow files in the caller's repo.

Serves · any GitHub repository that wants a receipt before a token is spent.

Truth lives · the verdict is the binary's exit code and its JSON, never the Action's own prose · a green comment the binary did not produce is a bug, not a nicety.

All the buildings: nika-spec · nika · nika.sh · nika-docs · nika-client · nika-vscode · nika-plugins · gh-nika · homebrew-tap · nika-action · nika-actions-starter · nika-registry · nika-estate

Every fact has one home · everything else is a gated projection. The living map: nika.sh/map.