skip to content

In Rego, where does a rule get a fact that the artifact under evaluation does not contain?

level: juniorimportance: must knowfreq 78%

answer

  1. The rule cannot invent it
  2. Somebody must put it within reach
  3. Absent is silent, not an error
  4. Loaded ahead, sent along, or fetched

basics

~20 s

A Rego rule cannot invent a fact. It must be loaded into the engine before the query, supplied by the caller inside the query itself, or fetched during evaluation with http.send. Nothing else reaches a rule.

solid answer

~40 s

Policies routinely need something the artifact does not carry: a component inventory lists each licence string, but not whether your organisation permits that licence. Rego has no ambient lookup, so a fact from outside has to be put somewhere the rule can reach. Three routes exist: it is loaded into the engine ahead of the query, the caller that builds the query includes it in what it submits, or the rule calls `http.send` and fetches it mid-evaluation. Only the third performs I/O during the decision. The point that matters early: a fact nobody arranged for is not an error. The reference is undefined, the rule body produces nothing, and a check that never ran looks exactly like a check that passed.

go deeper

for a junior

Be ready to name the three routes a fact can take into a rule, and to say plainly that a rule cannot go and find one on its own.

for a middle

Explain the mechanics: which routes are already in memory, that only an explicit call performs I/O, and that an absent fact is undefined rather than false or an error.

for a senior

Show the operational consequence: a control whose fact never arrived returns the same clean result as a passing artifact. Say what test or assertion you add so that fails loudly instead.

for a principal

Own the default. Decide in advance what the gate must do when a fact it depends on is unavailable, and make that outcome explicit in the rule rather than an accident of undefinedness.

## What a rule can actually see A Rego rule is evaluated over documents that are already in memory when the query starts. It reads them, combines them, and produces a result. It has no ambient lookup: there is no mechanism by which a reference to something absent makes the engine go and find it. That matters the moment a real policy is written, because real policies routinely need a fact that the thing under judgement does not carry. Take a licence check over a component inventory. The inventory is what you are judging, and it tells you the licence of every component: `AGPL-3.0`, `Apache-2.0`, `MIT`. What it does not tell you is whether your organisation permits `AGPL-3.0`. That is not a property of the artifact at all. It is a call your organisation made, on a different day, for reasons that have nothing to do with this build. The rule needs it anyway, because without it there is nothing to compare against. Call that an **external fact**: something a rule needs, that the query does not carry, and that the rule cannot derive from what it does carry. ## Three routes in There are exactly three ways an external fact reaches a rule body. **Loaded into the engine before the query.** The classification is placed in the engine's global `data` document ahead of time, and every subsequent query can read it. From the rule author's chair this is the simplest case: the fact is a path, reading it costs nothing, and arranging for it to be there is a separate job from writing the rule. **Supplied by whatever built the query.** The caller — a pipeline step, an admission controller, a service assembling a decision request — resolves the classification itself and includes it in the document it submits. The rule then reads it out of that document like any other field. **Fetched by the rule while it evaluates.** `http.send` makes an HTTP request from inside a rule body and returns the response as a value. This is the only route that performs I/O during evaluation, and the only one where the rule itself, rather than something upstream, is responsible for obtaining the fact. Which route suits a given fact is a genuine design question with real consequences, and it is not settled by taste. What a junior is expected to say is that the three are different mechanisms with different costs, and that the choice is made deliberately rather than by accident. ## The rule never fetches implicitly Worth stating plainly, because the opposite is the most common wrong model: no reference in Rego triggers a lookup. `data.licences.restricted["AGPL-3.0"]` does not consult a service. It reads memory. The only thing in a rule that touches the network is an `http.send` you wrote, at the place you wrote it. Read a rule, see no `http.send`, and you know it performs no I/O at all. ## A fact that never arrived is silent Here is the failure that catches everyone once. ```rego deny contains msg if { some c in input.components data.licences.restricted[c.licence] msg := sprintf("%s uses restricted licence %s", [c.name, c.licence]) } ``` If nothing ever loaded `licences`, that middle line is **undefined** — not false, and not an error. An undefined expression makes the whole rule body undefined, and an undefined body contributes nothing. So `deny` comes back empty and every artifact passes. Nothing is logged. Nothing fails. The caller receives a clean result, indistinguishable from the result a correct rule gives a genuinely clean artifact. **A check that never ran and a check that passed look identical from outside.** The habit that guards against it is a test that feeds a known-bad artifact through the rule and expects a denial, so a missing fact breaks the suite rather than the control. ## Why the fact is not written into the rule file You could of course put the restricted list in the policy file as a literal, and for something tiny and stable that is occasionally right. Usually it is not, and the reason is lifecycle. The rule — "no restricted licences ship" — changes rarely. The list of which licences are restricted changes whenever someone makes a new call about one. Baking the second into the first means editing and re-releasing a policy to record a fact that has nothing to do with its logic, and it turns the rule file into a place people go to look things up. Keeping the rule separate from the facts it judges against is what keeps the rule readable and the list maintainable. ## What you have signed up for The moment a rule depends on an external fact, its verdict stops being a function of the artifact alone. Submit the same inventory twice and you can get two different answers, because the thing it was compared against moved in between, and no diff in the policy repository will show it. That is not a defect; it is the deal you accept in exchange for keeping the rule and the facts apart. It is also why, later, somebody will want to know exactly which version of the fact a particular decision used.

  • Your rule references a licence classification that nobody ever loaded. What does the gate report?
    A clean pass. The reference is undefined rather than false or an error, so the rule body is undefined and a rule that collects violations contributes nothing. The caller sees an empty result and cannot tell it apart from a genuinely clean artifact. Catch it with a test that expects a known-bad artifact to be denied, so the missing fact fails the suite instead of the control.
  • Does a Rego rule ever go and look a fact up on its own when a reference is missing?
    No. There is no implicit resolution: a reference that is not satisfied by the documents already in memory is simply undefined. The only thing that reaches outside during evaluation is an `http.send` call an author wrote into a rule body. That is useful when reading someone else's policy, because the cost of every fact it uses is visible in the text.
  • Why not just write the restricted-licence list into the policy file as a literal?
    Because the rule and the list change on different schedules. The rule that restricted licences must not ship is stable; which licences count as restricted moves whenever someone makes a new call. Embedding one in the other means re-releasing a policy to record a fact unrelated to its logic. For a very small, stable set it is defensible; for a list somebody else maintains it is not.

An inspector can only judge against a standard somebody put on the desk. If nobody did, the inspector does not go looking for one; everything simply passes through.

saying these in an interview costs you the question

  • Thinks a missing reference makes the engine go and fetch it
  • Says an absent external fact causes the rule to deny
  • Believes an unavailable fact raises an evaluation error
  • Treats a list that changes weekly as part of the rule text
  • Assumes every fact a rule reads costs a network call

context