Policy condition reference
DVARA policy conditions describe a violation. When every condition inside one rule matches, DVARA applies that rule's DENY or WARN_AGENT action. This reference gives you the exact YAML shape and the boundary cases that decide whether a request matches.
Start with Enforce AI policy as code if you have not run the first model policy yet.
Read a rule as a violation
This rule reads as “deny when the requested model is outside the approved list”:
version: "1"
rules:
- id: approved-models-only
priority: 10
conditions:
model:
allowlist: [gpt-4o, gpt-4o-mini]
action: DENY
deny_message: "Choose an approved model."
Multiple conditions inside the same rule use AND. Use separate rules when either condition should independently cause a denial.
To deny write_file only on filesystem, combine mcp_server.denylist: [filesystem] with mcp_tool.denylist: [write_file]. Both violation predicates then match that call.
Do not pair mcp_server.allowlist: [filesystem] with the tool denylist for this purpose. The server allowlist does not match a call to an allowed server, so the combined rule does not fire.
Use the supported conditions
| Condition | YAML fields | Distribution | Rule matches when | Missing value |
|---|---|---|---|---|
model | allowlist, denylist | Both | Model is outside the allowlist or inside the denylist | Does not match |
max_tokens | limit | Both | Requested max_tokens is greater than limit | Does not match |
tools | allowlist, denylist | Both | Any offered or previously called tool is outside the allowlist or inside the denylist | No tools means no match |
data_residency | allowed_regions | Enterprise | Resolved runtime region is outside the list | Does not match |
time_of_day | timezone, deny_hours, deny_days | Enterprise | Current time falls in a denied range or day | timezone uses the process timezone when omitted |
budget_utilization | threshold_pct | Enterprise | Budget utilization is at or above the threshold | Does not match; threshold defaults to 75 |
mcp_server | allowlist, denylist | Enterprise MCP | Server is outside the allowlist or inside the denylist | Non-MCP requests do not match |
mcp_tool | allowlist, denylist | Enterprise MCP | Tool is outside the allowlist or inside the denylist | Non-MCP requests do not match |
mcp_arg | rules[].name, pattern, allowlist, denylist | Enterprise MCP | A required argument is absent or fails any declared constraint | Non-MCP requests do not match |
Enterprise LLM conditions work in the Development posture without a licence. A licence is required to activate the MCP plane and use MCP conditions in live traffic.
Cap requested output tokens
Add this policy to Open Source gateway.yaml, or paste the inner DSL into an Enterprise policy:
policies:
- id: output-size-limit
status: ACTIVE
dsl: |
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."
After restarting Open Source, send a request above the limit:
curl -s -w '\nHTTP %{http_code}\n' \
http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/approved",
"max_tokens": 4096,
"messages": [{"role": "user", "content": "Summarise the quarterly report."}]
}'
The provider is not called:
{
"error": {
"message": "Request at most 2,048 output tokens.",
"type": "policy_violation",
"code": "policy_denied",
"trace_id": "01K4V67R8CPZJBM8Y0HWR4D2XT"
}
}
HTTP 403
Set max_tokens to 2048 and the rule no longer matches. Omitting it also does not match, so this condition limits an explicit request value; it does not inject a default into the request.
Verify the effect in the audit trail:
jq 'select(.eventType == "POLICY_DENIED" and .payload.rule_id == "cap-output-size") |
{eventType, rule: .payload.rule_id, reason: .payload.reason}' var/audit.jsonl
{
"eventType": "POLICY_DENIED",
"rule": "cap-output-size",
"reason": "Request at most 2,048 output tokens."
}
Warn without blocking
Use WARN_AGENT when the caller should receive the model response but must react to a governance signal:
version: "1"
rules:
- id: warn-on-preview-model
priority: 25
conditions:
model:
denylist: [mock/preview]
action: WARN_AGENT
warn_message: "This preview model is not approved for production decisions."
Send the matching request and include response headers:
curl -s -i http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/preview",
"messages": [{"role": "user", "content": "Draft a non-binding summary."}]
}'
The request continues to the provider. The response remains OpenAI-compatible and carries the warning signal:
HTTP/1.1 200
X-Budget-Warning: true
Content-Type: application/json
{"id":"mock-6e9c","object":"chat.completion","model":"mock/preview","choices":[{"index":0,"message":{"role":"assistant","content":"This is a mock response"},"finish_reason":"stop"}],"usage":{"prompt_tokens":9,"completion_tokens":5,"total_tokens":14}}
Despite its legacy name, X-Budget-Warning represents any matching WARN_AGENT policy; it is not proof that a budget is configured. DVARA also writes the warning type, message, policy ID, rule ID, workspace, and request path to the audit trail.
Restrict tools offered to a model
The LLM request tools condition governs OpenAI-shaped model traffic. It is available in both distributions and is separate from MCP Gateway policy.
version: "1"
rules:
- id: read-only-customer-tools
priority: 30
conditions:
tools:
allowlist: [search_catalog, get_order_status]
action: DENY
deny_message: "This application may offer only approved read-only tools."
This request offers cancel_order, which is outside the allowlist:
curl -s -w '\nHTTP %{http_code}\n' \
http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{
"model": "mock/approved",
"messages": [{"role": "user", "content": "Check order ORD-1048."}],
"tools": [{
"type": "function",
"function": {
"name": "cancel_order",
"description": "Cancel an order",
"parameters": {
"type": "object",
"properties": {"order_id": {"type": "string"}},
"required": ["order_id"]
}
}
}]
}'
DVARA returns the rule's message before provider dispatch:
{
"error": {
"message": "This application may offer only approved read-only tools.",
"type": "policy_violation",
"code": "policy_denied",
"trace_id": "01K4V6DYQ7B84GYAK2T0P9S3XR"
}
}
HTTP 403
Replace cancel_order with get_order_status and the rule no longer matches. Confirm the denied case in local audit with:
jq 'select(.eventType == "POLICY_DENIED" and
.payload.rule_id == "read-only-customer-tools") |
{eventType, rule: .payload.rule_id, reason: .payload.reason}' var/audit.jsonl
An offered tool counts even when the model has not called it. DVARA also checks tool calls already present in message history, so a caller cannot bypass the rule by replaying an earlier tool call.
Enforce residency, time, and budget separately
These conditions are available in the Enterprise platform. Separate rules preserve a precise reason and avoid accidental AND logic:
version: "1"
rules:
- id: keep-traffic-in-approved-regions
priority: 10
conditions:
data_residency:
allowed_regions: [eu-west-1, eu-central-1]
action: DENY
deny_message: "This workload must run in an approved EU region."
- id: stop-weekend-processing
priority: 20
conditions:
time_of_day:
timezone: Europe/Dublin
deny_days: [SATURDAY, SUNDAY]
deny_hours:
- start: "22:00"
end: "06:00"
action: DENY
deny_message: "Processing is closed during the maintenance window."
- id: stop-at-budget-cap
priority: 30
conditions:
budget_utilization:
threshold_pct: 95
action: DENY
deny_message: "This workspace has used at least 95% of its budget."
The residency rule reads the region resolved by DVARA, not a caller-supplied request field. If no region is available, the closed-form matcher does not fire; use a CEL rule when missing region data must itself be denied.
The time range includes 22:00 and excludes 06:00, and it correctly crosses midnight. Always set timezone; omission uses the process timezone, which can differ between deployments.
The budget rule matches at 95 as well as above it. Missing or unreadable utilization does not match. Omitting threshold_pct uses 75.
Flightdeck dry-run cannot populate region, time, budget, requested tokens, or offered tools. Put these policies in Shadow and send representative governed traffic before activation.
In Governance → Policies → Shadow, verify that the rule breakdown names keep-traffic-in-approved-regions, stop-weekend-processing, or stop-at-budget-cap for the traffic you expected to match. A missing rule means the required runtime context was absent or the threshold was not reached; it is not evidence that the rule enforced.
Govern one MCP server, tool, and argument
This Enterprise policy limits filesystem__write_file to /workspace while allowing other tools. The MCP plane must be active under a valid licence.
version: "1"
rules:
- id: keep-writes-in-workspace
priority: 10
conditions:
mcp_server:
denylist: [filesystem]
mcp_tool:
denylist: [write_file]
mcp_arg:
rules:
- name: path
pattern: "^/workspace(/.*)?$"
action: DENY
deny_message: "Agents may write files only inside /workspace."
pattern defines the allowed full value; a mismatch violates the condition. A missing path also violates it. DVARA evaluates the server ID and unprefixed tool name from the MCP call; the client still sends the namespaced tool name.
Replace <dvara-host> and <your-api-key>, then call the governed tool:
curl --silent https://<dvara-host>/mcp \
--header 'Authorization: Bearer <your-api-key>' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json, text/event-stream' \
--header 'X-Session-Id: filesystem-run-42' \
--data '{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "filesystem__write_file",
"arguments": {"path": "/etc/hosts", "content": "blocked change"}
}
}'
The native MCP endpoint returns a JSON-RPC policy error and does not call the tool server:
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32001,
"message": "Agents may write files only inside /workspace."
}
}
Open Agents → Tool Calls to confirm the denied call, or filter the audit trail for MCP_POLICY_DENIED. The event carries the workspace, server, tool, policy ID, rule ID, decision, reason, and trace ID.
{
"eventType": "MCP_POLICY_DENIED",
"workspaceId": "acme-support",
"payload": {
"server_id": "filesystem",
"tool_name": "write_file",
"policy_decision": "DENY",
"policy_id": "mcp-filesystem-policy",
"rule_id": "keep-writes-in-workspace",
"deny_reason": "Agents may write files only inside /workspace."
}
}
Validate authoring errors before rollout
The DSL uses a closed schema. This typo is rejected because condition must be conditions:
version: "1"
rules:
- id: misspelled-condition
condition:
model:
denylist: [gpt-4o]
action: DENY
The Enterprise Console keeps the form open and shows Failed to create policy: …; the Portal shows The policy DSL is not valid: …. The detailed compiler message names the offending key or rule, and the invalid policy is not saved.
Open Source names and skips an invalid policy during startup while continuing to load valid policies. Treat this as a failed deployment, correct the YAML, and restart. Enterprise data-plane refresh reports the partial load as:
gateway_config_refresh_total{table="policies",status="degraded"}
Resolve activation conflicts
Flightdeck checks closed-form rules against policies that already enforce. A warning can look like:
Activated, but it conflicts with policies already enforcing: overlapping model denylist with Approved production models
Activation still succeeds. Compare the named rules, decide which policy owns the decision, give intentional rules distinct priorities, and move the replaced policy to Archived after the new rule proves safe. CEL rules cannot be compared statically, so Flightdeck tells you to verify them with representative traffic.
Use CEL for expressions, not ordinary matchers
Enterprise CEL rules cover OR, NOT, arithmetic, and relationships between fields. Use expression instead of conditions on that rule; using both or neither fails validation.
version: "1"
rules:
- id: large-gpt4o-request-outside-eu
expression: |
request.model == "gpt-4o" &&
context.region != "EU" &&
request.message_count >= 50
action: DENY
deny_message: "Large gpt-4o requests must run in the EU."
Cast dynamic fields before calling a string method, for example string(request.model).matches("^gpt-.*$"). Time functions are not available; use time_of_day for maintenance windows. See Routes and policies in Flightdeck for the complete CEL evaluation surface.