What does opa check --strict reject in a .rego file that plain opa check accepts?
answer
- a compiler with a pedantic mode
- dead imports and unread assignments
- input and data become reserved
- shadowing the document the rule reads
basics
~20 sStrict mode promotes lint-grade problems to compile errors: duplicate imports, unused imports, unused local assignments, using input or data as a variable or rule name, and deprecated built-in aliases. Plain opa check only reports genuine parse and compile errors.
solid answer
~50 s`opa check` compiles the modules you point it at and reports parse and compile errors — an unknown function, a bad reference, a recursive rule. It happily accepts a file full of dead weight. `opa check --strict` turns a defined set of those into errors: duplicate imports, imports nothing references, local assignments nothing reads, deprecated built-in aliases, and using `input` or `data` as a variable or rule name. That last one matters most for a gate: a local named `input` shadows the document the rule is supposed to inspect, so the rule evaluates over something else and quietly stops producing violations. In the policy repository's CI I run `opa check --strict` over the tree, `opa fmt --diff --fail` so formatting is mechanical rather than a review argument, and a Rego linter such as Regal for the checks the compiler does not make.
go deeper
Know that .rego files get compiled and formatted like any other source, and that opa check and opa fmt are the two commands you run before pushing.
Be able to name what strict mode adds — duplicate and unused imports, unused local assignments, reserved input and data, deprecated built-ins — and explain why a shadowed input silently disarms a rule.
Show how you wire the three checks into the policy repository's CI as separate blocking steps, and admit what they still do not prove about whether a rule fires.
Decide how much of a linter's rule set to make blocking versus advisory, and how to introduce it over an existing repository without a flag day that stalls every policy change in flight.
## Two different jobs `opa check` is the compiler front end run on demand: point it at a directory of `.rego` files and it parses and compiles them, reporting what would stop the policy from loading — a syntax error, a reference to a built-in that does not exist, an illegal recursion, a type error against a schema if you supply one with the schema flag. If `opa check` is clean, the modules can be loaded. That is a low bar. Plenty of source compiles fine and still does not do what its author believes. `--strict` raises the bar by promoting a fixed set of problems from tolerated to fatal: | Strict-mode error | Why it exists | |---|---| | Duplicate imports | Two imports resolving to the same name; one of them is doing nothing | | Unused imports | A dead import, usually the residue of a refactor that moved a helper | | Unused local assignments | A value computed with `:=` and never read — often the line that was supposed to feed the condition | | `input` or `data` used as a variable or rule name | Shadowing the documents the policy reads | | Deprecated built-in aliases | Old spellings kept alive for compatibility that will not survive a major | ## The shadowing case is the dangerous one Consider a rule meant to reject unencrypted managed volumes. Someone iterating over a list picks the shortest available name for the loop variable and writes something like `some input in input.spec.volumes`. Inside that body, every subsequent mention of `input` resolves to the local, not to the document the decision point handed the engine. The rule still compiles. It still runs. It just evaluates against the wrong value, and because a rule body that is not satisfied produces **no result at all** rather than a false, the gate sees an empty violation set and reports a pass. That is the signature of this whole class of defect: the failure is an absence. Nothing is logged, nothing is thrown, and the only evidence is that a control which used to block things has stopped blocking anything. `--strict` catches this one at compile time by making `input` and `data` reserved. ## Formatting is a separate gate `opa fmt` is the canonical formatter. Run with `--diff` it prints what it would change; with `--fail` it exits non-zero when any file is not already formatted; with the write flag it fixes files in place. Wiring `opa fmt --diff --fail` into the policy repository's CI is worth doing for a reason beyond tidiness: policy diffs are read under time pressure during incidents and reviews, and a diff that mixes a semantic change with a re-indentation of forty lines is a diff nobody reads carefully. The formatter is also the mechanical half of a syntax migration — it rewrites whole files far more reliably than hand editing. ## What a linter adds on top Compilation asks whether the source is legal. A Rego linter — Regal is the one built for this — asks whether it is sensible. Its checks cover things the compiler has no opinion about: a variable or rule whose name shadows a built-in, imports that go nowhere, unused variables, constructs with a clearer idiomatic form, and rules whose head can never bind. Some of its checks look across the whole policy repository rather than one file at a time, which is how you catch a helper nobody calls any more. Rules are individually configurable, so a team can adopt the set it agrees with and fail the build on that, rather than taking all of it or none of it. ## What none of this proves Strict compilation, formatting and linting are all statements about **source**. None of them tells you a rule is correct or that it will ever fire. A rule whose body can never be satisfied for any real input compiles cleanly, formats cleanly and lints cleanly; only evaluating it against representative input shows that it is inert. The static checks earn their place because they are instant, deterministic and catch the specific failures that produce silence rather than noise — they are the floor, not the ceiling. ## A workable CI shape for a policy repository Run the formatter check, the strict compile and the linter as three separate steps so the failure names its own cause. Make all three blocking on the default branch. Add one step people forget: after the bundle is built, assert that the packages and rule names the gates actually query are present in it. A file that fails to compile is loud; a file that was never included in the build is silent, and the assertion is the only thing standing between that and an enforcement gap.
- How do you keep formatting from turning into review noise?Make it mechanical and non-negotiable. `opa fmt` with the write flag fixes files locally; `opa fmt --diff --fail` in CI exits non-zero if anything is unformatted, so nobody argues about layout in a review. The real payoff is diff quality: policy changes get read during incidents, and a semantic change buried in a re-indent is a change nobody actually reviewed.
- What does a Rego linter catch that opa check --strict does not?Regal checks style and sense rather than legality: names that shadow built-ins, unused variables, rules whose head can never bind, and constructs with a clearer idiomatic form. Some checks span the whole repository rather than one file, so an orphaned helper shows up. Its rules are individually configurable, so a team can fail the build on the subset it has actually agreed to.
- Will these checks tell you a rule actually blocks anything?No. They are statements about source. A rule whose body can never be satisfied compiles, formats and lints perfectly and still never produces a violation. Static checks catch the failures that are silent — dead imports, shadowed documents, files that will not compile — but only evaluating the rule against representative input shows that it fires when it should.
saying these in an interview costs you the question
- Assumes plain opa check already reports unused imports
- Treats a formatting check as equivalent to compilation
- Believes a file that compiles must therefore enforce something
- Thinks strict mode changes how decisions are evaluated at runtime
- Names input as a loop variable and sees no problem