Tooling · Architecture

Compliance as Code:
Building a Harness That Enforces Architecture

An architecture doc that nobody mechanically checks against is a suggestion with extra steps. The interesting engineering problem isn't writing the doc — it's building something that can tell you, automatically, the moment code stops following it.

Design docs rot the same way comments do — quietly, a little at a time, until the day someone reads the doc, reads the code, and notices they've been describing two different systems for months. The fix that actually held up here wasn't writing better docs. It was building a harness: an automated evaluator that statically checks whether real code follows the documented rules, with no human review required to catch drift.

What a Harness Rule Is Allowed to Assume

The business example in this repo's examples/ directory — the Account domain, and later Card and Payment — is an illustrative sample, not a fixture the harness is allowed to depend on. A harness rule must never assume "the Account domain behaves like this" as its premise; it has to check the architectural pattern in a way that would hold for any domain with that shape.

The harness evaluates compliance with architectural rules — layer placement, dependency direction, naming, transaction boundaries, the Outbox pattern — never whether the business logic inside those structures happens to be correct. Order cancellation rules, payment approval conditions, inventory reservation policy: none of that is in scope. That kind of content can appear in a doc's example or in the runnable examples/ code, but it can never become a required premise of a core rule. Blur that line, and a new evaluator quietly couples itself to one specific business domain, degrading the harness from a framework-agnostic architecture guide into an Account-service-only linter.

Assertions Over One Golden Implementation

The harness prefers partial scoring built from many small, independent assertions over pinning one single "this file must look exactly like this" reference implementation. Each rule checks individually whether a specific violation is present and the results sum into a score — the premise being that multiple valid implementations of the same principle can coexist, and the harness shouldn't punish a structurally sound choice just because it differs cosmetically from the example in examples/.

Why a Structural Check Isn't Enough on Its Own

The first generation of harness rules checked exactly the things you'd expect: is there a domain folder, does the Interface layer avoid importing Infrastructure directly, does a Repository interface live where the docs say it should. That catches an entire class of drift — and it also has a precise blind spot. It says nothing about whether a method inside the right file is named correctly, or whether a class in the right folder depends on the right thing.

A real audit surfaced exactly this gap: a Repository method-naming convention (list lookups always named find<Noun>s, saves always save<Noun>) was violated in four of five language implementations, in different ways each time — a bare save with no noun in two of them, a "dedicated findOne plus separate findAll" pair reintroduced in an older domain in four of them. Every one of those files passed every structural rule that existed at the time, because none of those rules had ever looked at a method name.

The actual root cause, stated plainly

The recurring diagnosis, across several rounds of finding the same class of drift: no tool checked for this specific thing. Structural checks verify placement. They say nothing about naming conventions, dependency direction inside an already-correct folder, or exact method-name patterns — each of those needed its own dedicated rule, written only after a manual audit found the gap by hand at least once.

The Failure Mode Even a Careful Audit Misses

There's a harder version of the same problem: a "does the code match its own docs" audit can pass cleanly while both the code and the doc are wrong together. One implementation's own CQRS documentation had captured a Query Handler directly using a write-capable Repository as the correct example — the code matched the doc perfectly, and both were violating the root principle that a Query side should never see a write-capable interface at all. An audit that only checks doc-vs-code agreement is structurally incapable of catching this, because agreement is exactly what it's checking for, and here the agreement itself was the bug.

What actually surfaced it was comparing the local doc against the root principle instead — and once that comparison was made a standing rule instead of a one-time manual pass, it caught the same class of violation independently in other files the first time it ran.

Structural Rules Also Miss Cross-Language Inconsistency

A subtler blind spot: audits that go one language at a time can never see a structural disagreement that only exists between languages. A notification-sending concern lived inside the domain module in two of five language implementations and in a separate shared top-level module in the other three — neither choice was obviously wrong on its own, each passed its own language's harness cleanly, and the inconsistency was invisible to any single-language review by construction. It only became visible once the same concept was compared side by side across all five at once, which is a fundamentally different kind of audit than "review this one codebase against its own docs."

Turning a Finding Into a Permanent Rule

The pattern that held up across many rounds: find a real violation by hand once, fix it, then write a harness rule that would have caught it — and run that new rule immediately, before assuming the codebase is now clean. The Repository-naming rule is the clearest example: once it existed as a mechanical check rather than prose, running it against a domain nobody had thought to re-check — the authentication domain, in three of the five languages — immediately turned up three more real violations that had never been in scope for any prior audit, because every earlier audit had only ever looked at the two business domains everyone kept thinking about. A dependency-direction rule, an ID-format rule, an error-response-schema rule, a soft-delete-filter rule — roughly thirty rules accumulated this way across five languages, each one born from an actual bug like this one, not a hypothetical one.

The yield drops over time, and that's expected, not a sign the exercise stopped being worth it. Early rounds found three or four real violations per new rule category; by the fourth round of this exercise, most new rules found zero, because the low-hanging cross-language drift was already closed. Diminishing returns, but not zero returns — a rule that costs an afternoon to write and catches nothing today is still standing guard against next month's regression.

What This Buys You That a Doc Alone Never Could

A CI pipeline that runs the harness on every change means a pull request that violates layer placement, naming convention, or dependency direction fails the build before a human ever has to notice it in review — the same way a linter catches a syntax issue before a reviewer has to point it out by hand. The self-review checklist this repo keeps is deliberately written to double as an evaluator spec: every new checklist item gets asked, "can this also be verified mechanically," before it's accepted as prose-only.

Further reading in the repo

docs/harness.md — the harness's own design principles, in full · docs/checklist.md — the self-review checklist most of these rules were built from