skip to content

How a Rule Resolves

A rule defines a value in a virtual document rather than running a procedure, and an unbound variable turns an expression into iteration. Interviewers start here: it decides what a rule returns.

on this pageshow

explore

questions

14

In Rego, what is the difference between the input document and the data document?

level: juniorimportance: must knowfreq 80%

answer

  1. two document roots, not one
  2. one of them arrives per query
  3. loaded JSON and rule output share a tree
  4. package path becomes a data path

basics

~20 s

input is the document the caller sends with a single query, the thing being judged. data is the tree the engine already holds: JSON loaded alongside the policy, plus the virtual documents that rules themselves define.

solid answer

~40 s

Rego evaluates against two document roots. `input` is supplied by the caller with each query and lives only for that evaluation — in an infrastructure gate it is the artifact under judgement, such as an exported plan. `data` is everything the engine holds independently: base documents loaded from JSON or YAML files, and the virtual documents that rules produce. A rule named `violations` in `package estate` publishes its value at `data.estate.violations`, so rule output and loaded reference data share one addressable tree and a path alone cannot tell them apart. Rules read both by path — `input.resource_changes`, `data.catalog.families` — with no load or fetch call, and they can define documents only under `data`; `input` is read-only.

go deeper

for a junior

Be ready to say in one breath which root the caller supplies and which the engine holds, and to name a concrete path in each for a gate you have seen.

for a middle

Explain that rules define virtual documents under data at their package path, that a base document and a rule cannot share a path, and that references are plain path lookups with no fetch step.

for a senior

An interviewer expects you to reason about what belongs in each root when wiring a gate, and to spot that a policy needing a fact the caller never sends is a data-loading problem, not a rule problem.

for a principal

Own the contract: which facts the calling system is obliged to put in input, which the platform loads into data, and what the blast radius is when either drifts out of step with the rules.

### Rego has two document roots, and only one of them comes from the caller A Rego policy never "receives arguments" the way a function does. It evaluates against a document tree, and that tree has exactly two roots: `input` and `data`. **`input` is the document under evaluation.** The caller supplies it with the query, it exists only for the duration of that one evaluation, and nothing in the policy can write to it. In an infrastructure gate, `input` is typically the thing being judged — the JSON a plan export produced, a manifest, a request payload. Policies read it by path: `input.resource_changes`, `input.resource_changes[0].change.after.machine_type`. **`data` is everything the engine already holds.** Two different kinds of thing live in this single tree, and the query path cannot tell you which is which: - *Base documents* — plain JSON or YAML loaded alongside the policies. A file placing `{"catalog": {"families": ["n2", "c3"]}}` into the tree makes `data.catalog.families` readable by any rule. This is where slow-moving reference facts live: an approved machine-family catalogue, an owner registry, a list of sanctioned regions. - *Virtual documents* — the outputs of rules. A rule named `violations` declared in `package estate` publishes its value at `data.estate.violations`. It is computed on demand, when something asks for that path, not stored. That last point is the one candidates most often miss. Rules do not "return"; they **define documents inside `data`**, addressed by package path plus rule name. This is also why one rule can consume another simply by naming its path — there is no call, just a reference to a document that happens to be computed. ### Reading either root Both roots are read the same way, with dotted or bracketed paths: ```rego package estate approved_family if { family := input.resource_changes[0].change.after.machine_type family in data.catalog.families } ``` `import data.catalog` lets the body write `catalog.families` instead of the full path — pure shorthand, no loading semantics attached. There is no `read()`, `lookup()` or `fetch()`: if the path is present the reference evaluates to its value, and if it is absent the reference is undefined, which makes the expression — and therefore the rule body — produce nothing rather than raise an error. ### The boundary between the two The mechanical split is fixed: | | `input` | `data` | |---|---|---| | Who supplies it | the caller, per query | loaded with the policy, or computed by rules | | Lifetime | one evaluation | until the engine is reloaded | | Writable by a rule | never | yes — every rule defines a document under it | | Typical content in a gate | the artifact being judged | reference facts plus rule output | Two collisions are worth knowing. First, a base document and a rule cannot occupy the same path: if JSON is loaded at `data.catalog.families` *and* a rule named `families` exists in `package catalog`, the engine reports the conflict instead of choosing one. Keep loaded-data paths and rule packages disjoint. Second, `input` is a reserved root — you cannot declare a package or a rule under it, so any fact the policy needs but the caller did not send has to arrive through `data`. ### Why interviewers open here Every later question about rule shapes, query paths and gate wiring assumes this model. A candidate who thinks `data` is "the config file" and `input` is "the request body" is half right and will still be surprised the first time they query `data.estate.violations` and get a computed answer back — because the rule they wrote is, from the caller's point of view, indistinguishable from a JSON file someone loaded.

  • Can a rule write into input?
    No. `input` is read-only for the policy: the caller supplies it with the query and nothing in the policy can add to it or persist it. Rules define documents only under `data`, at their package path. So any fact a policy needs but the caller did not send must arrive through `data` — loaded alongside the policy — rather than being manufactured into the input.
  • What happens if a loaded JSON file and a rule both claim data.catalog.families?
    The engine refuses it. A base document and a virtual document cannot occupy the same path, so JSON loaded at `data.catalog.families` colliding with a rule named `families` in `package catalog` is reported as a conflict rather than silently resolved in favour of one. Keep loaded-data paths and rule package paths disjoint.
  • How does a rule reference a fact that was loaded into data?
    By its path — `data.catalog.families` — or via `import data.catalog`, after which the body can write `catalog.families`. The import is pure shorthand; there is no loading or lookup step. The whole tree is addressable as a document, so a path that does not exist is simply undefined rather than an error.

input is the envelope handed over for this one decision. data is the filing cabinet behind the desk: partly folders someone filed there in advance, partly notes the standing rules write themselves.

saying these in an interview costs you the question

  • Swaps them: calls data the request and input the config
  • Thinks a rule must call a function to load data
  • Believes input is stored in the engine between queries
  • Cannot say where a rule's own output becomes readable
  • Assumes a rule can add fields back into input

context

open as a page

In Rego, what is the difference between an expression that is undefined and one that is false?

level: juniorimportance: must knowfreq 74%

basics

~20 s

False is a value; undefined means Rego found no value at all. Both stop a rule body, but false is an answer, while undefined leaves the rule with no result to emit and no error to report.

open as a page

In Rego, what is the difference between =, := and == in a rule body?

level: juniorimportance: must knowfreq 74%

basics

~20 s

:= declares a local variable and assigns a value to it. == compares two values that are already bound and yields true or false. = unifies: it binds whichever side is still unbound, and compares when both sides are bound.

open as a page

In Rego, what do complete, partial set and partial object rules each define?

level: middleimportance: must knowfreq 68%

basics

~20 s

A complete rule defines at most one value and is undefined when no body succeeds. A partial set rule, written with contains, collects elements; a partial object rule, written with a bracketed key, collects pairs. Partial rules are always defined.

open as a page

Why does a Rego deny rule stop denying, without erroring, when the field it reads is missing?

level: middleimportance: must knowfreq 60%

basics

~20 s

A missing key makes the reference undefined rather than false, and undefined propagates through the whole body. The rule then contributes no message, the deny set stays empty, and the gate reports a clean pass with nothing to alert on.

open as a page

In Rego, what does the wildcard _ do in a reference like input.spec.containers[_].name?

level: middleimportance: must knowfreq 60%

basics

~20 s

The underscore is an anonymous variable, so the reference iterates: OPA tries every element of the array and the expression succeeds if at least one element makes it hold. Each underscore is a separate variable whose value you cannot refer to later.

open as a page

A Rego advisory rule warns 'TLS policy is not approved' with no resource name — how do you find which listeners matched?

level: seniorimportance: must knowfreq 50%

basics

~20 s

Reproduce it locally with opa eval on the same template, add a print of the resource id and the value the rule reads, and read it back with --explain=notes. Then fix the message to carry that context.

open as a page

In Rego, what does print() do in a rule body and where does its output go?

level: juniorimportance: should knowfreq 58%

basics

~20 s

print() is Rego's debugging built-in. It writes its arguments to the stderr of the tool evaluating the policy and always succeeds, so adding it never changes a decision. The JSON result on stdout never contains it.

open as a page

In Rego, what does `not input.metadata.annotations.exempt` evaluate to if the object has no annotations?

level: middleimportance: should knowfreq 46%

basics

~20 s

It succeeds. A negated expression in Rego is satisfied whenever the inner expression is undefined or false, so a negated reference to a path that does not exist always passes — absent and explicitly-not-set look identical to the rule.

open as a page

In Rego, how do you require that every container in a list has CPU and memory limits?

level: middleimportance: should knowfreq 48%

basics

~20 s

Use the every keyword: every c in the container list, the body asserting the limits exist. The alternative is to find a counterexample with iteration and negate it. Plain iteration cannot express this, because an unbound variable means at least one element.

open as a page

A Rego complete rule ran green for months and now errors on one plan. Why?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Most likely the input finally contained two matching resources. A complete rule may produce only one value, so a body that iterates and succeeds twice with different values raises an evaluation conflict and the whole query fails.

open as a page

Your Rego secrets rule has denied nothing in six weeks of evaluation — how do you find out why?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Silence is not evidence: a clean estate and a broken rule produce identical output. Replay a violating fixture and a sample of real objects offline — if the bad fixture also passes, the rule's body is undefined.

open as a page

Your Rego deny rule names only the first container missing resource limits — how do you fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

The comprehension collapses every offender into one array, and indexing it at [0] picks a single name. Move the iteration into the rule body so each binding produces its own set entry, and iterate the container field names so init and ephemeral containers are covered too.

open as a page

In opa eval, how do --explain=notes and --explain=fails differ?

level: middleimportance: nice to knowfreq 34%

basics

~20 s

Both filter the same evaluation trace. notes shows only what the policy itself said — print and trace output. fails shows only the expressions that did not hold, which is how you find where an undefined rule stopped.

open as a page