How do you use OPA to list every database violating a backup rule held in an external inventory?
answer
- do not stream the estate through the engine
- the engine produces a filter, not a list
- compile once, let the store scan
- unknown resource set, residual condition
- push down what translates, finish the rest
basics
~20 sCompile the rule with the resource set declared unknown, then translate the residual conditions into the inventory's own query language and run them there. OPA produces the filter; the inventory produces the list. Never stream a million records through the engine.
solid answer
~50 sThe auditor's question is "which managed databases are below the seven-day backup floor today", and the inventory holding those records is a CMDB OPA does not have. So I do not feed OPA the estate — I compile the policy against it. I call `/v1/compile` with the violation query and `input.resource` as the unknown, supplying whatever I do know as ordinary input so those expressions collapse. What comes back is a residual: a disjunction of conditions over the resource fields. I translate that into the CMDB's query language, run it, and the rows returned are the report. Two things need care: any residual clause I cannot translate — an unindexed field, a built-in with no equivalent — must not be silently dropped, so I filter on the translatable part and finish the evaluation in OPA over the candidate rows. And I keep the generated query alongside the result, because the auditor's real question is whether the report and the rule are the same thing.
go deeper
Understand the split of work: OPA decides what the condition is, the data store finds the rows. Be able to say why sending hundreds of thousands of records into the engine is the wrong shape.
Be ready to walk the mechanics end to end: declare the unknown, call the compile endpoint, map conjunctions to AND and the query list to OR, and handle the empty and always-true results.
An interviewer expects the failure modes: wrong polarity, untranslatable clauses, and over-fetch-then-finish as the sound fallback. Show you would rather over-fetch than quietly report the wrong set.
Own the evidence story as well as the query: what you retain so the report is traceable to a policy revision, and where you draw the line before this becomes bespoke infrastructure.
## The shape of the problem An auditor asks a question that no per-change gate can answer: *which managed databases are out of policy on the backup-retention floor, right now, across the whole estate?* The rule exists in Rego. The facts do not live in OPA — they live in an asset inventory or CMDB with tens or hundreds of thousands of rows, refreshed continuously. This is a reporting question, answered after the fact with a list, not a block on anyone's change. There are three ways to answer it and only one scales. 1. **Load the inventory into OPA as `data` and evaluate normally.** Simple, correct, and fine up to a point — but the whole document sits in memory and must be kept fresh. At estate scale this is the thing you are trying to avoid. 2. **Loop: one evaluation per resource.** Correct, and trivially parallel, but it is one round trip per row and the cost grows with the estate rather than with the answer. 3. **Compile the policy into a query the inventory can run itself.** The engine evaluates the rule *once*, symbolically, and the data store does the work it is already good at. ## Doing the third one Declare the resource set unknown and ask for the violation. Concretely: `POST /v1/compile` with the query naming the rule you want, `unknowns: ["input.resource"]`, and an `input` document carrying everything you genuinely know. Anything you supply as known input is decided during compilation and disappears from the residual, so the more context you pin down, the smaller the condition you have to translate. The response is a list of residual queries — a disjunction of conjunctions — and possibly support modules. Translate the queries into the store's language: each conjunction becomes an `AND`-joined predicate, the list becomes `OR`. `input.resource.backup_retention_days < 7` becomes a comparison on the retention column; `input.resource.type == "managed_database"` becomes an equality. The rows the store returns are the report. ## The three ways this goes wrong **Compiling the wrong polarity.** If you compile the *compliant* rule you get the condition for passing, and negating a residual is not a safe textual operation once `default` declarations or support modules are in play. Compile the rule that expresses the violation directly, so the residual you translate is already the set you want to list. **Silently dropping what you cannot translate.** Sooner or later a residual clause references a field the store does not hold, or a Rego built-in with no equivalent — a regex over a name, an arithmetic relation between two fields, a lookup into another document. Dropping the clause makes the report wrong in a direction nobody notices: usually over-reporting, sometimes under-reporting. The safe pattern is over-fetch and finish: push down the clauses you can translate, treat the result as a *candidate* set, and evaluate the full rule in OPA over those candidates. You keep exactness and still avoid scanning the estate. If the translatable part is empty, you are back to option 1 or 2 and should say so rather than pretend. **Mishandling the degenerate results.** No residual queries at all means nothing can violate — the correct output is an empty report, not an unfiltered query. A single empty query means everything violates — the correct output is every row, not zero. These are the two lines of translator code that get written last and reviewed never. ## What the auditor actually needs The list is the smaller half of the answer. What makes it evidence is that the report and the rule are demonstrably the same rule: record the policy revision, the compile request and the generated store query alongside the result set, so the chain from written policy to reported row is inspectable. A list of database names with no provenance is a spreadsheet; the same list with the rule and the query that produced it is a control that ran. It is also worth saying plainly what this technique does not give you. It answers "who is out of policy now", from the inventory's point of view, with the inventory's freshness. It is not a decision on anyone's change, and the answer is only as current as the records the store holds.
- Why compile the violation rule rather than compile the compliant rule and negate the residual?Because negation is not a safe textual transformation of a residual. Once a `default` declaration or negation over an unknown appears, the residual is expressed through support modules, and flipping a generated query's polarity does not flip the policy's meaning. Compile the rule that already expresses the set you want to list, and translate it directly.
- The residual references a field your inventory does not index — what do you do?Over-fetch and finish. Push down the clauses that do translate, treat the returned rows as candidates, and evaluate the whole rule in OPA over that smaller set. Never drop the untranslatable clause: it makes the report quietly wrong. If nothing translates, fall back to loading or looping and be explicit that you did.
- What do you keep alongside the list so an auditor can trust it?The policy revision the report was compiled from, the compile request including the unknowns, and the generated store query. That chain shows the reported rows came from the written rule rather than from someone's hand-typed query, which is the difference between a spreadsheet and evidence that the control ran.
saying these in an interview costs you the question
- Proposes loading the whole inventory into OPA as data
- Assumes OPA can query the CMDB itself
- Negates a residual query to invert the policy
- Drops residual clauses the store cannot express
- Ships the list with no link back to the policy revision