Are We Drifting? — Part 14: The Wall of Gates and What Comes Next
Are We Drifting? — Part 14: The Wall of Gates and What Comes Next
Section titled “Are We Drifting? — Part 14: The Wall of Gates and What Comes Next”Every layer of the stack turns out to be the same move: one declarative source → many generated → keep them honest. This part collects the gates built so far, grades them on the ladder, answers the title, and points at where the pattern goes next.
The wall, in one place
Section titled “The wall, in one place”A guardrail that does not run is theatre. So here is the actual set of checks that run — most on every pull request — each tagged with its tier and the part of the series that covers it:
| Gate | What it catches | Tier | Series |
|---|---|---|---|
var _ ServiceHandler = (*Service)(nil) | handler drifts from generated interface | 1 — compile | Part 11: The FE↔BE Mirror |
tsc | exhaustive matchState, typed everything | 1 — compile | Part 11: The FE↔BE Mirror |
liftmere/no-fixture-outside-route | fixture imported outside route/story/test | 2 — lint-error | Part 12: Testing Without Drift |
liftmere/no-raw-hex-color / no-raw-tok-vars | raw color values in component code | 2 — lint-error (ambient) | Part 11: The FE↔BE Mirror |
liftmere/no-uistate-screen-prop | parallel uiState prop on a screen | 2 — lint-error | Part 11: The FE↔BE Mirror |
liftmere/no-null-suspense-fallback | blank first paint in the router | 2 — lint-error | Part 11: The FE↔BE Mirror |
liftmere/no-smart-quotes | curly quotes in TS string literals | 2 — lint-error (ambient) | Part 1: The Drift Problem |
liftmere/no-direct-event-log-append | raw event-log writes outside the sanctioned writer | 2 — lint-error | Part 8: Projected Truth |
buf lint | proto contract stays well-formed | 2 — lint-error | Part 11: The FE↔BE Mirror |
make check-vocab | committed .gen.go drifts from its YAML manifest | 3 — CI script | Parts 3–6 |
make check-sqlc | generated DB models drift from hand-authored SQL | 3 — CI script | Part 7: Models at Boundaries |
make check-mocks | generated mocks drift from their interfaces | 3 — CI script | Part 12: Testing Without Drift |
check-connect-errors | connect.NewError called outside the error adapter | 3 — CI script | Part 5: The Error Manifest |
check-sql-scope | owner-scoped table queried without a user_id filter | 3 — CI script | Part 7: Models at Boundaries |
check-migrations.sh | migration file edited after applied | 3 — CI script | Part 8: Projected Truth |
generate-features --check | committed feature registry is stale | 3 — CI script | Part 11: The FE↔BE Mirror |
defineMcpServerContract | MCP tool surface diverges from the operations registry | 3 — contract test | Part 9: One Operation, Three Interfaces |
make agents-check | rendered agent file diverges from its manifest | 3 — CI script | Part 10: The Agent OS |
rebuild-from-log | any projection is reproducible from the event log | recoverable | Part 8: Projected Truth |
liftmere/no-fixture-outside-route (burndown) | ~113 remaining violations | 4 — warn (scoreboard) | Part 12: Testing Without Drift |
No single one of these is clever. Together they are the difference between “we have a convention” and “the convention is true.” Each removes a coherence check a human would otherwise perform by eye — and each is the reason a large, AI-generated diff can be trusted instead of re-audited.
The ladder keeps it honest
Section titled “The ladder keeps it honest”Not every rule sits at the same strength, and pretending otherwise would itself be drift — between what a doc claims and what the build enforces. So every guardrail carries a tier on a shared :
1 compile-enforced the compiler/type system catches it — cannot ship2 lint-error a rule at error — PR fails CI3 coverage-script a CI script fails on a missing pairing or forbidden diff4 warn (scoreboard) reported, non-blocking, during a burndown to zero5 convention documented but unenforced — a known gap, a future targetThe tier tells you the real status of a rule at a glance. A sub-system at tier 5 is honest about being a wish; one at tier 1 cannot be violated. The frontend mostly lives at tiers 1–2; the backend has a genuinely-gated core and a set of sub-systems still climbing from convention toward a wired linter. The point of the ladder is not to claim everything is enforced — it is to make the gaps legible so they can be closed on purpose.
When the gate fires
Section titled “When the gate fires”Strength is one axis; timing is the other. A gate runs at some point in the life of a change — and the earlier it fires, the cheaper the failure.
The same gate is a different cost at each tier. A migration mistake refused on git commit costs the developer ten seconds; the same mistake caught at runtime is an incident. So the wall is not only a set of rules — it is a set of rules placed as early as each one can run.
The earliest tier is the pre-commit subsystem: a handful of hooks that fire on every commit, before code reaches CI — a secret scan, buf lint, migration discipline, the connect.NewError boundary check, the owner-scope check. Several of them appear in the wall above and run again in CI on purpose: the commit-time copy exists for the feedback loop, the CI copy for the guarantee, so bypassing local hooks still gets caught at the PR. The full commit-time tier — every hook and why it earns its place at the front — is its own reference: Commit-time gates.
A gate’s value is inseparable from when it fires. Move the check as early as it can run, and the expensive version of the failure never happens.
So — are we drifting?
Section titled “So — are we drifting?”On every layer where a gate runs: no, structurally. A value cannot exist on one side that another side has not heard of. A generated file cannot disagree with its manifest. A handler cannot diverge from its contract. An agent cannot behave differently across frameworks. A projection cannot win an argument with the event log. Those are not promises maintained by discipline; they are properties maintained by the build, on every PR, by machines rather than memory.
On the layers where a gate does not yet run: maybe — and we know exactly where. That is what the ladder has been tracking the whole way, and what the next section builds on.
Where this goes next
Section titled “Where this goes next”Here is the frontier the whole pattern points at — the next set of seams that want to become one source with many projections and a gate.
- The capstone: a frontend form and its backend validation from one manifest. This is the unbuilt piece the pattern keeps pointing at — the one place where the shape exists in every adjacent layer but has not yet been closed. The plumbing is already there — operation inputs are Zod schemas,
zod-to-json-schemais in use, the vocabularies generate validators — and the natural extension is to generate a form and its server-side validation from a single declaration. When that lands, the tightest FE↔BE seam there is — “what may the user submit, and what will the server accept?” — becomes one source with two projections and a gate. That is where this goes. - More vocabulary kinds.
permission,route, andeventare sketched in buildmere’s plugin contract, and bringing them under the same manifest is the next reach; permissions and routes are governed by other means today. - More language emitters. The
errorkind emits Go; a TypeScript emitter generates the frontend’s error constants and closes the Part 5: The Error Manifest coupling completely. The same move applies to a metric TypeScript emitter. buf breakingas a blocking gate. The rule is configured; wiring it to fail CI while the proto is mid-rename is the next turn of the ratchet.- Production observability. Config declares an OTLP endpoint, and the direction is full production OTLP with real spans built on top of the first tracing cut.
- A devportal-state CLI fallback and a pair of platform adapters (
AGENTS.md, a Cursor_platform.mdc) extend the agent-OS pattern to the next surfaces.
Why this works now
Section titled “Why this works now”Step back to where Part 1: The Drift Problem started. Two forces make this the right architecture for this moment, not just good hygiene.
The rails unlock velocity. When writing code and recalling patterns are cheap, the scarce resource is coherence. The gates above are what let you accept a large generated diff and trust it, because every way it could drift is a way the build refuses. The speed is not the typing — it is the not-having-to-recheck.
And the rails got cheap to build. The old reason this kind of governance was rare is that a manifest system, a custom linter, a codegen kernel, an event-projection plane were expensive to build and maintain. The same economics that made generation cheap collapsed that cost: a new vocabulary kind, a new lint rule, a new gate is now an afternoon. The force that created the drift problem is the one that funds the cure.
Constraints are not the brake on cheap generation. They are what makes it safe to trust — and trust is what lets you go fast. “Governance is expensive” is an old-world assumption that no longer holds.
The question is permanent
Section titled “The question is permanent”“Are we drifting?” is not a question you answer once. It is asked continuously — by check-vocab on every commit, by the contract test on every MCP boot, by agents-check on every sync, by the compiler on every build. The work was never to answer it by hand. The work was to build the gates that ask it for you, forever, and to be honest about the few places they do not run yet.
Define the concept once. Generate the rest. Let the gates keep them honest. That is the pattern — and as the system grows, each new layer joins the same shape. This corpus grows with it: the question does not change, the gates keep answering it, and new parts land as new seams come under the same discipline.