Policy-as-code is an engineering discipline, not a checkbox

28 Apr 2026 · Security & Compliance · 6 min read

There are two ways to adopt policy-as-code. The first is to install OPA, copy a community bundle of rules, wire it into the pipeline, and tell the auditor you have policy-as-code. The second is to treat policies as production software. Only one of these survives contact with an actual audit.

Policies are code. Treat them like it

A Rego policy that blocks deployments is as critical as the deployment system itself — arguably more so, since a buggy policy fails in one of two expensive directions: it blocks legitimate releases (delivery stops, trust erodes, people route around it) or it silently passes violations (you have compliance theatre with extra YAML).

So policies get the full treatment: version control, code review, CI, and above all tests. Every policy should ship with examples of inputs it must reject and inputs it must allow:

test_denies_public_bucket {
    deny[_] with input as {"resource": {
        "type": "aws_s3_bucket",
        "acl":  "public-read"
    }}
}

test_allows_private_bucket {
    count(deny) == 0 with input as {"resource": {
        "type": "aws_s3_bucket",
        "acl":  "private"
    }}
}

If a policy has no failing test case, you do not know that it does anything. We have reviewed estates where a third of the policy bundle was dead — rules that could never fire because the input shape had drifted years earlier. Nobody noticed, because nothing was testing the tests.

Every denial is a user interaction

The moment a policy blocks an engineer’s release, your compliance function has a user interface, and most of them are terrible. denied by policy bundle-7 teaches the engineer one thing: policy is an obstacle to be escalated around.

A denial should say what failed, why the rule exists, and what to do next — in that order. The “why” is not decoration; it is the difference between a control your engineers maintain voluntarily and one they quietly resent. Write denial messages with the care you would give an API error response, because that is exactly what they are.

Warn first, enforce second

Rolling out a new policy in enforcing mode on day one is how policy programmes die. We run every new rule in warn mode first — logging what it would have blocked — for long enough to see its false-positive rate against real traffic. Some of our clients are surprised to find a proposed rule would have blocked 30% of legitimate releases. Better to be surprised by a log line than by a stopped release train.

The warn period also produces something auditors love and almost nobody has: evidence of the control being calibrated. A policy that went through observed-then-enforced rollout, with its false-positive rate documented, is a control with an engineering history rather than an assertion.

The payoff

Done this way, policy-as-code inverts the usual relationship between compliance and speed. Controls enforced in the pipeline do not queue, do not reschedule, and do not interpret the rules differently on Tuesdays. Our retail banking client cut deployment lead time 70% while strengthening their audit position — the controls did not get weaker, they got faster. That is the trade available when policy is engineered rather than installed.

24 Mar 2026 The case for boring technology in regulated industries Engineering Culture 12 May 2026 Build less platform than you think Platform Engineering

Controls slowing you down? They shouldn’t be.

Start a project