Policy-as-Code for AI Systems: Enforcing Governance at the Infrastructure Layer
The compliance policy existed. The model didn't care. Turning AI governance from a signed document into infrastructure gates nothing can bypass
Join the DZone community and get the full member experience.
Join For FreeTell me about that one document that no one has read. Your company's governance policy for AI systems. Forty-something pages, buried in a wiki or Confluence somewhere. The document that legal and compliance teams spent months writing, reviewing it and referencing the National Institute of Standards and Technology Artificial Intelligence Risk Management Framework and the European Union Artificial Intelligence Act, and no doubt a handful of other standards you can't quite recall.
Meanwhile, every single model your team has deployed in the last year hasn't consulted that document before deployment. As I've learned in my own experience building enterprise-grade AI solutions, the space between "we have a policy" and "the policy actually prevents something undesirable from happening" is where the headaches for compliance engineers and auditors begin and where the fines from regulators are born.
Thus, your governance policy should not be a document. It should be code.
Documents Cannot Block a Deployment
I understand why companies document governance policies. You need those policies to exist for auditing and legal clarity. But a PDF cannot stop a CI/CD pipeline from deploying code any more than a procedure printed on paper can stop a nuclear reactor from melting down.
ISO 42001, NIST’s AI Risk Management Framework, and Annex IV of the EU AI Act all define what actions are necessary, but not where in your stack those actions need to happen. Frequently, the result is that the compliance team drafts a policy, the engineering team gets a slide deck during an all-hands meeting, and everyone returns to their desks confident that they just need to remember to do things differently.
Let me give you an example. Say a recommendation model was deployed into production after being trained on a dataset containing personally identifiable information (PII) that was supposed to be restricted to internal analytics. The dataset had been reclassified two months earlier, but no one had updated the pipeline configuration. It took eleven weeks after deployment for a data subject access request to uncover the issue. By then, six million users had received recommendations based on data that no one was permitted to access.
It took three people-weeks of work to retrain and redeploy the model, audit all downstream consumers, and report the violation to the relevant regulatory agencies. Yes, this is a theoretical scenario, but it is hardly an unusual one. Readers working on frontier AI models can probably relate, because situations like this happen at large technology companies more often than you might think. That makes it essential to adopt the following techniques to avoid problems like this in the first place.
Every Governance Rule Becomes a Gate
If you’ve worked in infrastructure engineering, this is not a novel problem. Policy-as-code tools such as Open Policy Agent have existed for years, and today we have Kubernetes clusters in production that simply cannot admit pods that fail to meet specific security requirements. No one manually checks every time a pod is deployed to see whether it has the right security context, because the system itself rejects insecure pods.
That is how policy-as-code should work in AI systems: as a zero-trust model. Every assertion in your governance policy should translate into a machine-enforced check applied to some part of the system, returning a non-zero exit code when the check fails. Your policy becomes a set of gates that the system must pass to continue operating and must fail fast when a requirement is not met.
There are three ways this becomes useful, so let’s look at each in turn.
Before Training Starts: Verify What Data the Model Can See
One of the most common requirements in AI governance frameworks is to classify datasets by sensitivity and restrict model training to data with a classification level equal to or below what has been approved for the model. Here’s what that might look like:
# policy/data_classification.py
from governance_engine import PolicyCheck, DataSensitivityTier
class DataClassificationGate(PolicyCheck):
"""
Blocks model training if the dataset's sensitivity level
exceeds what this model is approved to access.
Maps to: NIST AI RMF GOVERN 1.2, ISO 42001 Section 6.1
"""
def evaluate(self, pipeline_context):
dataset = pipeline_context.dataset
model_approval = pipeline_context.model.data_access_tier
# Get the actual classification, not the cached one
current_sensitivity = dataset.get_live_classification()
if current_sensitivity.tier > model_approval.max_tier:
return self.fail(
reason=(
f"Dataset '{dataset.name}' is classified "
f"{current_sensitivity.tier.name} but model "
f"'{pipeline_context.model.name}' is only approved "
f"for {model_approval.max_tier.name} data. "
f"To request reclassification: internal-wiki/data-tier-request"
),
remediation_link="internal-wiki/data-tier-request",
escalation="data-governance-oncall"
)
# Everything checks out, log it for the audit trail
self.record_evidence(
check="data_classification_pre_training",
result="PASS",
model_id=pipeline_context.model.id,
dataset_id=dataset.id,
classification=current_sensitivity.tier.name,
timestamp=self.now()
)
return self.passed()
A few things are worth noting about this example. First, the classification is dynamic. We are not relying on whatever value was assigned to the dataset when it was ingested, because that could easily be out of date. Datasets change, columns are added, and sensitivity is periodically reevaluated. If a dataset’s classification is raised, any model trained on it should have its data access tier raised as well.
Second, notice that the failed assertion includes a remediation link. We are giving the person who triggered the failure a direct pointer to what they should do next. This is critically important. One reason companies struggle so much with governance policy is that engineers understandably resist controls that slow down their work. Productivity will suffer if they are blocked without a clear path forward.
If your policy is a wall they have to climb over, they will find ways to avoid climbing it. It is better to turn that wall into a door that opens only when the proper permission is presented.
At Serving Time: The Gateway That Cannot Be Skipped
If you're at all familiar with the announcement of Meta's Llama Guard model as part of the Purple Llama research series in late 2023, you might recognize this next section. To quote the announcement, "Unlike traditional approaches, Llama Guard does not utilize rule lists or keyword matching; instead, it leverages a separate model for enhanced security, trained to discern whether the input or output of Llama models violates any given safety policy." In other words, the Llama Guard model is policy-as-code: all safety policies are encoded directly in the system prompt of the model, allowing it to classify any given prompt or response as either safe or unsafe. This is a tremendously powerful technique, and while you might not be in a position to train dozens of guard models for every system you want to protect, you can most likely implement a similar pattern at the serving gateway layer for your models.
# policy/serving_gateway.yaml
# This config lives WITH the model definition.
# When the model deploys, its governance deploys with it.
model_policies:
- applies_to: "credit-risk-scoring/v2"
enforcement_mode: BLOCK # vs AUDIT_ONLY for rollout
pre_inference:
- check: caller_authentication
require_tier: 3 # Only Tier 3+ services can call this
deny_message: "Credit models require Tier 3 service auth. See internal-wiki/model-auth"
- check: input_data_restricted_fields
blocked_fields: ["social_security_number", "date_of_birth", "ethnicity", "address"]
action: STRIP_AND_LOG # Remove the field, log the attempt
post_inference:
- check: output_contains_pii
scan_for: ["ssn_pattern", "phone_pattern", "email_pattern"]
action: REDACT
- check: fairness_monitor
protected_attributes: ["gender", "race", "age_group"]
max_disparity_ratio: 1.25
action: ALERT # Don't block, but fire an alert to the fairness oncall
always:
- check: audit_log_all
fields: ["caller_id", "input_hash", "output_hash", "latency_ms", "timestamp"]
retention_days: 2555 # 7 years, regulatory requirement
This pattern encodes several important governance rules. First, notice that the policies are attached directly to the model itself. When the model is deployed, its policies are deployed with it. You cannot deploy the model without also deploying the policies that govern how it may be used.
That means the policies are not stored separately in a governance database or knowledge management system. Instead, they live directly in the model’s configuration. This has several advantages, including making the policies much easier to discover. There is no way to deploy the model without reviewing its policies, which helps ensure those policies are considered and followed.
After Deployment: Compliance Drifts Without Continuous Checks
So you've deployed your model, and it passed all the policy checks during deployment. Great. Now, assume several months later the model starts serving requests that violate policy. The world changes over time; new regulations come into law; models' concepts drift; users' behaviors evolve. This is entirely realistic, and not hypothetical - the EU AI Act's implementing acts are currently still being drafted. You must perform both continuous checks and deployment-time checks in order to account for changes in the model after deployment:
# policy/continuous_compliance.py
from governance_engine import ContinuousMonitor, AlertSeverity
monitor = ContinuousMonitor(
schedule="*/5 * * * *", # Every five minutes
alert_channel="ai-compliance-oncall"
)
@monitor.regulation("eu_ai_act.article_14")
def check_human_oversight(system_context):
"""
EU AI Act Article 14 requires that high-risk AI systems
are designed to allow effective human oversight.
We interpret this as: there must be a working override
endpoint, it must respond fast enough to be meaningful,
and a human must have actually reviewed outputs recently.
"""
if system_context.risk_classification != "HIGH":
return # Only applies to high-risk systems
# Can a human actually intervene?
override = system_context.get_override_endpoint()
if not override.is_healthy():
monitor.alert(
AlertSeverity.P1,
f"Human override endpoint DOWN for {system_context.model_id}. "
f"High-risk system operating without oversight capability.",
runbook="confluence/internal/ai-override-recovery"
)
# Is the override fast enough to matter?
if override.p99_latency_ms > 500:
monitor.alert(
AlertSeverity.P2,
f"Override latency {override.p99_latency_ms}ms, too slow "
f"for meaningful human intervention.",
runbook="confluence/internal/ai-latency-optimization"
)
# Has anyone actually looked at this thing recently?
days_since_review = system_context.days_since_last_human_review()
if days_since_review > 30:
monitor.alert(
AlertSeverity.P3,
f"No human review of {system_context.model_id} in "
f"{days_since_review} days. Scheduling mandatory review for the compliance team.",
auto_action="schedule_review"
)
The value of this approach is that it ties directly back to a specific regulation. If an auditor asks, “How do you know you’re complying with the EU AI Act?” you can show exactly which alerts the check has triggered and when it last ran successfully.
That is critical for demonstrating that your systems are actually doing what they are supposed to do. It is much easier to say, “Here is a dashboard showing the results of this check over the last 180 days,” than to explain in natural language all the steps you take to maintain ongoing compliance with a regulation.
Mistakes That Cost Us Months (and Probably Millions)
Don’t Ship Fifty Policies on Day One
Leadership will ask why it is taking so long. Even so, don’t try to deploy data classification, access control, drift monitoring, and fairness audits all at once. Engineers will start filing bypass requests faster than your governance team can process them.
Instead, pick one policy that would have prevented your last major incident if it had been in place. Deploy that policy. Let engineers see it, push against it, and learn how it works and how it can help catch real production issues before they become public relations nightmares. Once they understand it, iterate from there.
Make Failures Helpful, Not Hostile
If a policy fails, make sure it tells the user what they did wrong, why it is a problem, and how to fix it. A policy that simply says “DENIED” will have much lower adoption than one that says, “This dataset contains Tier 3 data, but your model is only approved for Tier 2 datasets. See the link below if you want to formally request a change to your model’s approval tier.”
Version Your Policies in Source Control
Use proper version control for your policies—not a governance database or a wiki, but real source code version control that enforces pull requests and code review. That way, when a regulation changes, you can update the policy, review the changes, test it, and deploy it to production just like any other code. It also makes it much easier to roll back a change if something goes wrong.
The Audit Trail Is the Product
Every time a policy runs, capture the outcome along with information about the model, dataset, and any other contextual details that could be useful to an auditor. If someone asks, “How are you preventing the use of sensitive data in model training?” you should be able to show them a dashboard containing the results of every relevant data classification check run over the last 90 days.
The Regulatory Reality
The regulatory landscape around AI is expanding quickly. The EU AI Act is now in force, the NIST AI Risk Management Framework is becoming an important reference point, state legislatures are passing new laws, and standards such as ISO/IEC 42001 are increasingly becoming de facto requirements. None of these developments are going away, and companies cannot expect a PDF policy reviewed once a year to be enough for compliance.
The companies that handle this environment well will treat compliance as a force multiplier rather than overhead. Policy-as-code at deployment time gives you gates, while continuous runtime monitoring gives you guardrails. Together, they give engineers enough context to reason about their systems while adding friction only where it matters most. The result is safer products, more productive engineers, and a compliance posture you can actually demonstrate to an auditor.
Opinions expressed by DZone contributors are their own.
Comments