Skip to content

Policy Engine overview

The Policy Engine is the single enforcement layer through which every AI request in Arbitex passes. It evaluates each request against an ordered chain of policy rules, and produces exactly one outcome: allow, block, cancel, redact, or route to a different model. It replaces the previous mix of ad-hoc DLP enforcement, compliance bundle toggles, content filters, and routing rules with a unified evaluation pipeline that has explicit ordering, first-match semantics, and a full audit trace for every decision.

Every request passes through four sequential stages:

flowchart TD
subgraph S1["STAGE 1 — INTAKE"]
Parse["Parse request\nuser, provider, model, prompt"]
Auth["Authenticate (API key / JWT)"]
PreLog["Pre-log: tamper-evident audit entry"]
Parse --> Auth --> PreLog
end
PreLog --> Fork{{"Parallel"}}
Fork --> PA["PAYLOAD ANALYSIS\nEntity scan (DLP)\nGeoIP + CredInt\nSession context"]
Fork --> QR["QUOTA / RATE CHECK\nRequest count\nToken budget\nDollar budget"]
PA --> S2
QR --> S2
subgraph S2["STAGE 2 — POLICY EVALUATION"]
EvalChain["Evaluate policy chain\nuser chain -> org chain\npacks in sequence order\nrules in sequence order\nfirst terminal match wins"]
end
S2 --> S3
subgraph S3["STAGE 3 — ACTION EXECUTION"]
Block["BLOCK / CANCEL\nreturn error"]
RedactFwd["REDACT\ntransform + forward"]
AllowFwd["ALLOW / ROUTE_TO\nforward to provider"]
Output["Output evaluation\napplies_to = output | both"]
AllowFwd --> Output
RedactFwd --> Output
end
S3 --> S4
subgraph S4["STAGE 4 — AUDIT"]
Audit["Atomic write: matched pack/rule,\nmatch reason, action, latencies,\nSIEM forwarding status"]
end

A Policy Pack is a named, ordered collection of policy rules. Packs come in two types:

Compliance Bundle (pack_type: "bundle") — a pack built and maintained by Arbitex. Each bundle maps to a named regulatory standard (such as pci_dss or hipaa). The rules inside a Compliance Bundle are read-only: your organization activates the bundle and positions it in your chain, but cannot modify or suppress the individual rules inside it.

Custom Pack (pack_type: "custom") — a pack your organization creates and owns. You write the rules, set the conditions, choose the actions, and control the sequence. Custom packs are how you implement org-specific requirements that fall outside of a compliance framework.

Both types are evaluated identically by the engine. The distinction is ownership and editability, not evaluation behavior.


A rule is the atomic unit of enforcement. Each rule has three parts:

Conditions — the match criteria that must all be true for the rule to fire. If any condition is not met, the rule is skipped. All conditions are AND-logic: every non-empty condition must match. See Policy Rule Reference for the full list of condition fields.

Action — what happens when the rule fires. See Actions below.

applies_to — whether the rule applies to the input (the user’s prompt), the output (the model’s response), or both. This lets you write output-scanning rules that inspect model responses before they are delivered.


The Policy Pack Chain is the ordered sequence of packs that Arbitex evaluates for a given request. You control which packs are in the chain and in what order. The chain is evaluated sequentially from the lowest sequence number to the highest.

Evaluation follows first-match semantics: as soon as a rule fires a terminal action, evaluation stops. Rules in lower-sequence packs take precedence over rules in higher-sequence packs. Rules within a pack are evaluated in their own sequence order.

Two scope levels exist:

  • User chain — attached to a specific user. Evaluated first. Used for personal overrides (for example, whitelisting a trusted researcher from a BLOCK rule that applies to everyone else).
  • Org chain — attached to the organization. Evaluated after the user chain. This is the primary enforcement chain for most organizations.

Each chain has a combining algorithm that controls what happens when multiple rules could match:

first_applicable (default) — the standard firewall model. The first terminal match wins, regardless of what other rules might have matched further down the chain. This is the recommended setting for most organizations.

deny_overrides — the XACML paranoid posture. Any BLOCK or CANCEL action beats any ALLOW, regardless of sequence order. Use this when your compliance posture requires that a denial can never be bypassed by an earlier whitelist rule. Note: in this mode, ALLOW rules do not stop evaluation — the engine continues scanning for any BLOCK or CANCEL further down the chain.


Groups are conditions on individual rules — not separate chains. There is no per-group policy chain; the org chain is shared, and rules within it can target specific groups using the user_groups condition.

Two additional conditions expand what rules can target:

  • channel — restricts a rule to specific caller types. Set channel: ["interactive"] to target browser/SSE callers only, or channel: ["api"] to target programmatic API callers only. Governance PROMPT rules typically use channel: ["interactive"] to avoid surfacing human-facing dialogs to automated callers.
  • intent_complexity — targets requests by inferred complexity: "simple", "medium", or "complex". Arbitex computes intent complexity during the intake pipeline after DLP entity detection. Use this condition for cost-based routing — for example, routing "complex" requests to a higher-capability model and "simple" requests to a more economical tier.

When a rule has user_groups set, it fires only if the requesting user is a member of at least one of the listed groups. Group membership comes from your directory sync (SCIM/Entra ID groups). If the user is not in any of the listed groups, the rule is skipped entirely and evaluation continues to the next rule.

Example:

Your organization has two groups: finance and openai_block. Tom is a member of both.

Pack: "Trading Desk Rules"
Rule 1 (sequence=1):
conditions:
user_groups: ["openai_block"]
providers: ["openai"]
action: BLOCK
message: "OpenAI access is not permitted for your group."
Rule 2 (sequence=2):
conditions:
user_groups: ["finance"]
entity_types: ["credit_card"]
action: REDACT
redact_replacement: "[CC-REMOVED]"

When Tom sends a request to OpenAI:

  1. Rule 1 evaluates. Tom is in openai_block and the provider is openai — both conditions match. Action is BLOCK. Evaluation stops.

If Tom sends a request to Anthropic containing a credit card number:

  1. Rule 1 evaluates. Tom is in openai_block but the provider is anthropic, not openai — condition fails. Rule skipped.
  2. Rule 2 evaluates. Tom is in finance and a credit_card entity was detected — both conditions match. Action is REDACT. The credit card number is replaced before the prompt is forwarded.

ActionTerminal?Behavior
ALLOWYesExplicit whitelist. Stops all further evaluation. The request proceeds as submitted.
BLOCKYesDenies the request. Returns an error response to the caller. The optional message field sets the error text.
CANCELYesSilent drop. The request is discarded without returning an error to the caller.
REDACTNoReplaces matched content with the redact_replacement string (default: [REDACTED]). Evaluation continues to the next rule. Multiple REDACT rules can fire on a single request, each replacing different content.
ROUTE_TOYesOverrides the destination model. You can specify an exact model ID (route_to_model) or a provider-agnostic tier (route_to_tier: haiku, sonnet, or opus). Stops evaluation after routing is determined.
PROMPTYesGovernance challenge. Pauses the request and surfaces a justification dialog to the user. If the user provides an acceptable justification, the request re-submits with the audit metadata attached. If the user cancels, the request is dropped. Only fires on channel: ["interactive"] callers — API callers receive HTTP 449 directly. The optional prompt_message field sets the challenge text shown to the user.

Terminal actions stop evaluation immediately. Non-terminal actions (REDACT) apply their transform and allow evaluation to continue, so subsequent rules can still match.

When a PROMPT rule fires on an interactive caller, Arbitex returns HTTP 449 Retry With to the frontend, which triggers the GovernancePromptDialog. The user enters a justification; on submit, the original request re-submits with an X-Governance-Justification header containing the justification text and a X-Governance-Challenge-Id linking the re-submission to the original audit entry. If the user cancels the dialog, the request is silently dropped.

For API callers (channel: "api"), PROMPT rules are typically excluded using the channel condition — see Policy Rule Reference — channel condition. If a PROMPT rule fires on an API caller anyway, the caller receives HTTP 449 with a machine-readable body explaining the governance requirement.


If no rule matches anywhere in the chain, the default action is ALLOW. The request is forwarded to the requested provider and model unchanged.

This is the permissive default. If your organization’s posture requires that anything not explicitly permitted is denied, add a catch-all BLOCK rule at the end of your pack with no conditions set. A rule with no conditions matches every request that reaches it.

Rule (sequence=999):
conditions: {} # no conditions — matches everything
action: BLOCK
message: "No policy explicitly permits this request."

Position this rule last in sequence so that all your specific allow and routing rules evaluate first.


The Policy Engine never silently resolves conflicts between rules. If two rules could apply to the same request, the rule that appears earlier in the sequence wins (under first_applicable) — full stop. Your organization owns the sequence, and therefore owns the outcome.

Example: You have a Compliance Bundle at sequence=1 and a Custom Pack at sequence=2. Both contain rules that would match a specific request. The bundle rule at sequence=1 fires first; the custom pack rule at sequence=2 is never evaluated for that request.

If you want your Custom Pack rule to take precedence over the bundle rule for specific cases, move the Custom Pack to sequence=1. This is deliberate — compliance teams understand priority ordering, and making it explicit and auditable is preferable to a conflict-resolution algorithm that would itself introduce surprises.

The audit log records exactly which pack and rule matched, at which sequence position, and why. There is no ambiguity in the audit trace.


The 3-tier DLP pipeline and the Policy Engine are complementary systems that handle different stages of request processing:

DLP pipeline (detection) — the DLP pipeline inspects every request using pattern matching (Tier 1), named entity recognition (Tier 2), and contextual NLI validation (Tier 3). Its job is to find sensitive data and classify it: entity type, confidence score, position in the text.

Policy Engine (enforcement) — the Policy Engine receives the DLP findings as input and decides what to do. A policy rule can reference detected entity types and confidence thresholds as conditions. The engine then executes the action: block, redact, allow, route.

The DLP pipeline produces a list of detected entities. The Policy Engine consumes that list and makes the enforcement decision. Neither system is a subset of the other — you need both.

See DLP Overview for details on the detection pipeline.


Every evaluation produces an audit entry written atomically with the action. The audit record includes:

FieldDescription
request_idUnique request identifier
user_id, tenant_idWho made the request
matched_pack_idWhich pack contained the matching rule
matched_rule_idWhich specific rule matched
matched_sequenceSequence position of the matching rule
match_reasonHuman-readable explanation of why the rule matched
action_takenThe terminal action executed
applies_toWhether the match was on input or output
entity_types_detectedEntity types found by DLP
stage_latenciesMilliseconds for intake, payload analysis, evaluation, provider call
outcomeFinal outcome: ALLOW, BLOCK, CANCEL, REDACT, or ROUTE_TO
siem_forwardedWhether the entry was forwarded to your SIEM

The response to the caller includes X-Policy-Action and X-Matched-Rule headers (omitted for ALLOW to avoid leaking policy details to callers).