Are We Drifting? — Part 15: Structure Is the Rail
Are We Drifting? — Part 15: Structure Is the Rail
Section titled “Are We Drifting? — Part 15: Structure Is the Rail”Earlier parts treated structure and enforcement as two of the three legs of a . This part is about the relationship between them — because it is a loop, and the loop is what makes the whole thing scale.
The loop
Section titled “The loop”Structure gives enforcement the rails to scale.
A rule that has to find its targets by hand does not scale: someone has to maintain the list, and the list drifts. A rule that targets a shape — every file named screen.tsx, every package under internal/, every function returning error from a handler — scales to the whole fleet for free, because the shape is the selector. This is : adopt the shape and the enforcement attaches automatically; new code is governed the moment it takes the form, with no list to update.
And enforcement is what keeps the structure intact.
A folder convention with no gate is a folder convention until the first deadline, then it is a suggestion. The rule that forbids a fixture import outside a route is what keeps the layering real — without it, “screens are pure” decays into “screens are mostly pure,” and the structure the next rule depends on is gone. Enforcement is how a structure stays a structure.
Structure makes enforcement cheap to scale. Enforcement makes structure safe to rely on. Each one funds the other.
The loop compounds. More structure means cheaper, broader enforcement; broader enforcement protects more structure; protected structure is something the next rule can assume. The codebase ratchets toward more legible, not less.
Structure is not one level — it is every level
Section titled “Structure is not one level — it is every level”The word “structure” hides how many levels it runs at. The same loop — a convention paired with a gate — repeats from the coarsest grain to the finest. Each level’s gate is what lets the next level’s rule assume the level beneath it holds.
| Level | The structural convention | The gate that holds it |
|---|---|---|
| Directory | a domain’s handler + repo co-located in internal/<domain>/; a feature’s five layers in features/<family>/<feature>/ | depguard — no cross-domain imports (lint-error) |
| File name | screen.tsx, route.tsx, controller.ts, *.fixtures.ts, 00001_*.sql | the rule self-targets by the name — the name is the selector |
| Module boundary | packages/ui is product-agnostic; the state system lives in one place | no-state-system-in-ui; middleware only in internal/server (depguard) |
| Imports | a screen never imports a fixture; a handler never imports pgx | liftmere/no-fixture-outside-route; depguard (handler ∌ pgx, repo ∌ connect/http) |
| Function signature | a metric is recorded through a generated, typed method; a handler returns a typed Connect error | RecordUploadIntentCreated(ctx, contentType, outcome) — labels are parameters; forbidigo bans bare fmt.Errorf |
| Type shape | a handler implements the generated interface; a screen handles every state | var _ ServiceHandler = (*Service)(nil) (compile); exhaustive matchState (compile) |
Read top to bottom and the grain gets finer — a directory, then a file, then a module edge, then a single import line, then the shape of one function, then a type. Read any row left to right and it is the same move every time: a shape, and a gate that refuses anything that is not the shape.
The fine-grained levels are only affordable because the coarse ones hold. no-fixture-outside-route can be a simple, file-granular rule precisely because the file-name convention (route.tsx) and the directory convention (one feature per folder) are already enforced beneath it. Pull out a lower level and the rules above it lose their footing. The structure is a stack, and each gate is a floor the next one stands on.
Why this is the deepest investment
Section titled “Why this is the deepest investment”The instinct is to think of the lint rules as the asset. They are not. The rule is cheap — especially now. The asset is the structure the rule rides on, because that is what makes the rule general instead of bespoke, and what makes the next rule possible at all.
This is why “add a guardrail” is rarely the first move. The first move is to give the thing a shape — a canonical file, a folder, a naming convention, a typed signature — and the guardrail follows almost for free, because it has something to target. A team that invests in structure gets enforcement at a discount forever after. A team that writes rules against an unstructured codebase pays full price for every one and watches them rot.
The velocity payoff
Section titled “The velocity payoff”The coherence check this removes is the one that does not look like a check at all: where does this go, and what shape does it take?
When structure is enforced at every level, that question has one answer per level, and the answer is mechanically true — a new domain is a folder, a new feature is five named files, a new metric is a typed method, a new handler implements a generated interface. An agent does not guess the shape; the shape is the convention, and the gate refuses the guess that misses it. You accept a large generated change and trust it not because you reviewed the placement of every file, but because misplacement is a shape the build does not allow.
That is the rail metaphor made literal. Rails do not make a train fast. They make it possible to go fast safely — and to add another train without re-surveying the route.
What’s next
Section titled “What’s next”Structure is the rail; the gates are placed along it. The rest of the series walks specific rails — the vocabularies, the operations, the agents — and a running inventory of the gates. The question stays the same at every level, coarse or fine.
Are we drifting? Not where the shape is named and a gate holds it — and the more levels that is true at, the less there is left to drift.