Thanks for looking under the hood. Ground rules that keep this codebase easy to work on:
Development
mix deps.get
mix test # full suite against the in-memory adapter
BELAY_PG=1 mix test # same suite against Postgres
The Postgres run expects a server at localhost:55433 (password belay):
docker run -d --name belay-postgres -e POSTGRES_PASSWORD=belay \
-e POSTGRES_DB=belay_test -p 55433:5432 postgres:16
CI additionally runs the PostgreSQL suite against supported endpoints 14 and 18.
The rules that matter
- Storage semantics live once. Anything both adapters must agree on belongs in the pure shared-logic module (lib/belay/storage.ex) or in the shared test suite. If you add a storage operation, implement it in Memory and Postgres and cover it with a test that runs against both — the suite is the contract.
- Never read the wall clock in engine code. Take
nowas an argument (SQL included:$nowparameters, notnow()). Tests advance aBelay.Clock.Sim; aProcess.sleepin a test is a review flag. - No leaders. If your feature needs cluster-wide once-ness, express it as an idempotent operation deduped by the database (unique index, row-level atomicity), not by election.
- First-class columns over meta blobs. If the engine branches on it, it gets a column and an index, not a JSON path.
- Failure honesty. Document at-least-once edges and race windows in the moduledoc where they live. A known caveat in writing beats an implicit one in production.
Pull requests
- One logical change per PR; tests required, on both adapters where storage is touched.
mix compile --warnings-as-errorsmust pass.- Public functions get
@docand specs; internal modules get@moduledoc falseand a one-paragraph comment saying what they own.
Reporting issues
Include: Belay version, storage adapter, Postgres version, and — for
engine bugs — the smallest failing scenario you can express with
Belay.Testing.drain/2 and a SimClock. Security reports: email the
maintainer rather than opening a public issue.