From a9aab9d0408c308b7852f0429f42ca48e8a62cfe Mon Sep 17 00:00:00 2001 From: sjat Date: Wed, 10 Jun 2026 14:32:40 +0200 Subject: [PATCH] =?UTF-8?q?docs(adr):=20ADR-023=20=E2=80=94=20ADR=20struct?= =?UTF-8?q?ure=20&=20lifecycle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/decisions/023-adr-structure.md | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 docs/decisions/023-adr-structure.md diff --git a/docs/decisions/023-adr-structure.md b/docs/decisions/023-adr-structure.md new file mode 100644 index 0000000..9c7246d --- /dev/null +++ b/docs/decisions/023-adr-structure.md @@ -0,0 +1,101 @@ +# ADR-023 — ADR structure & lifecycle + +## Status + +Accepted (2026-06-10). Meta/doctrine ADR — pins how ADRs are written; the +`adr-structure` check (`scripts/repo-scan.py`) and `docs/decisions/adr-template.md` +ship with it, and ADRs 001–018 were retroactively restructured to conform. Resolves +the FRICTION signal (2026-05-31) about ADR-writing policy being unsettled. + +## Context + +boma records architectural decisions as numbered ADRs in `docs/decisions/`, and +CLAUDE.md treats them as load-bearing. Yet no ADR said how an ADR is written. The +newest ADRs (019–022) converged on a clean shape — Status → Context → Decision → +Consequences → Related — but only by imitation. ADRs 001–018 predate it and drifted +widely: most lacked a `## Status` section entirely (016–018 carried only a trailing +build-state note), and many lacked an explicit `## Decision` or `## Consequences` +heading, their decisions spread across ad-hoc topical sections. The result was +structural drift and no uniform way to tell an active decision from a superseded or +deprecated one. + +## Decision + +### 1. Title & filename + +Title line: `# ADR-NNN — : <optional clarifying subtitle>` (em-dash). 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. Mandatory sections, in this order + +- `## Status` — `Accepted (YYYY-MM-DD)`, plus an optional one-line note. +- `## Context` — the forces, the problem, what exists today, why now. +- `## Decision` — what we are doing; numbered sub-decisions for multi-part ADRs. +- `## Consequences` — results, trade-offs explicitly accepted, follow-on work. + +### 3. Optional sections (use only where they genuinely apply) + +`## Related`, `## Scope`, `## Guardrails` / `## Enforcement`, `## What was ruled out`, +`## Verified facts (ADR-014)`. + +### 4. Status lifecycle + +Three states; no "Proposed" stage (boma is single-contributor and trunk-based with no +review gate, so an ADR is born committed-to). + +- Born **`Accepted (YYYY-MM-DD)`**. +- Replaced → old ADR's Status becomes **`Superseded by ADR-NNN (YYYY-MM-DD)`**; the new + ADR records `Supersedes ADR-MMM` in its Status and `## Related`. The link is + **bidirectional**. +- Retired with no replacement → **`Deprecated (YYYY-MM-DD)`** + a one-line reason. + +**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. + +### 5. Template & enforcement + +`docs/decisions/adr-template.md` is the scaffold for new ADRs. The `/review-repo` +command's pre-scan (`scripts/repo-scan.py`) emits an `adr-structure` finding for any +numbered ADR missing a mandatory section or with an unparseable Status line. It checks +**presence and Status, not section order** — order is a convention the template carries, +deliberately not gated, to keep enforcement lightweight (consistent with boma's other +doctrine ADRs adding no CI gate). + +### 6. Retroactive conformance of the back-catalogue + +ADRs 001–018 are restructured to satisfy this standard rather than grandfathered. The +restructure is **presentational** — existing headings are relabelled, regrouped, or +demoted under a `## Decision` umbrella; a dated `## Status` is added; a `## Consequences` +section is assembled from implications the ADR already states. **The substance of no +decision is changed.** This keeps the check uniform (no number threshold) and the corpus +a consistent, legible decision history. + +## Consequences + +- New ADRs have one obvious shape and a scaffold; structural drift stops. +- Every ADR declares its lifecycle state uniformly, and reversals are traceable. +- The whole corpus conforms; the check needs no grandfathering and stays simple. +- One-time restructure churn across ADRs 001–018 (heading reorganization + a Status and + a Consequences section per file; no decision substance changed). +- `/review-repo` grows one deterministic check; no new CI machinery. +- This ADR is the first conformant example and is held to its own check. + +## What was ruled out + +- **A "Proposed" draft stage** — no review gate exists for it to serve. +- **A `make lint` / CI gate for ADR structure** — heavier than the risk warrants; + the `/review-repo` check and the template suffice. +- **Machine-enforcing section order** — brittle for marginal value; left as a + template-demonstrated convention. +- **Grandfathering 001–018 from the check** — rejected in favour of restructuring the + whole corpus to conform, so the standard applies uniformly with no exceptions. + +## Related + +- ADR-014 — knowledge sourcing (the `Verified facts` optional section). +- ADR-019/020/021/022 — the emergent structure this ADR codifies. +- `docs/decisions/adr-template.md` — the scaffold. +- `scripts/repo-scan.py` — the `adr-structure` enforcement check.