docs(adr): design spec for ADR structure & lifecycle (ADR-023)

Codifies the structure ADRs 019-022 converged on, pins an
Accepted/Superseded/Deprecated lifecycle with a no-silent-rewrite rule,
adds an adr-template.md scaffold, and plans a Status-header backfill of
ADRs 001-018. Basis for ADR-023.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-10 13:45:21 +02:00
parent 4116286ed0
commit dfbe37916f

View file

@ -0,0 +1,132 @@
# Design — ADR structure & lifecycle
- **Date:** 2026-06-10
- **Status:** Approved design — implementation plan to follow
- **Resolves:** the absence of a written standard for how ADRs in
`docs/decisions/` are structured. The newest ADRs (019022) have converged on a
clean pattern (`Status``Context``Decision``Consequences``Related`),
but it lives only as imitation; ADRs 001018 predate it and most lack a `Status`
section.
- **Becomes:** ADR-023 (this design is the basis for that ADR).
- **Reuses:** boma's existing `*-template.md` convention (`service-security-template.md`,
`service-verify-template.md`, `service-access-template.md`, `service-backup-template.md`);
ADR-014 (knowledge-sourcing → the optional `Verified facts` section); ADR-019/020/021/022
(the emergent structure being codified); the `/review-repo` command (enforcement home).
---
## Problem
boma documents architectural decisions as numbered ADRs in `docs/decisions/`, and
CLAUDE.md treats them as load-bearing ("Before assuming a role, provider, or pipeline
exists, check STATUS.md"; the entire "Further reading" table points into them). Yet
there is no ADR that says how an ADR is written. The result:
- **Structural drift.** ADRs 001018 are freeform; 019022 converged on a consistent
shape but only by imitation. A new ADR's structure depends on which existing one the
author happened to copy.
- **No status discipline.** Most early ADRs have no `## Status` section, so there is no
uniform way to tell an active decision from a superseded or deprecated one — and no
written rule for how a decision gets reversed without silently rewriting history.
- **No scaffold.** Every other recurring document type in boma has a template
(`service-security-template.md`, etc.). ADRs do not.
This design codifies the structure 019022 already demonstrate, pins a status
lifecycle, ships a template, and reconciles the back-catalogue.
## Scope
- **In:** the canonical section set (mandatory + optional); title and filename
convention; the `Accepted / Superseded / Deprecated` status lifecycle and the
no-silent-rewrite rule; cross-reference convention; an ADR template file; a
lightweight `/review-repo` structure check; a one-time backfill of `## Status` into
the ADRs that lack one.
- **Out (for now):** rewriting the *decisions* in any existing ADR; normalizing the
body section names of 001018 beyond adding `Status`; a `make lint` / CI gate for
ADR structure (explicitly rejected in favour of the `/review-repo` check —
consistent with boma's other doctrine ADRs, which add no CI gate); a "Proposed"
draft stage (rejected — boma is single-contributor and trunk-based with no review
gate, so ADRs are born Accepted).
## Decision
### 1. Title & filename
- Title line: `# ADR-NNN — <Title>: <optional clarifying subtitle>` (em-dash `—`,
matching every existing ADR).
- Filename: `NNN-kebab-title.md`, zero-padded 3-digit, monotonic, **never reused**
(a superseded ADR keeps its number and file).
- A new ADR is registered as a row in the CLAUDE.md "Further reading" table.
### 2. Canonical sections
**Mandatory — every ADR, in this order:**
| Section | Holds |
|---|---|
| `## Status` | `Accepted (YYYY-MM-DD)`, plus an optional one-line note (what it resolves/supersedes, or a doctrine-not-yet-built caveat as ADR-022 uses) |
| `## Context` | the forces, the problem, what exists today, why now |
| `## Decision` | what we are doing — numbered sub-decisions for multi-part ADRs, as 020/021/022 do |
| `## Consequences` | results, trade-offs *explicitly accepted*, follow-on work |
**Optional — use only where genuinely applicable, never as padding:**
- `## Related` — links to other ADRs by number.
- `## Scope` — explicit in/out-of-scope boundaries.
- `## Guardrails` / `## Enforcement` — how the decision is mechanically enforced
(lint, CI, hooks).
- `## What was ruled out` — rejected alternatives, each with its reason.
- `## Verified facts (ADR-014)` — version-stamped facts per the knowledge-sourcing rule.
### 3. Status lifecycle
Three states; no "Proposed" stage.
- Born **`Accepted (YYYY-MM-DD)`** — the sole author commits to it on writing.
- Replaced by a later decision → the old ADR's Status becomes
**`Superseded by ADR-NNN (YYYY-MM-DD)`**; the superseding ADR records
`Supersedes ADR-MMM` in its own `## Status` and `## Related`. The link is
**bidirectional** — both files must point at each other.
- Retired with no replacement → **`Deprecated (YYYY-MM-DD)`** plus a one-line reason.
**Load-bearing rule — no silent rewrites.** An `Accepted` ADR is not edited to reverse
its decision. Typo and clarity fixes are fine; a *material reversal* requires a new ADR
and a `Superseded by` marker on the old one. The history of decisions stays legible.
### 4. Cross-references
Reference other ADRs by number inline (`ADR-019`), and collect the relationships in a
`## Related` section.
### 5. Template file
Ship `docs/decisions/adr-template.md` — consistent with boma's existing
`*-template.md` convention. It contains the mandatory section headers pre-filled with
short HTML-comment hints, and the optional sections listed as commented stubs to
uncomment when relevant. It is a skeleton, not a numbered decision, so it does not take
an ADR number.
### 6. Retroactive backfill (001018)
A **separate follow-up step** after the ADR and template land: add a `## Status`
section to every ADR that lacks one. Status value is `Accepted (YYYY-MM-DD)` where the
date is reconstructed from each file's **first git-commit date**. **Only the Status
header is added — no decision content is touched.** ADRs already carrying a `## Status`
(019022) are left alone.
### 7. Enforcement
Lightweight, no CI gate. The `/review-repo` command gains an ADR-structure check:
every file in `docs/decisions/` matching `NNN-*.md` has the four mandatory sections and
a parseable `## Status` line. The template carries the convention forward for new ADRs.
## Consequences
- New ADRs have one obvious shape and a scaffold to start from; structural drift stops.
- Every ADR declares its lifecycle state uniformly, and reversals are traceable rather
than silent — the back-catalogue becomes a legible decision history.
- One-time churn: a backfill commit touching ~18 files (Status header only).
- `/review-repo` grows a new check; no new CI machinery, matching boma's habit of not
gating doctrine in CI.
- This ADR is itself the first conformant example — it must follow its own structure.
## Open questions
None outstanding — title/filename, the 3-state lifecycle, template name
(`adr-template.md`), enforcement (`/review-repo`, no CI gate), and the
Status-only backfill were all confirmed during brainstorming.