Skip to content
ADRs

Architecture Decision Records

One short, backward-looking document per non-obvious design decision — its context, the decision, and the consequences — so a future contributor can see why a thing is the way it is and not accidentally undo a deliberate choice. Why this format and not a monolithic design doc: 0001 .

The system in one pass

certel is a self-contained certificate monitor (0002 ): one binary, one YAML file, its own scheduler, state and alerting — no Prometheus/Alertmanager required. The ADRs each pin one link of the chain:

  • Probe. On a fixed cycle the scheduler (0014 ) probes each target — identity protocol//address/servername (0016 ) — over implicit TLS or STARTTLS. The probe accepts legacy handshakes and verifies trust itself (0013 ), verifying the chain before diagnosing expiry (0012 ).
  • Decide. Statuses map onto the ok < warning < critical < emergency ladder (0008 ); the day thresholds have exact boundary semantics (0011 ); network-shaped statuses must persist flap_streak cycles before they count (0009 ). The alert Manager keeps one decision stream per target with a severity-aware repeat cadence (0010 ) and commits confirmed state to SQLite atomically with the deliveries it owes (0003 ).
  • Deliver. A decision enqueues one rendered notification per attached notifier — fan-out is delivery-only, the repeat clock lives on the target (0006 ) — into a kind-agnostic outbox (0004 ) drained by isolated per-notifier dispatchers. Notifiers are plain config webhooks (0005 ) with structured bodies (0018 ) and a stateless min_severity floor (0007 ).
  • Observe & contract. Optional Prometheus metrics with ssl_exporter-compatible names (0015 ); the whole config surface was reviewed and locked before 1.0 (0017 ).

Conventions

  • Location & filename: docs/adr/adr-NNNN-kebab-case-title.md, four-digit zero-padded number, assigned monotonically in order added (never renumbered).
  • Template: copy adr-0000-template.md .
  • Status: one of Proposed · Accepted · Superseded by NNNN · Deprecated. ADRs are append-only: a decision that changes gets a new ADR that supersedes the old one; the old one is marked Superseded by NNNN and left in place, not edited away.
  • Cross-reference, don’t duplicate. docs/alternatives.md (the build-vs-buy survey) and docs/metrics.md (the full metrics surface) are longer living design docs; ADRs link to them rather than copying their content.

Lifecycle: how a todo/ becomes an ADR

The todo/ tree holds forward-looking work items; ADRs are backward-looking rationale. When a todo/ design lock is resolved and its feature ships, its outcome graduates into an ADR and the todo is deleted. The ADR must absorb everything decision-relevant from the todo — ADRs do not cite deleted todo files; the shipping commit’s history holds their full text if archaeology is ever needed. Open todos (e.g. config-reload.md, notifier-failure-backoff.md, packaging.md) are not ADRs yet; they become one only once decided and shipped.

Not every change earns an ADR. Bug fixes and mechanical hardening — timeouts, path escaping, CI pinning, input validation — live in git history, not here. An ADR is for a decision that shaped the design and could plausibly be reversed by someone who didn’t know why.

Index

ADRDecision
0001Record architecture decisions in ADRs
0002Build a self-contained monitor, not an exporter add-on
0003Persist alert state and queue deliveries in SQLite
0004A kind-agnostic, notifier-scoped delivery queue
0005Config-only named webhook notifiers
0006Multi-notifier fan-out is delivery-only; the repeat clock lives on the target
0007Per-notifier min_severity as a stateless delivery floor
0008Three-tier severity ladder and the status→severity mapping
0009Debounce network-shaped statuses; fact statuses alert on the first cycle
0010Severity-aware repeat cadence
0011Alert when strictly fewer than N whole days remain
0012Verify the chain before diagnosing expiry
0013Accept TLS ≥ 1.0, verify trust manually; the weak_signature status
0014Size the scheduler cycle and liveness to the worst legal cycle
0015Prometheus metrics surface and ssl_exporter name compatibility
0016“target” as the domain vocabulary and Target.Key identity
0017Lock the config surface before 1.0
0018Structured webhook bodies with ${namespace.path} interpolation