Current section
Files
Jump to
Current section
Files
CHANGELOG.md
# Changelog
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Versioning: [SemVer](https://semver.org/).
## [Unreleased]
## [0.9.0] - 2026-07-31
No change to `src/`. This release is the scaffold, its guard, and a
documentation pass; consumers of the library itself get the same behaviour
they had on 0.8.0.
### Added
- **`rebar3 new hecate_service`**, a real rebar3 template in `priv/templates`,
generating a repository that compiles, tests and deploys: the OTP application
and supervisor, the six-callback service module, a eunit suite asserting the
contract, a relx release, a `Containerfile`, both CI workflows, an executable
`scripts/health.sh`, `deploy/docker-compose.yml`, and the usual documentation.
- `scripts/install-templates.sh`, because rebar3 only finds custom templates
under `~/.config/rebar3/templates` and an empty directory has no dependency to
carry them there.
- `hecate_service_template_SUITE`, which generates a service for real through
`rebar3 new` and then compiles it against this library, so the
`-behaviour(hecate_om_service)` attribute checks all six callbacks. It also
asserts the file set, that `health.sh` is executable, that no unrendered
variable survives, and that GitHub Actions expressions are intact.
- `.github/workflows/lint-and-test.yml`. This repository had no CI at all, which
is how the old templates drifted unnoticed.
### Changed
- **`scripts/scaffold-service.sh` is now a wrapper over the template** and takes
the service name once, deriving the snake_case application name from the
kebab-case repository name. It previously rendered a handful of files with sed
and left you to write `rebar.config`, the `.app.src` and the supervisor by
hand, so a scaffolded service did not compile.
### Fixed
- The hex package links and the ex_doc `source_url` pointed at Codeberg.
GitHub has been canonical since 2026-07-26.
- `guides/container_deployment.md` described a deployment that does not exist:
system-wide Podman Quadlets reconciled by `hecate-gitops`, a
`hecate-realm-admin` CLI, a loopback-published health port. It referenced
three template files that no longer exist. Rewritten to describe what the
generated service actually does, with a closing section naming what is
intended rather than built.
- `guides/service_anatomy.md` showed a repository layout with `quadlet/`, a
`manifest.json` and a flat `src/`, none of which the scaffold produces.
- The README's status line claimed v0.5.0.
### Removed
- **The old `templates/` directory.** It had drifted from the estate it was
meant to serve: a Quadlet unit that nothing on the fleet uses (the beam nodes
run docker compose under a pull-based reconciler), `TODO` comments in place of
two callbacks, a store-backed service as the default in a mostly producer-only
estate, an `identity_spec` claiming two actions and a wildcard resource for a
service that could exercise none of them, and no test file at all. Nothing
consumed it: the `hecate-om scaffold` CLI its own README documented was never
written.
## [0.8.0] - 2026-07-26
### Changed
- **Requires macula `~> 7.0`** (was `~> 6.0`). macula 7.0.0 teaches the
canonical encoder floats (IEEE 754 binary64, RFC 8949 major type 7), so
services publish raw float telemetry again and stop scaling to integers to
get a number past our own codec.
This is a WIRE change upstream: a peer on macula 6.x finds no clause for
major 7 and rejects a frame carrying a float. Both ends of any topic that
will carry floats must be on 7.x, so roll stations and services together
rather than piecemeal.
0.7.0 was the stopgap that made the old restriction loud instead of silent.
This is the release that removes the restriction. See macula CHANGELOG 7.0.0.
## [0.7.0] - 2026-07-26
### Changed
- **Requires macula `~> 6.0`** (was `~> 5.1`). BREAKING for consumers, because
macula 6.0.0 changed publish behaviour and that passes straight through:
`macula:publish/4,5` now returns `{error, {unsupported_payload_type, Type,
Path}}` where it previously returned `ok`, for raw floats, tuples, colliding
map keys, out-of-range integers and oversized payloads.
Those publishes were not working before. A float was silently rewritten as a
six-decimal text string, and an unrepresentable term killed the shared
peering connection while its sender was told `ok`. Services publishing raw
floats must scale to integers (micro-units) or send binary strings.
No wire-format change, so a service on 0.7.0 interoperates with stations and
peers on older macula; the guard is entirely sender-side.
See macula CHANGELOG 6.0.0.
## [0.6.0] - 2026-07-14
### Fixed
- `GET /health` is now actually served. `hecate_om_health_handler` defined the
route but nothing ever mounted it on a listener, so `/health` was dead code
and every hecate_om service reported unhealthy to Podman/k8s (nothing bound
`health_port`). `hecate_om_sup` now starts a Cowboy listener on `health_port`,
dispatching to the handler — gated on a valid `health_port`, so a service that
wants no HTTP health endpoint simply omits the config. `snapshot/0` already
calls the registered service's `health/0` live, so a healthy service returns
200.
### Changed
- `macula` dependency bumped to `~> 5.1` (connect-hang fix).
## [0.5.0] - 2026-07-04
### Added
- Optional `store_integrity/0` service callback. When exported, `hecate_om:boot/1`
threads its value (`disabled`, or `#{enabled => true, key_source => ...}`) into
the reckon-db store config, enabling per-store HMAC event tamper-resistance.
Defaults to `disabled` (backward compatible). `hecate_om_store:ensure/5` +
`ensure_store/5` accept the integrity config explicitly.
## [0.4.0] - 2026-07-02
### Added
- **Optional `store_mode/0` service callback** — `single` (default) or
`cluster`. When a service exports it, `hecate_om:boot/1` auto-starts its
reckon-db store in that mode; `cluster` enables reckon-db discovery + Ra
clustering so the store spans every node that starts the same `store_id`.
New `hecate_om_store:ensure/4` + `ensure_store/4` carry the mode; the
`/2` and `/3` arities keep defaulting to `single` (backward compatible).
Previously the auto-started store was always `single`, with no override.
## [0.3.4] - 2026-06-24
### Fixed
- **`hecate_om_store:ensure/3` and `ensure_store/3` were not exported** in
0.3.3, so `hecate_om:boot/1`'s cross-module call crashed with `undef` at
boot (`{hecate_om_store_failed, ..., undef}`). The store-index wiring added
in 0.3.3 was therefore dead on arrival. Added both to `-export`.
## [0.3.3] - 2026-06-24
### Added
- **Optional `store_indexes/0` service callback.** When a service exports it
alongside `store_id/0` + `data_dir/0`, `hecate_om:boot/1` installs the
returned reckon-db secondary index declarations (e.g. `{payload, Key}`,
`{payload_hash, [Keys]}`) on the auto-started store. Previously the
auto-wired store was created with **no** indexes, so a service that also
declared indexes via its own `start_store` call hit `{already_started}`
and its declarations were silently dropped — CCC payload indexes never got
registered. `hecate_om_store:ensure/3` and `ensure_store/3` carry the index
list; the `/2` arities delegate with `[]`.
### Changed
- Bumped the reckon-db stack pins to the current ecosystem: `reckon_db
~> 5.4` (was `~> 2.3` — needed for the `#store_config.indexes` field),
`evoq ~> 1.21` (was `~> 1.15`), `reckon_evoq ~> 2.6` (was `~> 2.1`).
## [0.3.2] - 2026-06-03
### Added
- **`MACULA_STATION_SEEDS` env override for station seeds.** When set
(comma-separated station URLs), it takes precedence over the
`station_seeds` app env in `hecate_om_identity:configured_seeds/0`;
empty/unset falls back to the app env. Lets one deployed image dial a
distinct station per instance without a rebuild (e.g. one bot per node,
one station each), matching the existing seed-via-env convention.
## [0.3.1] - 2026-06-01
### Fixed
- **Mesh connect no longer gated on the service-principal cert.**
hecate_om_identity:attach_client/1 previously short-circuited to
`undefined` (never calling macula:connect/2) whenever the cert file was
absent, leaving every cert-less service permanently `no_client`. The cert
was a spurious gate: it is never passed to `connect` (the SDK
auto-generates an ephemeral peering identity for empty opts), only loaded
and held for `service_cert/0` / the v2 realm-membership swap-in. Connect
now keys off configured `station_seeds`, not cert presence.
- **Connect is deferred off the init path and retried.** At boot `hecate_om`
could start before the macula SDK app was fully up; a single inline connect
raced it and lost. `init/1` now schedules `self() ! connect`, retries every
`?RECONNECT_MS` until a pool attaches, monitors the pool, and re-attaches
if it later dies.
### Added
- Optional `identity_key_path` env: when set + loadable, the service peers
under a stable on-disk macula-native keypair (consistent node id across
restarts) via `#{identity => KeyPair}`; otherwise the SDK auto-generates an
ephemeral identity. Identity is for peering, not authorization.
## [0.3.0] - 2026-05-19
### Added
- `hecate_om_store` module: canonical reckon-db + evoq wiring helper.
Encapsulates `reckon_db_sup:start_store/1` + 30s readiness wait +
`evoq_store_subscription:start_link/1`. The pattern documented as
mandatory in `hecate-corpus/skills/ANTIPATTERNS_EVENT_SOURCING.md`
now lives in one place.
- Optional callbacks on `hecate_om_service`: `store_id/0` and
`data_dir/0`. When a service module exports both, `hecate_om:boot/1`
auto-runs the canonical wiring before `ServiceMod:start/1`.
- New template `templates/sys.config.src.tmpl` with the canonical
reckon_db + evoq blocks.
- `scripts/scaffold-service.sh` now renders `config/sys.config.src`
alongside the service modules.
### Changed
- `_service.erl.tmpl` includes the optional `store_id/0` + `data_dir/0`
callbacks by default; producer-only services remove both.
- `rebar.config` adds reckon_db, evoq, reckon_evoq as deps so services
using `hecate_om` get the store-wiring stack for free. Producer-only
services inherit the image-size cost but not the runtime cost
(nothing starts unless the service module declares `store_id/0`).
### Why
Each new CMD/PRJ service was rediscovering the canonical reckon-db
wiring (or, more often, missing pieces of it). The parksim trio
shipped without `{evoq, [{event_store_adapter, ...}]}` and without
any `reckon_db_sup:start_store/1` call, leaving evoq in default
in-memory mode despite being configured as event-sourced. This
release moves the pattern into the library so future services pick
it up just by exporting two callbacks.
## [0.2.0]
### Added
- Initial scaffold: `hecate_om_service` behaviour, helpers for
identity claim, capability advertise, and `/health` endpoint.
- Templates for `Containerfile`, Quadlet unit, `manifest.json`, and
CI workflow.
- Guides: service anatomy, identity model, container deployment.
### Planned
- UCAN-delegated identity wiring once `hecate-realm` issues service
principals
- Common Test framework helpers for service test suites
## [0.1.0] - YYYY-MM-DD
_Not yet released._