The file that opens before the first commit
April 30, 2026, late afternoon. Rembrandt's /admin page has just betrayed me for the third time in two days. Blandine, an administrator, can't see the Attendance tab even though the "Attendance" checkbox is ticked in the rights grid she manages herself. Thirty minutes of digging earlier, I'd understood: the checkbox writes to dashboard_cards (display preference) and never touches can_access_emargement (the actual permission flag). Two semantic layers stacked in the same grid, treated as one.
Niran is in the room, hood loose at his back, a folded burger wrapper on the corner of his laptop. He's working on a financial analysis for Antoine, not watching my screen. I'm about to open app/admin/page.tsx and patch. Instead, I create an empty file: docs/adr/0040-admin-rbac-vs-dashboard-cards.md. Not a single line of code touched. A Markdown page.
This inversion of order — the ADR before the first commit, never after — is the single discipline that holds Rembrandt's architecture together. I want to describe why.
The retroactive ADR is a lie
The Architecture Decision Record, in the short form I keep, has four sections: chosen decision, rejected alternatives with their reason for rejection, positive and negative consequences, references. One page. The trap is to write it after the fact. Code is already pushed, the decision was made by reflex, the ADR documents what happened and gives it retrospective coherence. Anything that doesn't fit the final decision is smoothed over, rejected alternatives are written knowing they'll be rejected, negative consequences are cautious because we're already living them.
It's a polite lie. Worse, it's a useful one: it reassures the future reader who thinks they're reading a tested decision when they're reading the rationalization of a first reflex.
With Claude Code, the risk multiplies. I describe a problem, the agent returns a coherent architecture in minutes, I accept it because it stands up. I haven't tested the alternative. I haven't even noticed there was one. The retroactive ADR ratifies that non-choice. The pre-written ADR forbids it, because alternatives must be written before you know which one will win.
What the form forces
Here's the skeleton of 0040-admin-rbac-vs-dashboard-cards.md as it sits in production:
<!-- docs/adr/0040-admin-rbac-vs-dashboard-cards.md -->
# ADR-0040 — /admin page: RBAC / dashboard_cards / role separation
- Date: 2026-04-30
- Status: Accepted
- Decider: PBR
## Context
[three distinct semantic layers, Blandine incident, ~30 min diagnosis]
## Decision
1. Invariant `dashboard_cards ⊆ accessibleSlugs` (server validation)
2. Split UI: 3 ordered sections
3. Server action `toggleUserFlag` with strict whitelist
4. Fix `listUserRoles` (explicit SELECT)
## Rejected alternatives
1. Cosmetic patch (relabel) — doesn't address root cause
2. Merge dashboard_cards into RBAC — kills granularity
3. Postgres trigger as DB safeguard — TS↔SQL duplication cost too high
## Consequences
[positive, negative, deliberately deferred]
The section that does all the work is Rejected alternatives. Three alternatives, each with a short technical reason for rejection. The cosmetic patch is rejected because it doesn't address the root cause. The merge is rejected because it removes granularity. The DB trigger is deferred because the matrix lives in TypeScript, and duplicating it in SQL creates its own drift. None of those sentences came naturally from the agent. Each came from a question I posed, in writing, inside the ADR: "what if we did this? — why not?". The written answer eliminated the option. The form did its work.
The Claude Code workflow
Three specific disciplines, without which the ADR is worse than nothing.
Phase 0 — grep existing ADRs. Before opening 0040, I grep dashboard_cards and permissions across docs/adr/. I find ADR-0014 (systematic RLS) which sets the RBAC scene and ADR-0017 (inscription source of truth) which spares me from reinventing a pattern. Without that grep, I'd have duplicated reasoning already done. Claude Code would have regenerated a local coherence, blind to sibling decisions.
Challenger agent on structurally significant changes. For ADR-0044 (DB ↔ code contract tests, shipped J1 on May 2nd), the challenger agent raised six objections, classified blocking / important / cosmetic. The "Response to challenger" section is part of the final ADR — it's the audit trail of adversarial pre-engagement. A decision that hasn't been attacked hasn't been tested. A decision that's been attacked and held up gets recorded.
Demand a technical reason for rejection. Alternatives discarded without a reason are a trap. "Too complex" rejects nothing. "Duplication cost between TS and SQL too high for marginal benefit vs app-side double validation" rejects something. I reread that section and anything that looks like an unstated intuition goes back as a question.
The rule
Before any project touching more than two files or changing a business invariant, open the ADR file. No code until the four sections hold together. The ADR can be incomplete, wrong, in need of revision — it exists. The next commit can contradict it. But it exists before, and that anteriority is what filters out decisions of convenience.
When the ADR is written after, it served no purpose. Better not to write it than to lie.
Coda
Fifty-four ADRs in thirty-five effective working days. Fifty-one published in the repo, three fiscal ones kept private (0011–0013). Roughly one and a half per day, marginal cost near zero with Claude Code. That cadence is not a trophy — it's a sign that the ADR format, hammered for twenty years inside heavy teams, becomes a daily instrument once writing is free.
Niran has finished his analysis. He shuts the laptop, folds the wrapper, says "I'm out" on his way past. The 0040 file is done. The code isn't written. The layer can be laid.
Companion code: rembrandt-samples/writing-adrs-claude-code/ — Rembrandt ADR template + Phase 0 grep checklist + adr-grep.sh script, MIT license.

Top comments (0)