skip to content

Why does a policy rule's test suite need near-miss allow cases, not just deny cases?

level: juniorimportance: must knowfreq 68%

answer

  1. test both sides of the boundary
  2. the change it must not block
  3. deny cases only prove it can refuse
  4. one field away from allowed
  5. over-blocking is what kills a gate

basics

~10 s

Near-miss cases are legitimate inputs sitting just inside the boundary — the changes the rule must let through. Deny cases only prove a rule can refuse something. They never prove it refuses nothing else.

solid answer

~50 s

A deny case proves the rule fires on an input you already knew was bad. It says nothing about what else it fires on. Take a rule that permits only approved instance families in approved regions: the near misses are the inputs one field away from the denied one — the same family in an approved region, an approved family in the region you just refused, the same resource declared through a shared module instead of directly. Those are changes real teams ship every day, and wrongly blocking one is the failure that gets a gate switched off or routed around. So the table carries both sides of every boundary: for each deny case, the closest input that must be allowed. I take the allow side from changes that have already merged, so the expected verdict is ground truth rather than my own opinion of what should pass.

go deeper

for a junior

Be ready to say plainly that a near-miss case is a legitimate change the rule must let through, and that deny cases alone cannot show a rule is not over-blocking.

for a middle

Explain how you construct the pair: take one input and change only the field the rule judges, so the two cases differ exactly at the boundary.

for a senior

Show you weigh the two failure directions differently — a rule that over-blocks costs you the team's willingness to be gated at all, which is harder to recover than a missed misconfiguration.

for a principal

Own the argument that a policy programme's currency is trust, and that allow-case evidence drawn from real merged work is what lets you defend a new rule to the teams it will constrain.

## What a near-miss case is A policy rule draws a boundary through the space of possible changes. A **near-miss test case** is an input that sits as close to that boundary as you can get while still being on the allowed side — or, symmetrically, an input that differs from an allowed one by exactly the field the rule cares about. It is the case where a slightly-wrong rule and a correct rule disagree. Make it concrete with a rule that says: an instance may only be created with an approved family, and only in an approved region. Approved families are the general-purpose `m` family; approved regions are `eu-west-1` and `eu-central-1`. The obvious deny case is an unapproved family in an unapproved region — it is wrong on both axes, and almost any rule you write will refuse it. The near misses are the interesting ones: - approved family, approved region — must be allowed; - approved family, unapproved region — must be denied, **because of the region**; - unapproved family, approved region — must be denied, **because of the family**; - approved family and region, but the resource comes from a shared module rather than being declared directly — must still be allowed; - an approved resource sitting in a change that also creates ten unrelated resources — must still be allowed. ## Why deny-only suites give false confidence A test suite made only of deny cases tests one direction of the rule. It answers "can this rule say no?" A rule that denies **everything** passes every one of those cases. So does a rule whose condition is subtly too broad — one that matches on the region field being present rather than on its value, say, or one that checks the family list with a typo so that no family is ever approved. The direction that is not tested is the one that actually costs you something. A rule that fails to block a bad change costs you a misconfiguration you would probably have caught elsewhere. A rule that blocks a good change costs you the gate itself: the team on the other side raises a ticket, gets an exception, and the next time someone proposes a rule the answer is no. In policy work the allow cases are the ones that protect the programme, and they are the ones authors skip, because writing them feels like testing that nothing happens. ## Where the cases come from The strongest allow cases are not invented. They are captured from changes that have already been reviewed and merged — for this rule, the plan documents from the last few dozen merged pull requests that create instances. Each one arrives with its expected verdict attached for free: a human approved it and it shipped, so the rule must not block it. That turns a guess about what "should" pass into a recorded fact about what did. Deny cases usually still have to be written by hand, because non-compliant changes rarely survive to be merged. That asymmetry is fine — the point is that the side of the boundary you are most likely to get wrong is the side where real evidence exists. ## Organising them Write the cases as a table rather than as a pile of separate tests: one row per fixture, each row naming the input, the expected outcome and the reason. A table makes the boundary visible as a shape — you can see at a glance that you have an allow row and a deny row for each axis, and that you have no row at all for the third axis you just added to the rule. It also makes the suite cheap to extend when the approved-region list changes, which it will. ## What good looks like in an interview Saying "I test both the deny and the allow path" is the baseline. What separates a good answer is naming the specific near misses for the rule in front of you, saying where the allow fixtures come from, and stating the consequence in the right direction: a rule that over-blocks is a bigger operational problem than a rule that under-blocks, because it burns the credibility you need to enforce anything at all.

  • A rule that denies every input passes your deny cases. What single fixture exposes it?
    One allow case: a change everybody agrees is fine — an approved instance family in an approved region — asserted to produce no violation. A deny-everything rule fails it immediately. That is why every boundary needs a row on each side, and why the allow row is the one worth capturing from a change that actually merged.
  • Deny fixtures are hard to capture from merged changes. How do you get them?
    Write them by hand, but derive each one from a real allow fixture rather than from scratch: take a captured plan and change exactly the field the rule judges — swap the region for an unapproved one, leave everything else alone. The deny case then shares the realistic shape of a genuine change, and the diff between the pair is exactly the rule's boundary.
  • How many near-miss cases per rule is enough?
    One allow and one deny per condition the rule tests, plus one for each shape the input can arrive in — declared directly, produced by a shared module, one offending resource among many. If you cannot point at the row that would fail when a given condition is broken, you are missing a case. Count conditions, not tests.

A smoke alarm that shrieks at a lit match proves only that it is loud. What you need to know is whether it stays quiet while someone makes toast.

saying these in an interview costs you the question

  • Only writes deny cases and calls the rule tested
  • Thinks a passing suite means the rule cannot over-block
  • Invents allow inputs instead of capturing merged ones
  • Treats blocking a good change as a minor annoyance
  • Tests inputs far from the boundary on both sides

context