Skip to content
← All insights
VerificationAUG 05, 20264 min read

A gate that passes on mutated input is not a gate

Every green suite eventually turns out to be green for reasons unrelated to correctness. The cheapest defence is borrowed from experimental practice: break the thing on purpose first.

Algodyne

A check that has never failed is not evidence that the system is correct. It is evidence that the check ran. Those two feel identical right up until the moment they are not, and the distance between them is where regressions ship.

The cause is rarely laziness. A check can be written carefully, reviewed by someone competent, and still be scoped to a path the defect does not touch — or assert something that holds whether or not the code works. Nobody notices, because the observable signal in both cases is the same: green.

How green suites lie

The most common failure is scope. The test exercises a wrapper while the logic lives one layer down, or it drives a code path that the production caller never takes. Coverage tooling reports the lines as covered, because they were executed. Execution is not assertion.

The second is a tautological assertion. The test checks that a function returns a value of the right shape, or that a list is non-empty, or that no exception was raised. All of those hold for a broken implementation. They are real assertions about real behaviour and they discriminate nothing.

The third is a mocked boundary that no longer resembles the thing it stands for. The mock was accurate when it was written. The real dependency changed. The test still passes, and it now verifies a system that does not exist.

What unites all three is that the check would produce an identical result against correct and broken code. That is the property to test for, and it is testable directly.

The negative control

Experimental science does not trust an instrument that has only ever produced the expected reading. You run a control: apply the procedure to a sample known to be negative, and confirm the instrument says so. An instrument that reports a positive result on everything is not a sensitive instrument, it is a broken one, and the only way to find out is to feed it something you already know the answer to.

Applied to software: before relying on a gate, break the thing it guards on purpose and confirm the gate fails. If it stays green, you have learned for the price of a minute that the gate was decorative. If it fails, you now know the check is connected to the behaviour you think it is connected to.

We treat this as a requirement rather than a nicety. A verification claim is not reportable until the control has been run and its failure observed in tool output — not reasoned about, not expected, observed.

This is adjacent to mutation testing, and it is worth being precise about the relationship rather than claiming novelty. Mutation testing does this exhaustively and automatically: generate many mutants, measure what fraction the suite kills, report a score. That is more rigorous and considerably more expensive. A negative control is the same idea applied by hand at the moment of the claim, targeted at the specific behaviour being asserted. Where a mutation harness is affordable, run it. Where it is not, the manual control still catches the failure that matters most, which is a gate wired to nothing.

Confirm the mutation actually landed

The subtle failure is a control that proves nothing because the mutation never took effect. The edit went to a stale copy, a parallel path, a generated file that gets overwritten, or an artifact the check does not read. The gate stays green, and the resulting green is indistinguishable from success — so the control produces false confidence rather than none, which is worse than skipping it.

So the control asserts its own preconditions. The original state was present before the change. The new state is present after. The check ran against the artifact under test rather than a proxy or an earlier snapshot of it. If the restore step cannot recreate the starting condition — an untracked file, a generated artifact, a mutated database — that is reported as an invalid control, never as a passing one.

The same discipline applies to the thing being verified. Verify the artifact that ships. A grep hit is a pointer, not a finding; a passing check against a build from an hour ago is a statement about that build. Where those are cheap to confuse, they will be confused.

When to skip it

Not every check earns a control. The ones that do are the checks whose failure would be expensive and whose silence is plausible: security boundaries, data-integrity constraints, release gates, anything guarding an irreversible action, and anything asserted about content rather than structure.

Checks with a naturally observable failure mode need it less, because they fail on their own during normal development often enough to prove they are wired up. A type checker demonstrates its own sensitivity daily.

The cost is a minute per gate, paid once, at the moment you are already thinking about the behaviour. It is the difference between having a test suite and having a belief about a test suite — and the belief is held most confidently precisely when it is least examined.