June 6, 2026 · 5 min read

Architecture that defends itself: encoding decisions as lint checks

Architecture decisions in a wiki erode. Encoded as custom lint checks that fail the build, they enforce themselves, even against the next AI-generated diff.

The audit found it on a quiet afternoon: a hand-rolled write to the system-of-record ERP, sitting in a request handler, going around the wrapper every other write in the codebase used. It was not malicious. It was a reasonable-looking line of code that no tool flagged. And it was the kind of thing you cannot catch by looking, because an untracked write is invisible by design. Nothing errors. The record just quietly does or does not land, and you find out when an operator opens the observability page expecting to see the write and sees nothing, long after the fact, with no trail back to the cause.

That afternoon is why I stopped trusting documentation to hold architecture decisions and started writing the important ones as lint checks.

Here is the decision in question. The portal reads from a per-seat ERP and writes back to it, and that ERP is the contractor's system of record. A write that does not land is worse than an error, because the user sees success while the truth diverges underneath them. So the rule is firm: every write to the ERP routes through an observability wrapper that records the attempt and verifies the result. For a while that rule lived in a doc, and a doc cannot stop a commit. The hand-rolled write proved it.

So I wrote a check. It scans the server code and fails the build on the patterns that mean an unwrapped write: a raw call to the low-level write primitive outside the wrapper, an ERP stored-procedure call in a file that does not import the guard, an UPDATE against a known ERP table in a file that routes through neither. The decision is no longer something I have to remember to enforce in review. It is something the build refuses to let me violate, on a system where a silent divergence shows up as a wrong number on a contractor's books.

The part I want to defend is the allowlist, because no real rule is absolute and the escape hatch is where these things usually rot. It is a small file, and every entry must name the file, the exact rule being exempted, and a written reason. An entry with an empty reason is not waved through; it fails the run on its own. You cannot opt out quietly. You can opt out loudly, in a file that shows up in the diff, with a sentence defending it, which is the right amount of friction for skipping a safety rule. The cheapest path is to wrap the write correctly. Cheating costs more than complying, which is the only design that survives a deadline.

The same shape shows up in the other three checks. A constraint registry generated from JSDoc and verified in CI, so the documented constraints and the enforced ones cannot drift apart without the build noticing. A guard that keeps raw color literals from creeping in around the design tokens. A guard that keeps component files in the shape hot reload needs. None of these is dramatic alone. Together they are a layer of conventions that enforce themselves, written in plain Node, easy to read and easy to extend when a new convention earns a check.

And they are not optional. The lint step is part of the risk-tiered ship gate, so on the changes that touch authorization or data writes, the full gate runs and these rules cannot be skipped. The decision and its enforcement ship together, on exactly the changes where getting it wrong would cost the most.

What changed for me is the question I now ask of any rule I care about. Not "is it written down somewhere," but "what stops the next commit from breaking it." If the answer is review and good intentions, it will break, and on a money system I will find out from a reconciliation, not a stack trace. If the answer is a check that fails the build, the rule is real in the only sense that pays: enforced by something that does not get tired, does not forget, and was not in the room to be talked out of it.

I build these guardrails into the AI systems I ship for clients, because putting AI to work in a real business means the business's data has to stay correct while the agent moves fast. If your integration trusts good intentions to protect the system of record, that is worth a look, and a good first conversation at /contact.

Want this kind of work in your business?

See the service that fits, or book a 30-minute call and tell me what you're working on.