Skip to content
All writing
4 min read

Governance gates belong in parallel, not in a chain

Lint, security and cost checks don't depend on each other. Running them one after another turns one review into several round trips.

Most infrastructure pipelines run their checks in a chain: lint, then plan, then policy, then cost. It looks logical, and it's how nearly every example pipeline is written. It's also why engineers start batching up changes and stop reading CI output.

What a chain does to feedback

In a chain, the first failure hides everything after it. Say you push a change with a lint error, a policy violation and a cost jump. You only hear about the lint error. Fix it, push, wait, and now you hear about the policy. Fix that, push, wait, and now it's the cost.

That's three round trips to find three problems you could have seen on the first run. If the pipeline takes five minutes, that's a fifteen-minute loop with two context switches in it. So people do the sensible thing and make fewer, bigger changes, which is the opposite of what the checks were meant to encourage.

They don't actually depend on each other

The chain suggests each step needs the one before it. Mostly they don't. TFLint reads the config. Conftest reads the plan. Infracost reads the plan. The plan is the only real prerequisite, and only for two of the checks.

In a chain

plan
TFLint
Conftest
Infracost
merge

The first failure hides the rest. You wait for every step, one after another, and find problems one push at a time.

In parallel

plan
TFLint
Conftest
Infracost
merge

Every check runs on the same plan at once. One run shows every problem; you only wait for the slowest check.

same checks, same plan, different wait

So run the plan, then run the checks side by side, and wait for all of them before merging. One run shows every kind of problem, and the reviewer sees the full picture, including what the change costs, before deciding anything.

Check policy against the plan, not the code

One detail matters even more than the ordering: policy should check the plan output, not the HCL. Looking at the source only shows what someone wrote. The plan shows what will actually be created, with module defaults, variables and computed values all filled in.

Take a rule like "no public S3 buckets". At source level it's easy to slip past, because the value that makes a bucket public can come from a variable, a default three modules deep, or a workspace override. In the plan, there's nowhere for it to hide.

Put cost next to the security checks

Cost usually gets looked at in a monthly review, not as a check. So it shows up weeks after the change, to someone who didn't make it. Running Infracost next to the security checks moves that conversation to when the change is still one revert away. It also says something about the platform: cost is part of getting a change right, not something to look at later.