Recording a decision
A choice with alternatives and costs gets one immutable record, numbered next in line, and the build checks that it is there.
Why the log exists
docs/ARCHITECTURE.md says what the design is, in the present tense, with the reasoning compressed out. Six months on, nobody can tell which lines are considered choices and which are placeholders that survived because nobody revisited them. The decision log is the reasoning: one record per choice, with the forces that pushed on it, what was decided, what else was on the table, and what it costs. The point is that the reasoning survives the people who did it. That is ADR-0001, and the log is the last part of this book.
A record is reviewed in the same pull request as the code that implements it. Commit messages are keyed to changes, and a decision made across five commits is unrecoverable from them.
When a change needs one
A record is for a choice: there were alternatives, each had a cost, and the next maintainer could undo the choice without knowing what it was load-bearing for. A bug fix is a commit. It becomes a record when fixing it meant choosing between designs, or when the bug was invisible for a reason worth writing down. ADR-0357 is one of those: the fix was two lines, and the rule it established applies to every test written since.
Two tests of whether a change needs a record:
- Could a reviewer ask “why not the other way?” and need more than a sentence?
- Would someone a year from now be tempted to reverse it?
A record with no costs listed has not been thought through. If there is nothing to put under Consequences, there was no decision.
The template
Start from the template. Its sections, in order:
| Section | What goes there |
|---|---|
| Title | # ADR-NNNN: Title, or # NNN. Title. Both house styles are in use |
| Status | Proposed, Accepted, or Superseded by ADR-NNNN. A bullet in the early records, a ## Status section in the later ones |
| Date | The day it was recorded |
| Relates to | The ARCHITECTURE.md section, and the records it leans on or amends |
| Context | The forces at play. What makes this a decision rather than an obvious call. The constraints stated honestly, including the ones about time or taste |
| Decision | What was decided, in the active voice. One paragraph if possible |
| Alternatives considered | What else was on the table, and the specific reason each was rejected. “It was worse” is not a reason |
| Consequences | What becomes easy, what becomes hard, and what is now expensive to reverse. The costs, not only the benefits. This is the section future readers come for |
Numbering
Records are numbered in the order they are recorded, not the order they were made. The next record takes the next free number, and the numbers run without a gap. One decision per file, named NNNN-kebab-case-title.md.
DecisionLogTest in build-logic holds the log to its own rules:
- the numbers are contiguous from
0001, so a citation resolves to exactly one record; - the heading carries the number the file name carries, in either house style;
- the status appears in the first fourteen lines, in one of the three spellings the log uses;
- every record has a line in the log’s own
README.md, so it is reachable from the directory GitHub shows.
./gradlew :build-logic:test --tests '*DecisionLogTest*'
Warning
A number reserved and not used goes red. Two records written in parallel take consecutive numbers, and whichever lands second renumbers if the first took its number.
Status values
| Status | Meaning |
|---|---|
| Proposed | Written down, not yet agreed. An open question |
| Accepted | Agreed and in force |
| Superseded | Replaced. The record names what replaced it |
Supersession
Records are immutable once accepted. A decision that turns out to be wrong is not edited. A new record supersedes it, the old one gains a Superseded by ADR-NNNN line in its status, and the wrong turn stays visible. ADR-0012 replaced ADR-0011 that way, and ADR-0510 replaced ADR-0009 and kept its text verbatim apart from the status line.
A record may also amend one without superseding it: a matrix that lost two rows, a mechanism that stayed while the numbers changed. The amending record says so in its status, and the amended one gains a blockquote pointing forward. ADR-0012 carries one from ADR-0041.
The log will contain records that are wrong. That is the intended behaviour.
How the guide links a record
From a chapter of this book, a link is relative and ends in .md:
[ADR-0063](https://github.com/DigitalSmile/goldberry/blob/master/book/src/adr/0063-data-flows-down-events-flow-up.md)
From docs/, the path is ../book/src/adr/NNNN-slug.md. From a Java doc comment, a record is the plain reference ADR-NNNN and not a link: 510 relative links from source into the book once resolved to nothing, and the plain reference is what they always effectively were.
Every chapter’s Read more names the records behind it, and a widget’s chapter links the record for each rule it states. A record that nothing links is a record nobody will find.
The title
A title states the decision as a sentence, so the table of contents reads as a list of what was decided:
- A version is a year and a count
- A package is a role, and the module is the fence
- Every package says what it is, and is null-marked
- A preflight check that cannot fail is not a check
Not Versioning, and not Use calendar versions. The sentence is the decision, and a reader scanning the log should be able to stop at the title.
Status and TODO
Two pages beside the log are updated when a record lands:
- Status says what is built, milestone by milestone. A record that completes a piece moves its row.
- TODO says what is deferred, known-broken, or specified and unbuilt. An entry leaves the top half when a record answers it and moves to Answered rather than being deleted, because each one records a trap somebody hit and the reasoning that got out of it. A record that closes an entry says so in its status, as ADR-0508 does.
docs/ARCHITECTURE.md §17.1 lists where the design documents disagree with each other, and a record that settles one strikes the entry through rather than deleting it.
Writing one
Take the next free number. The highest file in book/src/adr/ plus one.
Copy 0000-template.md to NNNN-kebab-case-title.md and fill every section. Consequences last, and honestly.
Add its line to the list at the end of book/src/adr/README.md, in the same shape as the line above it.
Link it from the chapter, the status row or the TODO entry it changes, and from any record it supersedes or amends.
Run the build-logic tests and checkMarkdown. Put the record in the same pull request as the code.
./gradlew :build-logic:test checkMarkdown