Skip to content
hekla

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.

  1. an erased subject
  2. an operator skip
  3. an invocation that journaled nothing
  4. an unreadable record
  5. rows already reclaimed by retention
  6. invocations dropped by --replay-limit
  7. an older version of the effect, excluded from the baseline rather than replayed
  8. an effect that reads a secret this machine has not set, per effect
  9. a reclaim that happened mid-run
  10. 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

  1. hekla check . in CI, on every push.
  2. hekla test ., likewise.
  3. hekla secrets . against the target environment.
  4. hekla plan --replay --json . against the live data directory, and read divergences.
  5. Stop the process, copy the source tree, start it.
  6. Watch /status until projectors are ready and effects are healthy.

Step 4 is the one that is unusual, and it is the one worth keeping.