A Boundary You Don't Enforce Is Just a Wish
My last piece argued that the decisions you don't write down become debt. I stand by it. But a few days later I opened my own repository and ran straight into the flip side of the same coin — the part that's easy to miss:
Writing a boundary down is not the same as enforcing it.
What I found in my own repo
My workspace is an Nx monorepo: one application and a few dozen shared UI libraries. The linter already had the rule that's supposed to police who can depend on whom — @nx/enforce-module-boundaries. It looked like this:
depConstraints: [
{ sourceTag: '*', onlyDependOnLibsWithTags: ['*'] },
]
Read it slowly. Anything may depend on anything. The rule was present, configured, and green in CI — and it enforced precisely nothing. On top of that, every one of my ~60 projects carried an empty tags: [].
I had written the boundary down. I had not built a wall.
The name for that gap
An architecture fitness function is an automated check that fails the build when the system drifts away from a property you care about — a layering rule, a dependency direction, a performance budget. The term is Neal Ford and Rebecca Parsons'; the idea is older than the phrase. The point is blunt: an architectural characteristic you can't test is an architectural characteristic you're merely hoping for.
Why this stopped being optional
For years you could get away with boundaries-as-convention, because the people crossing them were humans who had sat in the code review and absorbed the unwritten rules. That era is closing.
When your fastest teammate is an AI coding assistant, it will cross a boundary that exists only as a convention — confidently, plausibly, with the tests still green — because it is optimizing for "make this compile and pass", not for "respect an architectural intent nobody encoded". A convention lives in people's heads. The AI wasn't in the room when you agreed on it.
So the boundary has to live somewhere the AI cannot talk its way past: the build.
Giving the rule teeth
I gave every project a tag. Two categories were enough to make the point:
type:app— the application.type:ui— the shared component libraries.
And I replaced the do-nothing rule with two real constraints:
depConstraints: [
{ sourceTag: 'type:app', onlyDependOnLibsWithTags: ['type:app', 'type:ui'] },
{ sourceTag: 'type:ui', onlyDependOnLibsWithTags: ['type:ui'] },
]
In plain English: the app may consume shared UI; shared UI may compose other shared UI, but must never reach back into the application. That last rule is the one with value — a shared library that depends on the app it is meant to serve is a circular knot waiting to tighten.
Watching it work
The best thing about a fitness function is that you can watch it bite.
On the real code, it passed on the first run — the boundary I had just declared was already respected, so nothing broke. That matters: a good constraint ratifies the architecture you already have, then holds the line from there.
Then I added a single line to a shared button component: an import reaching back into the app. The build went red:
error Projects cannot be imported by a relative or absolute path,
and must begin with a npm scope @nx/enforce-module-boundaries
No debate in a pull request. No reviewer needing to remember the rule at 6pm on a Friday. No trusting that the assistant read the architecture doc. The wall simply refused to let the import through. I removed the line; green again.
The whole change was some tags and four lines of config — an afternoon, not a project.
Where this meets "write the decision down"
This is the exact point where a fitness function joins the argument I made last time.
A documented decision — "shared UI must not depend on the app" — is necessary, because it captures the why: the reasoning, the trade-off, the failure it prevents. An Architecture Decision Record is the right home for that.
But documentation is a message to humans who choose to read it. A fitness function is the same decision compiled into something that cannot be ignored — not by a rushed teammate, not by an AI, not by future-me who has forgotten the conversation.
The ADR explains the boundary. The fitness function is the boundary.
Written and enforced are two different jobs, and mature systems do both: the record so people understand the intent, the check so nobody — and nothing — can quietly cross it.
The rule I'd leave you with
A boundary you don't enforce is just a wish. And in the age of AI, a wish is exactly what your tools will step over.
The invariants that protect money, trust, or a clean architecture don't belong only in a document a model can paraphrase past. They belong in the build, where crossing the line turns something red.
Where in your codebase is there a boundary that everyone "knows about" but nothing actually enforces? That's your first fitness function.