Skip to content
Back to Ideas 4 min read

The Gate Always Asks

The Gate Always Asks

Encoding the release, part 4 of 8.

Automating a release means automating things you cannot take back. A merged PR, a published GitHub Release, an approved deployment that ships to npm — none of these un-happen. The interesting design question was never how to automate them; it was which of them a machine should be allowed to do without looking a human in the eye.

Our answer has three parts.

Every irreversible step pauses. Before the merge, the command states what it is about to do and waits: merge pr #198? (y/n). Before the GitHub Release: cut release v0.4.1? (y/n). Before approving the deployment gate: approve the release gate for run #16? (y/n). The pause is not ceremony — it is the last moment at which stopping costs nothing. Answer n and the rail stops with a named reason, leaving a state the next preflight can assess.

--yes exists, but it is not a skeleton key. Run ekohacks release 0.4.1 --yes and the merge and Release pauses answer themselves — appropriate for the steps that are pure rail once CI is green. The gate is different. The gate is the moment the package actually ships to the registry, the step our manual process always reserved for a maintainer's explicit approval. So the rule is absolute: the gate always asks.--yes does not reach it. Nothing does.

The rule is pinned by a test, not a code review. In the suite there is a test whose whole job is to answer "no" at the gate and then assert that the approvals tracker is empty:

const result = await runShip({ gh, confirm: () => Promise.resolve(false) });

expect(result).toEqual({ stopped: 'gate not approved' });
expect(approvals.data).toEqual([]);

And a second one that runs the entire chain with --yes and asserts the only question ever asked was the gate's. These are the most valuable tests in the repository, and neither tests that something works. They test that something refuses to happen. If a future refactor ever lets an approval sneak past a "no", or lets --yes leak into the gate, the suite fails before the code reaches main.

Mechanically it is all very boring, which is the point: the confirm is an injectable function, the policies take it as a parameter, and the chain decides which pauses --yes may auto-answer. No framework, no prompt library — a function returning a promise of a boolean, and a design decision about who gets to call it.

The human decision was the first requirement we wrote down for this stage, before any code: the one human decision stays human. Everything else in the series is machinery for deleting toil. This part is the machinery for keeping the judgement.

Next: One Command, Three Policies, Zero Duplication.

More from Ideas

·8 min read

Inversion of Control Doesn't Need a DI Container

We started designing a dependency scanner, and its needs led us to Inversion of Control. It turned out we were already doing it, without a DI container.

A
Anastasios Orfanidis
·7 min read

Unknown Is an Answer

A stub that holds no documents could not say whether a change still matched a query. Letting it answer unknown named a case the real database has too.

·7 min read

The Inbox That Delivers Nowhere

Signing in by emailed code puts a mailbox on the critical path. How we use Mailpit on Studomia, how our tests read the code, and what one question taught us.