Policy as code for AI governance
How to turn your AI usage policy into executable rules: risk classification, attribute-based authorization, guardrails and CI tests.
The executive article "AI committee: who decides, who approves, who answers" defines the decision structure. This piece addresses the next problem, the one that typically stalls companies that already have a committee: how to turn what was decided into something the system actually enforces, without relying on team training and good intentions.
Why a policy document isn't enough
An AI usage policy written in a PDF defines rules but doesn't enforce them. The gap between "it is written that" and "the system prevents" is where most AI governance incidents happen: someone uses an unapproved model, an agent takes an action without human approval, a high-risk use case reaches production without the review the committee required.
Policy as code closes that gap: policy becomes an executable, testable, versioned rule, evaluated at runtime or in a CI pipeline, held to the same rigor as production code.
Risk classification per use case as a rule
The first policy-as-code artifact is risk classification. Instead of a manually maintained spreadsheet, the use case declares its attributes and a rule determines the category:
use_case: "resume-screening"
attributes:
decides_about_person: true
reversible: false
sensitive_data: ["employment_data"]
monthly_decision_volume: 4200
classification_rule:
- if: decides_about_person == true and reversible == false
then: risk = "high"
- if: sensitive_data contains "health" or "biometric"
then: risk = "high"
- default: risk = "medium"This rule runs automatically when a new use case is registered, and recalculates the classification if attributes change — for example, if decision volume grows enough to cross a risk threshold.
Attribute-based authorization (ABAC)
Role-based access control (RBAC) isn't granular enough for AI: the same person may be authorized to use a model for one use case but not another, depending on risk, data type and time of day. Attribute-based authorization (ABAC) solves this by combining attributes of the user, the resource, and the context:
allow IF
user.role == "credit_analyst"
AND resource.use_case.risk IN ("low", "medium")
AND resource.data.classification != "restricted"
AND context.pending_human_approval == falseThe advantage of expressing this as a rule, rather than a fixed permission in a table, is that the same decision engine can be reused across multiple services — inference API, agent orchestrator, admin panel — without duplicating logic.
Input and output guardrails
Guardrails are the rules that intercept the call to the model before it goes out (input) and the response before it's delivered (output):
| Moment | Typical check | Action on violation |
|---|---|---|
| Input | Prompt contains data classified as restricted, unmasked | Block and return a policy error |
| Input | High-risk use case without logged human approval | Block until approval |
| Output | Response contains an unmasked PII pattern | Block or auto-redact |
| Output | Response indicates an irreversible action (e.g., cancellation, transfer) | Hold for human approval |
| Output | Confidence score below the threshold defined for the use case | Flag as uncertain and route for review |
Guardrails should be implemented as a layer separate from the application, not embedded in the code of each service that calls the model — that way, a policy change propagates without requiring a deploy of every consumer.
Mandatory human approval as a state, not a convention
High-risk use cases require human approval before execution. The robust way to implement this is not a manual side-check: it's modeling approval as a mandatory state in the decision's state machine.
states: pending -> awaiting_approval -> approved -> executed
-> rejected -> closedA high-risk decision simply has no transition path from "pending" to "executed" without going through "approved." This is enforced by the state engine, not by a process someone can skip under deadline pressure.
Policy tests in CI
If policy is code, it's testable as code. Every governance rule should have a set of test cases that run in the CI pipeline before any policy change reaches production:
test: "high_risk_use_case_without_approval_must_be_blocked"
given: use case with risk=high, human_approval=pending
when: request_execution()
then: result == "blocked"
test: "restricted_data_without_masking_must_block_input"
given: prompt containing an unmasked national ID
when: validate_input()
then: result == "blocked"This catches policy regressions — a rule change that inadvertently unblocks something that should have stayed blocked — before they reach production.
Automatic evidence for audit
A side benefit of policy as code is that every rule evaluation, by construction, already produces a structured record: which rule was evaluated, with which input attributes, what the result was, and when. This replaces the manual reconstruction of compliance evidence — which usually happens under pressure, the night before an audit — with a query against a log that already exists.
What to do on Monday
- Pick one policy rule already decided by the AI committee (e.g., "high-risk use cases require human approval") and formalize it as an executable rule, even if it runs in simulation mode at first.
- Identify where guardrails currently live "inside each application's code" and plan their extraction into a shared layer.
- Write the first policy test in CI covering the company's current highest-risk scenario.
- Check whether the approval state machine actually prevents — not just discourages — execution without logged approval.
Further reading
Executive track:
- The AI committee: who decides, who approves, who answers — the business view of this same topic.
