docs(adr): revise spec+plan — full retroactive restructure of 001-018

Replaces the Status-only backfill with a faithful presentational
restructure bringing the whole back-catalogue to 4-section conformance
(no grandfathering). Adds the faithfulness rule and per-file worklist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
sjat 2026-06-10 14:28:20 +02:00
parent a3ea0f7d80
commit 89179dd7c9
2 changed files with 126 additions and 85 deletions

View file

@ -23,7 +23,7 @@
- **Status lifecycle (3 states):** `Accepted (YYYY-MM-DD)` → optionally `Superseded by ADR-NNN (YYYY-MM-DD)` or `Deprecated (YYYY-MM-DD)`. No "Proposed" stage.
- **No silent rewrites:** material reversal = new ADR + `Superseded by` marker; bidirectional link.
- **Enforcement checks presence + parseable Status line, NOT section order.** Order is demonstrated by the template, not machine-enforced.
- **Backfill is Status-header-only** — no decision content touched.
- **Back-catalogue is fully restructured (no grandfathering)** — ADRs 001018 are brought to all-four-section conformance. The restructure is **presentational**: relabel/regroup/demote existing headings, add a dated Status, assemble a Consequences section from implications the ADR already states. **The substance of no decision is changed.** If a faithful Consequences cannot be drawn from existing content, escalate that file rather than inventing one.
---
@ -280,18 +280,20 @@ Create `docs/decisions/023-adr-structure.md`. It must pass its own check (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. Resolves the FRICTION signal (2026-05-31) about ADR-writing policy
being unsettled.
ship with it, and ADRs 001018 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 (019022) converged on a clean shape — Status → Context → Decision →
Consequences → Related — but only by imitation, and ADRs 001018 predate it: 001015
carry no `## Status` section at all, and 016018 have a trailing build-status note
rather than a lifecycle line. The result is structural drift and no uniform way to
tell an active decision from a superseded or deprecated one.
Consequences → Related — but only by imitation. ADRs 001018 predate it and drifted
widely: most lacked a `## Status` section entirely (016018 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
@ -338,12 +340,22 @@ numbered ADR missing a mandatory section or with an unparseable Status line. It
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 001018 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.
- One-time backfill churn: a Status header added to ADRs 001018 (header only; no
decision content changed).
- The whole corpus conforms; the check needs no grandfathering and stays simple.
- One-time restructure churn across ADRs 001018 (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.
@ -354,8 +366,8 @@ doctrine ADRs adding no CI gate).
the `/review-repo` check and the template suffice.
- **Machine-enforcing section order** — brittle for marginal value; left as a
template-demonstrated convention.
- **Normalizing the body of 001018** beyond adding `## Status` — out of scope; the
decisions themselves are untouched.
- **Grandfathering 001018 from the check** — rejected in favour of restructuring the
whole corpus to conform, so the standard applies uniformly with no exceptions.
## Related
@ -428,92 +440,96 @@ Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
---
## Task 5: Backfill `## Status` into ADRs 001018
## Task 5: Retroactively restructure ADRs 001018 to full conformance
**Files:**
- Modify: `docs/decisions/001-architecture.md``015-control-host.md` (insert a Status section)
- Modify: `docs/decisions/016-mesh-vpn.md`, `017-service-ui-verification.md`, `018-logging.md` (prepend a parseable Accepted line to the existing Status section)
**Goal:** every ADR in 001018 ends with all four mandatory sections present and a
parseable Status line, so the `adr-structure` check reports zero findings — **without
changing the substance of any decision.**
**Files (current findings — the exact worklist):**
- Missing `Status` + `Consequences`: `001-architecture.md`, `002-security.md`, `004-docker-model.md`, `005-bootstrapping.md`, `014-knowledge-sourcing.md`
- Missing `Status` + `Decision` + `Consequences`: `006-terraform.md`, `007-network.md`, `008-testing.md`, `009-provisioning-handoff.md`, `010-forgejo-ci.md`, `011-update-management.md`
- Missing all four: `003-toolchain.md`
- Missing `Status` + `Decision`: `013-heritage-v4.md`
- Missing `Status` only: `012-hardware-capacity.md`, `015-control-host.md`
- Have unparseable `Status` + missing `Consequences`: `016-mesh-vpn.md`, `017-service-ui-verification.md`, `018-logging.md`
(`010`/`011` use `## Decisions` (plural) → relabel to `## Decision`. The "missing
Decision" cases generally have the decision spread across topical `##` headings.)
**THE FAITHFULNESS RULE (non-negotiable):** This is a *presentational* restructure.
You MAY: add a `## Status` section; relabel a heading (`## Decisions``## Decision`);
introduce a `## Decision` umbrella heading and **demote** existing topical `##` headings
to `###` beneath it; add a `## Consequences` section. You MUST NOT alter any existing
sentence of decision prose, reword arguments, or add new policy. A `## Consequences`
section is assembled **only** from implications the ADR already states (its trade-offs,
"what was ruled out", "open questions", named follow-on work). **If an ADR states
nothing that can be faithfully cast as a consequence, STOP and report it as
DONE_WITH_CONCERNS / escalate — do not invent consequences.**
**Per-file date source:** the file's first git-commit (add) date —
`git log --diff-filter=A --format=%as -- <path> | tail -1` (yields `YYYY-MM-DD`).
- [ ] **Step 1: For each of 001015, insert a Status section after the title**
- [ ] **Step 1: Add a dated `## Status` section to each ADR**
For each file `docs/decisions/NNN-*.md` in 001015, get its date and insert a Status
section between the title line and the first `##` heading. Worked example for
`001-architecture.md` (its line 2 is blank, line 3 is `## Context`):
```bash
f=docs/decisions/001-architecture.md
d=$(git log --diff-filter=A --format=%as -- "$f" | tail -1)
# Insert after the title's trailing blank line, before "## Context":
```
Use the Edit tool to change, in `001-architecture.md`:
For 001015 (no Status today): insert, between the title line and the first `##`
heading, a Status section:
```markdown
# ADR-001 — Architecture overview
## Context
```
to:
```markdown
# ADR-001 — Architecture overview
## Status
Accepted (<d>)
## Context
```
where `<d>` is the date from the command above. Repeat for 002015, matching each
file's actual title text and its first `##` heading (the heading is not always
`## Context`).
where `<d>` is the file's first-git-commit date. For 016/017/018 (unparseable Status
today): prepend a parseable `Accepted (<d>). ` clause to the first line of their
existing `## Status` section so the build-state note becomes its tail, e.g.
`Accepted (2026-06-05). Designed. **Authorable now:** ...`.
- [ ] **Step 2: For 016, 017, 018, make the existing Status section parseable**
- [ ] **Step 2: Ensure a `## Decision` section exists**
These already have a `## Status` section whose first line is build-state prose. Get the
date (`git log --diff-filter=A --format=%as -- <path> | tail -1`) and prepend a
parseable Accepted line so the existing note becomes a trailing clause. Worked example
for `018-logging.md` — change its Status section from:
For ADRs flagged "missing Decision" (003, 006, 007, 008, 009, 010, 011, 013): relabel a
plural/synonym heading where one exists (`## Decisions``## Decision` in 010/011), or
introduce a `## Decision` umbrella immediately after `## Context` and demote the existing
topical `##` body headings (e.g. in 003: "Execution engine", "Python environment", …) to
`###`. Do not move or rewrite the prose under them.
```markdown
## Status
- [ ] **Step 3: Ensure a `## Consequences` section exists**
Designed. **Authorable now:** ...
```
For every ADR flagged "missing Consequences" (001, 002, 003, 004, 005, 006, 007, 008,
009, 010, 011, 014, 016, 017, 018): add a `## Consequences` section near the end,
assembled strictly from implications the ADR already states. Where an ADR has a trailing
section that *is* consequences under another name (e.g. "What was ruled out", "Open
questions", "Trade-offs"), you may keep that section and add a short `## Consequences`
that references/summarizes the already-stated trade-offs — without introducing new
claims. **Honour the faithfulness rule; escalate any ADR where no faithful Consequences
can be drawn.**
to:
```markdown
## Status
Accepted (<d>). Designed. **Authorable now:** ...
```
Repeat for 016 and 017 with their own dates and existing first lines.
- [ ] **Step 3: Verify the whole corpus now passes the check**
- [ ] **Step 4: Verify the whole corpus passes the check**
Run: `python3 scripts/repo-scan.py 2>/dev/null | python3 -c "import json,sys; v=[f for f in json.load(sys.stdin)['findings'] if f['check']=='adr-structure']; print('adr-structure findings:', len(v)); [print(' ', f['path'], '—', f['detail']) for f in v]"`
Expected: `adr-structure findings: 0`.
- [ ] **Step 4: Run the full repo-scan test suite**
- [ ] **Step 5: Verify faithfulness via diff**
Run: `git diff --stat` and spot-check `git diff docs/decisions/003-toolchain.md`.
Expected: changes are heading additions/relabels/level-demotions, a new Status section,
and a new Consequences section — **no edits to existing decision sentences.**
- [ ] **Step 6: Run the repo-scan test suite**
Run: `.venv/bin/pytest tests/test_repo_scan.py -q`
Expected: PASS — 5 passed.
- [ ] **Step 5: Commit**
- [ ] **Step 7: Commit**
```bash
git add docs/decisions/0*.md docs/decisions/1*.md
git commit -m "docs(adr): backfill Status section into ADRs 001-018
git commit -m "docs(adr): restructure ADRs 001-018 to ADR-023 conformance
Status header only (Accepted, dated from each file's first git-commit);
no decision content changed. Brings the back-catalogue to ADR-023 conformance.
Presentational only: add a dated Status section, relabel/regroup headings
under Decision, and add a Consequences section assembled from each ADR's
already-stated implications. No decision substance changed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```
@ -531,6 +547,6 @@ Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
## Self-review notes
- **Spec coverage:** §1 title/filename → Task 3 + template; §2 sections → Tasks 2/3 + check; §3 lifecycle → Task 3; §4 cross-refs → Task 3 `## Related`; §5 template → Task 2; §6 backfill → Task 5; §7 enforcement → Task 1 + Task 4. All covered.
- **Spec coverage:** §1 title/filename → Task 3 + template; §2 sections → Tasks 2/3 + check; §3 lifecycle → Task 3; §4 cross-refs → Task 3 `## Related`; §5 template → Task 2; §6 retroactive restructure → Task 5; §7 enforcement → Task 1 + Task 4. All covered.
- **Order nuance:** spec says sections come "in this order"; the check enforces presence + Status only. This is intentional and stated in both the spec's enforcement wording ("the four mandatory sections and a parseable Status line") and ADR-023's Decision §5 / "What was ruled out". Not a gap.
- **Type/name consistency:** `adr_structure_findings` and the `"adr-structure"` check key are used identically in the function, the `scan()` wiring, the tests, and both verification one-liners.

View file

@ -39,14 +39,17 @@ lifecycle, ships a template, and reconciles the back-catalogue.
- **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).
lightweight `/review-repo` structure check; a **one-time retroactive restructure of
ADRs 001018** to full conformance (all four mandatory sections + a parseable Status
line), reorganizing existing content under canonical headings.
- **Out (for now):** *changing the substance of* any existing decision (the restructure
is presentational — relabel/regroup/demote existing content, add a dated Status, never
alter what was decided); 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);
grandfathering pre-convention ADRs from the check (rejected — the whole corpus is
brought to conformance instead).
## Decision
@ -103,12 +106,29 @@ 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.
### 6. Retroactive restructure (001018)
A **separate step** after the ADR and template land: bring every pre-convention ADR to
full conformance — all four mandatory sections present and a parseable Status line. This
is a **presentational** restructure, governed by a strict faithfulness rule:
- **Add** a `## Status` section valued `Accepted (YYYY-MM-DD)`, the date reconstructed
from the file's **first git-commit date**. For 016018, whose existing trailing
build-state note is unparseable, prepend the dated `Accepted (...)` clause so the note
becomes a parseable Status line's tail.
- **Reorganize** existing content under the canonical headings: relabel a synonym
(`## Decisions``## Decision`), or introduce a `## Decision` umbrella and **demote**
the existing topical `##` headings to `###` beneath it. No sentence of existing prose
is altered.
- **Add** a `## Consequences` section built **only** from implications the ADR already
states (trade-offs, "what was ruled out", "open questions", follow-on work already
named). If an ADR genuinely states nothing that can be faithfully cast as a
consequence, that file is escalated for a human decision rather than inventing one.
- **Never** change the substance of a decision. A `git diff` of the restructure should
show heading-level changes, a new Status section, and a Consequences section assembled
from existing material — not edits to existing argument.
ADRs already conformant (019022) are left alone. End state: the `adr-structure` check
reports zero findings across the whole corpus, with no grandfathering.
### 7. Enforcement
Lightweight, no CI gate. The `/review-repo` command gains an ADR-structure check:
@ -120,7 +140,11 @@ a parseable `## Status` line. The template carries the convention forward for ne
- 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).
- One-time churn: a restructure touching ~18 files (heading reorganization + a Status
section + a Consequences section per file). Larger and more judgment-heavy than a
Status-only backfill, hence the faithfulness rule and per-file review.
- The whole corpus conforms — the check needs no grandfathering or number threshold, and
stays simple (presence + parseable Status, applied uniformly).
- `/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.
@ -128,5 +152,6 @@ a parseable `## Status` line. The template carries the convention forward for ne
## 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.
(`adr-template.md`), enforcement (`/review-repo`, no CI gate), and the **full
retroactive restructure** of 001018 (no grandfathering) were all confirmed during
brainstorming.