The incident review finds ADR-003: Use Redis for caching.
Redis is in the configuration. The record has successfully preserved the one fact the repository already reveals and lost everything the operator needs: what problem the cache solved, why the database was insufficient, which failure the team accepted, and when removing Redis would be safer than repairing it.
A longer document would not necessarily help. It could spend six pages describing Redis and still omit the choice.
The useful test for a decision record is retrieval. Can someone who was absent recover the question, constraints, alternatives, evidence, resulting change, and conditions which would reopen the decision? If not, the file is a receipt. It proves an option was selected without preserving why it deserved to be.
The code already records the selected technology
Configuration, imports, schemas, and deployment manifests usually expose what the system uses. Git history can show when those lines arrived. Neither source answers a question which was never retained.
Michael Nygard’s original ADR formulation used short text files with context, status, decision, and consequences. It also kept a reversed record and marked it as superseded instead of erasing it. The ADR GitHub organization describes the same central purpose: preserve one decision’s rationale, trade-offs, and consequences in a decision log.
That does not mean every record needs a large template. It means the record must contain enough of the decision to survive its authors.
The DEC-02 fixture makes this difference executable. It stores two useful synthetic records and the deliberately incomplete Redis control. A retrieval command asks eleven questions. The useful records answer nine. The Redis record refuses two:
Q10 explain why Redis was selected
outcome: evidence-gap
reason: decision rationale is absent
Q11 identify when Redis should be removed
outcome: evidence-gap
reason: reversal conditions are absentThe file exists. It has an ID, title, date, status, decision, and a valid link to the Redis configuration. Search finds it. None of those facts licenses the tool to invent the missing reason.
This is also the boundary for AI-assisted retrieval. A fluent reconstruction from surrounding code is still a reconstruction. It should be returned as an evidence gap or labelled inference, not silently promoted into team history.
Preserve the choice, not a template
The two useful fixture records retain ten kinds of information:
| Information | Question it must answer |
|---|---|
| Decision question | What choice was actually open? |
| Constraints | Which facts weakened or eliminated options? |
| Alternatives | What could credibly have been done instead? |
| Evidence | What was known then, from which source, with what uncertainty? |
| Decision | Which option was selected and why did it fit better? |
| Consequences | Which benefits, costs, and failure modes were accepted? |
| Obligations | What must now be measured, operated, or repaired? |
| Result links | Where did the choice change code or operations? |
| Reversal conditions | Which observation would make reconsideration rational? |
| Status events | Is the decision active, reopened, superseded, or retired? |
This is not a universal schema. A local library choice may not need an owner or operational threshold. A cross-team data boundary probably does. Optionality should follow the retrieval questions, not a desire to make every Markdown file look alike.
One field is conspicuously absent: a completeness score. Nine answered questions do not compensate for the missing tenth if that tenth is “when may we remove the failing dependency?” Missing properties should remain visible.
Rejected alternatives carry the reasoning
The first useful record asks how accepted rate-refresh work should leave an HTTP request. The synthetic system has one team, PostgreSQL, modest arrival rate, transactional coupling between the order revision and the job, and a two-second queue-age allowance.
The record considers three options:
- synchronous refresh keeps provider latency inside the request and exceeds the response budget;
- a database queue preserves the transaction boundary and stays on an operated surface; and
- an external broker creates independent publication and operation before throughput or ownership requires them.
“Use the database queue” is not the reasoning. The contrast among the three options is.
The retained observation gives that contrast a time boundary: 14 arrivals per second, 1.2 seconds of synthetic p95 queue age over 30 minutes, and 12 percent database IO over the same window. The record says the evidence is one synthetic half-hour snapshot, not a capacity forecast.
Two conditions reopen the choice:
- p95 queue age exceeds two seconds for 30 minutes; or
- database IO exceeds 20 percent for 30 minutes.
Both name a metric, comparison, window, source, and owner. Neither is currently true, so the evaluator leaves the decision active.
The record links to the queue configuration, capacity check, and recovery runbook. The fixture verifies each path and anchor and retains a SHA-256 for the linked file. A future reader can move from reasoning to the system which should still embody it.
This extends the idea in Design Is Good for a Change, Not in Isolation. The named pressure makes a design defensible. The rejected options and reversal conditions preserve when that defence stops applying.
A reversal condition opens a question
The second useful record chooses a transactional outbox for accepted-rate events. Direct broker publication leaves a dual-write gap. A periodic scan of the order table would infer publication intent and event identity after the transaction. The outbox commits order state and a stable event fact together, while delivery remains retryable.
That decision accepts duplicate-delivery handling, publication backlog, and database scanning. Its links point to the outbox schema, publisher, and a failure test which retains an unpublished row after broker failure.
The accepted evidence records 18 seconds of synthetic p95 unpublished age and 11 percent database IO over 30 minutes. A later observation records 94 seconds and 17 percent. The declared age condition is greater than 60 seconds for 30 minutes, so one condition has crossed.
The evaluator returns reconsider.
It does not return Kafka.
Crossing the threshold says the old evidence no longer supports leaving the decision alone. It does not say whether partitioning the outbox, scaling the publisher, changing the reader contract, or adopting an external log is now the best choice. Those options need current evidence and another decision.
“Revisit when scale increases” cannot support this workflow. It has no measure, window, source, or owner. It will be true in every planning discussion and actionable in none.
Keep the old answer when the evidence changes
The fixture retains the accepted outbox record separately from its current
state. Later evidence and a reconsider event are appended. The original
question, context, constraints, alternatives, reasoning, consequences, links,
conditions, and accepted event must remain byte-for-byte equivalent in the
current record.
That history answers two different questions:
- Why was the outbox reasonable on 3 February?
- Why did it require a new decision on 12 May?
Rewriting the original context with May’s knowledge would make the first answer look foolish or obvious. It was neither. It was bounded by earlier evidence.
The current AWS ADR process takes a strict approach: accepted records become immutable, and an approved replacement supersedes the previous ADR. That is a useful default when later changes would blur history. A small status event can point from the old decision to the new one without pretending the old record was never active.
Append-only history is not an excuse to preserve mistakes silently. A factual error can be corrected with an explicit correction event. Sensitive data may need removal under a different policy. The goal is durable reasoning, not forensic purity at any cost.
Link to the result without duplicating it
A record should not become a stale second copy of configuration, SQL, code, tests, dashboards, or runbooks. It should point to the narrow artifact which implements or observes the decision.
The fixture rejects repository-root links and missing anchors. “See GitHub” is not a useful route from an ADR to the outbox failure contract. A path and symbol, query, dashboard panel, or runbook section gives the reader somewhere exact to start. A revision or hash can preserve the identity which was reviewed.
Git’s log reference supports searching history by path and message.
That makes commit history a useful transport for decision links. It does not
make a vague commit message recover alternatives which nobody wrote down.
The direction of duplication matters:
- the record owns why the option was selected;
- code and configuration own the current mechanism;
- tests own executable behavior;
- dashboards own current observations; and
- runbooks own recovery actions.
Links let those artifacts disagree visibly. A removed symbol, renamed dashboard, or changed threshold becomes a maintenance signal instead of stale prose which still looks complete.
Some decisions belong somewhere else
An ADR is useful only when the reasoning must outlive its existing home.
A pull-request description is usually enough when the decision’s lifetime is one revision and its alternatives are visible in that review. An issue is better while the choice remains unresolved and coordination is the main work. A code comment is better for a local invariant which must move with one line of code. A runbook should own operational steps. A larger RFC earns its cost when many stakeholders, migrations, or dependent decisions need review before any one choice can be accepted.
The Agile Manifesto values working software over comprehensive documentation. “Over” is a comparison, not a ban. A short record which answers a future change can support working software. An ADR programme which demands a document for every dependency update can become the comprehensive documentation it was meant to avoid.
The smallest useful record is therefore not the shortest file. It is the least text which preserves the expensive-to-reconstruct reasoning.
The reproduction is synthetic. Its dates, services, roles, thresholds,
measurements, consequences, and decisions are not Brian’s experience. It
retains two useful records, one incomplete control, eleven retrieval tasks,
twenty-seven rejected semantic counterfactuals, five retained source responses,
thirty source hashes, a zero-finding dependency audit, and a clean export of
commit 5c92affd234ff79ff1441ef384ea58f2748d5064.
Before writing the next decision record, ask what a future reader will search for after the authors have left the room. Preserve the alternatives they cannot recover from code, the evidence which made one option reasonable, the exact resulting artifacts, and the observation which would make another decision worth its cost.
A decision record needs to explain why the path was taken. It also needs to leave a signpost back to the junction.