Deploying
What the artefact is, what to back up, what to watch, and the limits stated plainly.
The artefact is the source tree
There is no build step, no compiled output and no compile cache. What you deploy is the
.hk files, plus hekla.toml if you have one, plus the master key if any field declares a
@subject.
Deploy is copy and restart. There is no hot reload: the project is read at startup, and a running process never re-reads it.
The data directory
data/
events/ tephra segments, the immutable source of truth
projectors/{Name}.db one read model and its checkpoint
hekla.db effect journals, subject keys, deployed declarations
hekla.lock.db the exclusive one-process lock
Two of those are irreplaceable: events/ and hekla.db. Projector databases are
rebuildable from the log, so they are the part you do not need to keep.
The lock lives in its own file rather than inside hekla.db, because the operational
database is written continuously and an exclusive transaction held for the process lifetime
would deadlock against it.
Opening any of these files directly is not a supported surface, and the table layout is private.
Backup
Copy the directory of a stopped process.
The good pattern is to stop, copy, start, and then run hekla verify against the copy.
That checks the invariants and the backup in one pass, on a machine that is not serving
traffic.
Migrations
The operational database migrates itself on open, one schema version per transaction, and refuses a database newer than the build:
operational database is at schema version 10, newer than this build (9)
So a downgrade is a refusal rather than a corruption. hekla plan refuses a directory this
build has not migrated yet; run hekla serve against it once first.
The event log has no migration story. An event type carries no version and there is no
upcast hook. See Events.
Observability
Logs
tracing, filtered by RUST_LOG, default info. Installed by serve and verify only,
so every other subcommand stays plain and machine-parseable: hekla openapi . > openapi.json
and hekla plan --json are safe to pipe.
Colour is on only when stdout is a terminal and nobody opted out. NO_COLOR with any
non-empty value, or --no-color, turns it off.
Lines worth alerting on: a failed ctrl-c handler registration (shutdown must then be forced), a completed projector rebuild with its duration, and an in-flight invocation whose recorded source hash differs from the code on disk, logged by name at startup.
/status
The whole runtime in one document: log head, uptime, every projector’s position, lag, readiness and last error, and every effect’s state, lag, wedged lanes, pinning key and pinning position. This is what a console or a health dashboard should read.
/metrics
Prometheus text exposition. Gauges are read at scrape time, so there is no interval and no
[metrics] section.
Gauges
hekla_build_info, hekla_uptime_seconds, hekla_log_head_position, hekla_module_info,
hekla_projector_up, hekla_projector_position, hekla_projector_lag,
hekla_projector_readiness, hekla_projector_last_progress_timestamp_seconds,
hekla_effect_up, hekla_effect_position, hekla_effect_lag, hekla_effect_state,
hekla_effect_wedged_lanes, hekla_effect_consecutive_failures,
hekla_effect_retry_backoff_seconds, hekla_effect_last_progress_timestamp_seconds
Counters
hekla_events_appended_total, hekla_commands_total, hekla_command_refusals_total,
hekla_command_conflict_retries_total, hekla_projector_events_total,
hekla_projector_rebuilds_total, hekla_effect_invocations_total,
hekla_effect_restarts_total, hekla_effect_terminal_skips_total,
hekla_effect_live_suppressed_total, hekla_effect_collapsed_total,
hekla_effect_http_requests_total, hekla_reads_total, hekla_read_waits_total
Two labelling rules are design commitments rather than conventions.
A lane key is never a label. Every label is a declaration: a command, projector,
entity, effect or event name, a refusal code, or a fixed outcome word. The reason is
erasure, not cardinality. A lane key is a partition key, and /status may name it
because it is a live view an erasure passes through; a scrape may not, because it is a copy
taken somewhere hekla erase cannot reach. So the metric counts wedged lanes and
/admin/effects/{name} names them.
A refusal series appears on first use. heklang inlines a refusal, so hekla cannot
enumerate the codes to prime them at boot. Any expression over
hekla_command_refusals_total needs or vector(0). Every other counter reads 0 from the
first scrape.
Alerts
The repository ships a rules file at docs/monitoring/hekla-alerts.yml with sixteen alerts,
each carrying the reasoning for its threshold inline: HeklaDown, HeklaProjectorStalled,
HeklaEffectWedged, HeklaEffectQuarantined, HeklaEffectBlocked,
HeklaCommandConflictsExhausted, HeklaLogNotAdvancing, HeklaReadYourWritesTimingOut
and others. Start from it rather than from scratch.
/metrics is unauthenticated like everything else, so the scrape target is reached the same
way /admin is.
The limits, stated plainly
Saying these first is the point, so here they are without hedging.
Nothing is authenticated. The bind address is the boundary and it defaults to loopback. A caller who reaches the port can append events, browse the console, read metrics and skip an effect’s work.
One process per data directory, enforced by an exclusive lock. There is no clustering, no replication and no horizontal scaling.
Deploy is restart. No hot reload, no upload API, no live editing.
The event log has no schema evolution. No version on a type, no upcast hook.
One indexed filter field per scan. Composite-prefix filters are not built.
No automatic dead-lettering. A wedged effect is never advanced automatically; the manual skip endpoint is the only escape hatch.
No hekla fmt and no language server. Both arrive when heklang has a language server of
its own. hek fmt exists today and is what to point an editor at; see
Editor setup.
/docs needs outbound network, because it loads its reference UI from a CDN.
/openapi.json does not.
invoke is exactly-once only when the target command is idempotent under replay. Raw
http.* is at-least-once. That is an inherent property of the design rather than future
work: the journal can guarantee a call is not repeated after a crash, but it cannot make a
remote endpoint idempotent for you.
Pre-1.0. hekla is 0.4 and heklang is 0.5. Both carry breaking changes between minor versions, and neither has run your workload yet.
And one that will not change
heklang is the only authoring surface, permanently. There is no Rust, TypeScript or WASM
SDK, and there is not going to be one. A single pure sandboxed authoring language is what
makes the rebuild, the replay and plan --replay sound, and an escape hatch into a general
purpose language would take all three away.