In Rego, what happens when one function name has two definitions that disagree?
answer
- definitions accumulate, they do not override
- a set can hold both; a value cannot
- two definitions of true never disagree
- eval-time error, not load-time
- else gives you the ordering Rego lacks
basics
~20 sRego allows a name to be defined twice, but a function must not produce two different values for the same arguments. When both definitions hold and disagree, OPA fails the query with a conflict error.
solid answer
~50 sRego is declarative, so multiple definitions of one name are a union of possibilities, not a first-match chain. For a **partial set** rule like `deny contains msg` that is exactly what you want: every definition that holds contributes a message, which is why deny rules can be spread across files. For a **function** or a **complete rule** it is a hazard: if two definitions are satisfied for the same arguments and produce different values, OPA raises an eval conflict error and the query fails — no decision comes back. If both produce the *same* value there is no conflict, which is why boolean helpers written as `is_approved_base(img) if { ... }` can be defined several times harmlessly: each yields `true`, so they simply OR together. The fixes are to make the bodies mutually exclusive, to chain them with `else`, or to return a set instead of a scalar.
go deeper
Know that a name can be defined more than once in Rego and that the definitions are not tried in order. Recall that set-valued rules like deny accumulate while a single-valued rule must have one answer.
Be ready to explain why two definitions returning the same value never conflict, why two returning different values fail the whole query, and what else changes about evaluation.
Show that you would hunt for the input that satisfies both bodies rather than trusting a green test run, and that you know a conflict surfaces as an evaluation error at the decision point, not as a policy violation.
Set the house style that keeps this out of production: boolean helpers for accumulating conditions, explicit else chains where precedence is real, and a review habit of asking which single-valued rules can be satisfied two ways at once.
## Incremental definition, and where it turns on you Rego lets you define the same name in more than one place. This is deliberate — it is how a policy grows across files without a central registry — but the semantics depend on what kind of rule you wrote. ### Partial rules: definitions accumulate ``` deny contains msg if { not base_label_present msg := "image records no base image" } deny contains msg if { some b in unapproved_bases msg := sprintf("base image %q is not approved", [b]) } ``` `deny` is a partial set. Each definition that holds adds an element. Nothing conflicts, because a set does not have to choose. This is why the violation-message style scales: you can add a check in a new file and the existing ones are untouched. ### Complete rules and functions: definitions must agree A complete rule assigns one value: ``` max_severity := "high" max_severity := "critical" # both bodies hold (empty bodies always hold) ``` Both definitions are satisfied and they disagree, so evaluating this fails with an evaluation conflict — complete rules must not produce multiple outputs. OPA does **not** pick the first, the last, or the most specific. Functions behave the same way, per argument value: ``` risk(img) := "high" if { img.privileged } risk(img) := "low" if { img.config.User != "root" } ``` For an image that is both privileged and runs as a non-root user, both bodies hold and the two definitions disagree, so the call fails. Notice what makes this nasty: the policy loads cleanly and passes most of your tests. It only breaks on the input that satisfies both bodies — and in a gate, a failed evaluation is not a `deny` message, it is an error, whose handling depends on the decision point rather than on your rule. ### The case that is safe, and why ``` is_approved_base(img) if { img.config.Labels["org.opencontainers.image.base.name"] in approved_bases } is_approved_base(img) if { img.config.Labels["vendor.base.digest"] in approved_digests } ``` Each definition's value is `true`. Two definitions both producing `true` do not disagree, so there is no conflict — the two bodies simply OR together, and the function is defined when either holds. This is the standard way to express "approved if any of these holds", and it is why boolean helpers are the safest thing to define incrementally. ### Making disagreeing definitions safe Three tools, in rough order of preference: 1. **Mutually exclusive bodies.** Add the negated condition to the second definition so at most one can hold. Correct, but it duplicates the condition and drifts. 2. **`else`.** `risk(img) := "high" if { ... } else := "low"` gives an explicit ordered fallback, evaluated only when the earlier body is undefined. This is the readable way to express a precedence order, and it exists precisely because Rego otherwise has none. 3. **Return a set** and let the caller decide, if "more than one answer" is genuinely meaningful. A `default` definition — `default risk(_) := "unknown"` — is a fourth, related tool, but it is not a conflict resolver: it supplies a value only when no other definition is defined, and does nothing when two definitions both hold. ### The compile-time cousin: shadowing When you factor helpers into a shared package you meet a different name collision. `import data.lib.images` binds the name `images` in that file, and if the file also has a rule called `images` the compiler rejects the policy — an import must not shadow a rule or another import. This one you find at load time, not at 5pm on the input that happened to satisfy two bodies; the fix is to alias the import (`import data.lib.images as libimages`) or rename the local rule. ### What to demonstrate The useful reflex is to notice which kind of rule you are writing. Set-valued rules accumulate; single-valued rules and functions must agree. Whenever a helper can produce more than one answer for one input, either make the bodies exclusive or make the ordering explicit with `else` — and test the input that satisfies both, because that is the only input that reveals the bug.
- Why can deny be defined in five different files without conflicting?Because `deny` is a partial set rule. Each satisfied definition contributes an element and the results union, so there is nothing to choose between. Conflicts only arise for complete rules and functions, which must produce a single value for a given input or set of arguments.
- Does `default` fix two conflicting definitions?No. A default supplies a value only when the name is otherwise undefined. If two definitions both hold and disagree, the default is irrelevant and the query still fails with a conflict. Use mutually exclusive bodies or an `else` chain to impose an order.
- What breaks when an import has the same name as a local rule?The compiler rejects the policy: an import must not shadow a rule or another import in the same file. Unlike a value conflict, this fails at load time, so you find it as soon as the policy is built. Alias the import with `as`, or rename the local rule.
saying these in an interview costs you the question
- Says the last definition wins
- Says the most specific definition wins
- Thinks a conflict is caught when the policy loads
- Believes default resolves a conflict between two definitions
- Assumes deny rules conflict the way complete rules do