---
title: "Architecture Decisions Rot Because Nothing Runs Them"
description: "An architecture decision written in a wiki is a wish. The boundaries you can't enforce quietly erode until the diagram stops matching the code."
url: https://sade.dev/en/journal/architecture-decisions-rot/
lang: en
author: "Muhammet Şafak"
published: 2026-06-18
updated: 2026-09-06
section: Journal
tags: ["architecture","governance","opinion"]
summary: "An architecture decision is a wish, not a rule, unless something checks it on every commit. A decision has two parts: the rationale, which belongs in prose because a tool cannot hold it, and the constraint, which is a claim about the import graph that a tool can evaluate deterministically and fail the build on. Keep writing the why for humans; hand the boundary to a machine that never gets tired."
---

# Architecture Decisions Rot Because Nothing Runs Them

> An architecture decision is a wish, not a rule, unless something checks it on every commit. A decision has two parts: the rationale, which belongs in prose because a tool cannot hold it, and the constraint, which is a claim about the import graph that a tool can evaluate deterministically and fail the build on. Keep writing the why for humans; hand the boundary to a machine that never gets tired.

A new engineer asked me why the domain layer was importing the database package — "doesn't an ADR (architecture decision record) say it shouldn't?" It did. It had said so for two years. And for most of those two years, somewhere, the rule had been quietly broken: a helper here, a "just this once" import there, each one reasonable in its own pull request. The decision was still true on the wiki. It had stopped being true in the code months ago, and nobody noticed, because nothing was watching.

A decision you write down but can't enforce isn't a rule. It's a wish with good formatting.

## The diagram and the code drift apart in small, reasonable steps

Nobody decides to violate the architecture. It erodes one defensible commit at a time: a synchronous call added because the async path was slower to write, an import that crosses a boundary because the function you needed happened to live on the other side. Each diff looks fine on its own. The damage is **cumulative and structural** — invisible at the scale of a single change, obvious only when you step back and find the layering gone.

That's exactly the scale a human review operates at, and exactly the scale it misses. Review works best on the architecture of a change, but a reviewer sees one PR, not the thousandth import that finally dissolved the boundary. You can't ask a person to hold the whole dependency graph in their head on every commit. The wiki page can't either; it's prose, and prose doesn't fail a build.

## Make the constraint executable

The fix isn't a better-written ADR. It's moving the *enforceable part* of the decision out of prose and into something that runs.

"The domain must not depend on infrastructure" is not, at heart, an opinion — it's a constraint on the import graph: files under `domain/` may not import `db/`. That's a rule a tool can evaluate on every commit, deterministically, and fail the build when it's broken — before the violating import reaches `main`, not two years later. The cost of catching it moves from "an archaeology session when something finally breaks" to "a red check on the PR that introduced it."

This is the same move as [versioning a schema instead of mutating it](/en/journal/schema-evolution-without-breaking): a rule everyone *agrees* on is worth nothing until something mechanical holds the line, because human discipline doesn't scale to every commit across every contributor.

## What stays in the document, and what becomes code

This doesn't kill the ADR — it splits it. An architecture decision has two parts: the **rationale** (why we chose hexagonal layering, what we traded away, when we'd revisit it) and the **constraint** (domain imports nothing; the API talks to the DB only through a repository). The rationale belongs in prose — it's context a tool can't hold and a human needs. The constraint belongs in code, where it can be checked.

Keep writing the *why* down. Stop trusting prose to enforce the *what*.

## When does this stop mattering?

If your whole system is two layers and a team that fits around one table, the boundaries live in everyone's head, and a CI gate for them is ceremony. The moment a third contributor joins, or the codebase outgrows what one person can hold, the unenforced boundary is *already* eroding — you just won't see it until the new engineer asks why domain imports the database.

---

An architecture is not what you decided; it's what your code currently does. The only decisions that survive contact with a growing codebase are the ones something checks on every commit. Write the rationale for humans — and hand the boundary to a machine that never gets tired of enforcing it.

---

*See also:* [archlint](https://github.com/muhammetsafak/archlint) is the tool I built for exactly this — a deterministic import-boundary linter (Go, TypeScript and Python) that reads your layer rules and fails the build on a violation, with a packaged CI Action.

## Frequently asked

**Does this make the architecture decision record pointless?**

It splits the ADR, it does not kill it. A decision has a rationale — why this layering, what was traded away, when it would be revisited — and a constraint, such as the domain importing nothing from infrastructure. The rationale is context a tool cannot hold and a human needs. Only the constraint moves into code.

**Why does code review not catch architectural erosion?**

Because the damage is cumulative and structural. A reviewer sees one pull request, not the thousandth import that finally dissolved the boundary, and nobody can hold the whole dependency graph in their head on every commit. Review is at its best on the architecture of a change; erosion happens at a scale review does not operate on.

**When is a CI gate for this just ceremony?**

When the whole system is two layers and a team that fits around one table — the boundaries live in everyone's head. The moment a third contributor joins, or the codebase outgrows what one person can hold, the unenforced boundary is already eroding; you just will not see it until someone asks why domain imports the database.


## Sources

- [Documenting Architecture Decisions: the original ADR format — Title, Context, Decision, Status, Consequences](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) — Michael Nygard
- [ArchUnit user guide: layered-architecture rules checked as ordinary unit tests](https://www.archunit.org/userguide/html/000_Index.html) — ArchUnit
- [dependency-cruiser: validate dependencies against your own forbidden rules and report violations in the build](https://github.com/sverweij/dependency-cruiser) — dependency-cruiser
