Enforce AI policy as code
AI policy should make the same decision on every request, no matter which application sends it. DVARA evaluates policy before provider dispatch, refuses calls that break a rule, and records the decision.
This guide takes you from one working Open Source rule to a safe Enterprise rollout. Use the policy condition reference when you need every supported condition and its exact matching behaviour.
Choose the distribution
The YAML language is shared. Storage, lifecycle, and the available conditions differ.
| Capability | Open Source | Enterprise platform |
|---|---|---|
| Model allowlist and denylist | Yes | Yes |
| Maximum requested output tokens | Yes | Yes |
| Tool allowlist and denylist | Yes | Yes |
| Active policy enforcement | Yes | Yes |
| Draft, Shadow, Archived, dry-run, version history, and rollback | No | Yes |
| Data-residency, time-of-day, and budget conditions | No | Yes |
| MCP server, tool, and argument conditions | No | Yes, when the licensed MCP plane is active |
| CEL expressions | No | Yes |
| Central editing and fleet distribution through Flightdeck | No | Yes |
Open Source reads Active policies from gateway.yaml at startup. Restart the process after changing the file. The Enterprise platform stores and versions policies centrally, then sends changes to the data plane without a restart.
Enterprise LLM policy management works in the Development posture without a licence. A licence grants production rights and support and activates the MCP and A2A planes.
Block an unapproved model
Complete the Open Source quickstart, then add this global policy to gateway.yaml:
policies:
- id: approved-models-only
status: ACTIVE
dsl: |
version: "1"
rules:
- id: deny-legacy-model
description: Do not send new work to the retired model.
priority: 10
conditions:
model:
denylist: [mock/legacy]
action: DENY
deny_message: "This model is not approved. Use mock/approved instead."
The policy has no workspace, so it applies to every request. Add workspace: <workspace-id> beside id to limit it to one workspace. Restart the Open Source runtime after saving the file.
Send an allowed request:
curl -s http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/approved",
"messages": [
{"role": "user", "content": "Explain this policy in one sentence."}
]
}'
The Open Source quickstart defaults require-api-key to false, so this local request intentionally has no Authorization header. Require and send a DVARA API key in shared environments.
The Mock provider returns a normal Chat Completions response:
{
"id": "mock-6d0d5f08d3dd4aa5b536ba765cb0be63",
"object": "chat.completion",
"created": 1789092000,
"model": "mock/approved",
"choices": [{
"index": 0,
"message": {"role": "assistant", "content": "This is a mock response"},
"finish_reason": "stop"
}],
"usage": {"prompt_tokens": 12, "completion_tokens": 5, "total_tokens": 17}
}
Now request the blocked model:
curl -s -w '\nHTTP %{http_code}\n' \
http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/legacy",
"messages": [{"role": "user", "content": "Write a customer reply."}]
}'
DVARA stops the call before provider dispatch:
{
"error": {
"message": "This model is not approved. Use mock/approved instead.",
"type": "policy_violation",
"code": "policy_denied",
"trace_id": "01K4V5R9X21MDN8W5A6E7ZQ3TC"
}
}
HTTP 403
The trace ID changes on every request. The caller sees deny_message, so make it explain the safe alternative.
Verify the decision
When DVARA_AUDIT_FILE_PATH is configured, find the denial in the local JSONL audit file:
jq 'select(.eventType == "POLICY_DENIED") |
{eventType, workspaceId, policy: .payload.policy_id,
rule: .payload.rule_id, reason: .payload.reason}' \
var/audit.jsonl
The event identifies the policy and rule that stopped the call:
{
"eventType": "POLICY_DENIED",
"workspaceId": null,
"policy": "approved-models-only",
"rule": "deny-legacy-model",
"reason": "This model is not approved. Use mock/approved instead."
}
workspaceId is null because the example is global and the local request is anonymous. A workspace-scoped request carries the resolved workspace. The full event also carries the sequence, previous hash, HMAC, and signing time, making edits, removals, and reordering detectable.
Add request limits and tool controls
Open Source and Enterprise both support max_tokens and tools. Keep them in separate rules so the reason tells the caller which control failed.
version: "1"
rules:
- id: cap-output-size
priority: 20
conditions:
max_tokens:
limit: 2048
action: DENY
deny_message: "Request at most 2,048 output tokens."
- id: allow-read-only-tools
priority: 30
conditions:
tools:
allowlist: [search_catalog, get_order_status]
action: DENY
deny_message: "This application may offer only approved read-only tools."
The token rule matches only when the request supplies max_tokens above 2048; omitting max_tokens does not match it. The tool rule checks both tools offered in the request and tool calls already present in message history. Offering one unapproved tool is enough to deny the request before dispatch.
The policy condition reference contains copy-runnable request, denial, and audit examples for both controls.
Apply rules in a predictable order
Lower priority numbers run first; the default is 100. Conditions inside one rule use AND. The first matching DENY stops evaluation and provider dispatch, while WARN_AGENT records a warning and allows evaluation to continue. If no rule matches, DVARA allows the request.
Global and workspace policies share the same priority order. Scope does not add precedence, so give rules distinct priorities when order matters. Write action explicitly even though an omitted action defaults to DENY.
Author policies in Flightdeck
In the Enterprise Console, open Governance → Policies. Create a Draft, test the parts that dry-run can represent, then move it to Shadow to observe decisions on real traffic without blocking. Promote it to Active after reviewing divergence.
The Portal offers the same lifecycle for policies owned by the signed-in workspace. Operator-managed global policies remain visible only in the Console.
See Routes and policies in Flightdeck for the operator workflow and workspace policies for self-service authoring.
Test before enforcement
Flightdeck dry-run uses the model, workspace, API key, and one synthetic user message entered in the panel. It does not call a provider or store the synthetic request.
Use dry-run for model rules and CEL expressions based on the fields the panel supplies. It cannot reproduce requested token limits, offered tools, time, region, budget utilization, or MCP context. Test those conditions with representative governed traffic in Shadow before activation.
Promote and retire policies safely
The lifecycle has four states:
| Status | Runtime effect | Use it for |
|---|---|---|
| Draft | Not evaluated | Authoring and review |
| Shadow | Evaluated, never changes the live decision | Measuring false positives on real traffic |
| Active | Evaluated and enforced | Approved governance controls |
| Archived | Not evaluated | Retiring a policy without deleting its history |
Entering Shadow or Active validates the DSL again. Archiving remains available even when an old policy no longer compiles, so you can always take a broken rule out of service.
Activation can show a conflict warning such as Activated, but it conflicts with policies already enforcing: …. The policy still activates. Review the named overlap, adjust priority or conditions, and archive the replaced policy only after the new policy behaves as intended.
Rollback restores an earlier snapshot as a new version rather than erasing history. Shadow-to-Active promotion changes status inside the existing workspace; it does not copy the policy elsewhere.
Move a policy between workspaces
Use the reviewed GitOps export, edit, preview, and import workflow. Give the copied policy a new ID, set its target workspaceId, and import it in merge mode. Use SHADOW in the target when you want evidence before enforcement.
There is no direct cross-workspace promotion button. See Import and export configuration for the complete workflow.
Recover from invalid policy configuration
The schema rejects unknown keys, malformed YAML, unsupported actions, and rules containing both conditions and an Enterprise CEL expression. Only DENY and WARN_AGENT are valid actions.
In Open Source, a bad policy is named and skipped while valid policies continue to load. Fix the named policy and restart; do not treat a partially loaded policy set as a successful deployment.
Flightdeck validates before save and keeps the editor open with a visible compiler message, for example Failed to create policy: … in the Console or The policy DSL is not valid: … in the Portal. The invalid policy is not saved. These are browser workflows; the removed Automation API is not part of this contract.
Monitor policy refresh
Enterprise data planes report each policy refresh with:
gateway_config_refresh_total{table="policies",status="degraded"}
gateway_config_refresh_duration_seconds{config_type="policies",status="degraded"}
The status label is success, degraded, or failure. degraded means at least one policy failed compilation: the bad policy is skipped and named in logs while valid policies continue to enforce. failure means the refresh itself failed and the previous policy set remains active. Alert on either non-success status and use observability to connect the signal to logs and audit evidence.
Continue the governance path
- Add PII governance when a decision depends on sensitive data.
- Add guardrails for prompt injection, content safety, and response checks.
- Use audit and observability to investigate policy decisions across the Enterprise platform.