In a Terraform plan, how do you gate a change that lowers a database's backup retention window?
answer
- compare, do not threshold
- both sides of the diff, not one
- an absolute rule punishes the innocent editor
- what is before on a create?
- it stops decline, it never improves anything
basics
~20 sCompare both sides of the diff. Deny when change.after's retention is lower than change.before's, so an already sub-standard database can be edited for unrelated reasons but never made worse. Creates have no before, so apply the absolute minimum.
solid answer
~50 sA threshold rule reads only `change.after` and asks whether the end state is acceptable; a no-regression rule reads `change.before` too and asks whether this change makes things worse. They differ on legacy infrastructure. A database sitting at three days against a seven-day standard, updated for an unrelated capacity reason, is denied by the threshold rule even though the engineer lowered nothing — and that is how gates lose their mandate. Compose the two by action: on entries whose actions include `create`, apply the absolute minimum, because a new database has no history to protect; on `["update"]` entries, deny when after is lower than before. Branch explicitly rather than relying on the comparison, because `before` is null on a create and a comparison against nothing produces no result rather than a denial. Treat a replacement as a create, since the prior object does not survive.
go deeper
Know that a plan's change object carries both the current values and the planned ones, so a rule can compare them rather than only checking the end result against a number.
Explain the composition by action: absolute minimum on a create, before-versus-after comparison on an update, silence on a no-op. Be able to say why the create branch cannot rely on the comparison.
Show you have used this to ship a rule into a non-conforming estate. Explain the adoption argument, the null-before trap, and why a replacement must be treated as a create rather than an update.
Own the decision to accept a weaker rule now for enforcement you can actually turn on, and the commitment to retire it once the backlog is drained rather than leaving the estate frozen at its current values.
## Two different questions a rule can ask A threshold rule asks: *is the end state acceptable?* Read `change.after` for a managed database, compare its backup retention window against your minimum, deny if it is lower. A no-regression rule — a ratchet — asks a different question: *does this change make things worse?* Read both sides of the diff, `change.before` and `change.after`, and deny when the after value is worse than the before value, regardless of whether either satisfies the target. The two produce different verdicts on the same plan, and the difference matters most for infrastructure that predates the policy. Consider a database sitting at three days of retention against a seven-day standard, and an engineer who needs to change its instance size for capacity reasons. Under the threshold rule, that plan is denied: the resource is `["update"]`, so it is in scope, and `after.backup_retention_period` is 3, which is below 7. The engineer did not lower anything — they touched an unrelated attribute — and now they either fix somebody else's retention setting inside an unrelated change, or they route around the gate. Under the ratchet, `before` is 3 and `after` is 3, nothing got worse, and the change proceeds. Now consider a plan that drops retention from 30 days to 1. The threshold rule catches it. So does the ratchet, because 1 is lower than 30 — and the ratchet would still have caught a drop from 5 to 1, which a seven-day threshold rule catches for the wrong reason and a five-day threshold rule would miss entirely. ## Composing the two In practice you write both and combine them by action: - **Creates** (`actions` contains `create`, and `before` is `null`): apply the absolute minimum. A brand-new database has no history to protect and no excuse; this is where the standard is actually enforced. - **In-place updates** (`actions` contains `update`): apply the comparison. Deny when `after.backup_retention_period < before.backup_retention_period`, or when deletion protection goes from true to false. - **Everything else** (`no-op`, `read`, `delete`): no opinion. The `before is null` case is the one that bites. On a create there is no prior value, so a naive comparison of after against before compares a number to nothing. In most policy languages that does not evaluate to false — it produces no result, and the deny silently never fires. A newly created one-day-retention database would sail through a ratchet-only rule. Branch on the action explicitly rather than relying on the comparison to do the right thing on an empty operand. Note also that a **replacement** is not an update: it destroys and recreates the object, so treat it as a create and apply the absolute minimum. The `before` values in a replacement entry describe an object that will not survive the apply. ## What the ratchet buys, and what it does not What it buys is adoption. It lets you turn a rule on across an estate that does not yet satisfy it, without the rule's first victims being engineers who had nothing to do with the violation. Nothing gets worse from the day it ships, and every new resource is held to the full standard. What it does not buy is progress. A ratchet applied to a three-day database keeps it at three days forever; left alone, it will never reach seven. The ratchet is a holding action while a separate, owned effort drains the backlog, and it should be replaced by the plain threshold once the estate is clean — at which point the comparison becomes redundant, because nothing is below the line to protect. There is a second limitation worth stating out loud: a ratchet only sees resources the run touches. A non-conforming database nobody edits appears in no `["update"]` entry and is never evaluated by either rule. Change-scoped gates are blind to untouched infrastructure by construction. ## What people get wrong - Comparing `after` against a constant and calling it a no-regression rule. - Forgetting that `before` is `null` on a create, so the comparison quietly produces no result. - Treating a replacement as an update and carrying forward the doomed object's prior values. - Believing a ratchet improves anything — it only stops decline. - Reading the prior value out of the `configuration` section, which holds written expressions rather than the values in effect today.
- What does your rule do when change.before is null?That is a create, so there is no prior value to protect and the absolute minimum applies instead. Branch on the action explicitly: a comparison of a number against nothing does not evaluate to false in most policy languages, it produces no result, so a create with a one-day window would sail through a comparison-only rule.
- Why not enforce the absolute minimum everywhere and skip the comparison?Because it blocks anyone who touches a pre-existing sub-standard resource for an unrelated reason, and they usually cannot fix the finding inside their change. The comparison lets you switch the rule on across a dirty estate on day one: nothing gets worse, every new resource meets the standard, and the backlog is drained separately.
- What does a no-regression rule fail to achieve?Progress. A database held at three days stays at three days forever — the rule only stops decline. It is a holding action while an owned remediation effort raises the existing values, and it should be replaced by the plain threshold once the estate is clean, at which point the comparison protects nothing.
A speed limit says how fast you may go; a ratchet says you may not accelerate. On a road where half the traffic is already speeding, the second is the rule you can actually start enforcing today.
saying these in an interview costs you the question
- Compares after against a constant and calls it no-regression
- Forgets before is null on a create
- Treats a replacement as an in-place update
- Believes a ratchet raises existing values over time
- Reads the prior value from the configuration section