Your architecture is only as real as your repo harness
Learn how a repo harness turns architecture, module boundaries, contracts, and engineering rules into automated checks that prevent codebase drift.
“Harness” is still a new and loosely defined term. Some people use it to describe the system around a coding agent; others use it for the tools and checks around a repository. Here is how I define harness in two distinct ways:
A coding-agent harness is the deterministic logic around the LLM: the planning loop, tool calls, and memory architecture in systems like Claude Code and Codex CLI.
A repo harness is the collection of tools and automated checks that verifies a repository’s correctness—including its behavior, architecture, contracts, and engineering standards.
This article is about the repo harness.
In Beyond 2x AI productivity boost: Architecture is the missing piece, I described an architecture organized across system, application, and module boundaries. A repo harness makes that architecture enforceable: it turns those boundaries, public interfaces, contracts, and other rules into checks that reject violations before the change is accepted.
How a repo harness verifies correctness
A repo harness is there to verify repository correctness. So what does correctness mean? At a high level, it has two dimensions: behavioral correctness and repository conformance.
Behavioral correctness asks whether the software does what it is supposed to do. Teams build confidence in behavioral correctness through the familiar automated testing pyramid: many unit tests, fewer service and integration tests, and a small number of end-to-end tests. Testing strategy is not a new topic, and it is well covered elsewhere, so I will not revisit it here. The important point for this discussion is that the automated test suite is an important part of the repo harness.
Repository conformance—or repo conformance—asks whether a change preserves the intended shape and constraints of the repository: module boundaries, dependency direction, ownership, public interfaces, contracts, type safety, security requirements, and project-specific rules. Conformance violations can leave the software behaving correctly today while making the codebase harder and less safe to change tomorrow. This is the less familiar part of the harness—and where this post will focus.
AI is really good at patterns. Give it a good pattern and it will create more of it. Give it just one bad pattern and it will proliferate through the codebase. This is why it’s so important to ensure repo conformance upfront.
The checks vary, but reliable verification requires the same basic ingredients.
A precise rule. “Keep the code clean” is guidance. “Only this module may access these tables” is enforceable.
An observable violation. The repo harness needs evidence it can inspect. A behavioral test may compare an output or durable state with the expected result. A structural check may detect an illegal import, dependency cycle, foreign table access, or drifted contract.
An automated checker. Enforcement cannot depend on someone remembering the rule during review.
One command and a deterministic consequence. The same command should run for a developer, a coding agent, and remote CI. A violation should fail clearly, identify the broken rule, and point toward the fix.
Tests for custom enforcement code. Custom checkers and generators are code, so they need automated tests like any other code. A checker test should include a known violation and prove that the checker rejects it. A clean repository passing only proves that the current repository passes; it does not prove that the checker works.
A harness combines 3rd-party tools with repository-owned checks
A repo harness is not one product, and most of it does not need to be built from scratch. It usually combines general-purpose 3rd-party tools with tests, configuration, and custom checks owned by the repository.
3rd-party tools provide the general-purpose capabilities. Test runners, linters, type checkers, dependency analyzers, security scanners, migration tools, and dead-code detectors already solve common verification problems. Use an existing tool when it can reliably enforce the property you care about.
Repository-owned verification provides the local rules and fills the gaps. Automated tests encode the application’s expected behavior. Tool configuration, custom checkers, and generators encode repo-specific constraints such as dependency direction, module boundaries, table ownership, public-interface restrictions, and generated contract consistency. When an existing tool cannot express a constraint reliably, write a narrow custom checker or generator.
The harness is language-specific
Part of a repo harness exists to enforce guarantees that a language does not provide on its own. The same architectural rule can require different enforcement depending on the stack.
This matters especially with AI agents. An agent will often take the shortest valid path to complete a task. It does not treat a convention as a hard boundary simply because the convention is documented. If the language permits a shortcut, an agent will eventually use it.
Consider module privacy. In Python, a leading underscore marks a name as internal, but that is only a convention. Nothing prevents another module from importing it directly. If that boundary matters, the harness has to enforce it.
Java provides stronger access control. Private and package-private members are enforced by the compiler, and the Java module system can restrict which packages are exported. When the boundary is represented correctly, ordinary code outside it cannot reach those internals. In that case, the compiler and module system are already enforcing part of the harness.
Dependency cycles provide another example. Go rejects cyclic package imports during compilation. Python and TypeScript do not enforce an intended acyclic module architecture, so repositories using them may need additional dependency-analysis tooling.
There is no universal implementation of a repo harness. Start with the guarantees the language, compiler, and type system already provide. Then use linters, dependency analyzers, tests, or custom checks to close the gaps that matter to the repository.
The harness is never complete
A strong baseline is mandatory. Every repository needs the basic checks appropriate to its stack: automated tests, linting, type checking, security scanning, dependency hygiene, and build validation. These checks catch broad classes of problems and should be present from the beginning. But they are only a starting point.
Repo-specific checks are equally important. A payments application may need to enforce tenant and ledger isolation. A data platform may need schema-compatibility checks. General-purpose tooling cannot know which properties matter to a particular system.
Higher development speed produces feedback faster. When that feedback exposes a gap in the architecture, the code and harness should evolve together.
A postmortem should therefore ask not only, “What code allowed this failure?” but also, “What gap in the harness allowed this change to be accepted?” The fix may include a code change, a new conformance rule, a checker and tests for that checker, or clearer documentation of the repository’s constraints.
That does not mean mechanizing every preference. A noisy or ambiguous check teaches people to ignore the harness. Add enforcement when the property is important, the violation is observable, and the signal can be reliable.
Some drift is visible only over time
Per-change checks catch violations of rules the repository already knows. But some forms of degradation accumulate slowly. No individual change is clearly wrong and the pattern becomes visible only across many changes.
Module dependencies may all be legal while the graph becomes increasingly dense. Public interfaces, suppression allowlists, test runtimes, and artifact sizes may grow a little at a time. Domain concepts may acquire duplicate names and representations. External conditions also change: a dependency can gain a vulnerability without anyone touching the repository.
These properties are better evaluated by recurring checks. Deterministic checks can run as scheduled jobs and fail against an explicit threshold or baseline. More subjective concerns, such as domain model quality, can produce a report for periodic architectural review.
Recurring checks should not become a dumping ground for noisy analysis. If a check is precise, reliable, and cheap enough to run on every change, it belongs in the normal harness. If a recurring review reveals a repeatable failure mode, the next step is to turn that lesson into a deterministic rule wherever possible.
This is really it—a repo harness is not a complicated concept: establish a strong baseline, add the conformance checks your system needs, run them consistently, use recurring checks to catch slower drift, and evolve the harness as the codebase teaches you.
To make it all tangible, the appendix below catalogs representative checks generalized from my real-world experience, along with a few recent ideas.
Appendix: Sample harness checks
This is not a complete list of harness checks. These examples focus on less obvious forms of repo conformance: checks that prevent architectural drift and repository-specific failures ordinary tooling will not catch.
System level
The repository has an explicit directory structure
Problem. Developers and coding agents create new top-level folders or one-off directory shapes whenever the existing structure is not immediately obvious. Over time, the repository stops communicating where code belongs.
Rule. Tracked files must fit a declared directory structure. Adding a new structural concept requires an explicit change to that definition.
How.
Describe the permitted tree in a machine-readable manifest or the repository’s existing build and workspace configuration.
Compare tracked paths with that structure and fail on unknown structural paths.
If this requires a custom checker, test it with a known invalid path.
Cross-app contracts cannot drift
Problem. A contract is often represented independently by several applications or languages. A producer can change while a consumer, generated artifact, or hand-written representation silently remains on the old shape.
Rule. Every cross-app contract must stay synchronized across its producer, consumers, and committed schema. This includes REST APIs, queue and event payloads, WebSocket or SSE messages, webhook bodies, shared file formats, and custom cross-language protocols.
How.
In a code-first design, generate a canonical OpenAPI, AsyncAPI, Protobuf, JSON Schema, or equivalent artifact from the producer and commit it.
Regenerate that artifact in CI and fail if it differs from the committed version.
Generate each consumer’s internal types from the artifact and check those generated results for drift as well.
In a schema-first design, make the committed schema authoritative and generate both sides from it. If a representation must remain handwritten, compare it with the schema or test the contract directly.
OpenAPI Generator supports Java, Python, and TypeScript, while Buf can check Protobuf compatibility. Independently deployed consumers may need compatibility checks in addition to drift checks.
App level
Every public entry point declares its cross-cutting policy
This is one of the most important examples in this appendix. It demonstrates how you can enforce an important goal through complementary architecture & harness implementations. Here architecture reduces a complex cross-cutting requirement to a single declaration mechanism. And the harness verifies that this declaration mechanism is used on every entry point.
Problem. Authentication, authorization and permissions, auditing, and observability are easy to apply inconsistently when every endpoint or module operation handles them independently. A new entry point can omit a concern entirely or enforce it differently from the rest of the application.
Rule. Every endpoint and public module operation must use the application’s standard entry-point declaration. The declaration explicitly states the policies that vary by operation, while the runtime automatically applies the concerns that are universal.
For example:
@endpoint(
url="/cart",
authn=SignedIn,
permissions=[ViewCart, ViewPaymentMethods],
audit=False,
)
How.
Provide one standard declaration mechanism: a decorator, annotation, attribute, or typed registration object, depending on the language and framework.
Require explicit values for policies such as authentication mode, permissions, and auditing. Omission cannot silently mean “unprotected.”
Route invocations through shared runtime infrastructure that interprets the declaration and applies authentication, authorization, auditing, observability, and other cross-cutting behavior.
Have the harness inspect every registered endpoint and declared module interface and fail if the required policy metadata is missing.
Test both pieces: checker tests prove that missing metadata fails the harness, while behavioral tests prove that the shared runtime infrastructure enforces each declared policy.
The harness guarantees that every entry point declares a policy and that the declared policy is enforced. Choosing the correct permission remains a policy-design and testing problem.
Test-only code cannot enter production
Problem. Reset endpoints, seed helpers, fake controls, test imports, or environment overrides can become reachable in a production artifact even though the test suite itself works correctly.
Rule. Test support may exist in a controlled test topology but must be absent from production dependencies, routes, contracts, and build outputs.
How.
Isolate test support with mechanisms such as Java test source sets, dedicated Python test packages, or separate TypeScript entry points.
Prevent production code from depending on that source layer and exclude it from production builds.
Inspect the production artifact’s routes, imports, bundled files, or generated contracts for test-only capabilities.
Module dependencies and public interfaces follow the architecture
Problem. As an application grows, modules begin depending on modules they should not know about, form cycles, or bypass a module’s public interface by importing its internals.
Rule. Cross-module dependencies must follow permitted directions, remain acyclic when required by the architecture, and target only interfaces deliberately exposed by the owning module.
How.
Prefer native module and package visibility when the language provides the required guarantees.
Otherwise, use dependency analysis to reject forbidden edges, cycles, and imports of internal files or symbols.
Examples include Tach for Python, ArchUnit for Java, and dependency-cruiser for TypeScript and JavaScript.
In dynamic languages, keep the public surface statically discoverable so enforcement tools do not need to execute application code.
Account for gaps such as dynamic imports and reflection with a custom check or an explicit restriction.
Database models and migrations cannot drift
Problem. Application models can change without a migration, migrations can diverge from the models, or a migration history can fail when applied from an empty database.
Rule. The migration history must produce the database schema expected by the current application.
How.
Apply the complete migration history to a disposable database.
Derive the expected schema from the application’s current model or schema definition.
Compare the two schemas and fail on meaningful differences.
If applications run migrations during startup, separately test concurrent migration attempts.
Inline suppressions must be explicitly allowlisted
Problem. Inline directives such as “noqa,” “type: ignore,” “eslint-disable,” “ts-ignore,” “nolint,” or “SuppressWarnings” are sometimes necessary, especially in modules doing technically unusual work. But an untracked suppression can also bypass an important check and then be copied without its original context.
Rule. Every inline suppression must appear in a small, committed allowlist. An unlisted suppression fails the harness.
How.
Keep the approved suppressions in a simple text file, identified by their source location and directive.
Use a repository script to scan the source tree, normalize the suppressions it finds, and compare them with the allowlist.
Fail on both new suppressions and stale allowlist entries.
Keep the list small—likely dozens of entries concentrated in a few unusual modules—so every addition receives explicit review.
Test the script with an unlisted suppression and a stale allowlist entry.
Module level
Persistence models stay inside the module that owns them
Problem. When ORM entities or persistence records cross a module boundary, callers can become coupled to database columns, relationships, lazy-loading behavior, or session lifecycle. The module no longer owns its persistence implementation in practice.
Rule. When persistence is intended to be a module implementation detail, public interfaces expose domain values or DTOs rather than ORM entities.
How.
Use native visibility where the language can keep persistence types inside their owning module.
Otherwise, use architecture tests, import rules, or public-signature analysis to detect references to known persistence types.
Tailor the check to the ORM and type system rather than assuming one technique works everywhere.
If persistence models are deliberately shared contracts, encode that architecture instead of prohibiting them.
Module interfaces do not expose module-owned mutable state
Problem. Exporting a registry, cache, ambient context object, mutable collection, or singleton accessor lets callers read or change state outside the behavior owned by the module.
Rule. A module exposes behavior and immutable values, not direct access to the mutable state it owns.
How.
Use access modifiers, immutable interfaces, or ownership types where the language provides them.
Otherwise, inspect exported bindings and public return types with architecture tests or a custom check.
Target concrete ways state can escape—such as exported instances, mutable collections, or singleton accessors—rather than treating every class instance or collection as invalid.
Every database table has one owning module
Problem. A module can bypass another module’s interface by importing its persistence model or issuing SQL directly against its tables. The code may still work while ownership quietly disappears.
Rule. If the architecture assigns table ownership, only the owning module may access that table directly.
How.
Derive or declare which module owns each table.
Use dependency rules to prevent modules from importing another owner’s ORM models.
Where reliable, analyze literal SQL for references to tables owned by another module.
Enforce only the query forms the checker can inspect accurately, and centralize infrastructure exceptions such as migrations.


