The Gate Is One Command

ForkTex Engineering · September 5, 2026 · 7 min

Every repository we work in answers the same question the same way: is this change done?

The answer is make ci. Not a checklist, not a workflow file, not a conversation. One command, which a developer runs on a laptop and CI runs unchanged.

Why one command

The alternative is familiar. Quality logic accumulates inside a CI configuration — a lint step here, a coverage threshold there, a build matrix that grew an extra flag during an incident. Each addition is reasonable. The result is that the only way to know whether a change passes is to push it and wait.

That has two costs. The obvious one is latency: a five-minute round trip to discover a formatting error. The subtler one is that the checks stop being inspectable. A rule living in a workflow file is a rule most of the team has never read and cannot run. When it fails, the fix is guesswork against a log.

So we invert it. Every check is a named target in a Makefile. CI installs a toolchain and invokes the gate. It contains no quality logic of its own, and that is the property worth protecting: if CI can fail for a reason you cannot reproduce locally, the gate has a hole in it.

The atoms

The targets are deliberately boring and consistently named across repositories:

AtomWhat it does
installDependencies from the lockfile
format-checkFormatting, verified not applied
lintStatic analysis
typecheckTypes, no emit
testThe suite
codegen-checkRegenerate derived artefacts and diff
auditDependency vulnerabilities
ciAll of the above, in order

Consistency across repositories is most of the value. An engineer moving between projects does not need to learn a new vocabulary to find out how a thing is built. The atom name is the interface; whatever the target actually runs is the implementation, and it can differ.

Two rules keep the names honest. An atom that names a target with no recipe is a claim with nothing behind it, so every declared atom must actually run. And the same atom name means the same thing everywhere — test does not mean unit tests in one repo and a smoke suite in another.

There is no "passes locally"

A change is proposed as complete after the gate passes. Not after the tests the author remembered to run.

This sounds obvious and is routinely violated in one specific way: excluding a check to get something merged. A lint rule fires on a line that is fine, so a per-file ignore goes in, and the change ships.

An excluded check does not come back. Nobody revisits a suppression, because nothing surfaces it. The exclusion outlives the reason for it, and the next author inherits a gate with a hole they cannot see and did not agree to.

The habit that replaces it is usually to fix the thing the rule was pointing at. When a linter objects to an over-broad type, name the type. The rule was right; the suppression was faster.

There are legitimate exclusions. They carry a reason, and the reason is written where the exclusion is, not in a commit message nobody will read again.

Tests run against real infrastructure

One choice inside the gate deserves defending, because it is slower and we do it anyway.

Database tests run against a real database, started in a container for the suite. Not an in-memory substitute, not a mocked session.

A mock encodes what we believe the driver does. Nearly every bug worth catching at this layer lives exactly where that belief is wrong — a constraint that fires differently than expected, a transaction boundary that does not nest the way the documentation implies, a type that round-trips lossily. A mocked test agrees with the assumption that produced the bug, which is the least useful thing it could do.

The cost is real: container startup makes the suite slower to begin. It buys tests that fail when production would fail, which is the only property a test suite is actually for.

What the gate does not do

It does not commit. It does not push. It does not decide that work is finished — it decides that work is *proposable*.

That distinction matters when agents are writing code, which increasingly they are. The gate is what makes a machine-authored change reviewable on the same terms as a human one: the checks ran, they passed, and the person reading the diff can reproduce that in one command.