skip to content

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

level: middleimportance: must knowfreq 68%

answer

  1. the head decides the shape
  2. one value, or many
  3. contains builds a set
  4. a bracketed key builds an object
  5. only one of the three can be undefined

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.

solid answer

~50 s

The rule head decides the shape of the document at `data.<package>.<rule>`. A complete rule — `allow := true if { ... }` — defines at most one value; if no body succeeds the path is undefined unless a `default` declaration supplies a fallback, and two successes with different values are a conflict error. A partial set — `violation contains addr if { ... }` — adds one element per successful body evaluation and can never conflict. A partial object — `violation[addr] := reason if { ... }` — inserts a key/value pair per success, and conflicts only if the same key gets two different values. Both partial shapes are always defined: an empty set or empty object when nothing matches. `if` is required before any body and `contains` is required for a set, so the head alone tells a reader the shape.

code

rego · 12 lines
rego
package estate

violation[addr] := reason if {
	some rc in input.resource_changes
	addr := rc.address
	family := rc.change.after.machine_type
	not family in data.catalog.families
	reason := sprintf("machine type %v is not in the approved catalogue", [family])
}

# data.estate.violation -> {"google_compute_instance.web": "machine type ... "}
# ... and {} when the plan is clean, never undefined

go deeper

for a junior

Recall the three head shapes and what each produces: one value, a set of values, or an object of key-value pairs. Being able to point at a head and name its shape is the bar here.

for a middle

Explain definedness precisely — a complete rule can be undefined, a partial rule is empty instead — and why current syntax forces if and contains rather than inferring shape from the body.

for a senior

Show that you pick a shape from what the consuming gate must do with the answer, and that you can spot a rule whose head promises one value while its body iterates.

for a principal

Treat the query path and its shape as a published contract: changing a rule from a set to a keyed object silently breaks every caller, so version it like any other interface.

### The rule head decides the shape of the document Rego rules do not return values to a caller; each one **defines a document** at `data.<package>.<rule name>`. What that document *is* — a single value, a set, or an object — is decided entirely by the shape of the rule head. There are three, and every gate you will ever write picks among them. **Complete rule** — `name := value if { body }` ```rego default allow := false allow := true if { count(input.resource_changes) == 0 } ``` A complete rule defines **at most one value**. If no body succeeds, the document is undefined and the path yields no result — unless a `default` declaration supplies a fallback. If the head omits `:= value`, the value is `true` when the body succeeds. The hard constraint: across all of its bodies and all variable bindings, a complete rule may produce only one value. Two successes with *different* values is a conflict error at evaluation time, not a merge and not a first-wins. **Partial set rule** — `name contains value if { body }` ```rego violation contains addr if { some rc in input.resource_changes addr := rc.address rc.change.after.machine_type == "n1-standard-1" } ``` Every successful evaluation of the body adds one element. Duplicates collapse — it is a set. It can never conflict, because adding the same element twice is a no-op. Crucially, a partial rule is **always defined**: if nothing matches, the path holds an empty set, not undefined. Over the wire a set serialises as a JSON array. **Partial object rule** — `name[key] := value if { body }` ```rego violation[addr] := reason if { some rc in input.resource_changes addr := rc.address reason := "machine family not in catalogue" } ``` Each success inserts one key/value pair, and the result is an object. Like a set it is always defined — an empty object when nothing matches. Unlike a set it *can* conflict: if two successful bindings insert the same key with two different values, the engine raises a conflict error, the same class of failure a complete rule hits when it is over-defined. Choose a key that is unique per match — a resource address, not a resource type. ### `if` and `contains` are not decoration In current Rego, `if` is required between a rule head and its body; a head followed by a brace block with no `if` is a parse error. `contains` is required to declare a partial set. Both keywords exist because the older syntax was ambiguous: `p[x] { ... }` could be read as a set of `x` or as the start of an object, and readers guessed wrong. With the keywords, the head alone tells you the document's shape. `if` also permits a single-expression body without braces: `allow if input.trusted`. ### Choosing a shape for the caller The shape is a contract with whatever queries the path, so pick by what that caller must do: | Caller needs | Shape | What the query path returns | |---|---|---| | a yes/no gate decision | complete rule with `default` | a single boolean, always defined | | a list of human-readable failures | partial set | an array of messages, empty when clean | | per-resource attribution | partial object | `{"<address>": "<reason>", ...}` | The third is the one that upgrades a gate from "something failed" to "these two resources failed, for these reasons" without the caller parsing strings out of a flat list — and it is one query, not one per resource. One further constraint: a rule name defines exactly one document, so all definitions of that name inside a package must agree on shape. A complete `violation` and a `violation contains ...` in the same package is a compile error, not a union.

  • Why does current Rego require the contains keyword in a partial set head?
    Because the older syntax was ambiguous. A head like `p[x] { ... }` could be read as building a set of `x` values or as the start of a keyed object, and readers and tools guessed differently. Requiring `contains` for a set — and `if` before any body — makes the document's shape readable from the head alone, with no need to inspect the body.
  • Which shape do you pick when the gate must attribute each failure to a resource?
    A partial object keyed by the resource address, with the reason as the value. One query to that path returns `{"<address>": "<reason>", ...}`, so the caller renders per-resource messages directly instead of parsing identifiers out of a flat list of strings. Just make sure the key is unique per match: keying by resource type instead would collide.
  • Can a complete rule and a partial rule share the same name in one package?
    No. A rule name defines exactly one document with one shape, so every definition of that name in the package must agree — all complete, all `contains`, or all keyed. Mixing shapes is a compile error rather than a union, which is why the keywords in the head are worth reading carefully during review.

A complete rule is one labelled slot, a partial set is a bag you drop items into, and a partial object is a wall of pigeonholes where every item needs its own unique label.

saying these in an interview costs you the question

  • Calls every rule a function that returns a value
  • Thinks a partial rule is undefined when nothing matches
  • Uses contains and a bracketed key interchangeably
  • Believes default can back a partial set rule
  • Cannot say what the caller receives at the query path

context