158 lines
8.6 KiB
Markdown
158 lines
8.6 KiB
Markdown
# 02-DECISIONS
|
||
|
||
Architecture decision records — the "why" trail behind the rules in
|
||
[`00-META`](../00-META/) and the specifications in [`03-DESIGN`](../03-DESIGN/).
|
||
|
||
**Numbered `02` because a decision precedes the design it authorises.** Research concludes,
|
||
the decision is recorded here, and only then is the design written. Following the folder
|
||
numbers walks the process in the order it happens.
|
||
|
||
One file per decision, numbered, never deleted. A superseded record has its `status:` changed
|
||
and gains a pointer to what replaced it — **its reasoning is never rewritten**. The reasoning that
|
||
was rejected is the expensive half to rediscover.
|
||
|
||
## Progressive insight
|
||
|
||
A record is a decision, not a snapshot of everything that was true the day it was written, and
|
||
those two fail differently. **A fact a record asserted can turn out to be wrong while the decision
|
||
it supports stays right** — a count taken before anyone measured, a file named that does not
|
||
exist, a proof attributed to a step that cannot run it. Superseding a record for that buries a
|
||
correct decision under a second one, and teaches every reader to first work out which of two
|
||
records is live. Done a few times, the reading order stops being one.
|
||
|
||
So: **a correction of fact that leaves the decision standing is made in the record, in place,
|
||
marked and dated.**
|
||
|
||
> **Progressive insight — YYYY-MM-DD.** What was found, what the record said before, and what it
|
||
> says instead.
|
||
|
||
Three conditions, all of which hold:
|
||
|
||
- **It corrects a fact, not a judgement.** That a suite does not exist is a fact. That building it
|
||
is the wrong order is a judgement, and judgements supersede.
|
||
- **It adds; it never quietly replaces.** Where body text changes, the note says what stood there
|
||
before, so a reader who followed a citation to the old wording can find out what happened to it.
|
||
A correction nobody can see is indistinguishable from a record that was always right, which is
|
||
the failure the immutability rule exists to prevent.
|
||
- **The decision, the options weighed and the consequences stand untouched.** If the correction
|
||
changes what was decided, which alternatives were rejected, or a consequence another record
|
||
relies on, it is not an insight — write the superseding record.
|
||
|
||
**What still supersedes**, without exception: reversing a decision, changing its scope, rejecting
|
||
an option it accepted, or making a consequence false that a later record cites. The test is not
|
||
how large the edit looks in a diff; it is whether a reader who acted on the old text would now be
|
||
wrong about *what was decided* rather than about *a detail the decision did not rest on*.
|
||
|
||
**How this is checked.** `00-META/checks/records.py` requires every insight to be marked in the
|
||
form above and dated no earlier than the record's own `date:` — an unmarked edit is a rule
|
||
violation the reviewer looks for in the diff, and a marked one is legible in the record itself.
|
||
The git history is the backstop, not the record of intent; the note is the record of intent.
|
||
|
||
## A pointer back from what a record changes
|
||
|
||
A new record naming an old one is not enough. **Where a record changes a mechanism an older record
|
||
states — without reversing the decision, so no supersession — the older record gets a dated note
|
||
saying where its mechanism now lives.** A reader arrives at the old record by following a citation,
|
||
and finds text that is still the decision and no longer the method; nothing in it says a later record
|
||
moved the method, and the new record is not in their hands.
|
||
|
||
> **The mechanism changed — YYYY-MM-DD, by ADR NNNN.** What still stands, what moved,
|
||
> and why.
|
||
|
||
Three examples of the shape, all found by being missed: ADR 0066 still described a routed name being
|
||
written into every container after 0148 replaced that with resolution; ADR 0047 still said a module's
|
||
code runs in a container after 0150 made it a supervised process; and ADR 0016 still read as though the
|
||
lab were the test bed after 0149 said the live mesh is. Each was a citation leading to the wrong
|
||
answer, in a record that was not wrong about anything it decided.
|
||
|
||
**This is not machine-checked, and it cannot be from `extends:` alone.** 102 records extend another and
|
||
87 name a parent that does not mention them, which is correct: extending usually means building on a
|
||
context, and a one-directional pointer is the right shape for that. What needs a note is the narrower
|
||
case where the parent's own text has gone stale, and which case that is, is a judgement — so it is a
|
||
rule for the author and the reviewer, and the diff is where it is caught. Making it mechanical would
|
||
mean a record declaring the relationship in its frontmatter, which is a change to the record schema and
|
||
has not been decided.
|
||
|
||
The records run in the order the decisions were taken, oldest first.
|
||
|
||
**Every decision is a record.** There is no ledger and no index file — if a decision is worth
|
||
recording it is worth a record, and if it is not worth a record it is not recorded
|
||
([ADR 0019](0019-how-this-repository-works.md)). A "decision" small enough to be one line is
|
||
almost always a **rule**, and a rule belongs in
|
||
[`00-META/how-we-build.md`](../00-META/how-we-build.md), where it is enforced and keeps the
|
||
incident that earned it.
|
||
|
||
## Frontmatter
|
||
|
||
```yaml
|
||
---
|
||
status: proposed | accepted | superseded
|
||
date: YYYY-MM-DD # when the decision was taken, not when it was written down
|
||
deciders: name
|
||
reconstructed: true|false # true when the record was written after the fact from evidence
|
||
superseded-by: # 02-DECISIONS/NNNN-....md, when status is superseded
|
||
extends: # 02-DECISIONS/NNNN-....md, when this record widens an earlier one
|
||
---
|
||
```
|
||
|
||
## Body
|
||
|
||
```
|
||
# N. Title in plain language
|
||
|
||
## Context what was true, with evidence
|
||
## Considered Options numbered, each with why it was rejected
|
||
## Decision what was decided
|
||
## Consequences what follows, including what got harder
|
||
## References commits, pull requests, knowledge-base entries, prior art
|
||
```
|
||
|
||
State evidence, not assertion. *"Zero of 124 modules declare `brain` as a dependency"*
|
||
outranks *"the dependency rule is not followed"*.
|
||
|
||
## Reconstructed records
|
||
|
||
Records 0001–0014 were written on 2026-08-23, after the decisions they describe. Records 0015 onward were taken as records. Those
|
||
decisions were taken in implementation rather than in a document; the records state what was
|
||
decided and the evidence it was decided from, and each carries `reconstructed: true` and says
|
||
so in its first lines.
|
||
|
||
A reconstructed record is not a transcript. Where the deliberation is not recoverable, the
|
||
options section states what the alternatives were and why the chosen one won on the evidence
|
||
available — not a discussion that did not happen. Where a date is not establishable it says so
|
||
rather than guessing.
|
||
|
||
## Reading order
|
||
|
||
**A number identifies a record and never changes.** Records are referenced from outside this
|
||
repository — code comments, commit messages — so a number that moves invalidates them silently.
|
||
Renumbering once cost 96 references across two code repositories, and that is why the numbers
|
||
are now fixed.
|
||
|
||
So the folder is in creation order, and **the reading order lives here**: six topics, in the
|
||
order somebody would learn the system. Each record names its own in `topic:`.
|
||
|
||
1. **What the mesh is** — `topic: the mesh`. What it is for, and the parts it is made of.
|
||
2. **Its tiers, from the bottom up** — `topic: the tiers`. The foundation, the node and the controller, and
|
||
what each one provides to the one above.
|
||
3. **What runs on them, and how it gets there** — `topic: what runs on it`. Modules, seats and
|
||
provisions, and how they reach a node.
|
||
4. **How it is built** — `topic: building it`. The code, its repositories, and what is made from
|
||
it for a node.
|
||
5. **How it is checked** — `topic: checking it`. What judges a change, and what proves a decision holds.
|
||
6. **How we work** — `topic: how we work`. This repository, its playbooks, and how decisions and
|
||
designs are made.
|
||
|
||
**The list of records under each topic is generated when it is read, and never stored here**
|
||
([ADR 0248](0248-the-decision-index-is-generated-when-it-is-read-and-never-stored.md)). A stored list
|
||
was one more line in this file for every decision, so two decisions open at once always conflicted
|
||
([issue 297](../04-ISSUES/297-two-open-decisions-always-conflict/00-report.md)). Read it with:
|
||
|
||
```
|
||
python3 00-META/checks/index.py --print the records, by topic, in reading order
|
||
```
|
||
|
||
or the `hq-status` skill, or the records module's `records_decisions` from anywhere on the mesh.
|
||
`index.py` checks that every record's topic is one of the six above, and fails if this file stores the
|
||
list again.
|