The file proposes, the machine disposes. Arming a project means
declaring beats in
nika.yaml — what runs, when, under which
policies — and letting the machinery fire them: an OS unit
(launchd · systemd), or the resident nika serve where no OS
scheduler exists. Reading this page schedules nothing; firing is
always a machine act.The arm: block
Each entry under arm: in nika.yaml is one beat:
signature: and budget: are reserved and refused today — they
fail loudly during registry validation rather than silently doing nothing.
The miss policies (manqué:)
nika arm — the report
Bare, the verb READS the registry and reports what is armed, what
fired, and what fires next — it schedules nothing:
idle and never computed —
asking a disarmed beat for its next slot would print a date nobody will
see fire. A par: line is shown for what it is: declared, not verified.
Sidecar directories no registry entry names are the orphelins —
reported, NEVER erased (law N4). Exit 0 clean · exit 2 when the
registry refuses, and the refusal teaches the fix.
on-webhook is a declaration seam today, not a live listener. The report
shows no clock slot and arm fire records an honest webhook skip; an event
bridge that actually delivers the beat has not shipped yet.
nika arm fire <label> — the one firer
nika serve both end here, so the law lives exactly once. The label
is the workflow file’s radical (workflows/doctor.nika.yaml → doctor;
a radical collision takes -2, -3 in file order).
The stdout contract: a decision prints exactly one line —
fired … · skipped … · paused … · failed … — so a launchd or
serve log stays byte-pure. Two honest exceptions: a registry refusal
(exit 2) teaches multi-line, and an environment death (exit 3)
rides stderr.
What a fire guarantees:
- The per-beat lock is taken BEFORE the decision and held until AFTER
the receipt lands — at-least-once: a crash between the claim and
its receipt leaves a durable unmatched claim in
history.ndjson, never a silent double-fire. The current report does not fold that unsettled claim into its own line yet. Exactly-once is never promised; when the workflow’s effects are idempotent, the result is effectively-once. - Every fire is a FRESH run (law N2): a run you pause is parked with its
trace (
paused … · trace …), never resumed by the firer. - The clock is injectable (hidden
--now) so a replay and a test are deterministic.
What a fire leaves — the sidecar
.nika/arm/<label>/:
nika arm disarm <label>
Prints the disarm gesture (law N4): removing the line from the file is
not it — actif: false + raison: + jusqu_au: is, because that
leaves an auditable trail. --write removes only generated unit files
and prints the launchctl bootout or systemctl --user disable --now
command for the operator; it never executes that unload command itself.
When it removed at least one unit, it then attempts to journal the gesture
and reports a journal failure even though the file removal already happened.
nika arm --emit — the OS bridge
--emit launchd|systemd prints the OS unit that fires the beats
instead of reading the registry; add --write to install it:
launchd emits a macOS user agent
(~/Library/LaunchAgents/nika.arm.<radical>.plist); systemd emits a
user timer + service pair. Any of these flags set WITHOUT --emit
refuses honestly — a flag that does nothing is a lie.
nika serve — the resident firer
In normal mode, the resident loop ends at the same firer as an emitted OS
unit — for machines where no OS scheduler exists (a container, a bare
server, a VM you own):
--dry is deliberately narrower than a real fire: it previews the cadence
only. It does not take the beat lock or apply missed-run, overlap, cost, or
execution policy, so would fire is not a promise that the real firer will
run; nika arm fire can still answer skipped or refuse.
Startup, registry, clock, and loop failures exit 1. Once the loop is
running, each beat’s result is carried by its stdout line and ledger; a
completed --once sweep exits 0 even when an individual run failed or
paused. The hidden --now / --until pair injects the clock so a replay
stays deterministic.