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.
Evaluation flow
Section titled “Evaluation flow”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"] endCore concepts
Section titled “Core concepts”Policy Pack
Section titled “Policy Pack”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.
Policy Rule
Section titled “Policy Rule”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.
Policy Pack Chain
Section titled “Policy Pack Chain”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.
Combining algorithm
Section titled “Combining algorithm”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.
How group conditions work
Section titled “How group conditions work”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. Setchannel: ["interactive"]to target browser/SSE callers only, orchannel: ["api"]to target programmatic API callers only. GovernancePROMPTrules typically usechannel: ["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:
- Rule 1 evaluates. Tom is in
openai_blockand the provider isopenai— both conditions match. Action isBLOCK. Evaluation stops.
If Tom sends a request to Anthropic containing a credit card number:
- Rule 1 evaluates. Tom is in
openai_blockbut the provider isanthropic, notopenai— condition fails. Rule skipped. - Rule 2 evaluates. Tom is in
financeand acredit_cardentity was detected — both conditions match. Action isREDACT. The credit card number is replaced before the prompt is forwarded.
Actions reference
Section titled “Actions reference”| Action | Terminal? | Behavior |
|---|---|---|
ALLOW | Yes | Explicit whitelist. Stops all further evaluation. The request proceeds as submitted. |
BLOCK | Yes | Denies the request. Returns an error response to the caller. The optional message field sets the error text. |
CANCEL | Yes | Silent drop. The request is discarded without returning an error to the caller. |
REDACT | No | Replaces 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_TO | Yes | Overrides 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. |
PROMPT | Yes | Governance 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.
PROMPT action and HTTP 449
Section titled “PROMPT action and HTTP 449”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.
Default terminal action
Section titled “Default terminal action”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.
Conflict resolution by sequencing
Section titled “Conflict resolution by sequencing”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.
Relationship to the DLP pipeline
Section titled “Relationship to the DLP pipeline”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.
Audit trace
Section titled “Audit trace”Every evaluation produces an audit entry written atomically with the action. The audit record includes:
| Field | Description |
|---|---|
request_id | Unique request identifier |
user_id, tenant_id | Who made the request |
matched_pack_id | Which pack contained the matching rule |
matched_rule_id | Which specific rule matched |
matched_sequence | Sequence position of the matching rule |
match_reason | Human-readable explanation of why the rule matched |
action_taken | The terminal action executed |
applies_to | Whether the match was on input or output |
entity_types_detected | Entity types found by DLP |
stage_latencies | Milliseconds for intake, payload analysis, evaluation, provider call |
outcome | Final outcome: ALLOW, BLOCK, CANCEL, REDACT, or ROUTE_TO |
siem_forwarded | Whether 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).
Next steps
Section titled “Next steps”- Policy Rule Reference — every condition field and action type with examples
- Policy Engine — Admin Guide — step-by-step: create packs and rules in the admin UI, test with PolicySimulator
- DLP Overview — how entity detection works before policy evaluation runs
- Compliance Bundles — pre-configured packs for regulatory frameworks
- Audit Log — querying and exporting evaluation records
- PROMPT Governance — User Guide — what the GovernancePromptDialog looks like from a user’s perspective