|
| 1 | +# ADR 0001: The ratchets land before the code they protect |
| 2 | + |
| 3 | +**Status:** accepted |
| 4 | +**Date:** 2026-10-14 |
| 5 | +**Decided by:** Andy |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +This repository is built mostly by AI coding agents working from written |
| 10 | +plans. Agents are fast, and they are obliging. Asked to make a test pass by the |
| 11 | +end of a task, a good agent finds the shortest path, and the shortest path |
| 12 | +through an architecture usually runs through one of its boundaries. A person |
| 13 | +under a deadline does the same, just less often. |
| 14 | + |
| 15 | +Each such exception is defensible on the day it is made. The damage is in the |
| 16 | +sum, and nobody ever approves the sum. A design document cannot stop it, |
| 17 | +because a document has no way to fail a build. Review does not reliably stop |
| 18 | +it either: the reviewer who should catch the fifth exception is the one who |
| 19 | +waved through the first four. |
| 20 | + |
| 21 | +## Decision |
| 22 | + |
| 23 | +Every structural rule this repository depends on is a script that fails the |
| 24 | +build, and those scripts are in the first commit, before any crate exists. |
| 25 | + |
| 26 | +So the first commit holds: |
| 27 | +- this decision |
| 28 | +- the design spec |
| 29 | +- five ratchets under `scripts/ratchets/` |
| 30 | +- a self-test that proves each ratchet can fail |
| 31 | +- the CI workflow that runs them before anything else |
| 32 | + |
| 33 | +## Why first |
| 34 | + |
| 35 | +- **Today they cost nothing.** Against an empty tree, every ratchet passes on |
| 36 | + its first run. A ratchet introduced later arrives with every violation that |
| 37 | + accumulated before it, and its first conversation is about which of those |
| 38 | + really count. |
| 39 | +- **A rule that runs is a different thing from a rule that is written down.** |
| 40 | + "The core is pure" is a sentence in the spec. `core-purity.sh` is the same |
| 41 | + sentence, checked on every pull request, including the ones an agent opens at |
| 42 | + 3 a.m. |
| 43 | +- **A budget binds only when it starts below need.** `core-line-budget.sh` |
| 44 | + allows 800 lines for a core expected to settle at 400-600. Starting at |
| 45 | + whatever size the core reaches, plus some slack, would record the code's size |
| 46 | + rather than limit it. |
| 47 | + |
| 48 | +## Consequences |
| 49 | + |
| 50 | +- **Dependency rules are allowlists.** A denylist blocks only the crate |
| 51 | + someone thought of in advance. |
| 52 | +- **Source scans skip whole-line comments.** Otherwise the explanation of a |
| 53 | + rule becomes its first violation, and people learn to silence the check. |
| 54 | +- **Loosening a rule is a visible diff.** Raising a budget or widening an |
| 55 | + allowlist is allowed, and it is done by editing the ratchet in the same diff |
| 56 | + as the code that needs it. The trade is then in front of the reviewer, not in |
| 57 | + a setting nobody reads. |
| 58 | +- **Every ratchet ships with fixtures that prove it can fail** |
| 59 | + (`test-ratchets.sh`). That also stops a ratchet from being weakened quietly: |
| 60 | + an agent that "fixes" a red build by loosening the check turns the self-test |
| 61 | + red instead. |
| 62 | +- **They are a floor, not a proof.** The ratchets are text scans and metadata |
| 63 | + checks, and someone set on evading them can. What they catch is the |
| 64 | + ordinary, reasonable shortcut. |
0 commit comments