Change safety
hekla plan, replaying a deploy against its own journal, and the invariant sweep.
Deploying is restarting a process over a data directory that already has history in it. Two questions matter before you do: what would change, and would it still do the same thing.
What a definition hash covers
Every declaration’s hash covers what it does, taken from the same lowered form hek digest
prints. Comments, formatting and identifier positions are not in it. A const is inlined,
so changing one changes every declaration that reads it.
The runtime records those hashes when it deploys, in data/hekla.db. hekla plan compares
the recorded set against the candidate.
hekla plan
hekla plan .
compared 6 declaration(s) against what is deployed
behaviour command DoA (commands/a.hk)
behaviour command DoB (commands/b.hk)
contract projector UserStats (projectors/user-stats.hk)
projector UserStats rebuilds from zero, redoing 12481 position(s)
because `guard ShopIsConnected` changed: DoA, DoB
0 added, 0 removed, 3 changed; 1 projector(s) would rebuild
behaviour means the declaration does something different. contract means its shape changed, which for a projector is what forces a rebuild.
The because line is guard composition made visible: a changed guard is reported against
every command that reaches it, transitively, so a one-line edit to a shared proposition does
not look like a one-line change when it is not.
The rebuild line gives the cost up front: how many positions, not just which projector.
plan takes no lock and opens no event log without --replay, so it runs against a live
deployment. It exits 0 whether or not anything would change: a change is the answer, not a
fault.
plan --replay
This is the part no diff can do.
hekla plan --replay .
behaviour effect NotifyCustomer (effects/notify-customer.hk)
effect NotifyCustomer @ 4812: it reached a call the recorded run never made (http.post #0)
replayed 312 invocation(s) across 2 affected effect(s); 311 reproduce, 1 diverge
this project retains 7 day(s) of journals; anything older was reclaimed before the replay could see it
0 added, 0 removed, 1 changed; 0 projector(s) would rebuild, 1 recorded invocation(s) would diverge
Every recorded invocation of every affected effect is re-run against the candidate code, against the journal the original run left behind. Nothing is sent and nothing is mocked: the journal already holds what the world answered, so the question being asked is whether your new code would make the same calls.
--replay-limit caps how many of each effect’s most recent invocations are replayed,
default 1000, minimum 1, and it requires --replay. What the cap drops is named in the
report rather than dropped quietly.
An effect that reveals needs HEKLA_MASTER_KEY, and is reported as unreplayable without
one rather than skipped silently.
The ten blind spots
Every one of these is named in the report rather than assumed away, which is the property
that makes the coverage number worth anything. A run that ended in a terminal fail is not
among them: the record cannot say whether the deployed code failed there too, so a candidate
that would newly fail is reported as a divergence rather than skipped.
- an erased subject
- an operator skip
- an invocation that journaled nothing
- an unreadable record
- rows already reclaimed by retention
- invocations dropped by
--replay-limit - an older version of the effect, excluded from the baseline rather than replayed
- an effect that reads a
secretthis machine has not set, per effect - a reclaim that happened mid-run
- a missing or wrong
HEKLA_MASTER_KEY, per effect rather than per project
As a deploy gate
hekla plan --replay --json . > plan.json
plan exits non-zero for a directory that is not a project, for error findings, for a data
directory nothing was deployed to, and for a schema version this build does not expect.
hekla verify
plan asks about a change. verify asks whether what is already there is sound.
hekla verify .
It checks that a projector rebuilt from position 0 matches the live one, and that every recorded effect invocation still replays without performing anything.
Quarantine applies to effects: serve --verify stops handing a diverging effect work, and
/status and /admin/effects/{name} report state: quarantined with the divergence in
last_error. A projector quarantines itself for a different reason, a checkpoint that moved
backwards.
It takes the data-directory lock, so it refuses to run against a directory a server has open. Verify a copy of the directory, which checks the backup at the same time. Exit 0 only when the report is clean.
Continuous verification
hekla serve --verify .
Every completed effect invocation is replayed against a sealed journal as it runs. An effect
that breaks the invariant is quarantined: it stops being handed work, /status and
/admin/effects/{name} report state: quarantined with the divergence in last_error, and
nothing clears it on its own. The record is durable, so a restart honours it rather than
resuming. That is deliberate. A quarantine means the runtime found a divergence it cannot
explain, and resuming automatically would be resuming into the unknown.
[verify] enabled = true in hekla.toml does the same thing, and the flag can only turn it
on.
hek digest
For the narrower question of whether an edit changed anything at all:
hek digest --hash .
The digest is a deterministic rendering of what the program does with everything else taken
away: const, refusal and guard are inlined and do not appear. A secret has no entry
either, but it is not inlined, because there is no value in the program to inline: what
appears at each read site is the name, as (secret DISCORD_WEBHOOK). Reading a different
credential therefore moves the hash, and rotating a value never does. Two programs
with the same hash do the same thing.
Useful in CI as a cheap “is this a no-op refactor” check before running the expensive one.
A deploy, in order
hekla check .in CI, on every push.hekla test ., likewise.hekla secrets .against the target environment.hekla plan --replay --json .against the live data directory, and readdivergences.- Stop the process, copy the source tree, start it.
- Watch
/statusuntil projectors arereadyand effects arehealthy.
Step 4 is the one that is unusual, and it is the one worth keeping.