- 02 · Architecture
- docs/adr
Decision records
The decisions that were close calls, including the ones that cost something. Each records what was considered, what was chosen, and what the choice gives up — so the trade-off stays visible after the reasons are forgotten.
Records #
| ADR | Decision | Date | Status |
|---|---|---|---|
| 0001 |
One ARM node on Hetzner Cloud, running k3s
One Hetzner Cloud CAX21 — four Ampere vCPUs, 8 GB — running k3s, provisioned by Terraform and configured by Ansible. |
2026-07-04 | Accepted |
| 0002 |
Java 21 and Spring Boot, with the footprint measured and paid down in public
Java 21 on Spring Boot, Spring MVC on virtual threads, as a Gradle multi-project with a shared platform module for observability, error handling and event contracts. |
2026-07-04 | Accepted |
| 0003 |
Delivery is pulled by Flux, never pushed by CI
The cluster pulls. |
2026-07-04 | Accepted |
| 0004 |
Kafka as the broker, single node, replication factor 1
Apache Kafka in KRaft mode, one broker, replication factor 1. |
2026-07-16 | Accepted |
| 0005 |
Transactional outbox with a polling relay, not change data capture
A polling relay inside the API service, claiming rows with FOR UPDATE SKIP LOCKED. |
2026-07-16 | Accepted |
| 0006 |
Redis holds a read model, and the two services declare its shape separately
Redis stores it as JSON under a single typed serialiser. |
2026-07-16 | Accepted |
| 0007 |
Postgres and Redis gate API readiness; Kafka does not
The readiness group includes Postgres and Redis. |
2026-07-16 | Accepted |
| 0008 |
Every input is pinned, every output is signed
Inputs are pinned by immutable identifier. |
2026-07-22 | Accepted |
| 0009 |
Point-in-time recovery from archived WAL, with no operator and no derived image
Postgres' built-in continuous archiving: archive_command ships every completed WAL segment to the bucket, a nightly CronJob takes a pg_basebackup, and a restore lays down a base backup and lets the server replay archived WAL to a target time with restore_command. |
2026-08-06 | Accepted |
| 0010 |
Observability is self-hosted, budgeted, and measured at the gateway
Prometheus and Alertmanager for metrics and alerting, Loki with Grafana Alloy for logs, Tempo for traces, and Grafana over all three, provisioned as code. |
2026-07-29 | Accepted |
How they are written #
Every record has the same three parts. Context is the problem and the options, usually as a table with what each would cost here. Decision is one or two sentences. Consequences is the longest part, and the one that matters: what the decision gives up, what it makes true elsewhere in the repository, and when it should be revisited.
Records are amended rather than rewritten. ADR 0008 carries an amendment dated after it was accepted, explaining that a control it described had never been implemented — and what testing the implementation then found. A few were backfilled after the fact; their dates say both when the decision was made and when it was recorded.