Skip to content
hekla

hekla, the runtime

All ten subcommands, what each one needs, and what each one refuses.

hekla [OPTIONS] <COMMAND>

Ten subcommands. --no-color is global and is accepted before or after the subcommand; colour is off anyway when output is not a terminal or NO_COLOR is set to a non-empty value.

Every DIR argument defaults to ., and every --data-dir defaults to <dir>/data.

What each one needs

project data dir takes the lock master key exit 1 when
hekla check yes no no no any error finding
hekla test yes no no no a failing test or an error
hekla secrets yes no no no a required credential is unset
hekla openapi yes no no no errors, or nothing to describe
hekla plan yes yes no with --replay errors, or an unmigrated directory
hekla serve yes yes yes if any @subject it could not start
hekla verify yes yes yes if any @subject the report is not clean
hekla rewind yes yes yes no no such effect, or the lock is held
hekla erase no yes no no no operational database
hekla rotate no yes no yes the key is unset

The lock column decides what you can run against a live server: plan, erase and rotate are fine; verify and rewind need it stopped.

check

hekla check .
checked 2 module(s): 1 command(s), 1 projector(s), 0 effect(s), 1 event(s)
ok: no errors, 0 warning(s)

Runs the compiler and adds the runtime’s own rules: that a declaration sits in the directory its kind requires, and that a read model can be keyed and indexed the way the read API needs.

It runs nothing, opens no data directory and reads no environment, which is what keeps it usable as a CI gate.

Findings print as severity: file:line:col: message, with an optional = hint under them.

What it adds over the compiler

Errors: a command outside commands/, a projector outside projectors/, an effect outside effects/, an event field beginning with the reserved _hekla_, a sealed entity column that is not optional, an index covering a subject-encrypted column, and a filterable field colliding with a reserved read query parameter (limit, cursor, after, timeout_ms).

Warnings: a fold slice with no high-cardinality filter, an over-constrained slice, a declared secret nothing reads, and a [secrets] entry naming no declaration.

test

hekla test .
ok: "a new address joins the list"
ok: "the same address twice is refused"

2 passed, 0 failed

Runs every test declaration in the program, wherever it is declared, synthesising the event envelope the runtime would produce. A project with no tests prints 0 passed, 0 failed and exits 0.

serve

hekla serve . --addr 0.0.0.0:8080
Flag Default
--addr 127.0.0.1:8080
--data-dir <dir>/data
--verify off

See Running a project for startup order, shutdown and every configuration key.

plan

hekla plan --replay --json .
Flag
--json the whole plan on stdout, for a deploy gate
--replay re-run recorded effect invocations against the candidate code
--replay-limit N how many of each effect’s most recent invocations, default 1000, minimum 1
--data-dir

--replay-limit requires --replay, because a cap on a replay that is not happening is a request the command would otherwise accept and drop.

Exits 0 whether or not anything would change: a change is the answer, not a fault. Full treatment in Change safety.

verify

hekla verify .

Checks that a projector rebuilt from position 0 matches the live one, and that every recorded effect invocation still replays without performing anything.

Takes the lock, so it refuses against a directory a server has open. Verify a copy, which checks the backup at the same time. Exit 0 only when the report is clean.

secrets

hekla secrets .
  DISCORD_WEBHOOK  set      2633c771     env DISCORD_WEBHOOK_URL
  SENTRY_DSN       unset    (optional)   env HEKLA_SECRET_SENTRY_DSN
  STRIPE_KEY       MISSING               file /run/secrets/stripe

failed: 1 of 3 credential(s) unset; serving would refuse to start

Never prints a value. Reads the environment and [secrets] and nothing else: no data directory, no log, no lock. Exits non-zero when a required credential is unset, so it stands alone as a pre-deploy gate.

erase

hekla erase customer_id 7 .
erased subject `customer_id` = `7`

Deletes that subject’s encryption key, making every value scoped to it unreadable across the log and every read model at once. Irreversible. Needs no master key and takes no lock.

A subject with no key answers no key for subject ... (already erased or never created). Both exit 0.

rotate

hekla rotate .
rewrapped 412 subject key(s) under the primary master

Rewraps every subject key under HEKLA_MASTER_KEY, unwrapping with HEKLA_MASTER_KEY_PREVIOUS as needed. Ciphertext is unchanged, so reads keep working. The ordered procedure is in Keys and secrets.

rewind

hekla rewind NotifyCustomer 412 .

Moves an effect back to a position so it reprocesses everything after it and performs those side effects again. Irreversible.

Flag
--live also lower an on live boundary. Permanent, and off by default
--yes answer the prompt without silencing the summary
--data-dir

Position 0 reprocesses the whole log. Refuses while a server holds the directory, and without a TTY refuses unless --yes is given.

Deliberately not an HTTP endpoint. See Effects in production.

openapi

hekla openapi . > openapi.json

The generated OpenAPI 3.1 document on stdout, findings on stderr, so the redirect is safe. The same document /openapi.json serves.

Refuses a path that is not a directory, a project with errors, and a directory that declares nothing at all.

There is no hekla fmt or hekla lsp

Neither exists. They arrive when heklang has a language server of its own. Formatting is hek fmt’s job, and it rewrites every .hk file a project holds today.