Your CI gate denied a build on policy. What is your first step to reproduce that denial?
answer
- do not push again and hope
- the decision is a pure function
- three things to pin
- the engine did not read your source file
- replay, then shrink the input
basics
~10 sCapture the exact input document the engine evaluated and the version of the rules it used, then replay that pair on your own machine. Reproduce the decision before you start editing the job definition.
solid answer
~50 sA policy decision is a function of three things: the input document the engine actually evaluated, the rule set version it evaluated against, and any external data the rule consulted. So the first step is not to edit the pipeline and re-run it, it is to retrieve that input and replay it locally, off the enforcement path, against the same rules. Two things usually fall out immediately. First, the input is often not the file you wrote: your CI job definition gets templated, expanded and defaulted before the engine sees it, so the document the rule read may be shaped differently from the source in the repo. Second, once the denial reproduces locally you can shrink the input down to the smallest document that still gets refused, which points straight at the field path the rule cares about. Re-running the pipeline just builds a new input and teaches you nothing.
go deeper
Be ready to say that you fetch the exact input the engine evaluated and replay it locally before changing anything. Naming the three things that determine a decision - input, rule version, reference data - is what the interviewer is listening for.
Explain why the rendered document differs from the source file, and describe the reduce-and-perturb loop that turns a denial into a one-sentence hypothesis about a specific field path.
Show that you treat reproducibility as a property you design in: the gate must emit its evaluated input and the rule set must be fetchable at a pinned revision, or every denial becomes archaeology.
Own the argument that a decision nobody can replay is an unaccountable one, and that the cost of making replay cheap is repaid the first week a rule blocks a team you do not manage.
## Why "just re-run it" is not diagnosis A gate refuses a change. The reflex is to tweak the job definition and push again, or to hit re-run in case it was a fluke. Both are guessing, and both are expensive: each attempt rebuilds inputs, occupies shared runner capacity, and takes minutes. Worse, if a retry goes green you have learned nothing about why the first attempt was refused, and the rule will refuse someone else next week. Diagnosis begins by moving the decision **off the enforcement path**: reproducing it somewhere you can poke at it, where being wrong costs nothing and where you can iterate in seconds. ## A decision is a function of three inputs Every policy engine, whatever the language, answers a question of the form *given this document, under these rules, with this reference data, is it allowed?* Reproducing a decision therefore means pinning three things: 1. **The input document** - the concrete JSON or YAML structure the engine was handed. For a pipeline gate that is the rendered CI job; for infrastructure it is a plan; for a cluster it is the submitted object. 2. **The rules** - a specific revision of the rule set, not "whatever is deployed now". Rules ship continuously; the version that refused you this morning may already have been replaced. 3. **External data the rule consults** - the approved-destination list, the ownership table, the exception list - as it stood at decision time. Pin all three and the answer is deterministic: the same trio always yields the same verdict. Miss one and you are debugging a different decision than the one that blocked you. ## The source you wrote is not the input the engine read This is the single most common time-waster. Engineers read the job definition in the repo, reason about what the rule *should* have done, and conclude the engine is broken. But between the file in the repo and the engine sits a rendering step: templates are included, matrix entries are expanded into separate concrete jobs, variables are interpolated, platform defaults are filled in, and fields you never typed appear while fields you did type may be nested somewhere else. Consider a rule that requires any build job holding production credentials to declare an egress allowlist. In the source there is one job block with a clear allowlist; after expansion there may be six jobs, and the rule reads each one on its own. So the artifact you want is the **evaluated input**, not the source. Practically that means your platform has to make it retrievable: the gate emits the document it evaluated (or a pointer to it) as part of the run, and the pipeline keeps it long enough for a human to fetch it. If your gate does not do that, the fastest permanent fix in the whole of policy operations is to make it do so - without the evaluated input, every denial becomes an archaeology exercise. ## The replay loop With the trio in hand, the loop is short: - **Replay.** Evaluate the recorded input against the pinned rules locally. Confirm you get the same verdict the gate got. If you do not, you have a different and more interesting problem: something in the trio differs. - **Reduce.** Delete parts of the input until the denial disappears. The last thing you removed is the field the rule is reacting to. This is bisection, and it works even when you cannot read the rule language well. - **Perturb.** Add the field the rule seems to want and confirm the denial clears. Now you have a hypothesis you can state in one sentence: *the rule refuses this job because field X is absent / has value Y*. - **Decide.** Only now is it worth arguing about whether the rule is right, whether the job should change, or whether the rule never anticipated this shape of input. ## What good self-service triage looks like The target is that a blocked engineer can run one command, on a laptop, with no access to the enforcement infrastructure and no production credentials, and get back which rule fired and on which field path. That means: the evaluated input is retrievable, the rule set is versioned and fetchable at that version, the engine runs standalone, and reference data has a snapshot. None of that is exotic - it is just deciding, before the first denial, that the decision has to be reproducible. ## The habits to carry Never debug a denial by editing and pushing. Never reason from the source file when the engine read a rendered document. Never assume the rules today are the rules that refused you. And when the local replay disagrees with the gate, treat the disagreement itself as the finding, not as a nuisance.
- Why is retrieving the evaluated input more useful than reading the job definition in the repo?Because the engine never saw the repo file. Templates get included, matrix entries expand into separate concrete jobs, variables are interpolated and defaults are filled in before evaluation. Reasoning about the source tells you what you meant; the evaluated input tells you what was judged. Most denials that look absurd turn out to be a rendering surprise, not a rule bug.
- You have the input reproducing the denial locally. What do you do next?Reduce it. Strip the document down until the denial disappears; whatever you removed last is what the rule is reacting to. Then add the expected field back and confirm the denial clears. That gives you a one-sentence hypothesis - this rule refuses the job because this field path is missing - which is what you take to the rule owner or fix yourself.
- Why pin the rule set version rather than evaluating against whatever is currently deployed?Rules ship continuously. If the set moved between the denial and your investigation, you may reproduce nothing, or reproduce a different denial, and conclude the gate is flaky. Pinning the revision that made the decision keeps the replay honest, and if the pinned version denies while the current one allows, you have just learned that the rule was already fixed.
saying these in an interview costs you the question
- Re-runs the pipeline hoping the denial was transient
- Edits the job definition and pushes to see what happens
- Reasons from the repo source instead of the evaluated document
- Assumes the currently deployed rules are the ones that denied
- Asks the policy team what the rule does instead of replaying it