Skip to content

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.

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.

LevelThe structural conventionThe gate that holds it
Directorya 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 namescreen.tsx, route.tsx, controller.ts, *.fixtures.ts, 00001_*.sqlthe rule self-targets by the name — the name is the selector
Module boundarypackages/ui is product-agnostic; the state system lives in one placeno-state-system-in-ui; middleware only in internal/server (depguard)
Importsa screen never imports a fixture; a handler never imports pgxliftmere/no-fixture-outside-route; depguard (handler ∌ pgx, repo ∌ connect/http)
Function signaturea metric is recorded through a generated, typed method; a handler returns a typed Connect errorRecordUploadIntentCreated(ctx, contentType, outcome) — labels are parameters; forbidigo bans bare fmt.Errorf
Type shapea handler implements the generated interface; a screen handles every statevar _ 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.

Structure at every level Structure runs from directory down to type signature; each level pairs a convention with a gate, and each gate is a floor the next finer level stands on. The coarse levels are the foundation. finer grain → Directory depguard · no cross-domain import File name the name is the selector Module no-state-system-in-ui Imports no-fixture-outside-route Signature generated typed params · forbidigo Type var _ Iface = (*Service)(nil) each gate is a floor the next level stands on
Structure at every level — the coarse conventions are the foundation; each finer rule stands on the gate beneath it. Conceptual, not measured data.

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 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.

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.