The Pull Request That Looked Fine
A story-driven tutorial about why passing CI can still fail architecture when coding agents do not know ownership, seams, and dependency direction.
It was Friday afternoon, and Peter was trying to finish one last review before leaving the office. It was not a big pull request. At least, it did not look like one. The title was calm:
Add customer risk CSV export
Ledger House, the company Peter worked with, had an internal finance system used by accounting, support, and compliance. It handled customers, invoices, payouts, reports, audit trails, and monthly reconciliation. Nothing strange for a finance system. A lot of boring work, and as it often happens, boring work was exactly where important rules liked to hide.
This time Finance needed a CSV file with customer risk data before the monthly reconciliation meeting. It was one of those requests which sound harmless, because no one is changing how money moves. The assistant implemented it quickly. The tests passed. The CSV had correct columns. The numbers looked good. Peter opened the diff, expecting to spend five minutes there. Then he noticed the imports.
Act I. The Pull Request Was Correct
The assistant added a new button in the reports screen. When clicked, it called a new export function and produced the file Finance asked for. From the user’s perspective, everything was fine.
- the button was visible;
- the CSV downloaded correctly;
- customer risk level was included;
- inactive customers were skipped;
- the date format matched the existing reports;
- tests were green.
If architecture was only about working behavior, Peter could approve it and go home. But architecture is rarely that polite.
In the pull request, the reports UI imported a reporting query directly. The reporting query reached into customer internals to calculate risk. The audit data was read from a helper, because the assistant found a file that already had something similar. A small common utility appeared as well, because the assistant needed date formatting in two places. Nothing exploded. That was the uncomfortable part. The feature worked, but the system learned a new path.
Act II. The System Learned The Wrong Path
Peter looked at the change and saw a familiar problem. The assistant did not break syntax. It did not ignore the task. It did not write random code. It followed the visible path:
Need CSV -> find reports -> get customer data -> read audit data -> format file
That path is reasonable when you see the repository as a set of files. It is wrong when you see the repository as a system with ownership.
In Ledger House, customer risk was not just a field to be collected from any place that had it. It belonged to the customer module. Audit data was not just data lying around. It belonged to the audit trail module, because compliance needed that module to stay boring and predictable. Reports were allowed to ask for information, but they were not allowed to dig through internals.
This is the thing agents often miss. They can search the project. They can find examples. They can copy a pattern from a nearby file. But they do not automatically know which convention is a convenience and which one is load-bearing. The assistant saw files. Peter saw boundaries.
Behavior Is Not The Whole Review
Let us name the problem. A line of code can be correct and still be in the wrong place. That sounds annoying at first, especially when the feature works. But every architect has seen the price of letting this pass.
One read-only shortcut becomes two shortcuts. Then another team copies the same path, because it is now “how reports work”. After a few months, customers cannot change without reports breaking. Audit cannot change without exports breaking. The UI knows more than it should. The module names are still there, but they are mostly decoration.
This is not a dramatic failure. It is worse. It is normal. The pull request passes CI, because CI usually checks behavior. The architecture fails quietly, because the repository did not tell the assistant what kind of shape the code must keep.
Architectural Membership
The missing idea is architectural membership. When you add code, you should be able to answer more than “does it work?” You should also answer:
- who owns this responsibility?
- what public seam should other modules use?
- which direction may this dependency go?
- what check proves that the rule still holds?
In Peter’s review, the feature had no clear answer to those questions.
The export belonged to reports, but the risk calculation belonged to customers. The audit trail belonged to audit. The UI should not know how those things are combined. There should be an application-level seam that makes the collaboration explicit. Something like this is much easier to reason about:
reports/ui
-> reports/application
-> customers/public-risk-query
-> audit/public-audit-query
This is not about making more folders for fun. It is about making responsibility visible. The reports module may coordinate report generation. It should not own customer risk rules. It should not own audit access rules. If it starts owning them by accident, the module boundary is only a nice folder name.
Why The Assistant Chose The Shortcut
It is tempting to say that the assistant made a bad decision. That is only partly true. The assistant made the easiest decision available from the visible context.
It found a report. It found customer data. It found an audit helper. It connected them. This is exactly what a fast developer with no project memory can do. The problem is not speed. The problem is speed without local architecture.
Peter could write a review comment:
Please do not import customer internals from reports.
That would fix this pull request. It would not fix the next one.
If the rule matters, it should exist before the review. It should be discoverable
by the assistant before editing starts. It should be possible to check at least
the deterministic part. This is the place where enclosure enters the story,
not as an AI platform, not as a magic architect, but as a small operating
contract inside the repository. The source is available on GitHub:
github.com/9orky/enclosure.
The First Contract
Before Peter lets the assistant continue, he wants the repository to answer the questions that were previously living in his head. For this first problem, the contract can start small:
Reports may ask Customers for risk data through a public query.
Reports may not import customer internals.
Audit data must be accessed through the audit module public seam.
Before editing, report the owner, public seam, and checks to run.
This is not yet a full architecture tool. It is only the first useful rule. The important part is that the assistant must read it before touching code. That changes the cooperation. Peter is no longer only reacting after the fact. The repository starts explaining itself at the beginning of the work.
The assistant should be able to say:
Route read:
- Files: .enclosure/rules/INDEX.md, .enclosure/rules/shared/INDEX.md,
.enclosure/rules/local/reports.md
- Reason: customer risk export touches reports, customers, and audit data
- Stop point: reports rule gives ownership and dependency direction
- Owner: reports owns export orchestration
- Public seam: customers public risk query, audit public audit query
- Dependency direction: ui -> application -> public module seams
- Checks to run: boundary check, affected tests
This receipt does not guarantee perfect code. Nothing does. But it gives Peter something much better than a surprise diff. It gives him a chance to check the assistant’s understanding before the assistant starts moving code.
What To Look For In Your Repository
You do not need to start with a large rule system. Start with one painful review comment.
Good candidates sound like this:
- “this should go through the application service”;
- “do not import from internal”;
- “this belongs to the billing module”;
- “the UI should not know about this repository”;
- “please use the existing adapter”;
- “we never call this API directly”.
These comments are architecture trying to escape from people’s heads. Write one of them down. Then ask what part of it can be checked. Maybe the whole rule can be checked from imports. Maybe only half of it can. Maybe the first version is only a pre-edit question. That is still useful. The goal is not to automate all architectural judgment. The goal is to make the assistant cooperate with it.
Exercise
Take one recent pull request from your own project. Prefer a boring one. Boring pull requests are excellent, because they show the rules people forget to say out loud.
Answer these questions:
- who owned the change?
- what was the public seam?
- which dependency would make you stop the review?
- what did the tests prove?
- what did the tests not prove?
- what should the assistant have read before editing?
Then write the smallest possible rule:
When changing:
Owner:
Allowed public seam:
Forbidden dependency:
Checks:
Stop and ask when:
Do not try to build the perfect architecture contract today. Write the first rule that would have prevented one real mistake.
Summary
The assistant in the story did not fail because it could not code. It failed because the repository did not tell it what the code belonged to. Passing CI is important, but it is not the whole review. A change also needs to keep ownership, seams, and dependency direction intact. Fast code is easy. Fast code that still belongs to the architecture is the interesting part.
In the next tutorial, Peter will stop writing the same review comment after the
damage is done. He will put the first rule where the assistant must start:
inside .enclosure/.