skip to content

What does OPA's partial evaluation return when you mark part of the input unknown?

level: middleimportance: should knowfreq 48%

answer

  1. the answer is not yes or no
  2. some expressions cannot be decided yet
  3. what is left over after evaluating
  4. queries you still have to run
  5. residual queries plus support modules

basics

~20 s

It returns a residual policy rather than a decision. OPA evaluates every expression it can already decide, then hands back the conditions that still depend on the unknown values, plus any support rules those conditions reference.

solid answer

~50 s

In a normal evaluation you give OPA a complete `input` and it returns a value. In partial evaluation you additionally name some paths as *unknowns* — typically `input`, a sub-path like `input.resource`, or a `data` path that lives in a system OPA does not hold. OPA evaluates everything it can decide without them and returns what is left over: a set of residual queries, and support modules when the leftover cannot be written as a flat conjunction. The residual is exact, not approximate — anything satisfying a residual query satisfies the original rule. Two shapes are special: no queries at all means the rule can never be true whatever the unknowns are; one empty query means it is unconditionally true. You get this from `POST /v1/compile`, or from `opa eval --partial --unknowns ...` on the CLI.

code

rego · 8 lines
rego
package backup.retention

# a production managed database must keep >= 7 days of backups
compliant if {
	input.resource.type == "managed_database"
	input.resource.environment == "production"
	input.resource.backup_retention_days >= 7
}

go deeper

for a junior

Know the shape of the idea: you can ask OPA a question while telling it some values are not available yet, and it answers with the leftover conditions instead of a yes or no.

for a middle

Be ready to explain the mechanics: what an unknown is, that queries are a disjunction of conjunctions, why support modules appear, and the difference between an unknown path and a missing field.

for a senior

An interviewer expects you to name the two degenerate results and say what each means for a consumer, and to recognise when a residual is too complex to hand to anything but OPA itself.

for a principal

Own the framing that partial evaluation lets one policy serve both a per-change decision and an estate-wide query, and be able to say what that costs in machinery you now maintain.

## Normal evaluation versus partial evaluation Normally you hand OPA a policy, a complete `input` document and whatever `data` it holds, and ask for the value of a rule. OPA walks the rule bodies and returns a value, or nothing at all if the rule is undefined. Partial evaluation changes exactly one thing: you also tell OPA which parts of the world it is not allowed to look at, by naming them as **unknowns**. An unknown is a path — `input`, a sub-path such as `input.resource`, or a `data` path whose contents live in a system OPA does not hold. OPA then evaluates everything it *can* decide and returns what remains: a **residual policy**. ## A worked example Take a backup-retention floor: a production managed database must keep at least seven days of backups. ```rego package backup.retention compliant if { input.resource.type == "managed_database" input.resource.environment == "production" input.resource.backup_retention_days >= 7 } ``` Ask for `data.backup.retention.compliant` with `input.resource` declared unknown and every expression mentions the unknown, so all three survive into the residual. Now suppose you narrow the unknown: you already know you are asking about production, and you supply `input.resource.environment` as a known value. That expression is decided during the partial evaluation and vanishes; the residual shrinks to the type check and the retention comparison. That shrinking is the whole point — you are trading evaluation you can do now against evaluation you must defer. ## What actually comes back The `POST /v1/compile` response carries `queries` and, when needed, `support`. - `queries` is a **list** of queries. Each query is a conjunction of expressions; the list as a whole is a **disjunction**. The original rule holds when any one of the returned queries is satisfied. - A result with **no queries** means the rule was decided negatively without ever seeing the unknowns — it can never be true. - A result with a **single empty query** means it is unconditionally true; there is nothing left to check. - `support` holds generated Rego modules. OPA emits them when the leftover cannot be flattened into expressions: a `default` declaration on the rule, negation over an unknown, and partial set or object rules all produce rules rather than a flat body. A caller that only knows how to translate flat conjunctions should treat the presence of support modules as a signal to either evaluate the residual inside OPA or restructure the policy. Those two degenerate shapes are where integrations most often have bugs. If you compiled a *violation* rule and got no queries back, nothing violates; if you got one empty query back, everything does. A translator that maps "no queries" onto "no filter" inverts the answer completely. ## Where you get it Two entry points. `POST /v1/compile` takes `query`, an optional partial `input` for the parts you do know, and an `unknowns` list, and returns the residual. On the CLI, `opa eval --partial --unknowns input.resource 'data.backup.retention.compliant'` does the same thing; `--unknowns` defaults to `input`. ## Why anyone wants this Two uses dominate. The first is **pushdown**: the unknown is a set of records that lives in an inventory or database OPA does not hold, and the residual is translated into that store's own query language so the store does the filtering. The second is **build-time specialisation**: `opa build --optimize` runs the same machinery with `input` unknown and the bundle's `data` known, producing a policy pre-specialised for a named entrypoint so the hot decision path does less work. ## Misconceptions worth naming **Unknown is not missing.** A missing input field makes a reference undefined and the rule simply does not fire. Declaring a path unknown is a different act: it suspends the decision instead of resolving it negatively. **The residual is not a partial or approximate answer.** It is a smaller, equivalent policy. There is no confidence value, no maybe. **OPA does not go and fetch the unknown data.** It has no connection to your inventory and will not acquire one. Producing the residual is where OPA's job stops; running it against the real data is yours.

  • What does an empty result mean versus a result containing one empty query?
    No queries at all means OPA decided the rule negatively without the unknowns — it can never be true. A single empty query means the opposite: it is unconditionally true, with no condition left to check. Integrations that skip these two cases produce exactly inverted answers, so handle them explicitly before translating anything.
  • When does OPA return support modules instead of a flat residual query?
    When the leftover cannot be expressed as a conjunction of expressions: a `default` declaration on the rule, negation applied to an unknown, or a partial set or object rule. OPA generates rules in a support module and the residual query references them. If your consumer only translates flat conditions, that is your cue to evaluate the residual in OPA instead.
  • How do you run partial evaluation without standing up the OPA server?
    `opa eval --partial --unknowns input.resource 'data.backup.retention.compliant'` from the CLI. `--unknowns` defaults to `input`, so you only pass it when you want a narrower or different unknown. It is the fastest way to see what a rule reduces to before you write any integration code around `/v1/compile`.

It is like simplifying an algebraic expression when one variable is still symbolic: you fold away everything numeric and are left with a smaller formula in that one variable, exactly equivalent to the original.

saying these in an interview costs you the question

  • Says partial evaluation returns a partial or approximate decision
  • Thinks an unknown field makes the rule evaluate to false
  • Assumes OPA fetches the unknown data itself
  • Expects the residual to always be a flat, translatable condition
  • Treats an empty residual result as no filter rather than never true

context