0001. Record architecture decisions in ADRs
- Status: Accepted
- Date: 2026-07-12
Context
By the pre-1.0 mark, certel had accumulated many non-obvious design decisions —
alert_repeat_interval lives on the target not the notifier, unreachable is
critical not emergency, fan-out is delivery-only, min_severity is a stateless
floor, the SQLite outbox state model, per-notifier concurrency. The reasoning
behind each was real and deliberate, but it lived in three places that all decay:
commit messages (unindexed), todo/ prose files (deleted once the feature
shipped), and docs/alternatives.md (one topic only). A contributor had no
single place to read to understand the design and its trade-offs, and nothing
stopped a future change from quietly undoing a deliberate choice.
The candidate formats:
- ADR — one short doc per decision (context / decision / consequences), append-only, superseded-not-edited. Low ceremony.
- TSD (Technical/Software Design Doc) — one or a few long docs describing the whole system. Good for onboarding; heavy to keep current.
- FRD (Functional Requirements Document) — the product contract from the user’s point of view: the what, not the why.
Decision
We adopt ADRs as the primary format, laid out under
docs/content/adr/adr-NNNN-title.md with the conventions in this section’s
index
: monotonic four-digit numbering, a
Proposed/Accepted/Superseded/Deprecated status line, and an append-only rule
(a changed decision gets a new superseding ADR; the old one stays).
certel’s design is, concretely, a pile of individual “we chose X over Y” calls —
the exact unit an ADR captures. ADRs also pair naturally with how the reasoning
is produced: each resolved todo/ design lock graduates into one ADR. We
deliberately stand up no TSD or FRD, because their jobs are already covered:
the big-picture/onboarding role by the README
plus the two
standing design docs (alternatives.md
,
metrics.md
), and the feature/scope contract by the README and
config.example.yaml
. This chooses the primary
format; it does not forbid the others (see the alternatives below).
Consequences
- Every deliberate, reversible-by-the-uninformed decision gets a discoverable home.
- The
todo/→ ADR lifecycle is explicit (see the README): forward-looking work items graduate into backward-looking records when they ship. - The bar stays sustainable by excluding mechanical fixes and hardening from ADRs — those stay in git history. The failure mode being avoided is a heavyweight process nobody updates.
- Existing long-form design docs are cross-referenced, never copied, so there is one source of truth per topic.
Alternatives considered
- A single TSD for the whole system. Rejected as the primary format: it would go stale as one big document, and the README already carries the big picture. A short overview in this directory’s README is cheaper than a parallel design doc to maintain.
- An FRD. Not adopted now: it would restate the feature contract the README
and
config.example.yamlalready pin, and it captures the what, whereas the gap here is the why. Not rejected outright — a formal 1.0+ scope contract, acceptance testing, or external stakeholders could earn it a place later, as a complement to the ADRs. - Leaving the rationale in commit messages and
todo/files. Rejected — that is the status quo this ADR exists to fix: unindexed and deleted-on-ship.