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. - **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. - **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. - **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 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` `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 ship with it, and ADRs 001018 were retroactively restructured to conform. Resolves
being unsettled. the FRICTION signal (2026-05-31) about ADR-writing policy being unsettled.
## Context ## Context
boma records architectural decisions as numbered ADRs in `docs/decisions/`, and 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 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 → newest ADRs (019022) converged on a clean shape — Status → Context → Decision →
Consequences → Related — but only by imitation, and ADRs 001018 predate it: 001015 Consequences → Related — but only by imitation. ADRs 001018 predate it and drifted
carry no `## Status` section at all, and 016018 have a trailing build-status note widely: most lacked a `## Status` section entirely (016018 carried only a trailing
rather than a lifecycle line. The result is structural drift and no uniform way to build-state note), and many lacked an explicit `## Decision` or `## Consequences`
tell an active decision from a superseded or deprecated one. 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 ## 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 deliberately not gated, to keep enforcement lightweight (consistent with boma's other
doctrine ADRs adding no CI gate). 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 ## Consequences
- New ADRs have one obvious shape and a scaffold; structural drift stops. - New ADRs have one obvious shape and a scaffold; structural drift stops.
- Every ADR declares its lifecycle state uniformly, and reversals are traceable. - 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 - The whole corpus conforms; the check needs no grandfathering and stays simple.
decision content changed). - 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. - `/review-repo` grows one deterministic check; no new CI machinery.
- This ADR is the first conformant example and is held to its own check. - 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. the `/review-repo` check and the template suffice.
- **Machine-enforcing section order** — brittle for marginal value; left as a - **Machine-enforcing section order** — brittle for marginal value; left as a
template-demonstrated convention. template-demonstrated convention.
- **Normalizing the body of 001018** beyond adding `## Status` — out of scope; the - **Grandfathering 001018 from the check** — rejected in favour of restructuring the
decisions themselves are untouched. whole corpus to conform, so the standard applies uniformly with no exceptions.
## Related ## 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:** **Goal:** every ADR in 001018 ends with all four mandatory sections present and a
- Modify: `docs/decisions/001-architecture.md``015-control-host.md` (insert a Status section) parseable Status line, so the `adr-structure` check reports zero findings — **without
- 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) 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 — **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`). `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 For 001015 (no Status today): insert, between the title line and the first `##`
section between the title line and the first `##` heading. Worked example for heading, a Status section:
`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`:
```markdown ```markdown
# ADR-001 — Architecture overview
## Context
```
to:
```markdown
# ADR-001 — Architecture overview
## Status ## Status
Accepted (<d>) Accepted (<d>)
## Context
``` ```
where `<d>` is the date from the command above. Repeat for 002015, matching each where `<d>` is the file's first-git-commit date. For 016/017/018 (unparseable Status
file's actual title text and its first `##` heading (the heading is not always today): prepend a parseable `Accepted (<d>). ` clause to the first line of their
`## Context`). 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 For ADRs flagged "missing Decision" (003, 006, 007, 008, 009, 010, 011, 013): relabel a
date (`git log --diff-filter=A --format=%as -- <path> | tail -1`) and prepend a plural/synonym heading where one exists (`## Decisions``## Decision` in 010/011), or
parseable Accepted line so the existing note becomes a trailing clause. Worked example introduce a `## Decision` umbrella immediately after `## Context` and demote the existing
for `018-logging.md` — change its Status section from: topical `##` body headings (e.g. in 003: "Execution engine", "Python environment", …) to
`###`. Do not move or rewrite the prose under them.
```markdown - [ ] **Step 3: Ensure a `## Consequences` section exists**
## Status
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: - [ ] **Step 4: Verify the whole corpus passes the check**
```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**
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]"` 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`. 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` Run: `.venv/bin/pytest tests/test_repo_scan.py -q`
Expected: PASS — 5 passed. Expected: PASS — 5 passed.
- [ ] **Step 5: Commit** - [ ] **Step 7: Commit**
```bash ```bash
git add docs/decisions/0*.md docs/decisions/1*.md 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); Presentational only: add a dated Status section, relabel/regroup headings
no decision content changed. Brings the back-catalogue to ADR-023 conformance. 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>" 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 ## 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. - **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. - **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 - **In:** the canonical section set (mandatory + optional); title and filename
convention; the `Accepted / Superseded / Deprecated` status lifecycle and the convention; the `Accepted / Superseded / Deprecated` status lifecycle and the
no-silent-rewrite rule; cross-reference convention; an ADR template file; a no-silent-rewrite rule; cross-reference convention; an ADR template file; a
lightweight `/review-repo` structure check; a one-time backfill of `## Status` into lightweight `/review-repo` structure check; a **one-time retroactive restructure of
the ADRs that lack one. ADRs 001018** to full conformance (all four mandatory sections + a parseable Status
- **Out (for now):** rewriting the *decisions* in any existing ADR; normalizing the line), reorganizing existing content under canonical headings.
body section names of 001018 beyond adding `Status`; a `make lint` / CI gate for - **Out (for now):** *changing the substance of* any existing decision (the restructure
ADR structure (explicitly rejected in favour of the `/review-repo` check — is presentational — relabel/regroup/demote existing content, add a dated Status, never
consistent with boma's other doctrine ADRs, which add no CI gate); a "Proposed" alter what was decided); a `make lint` / CI gate for ADR structure (explicitly
draft stage (rejected — boma is single-contributor and trunk-based with no review rejected in favour of the `/review-repo` check — consistent with boma's other doctrine
gate, so ADRs are born Accepted). 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 ## 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 uncomment when relevant. It is a skeleton, not a numbered decision, so it does not take
an ADR number. an ADR number.
### 6. Retroactive backfill (001018) ### 6. Retroactive restructure (001018)
A **separate follow-up step** after the ADR and template land: add a `## Status` A **separate step** after the ADR and template land: bring every pre-convention ADR to
section to every ADR that lacks one. Status value is `Accepted (YYYY-MM-DD)` where the full conformance — all four mandatory sections present and a parseable Status line. This
date is reconstructed from each file's **first git-commit date**. **Only the Status is a **presentational** restructure, governed by a strict faithfulness rule:
header is added — no decision content is touched.** ADRs already carrying a `## Status`
(019022) are left alone. - **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 ### 7. Enforcement
Lightweight, no CI gate. The `/review-repo` command gains an ADR-structure check: 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. - 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 - Every ADR declares its lifecycle state uniformly, and reversals are traceable rather
than silent — the back-catalogue becomes a legible decision history. 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 - `/review-repo` grows a new check; no new CI machinery, matching boma's habit of not
gating doctrine in CI. gating doctrine in CI.
- This ADR is itself the first conformant example — it must follow its own structure. - 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 ## Open questions
None outstanding — title/filename, the 3-state lifecycle, template name None outstanding — title/filename, the 3-state lifecycle, template name
(`adr-template.md`), enforcement (`/review-repo`, no CI gate), and the (`adr-template.md`), enforcement (`/review-repo`, no CI gate), and the **full
Status-only backfill were all confirmed during brainstorming. retroactive restructure** of 001018 (no grandfathering) were all confirmed during
brainstorming.