OPA policies
See OPA policies → for the conceptual model: the embedded policy engine, deny-overrides, and why an in-process policy engine adds no network hop. This page is the field-level reference: the Rego package each layer queries, the stored policy-definition shape, the input document a policy is evaluated against, and the two surface-level blocks (OPA Policy, VP Evidence) that turn evaluation on for a surface.
Evaluation layers
Rego policy is evaluated at four layers, each a fixed Rego package queried through a single in-process evaluation path. A deny at a layer stops the request immediately (fail-closed); an earlier layer’s deny can never be overridden by a later layer’s allow:
| Layer | Rego package | Query path | Evaluated by |
|---|---|---|---|
| Trust Fabric (labeled in the dashboard; the internal package name is retained unchanged) | agentstream.policy | data.agentstream.policy.allow | Appliance-to-appliance (DIDComm gateway) traffic, evaluated before any surface-level policy. |
| Surface / fabric | surface.policy | data.surface.policy.allow | A2A/MCP-style fabric surfaces. Renamed from the legacy channel.policy package; a definition still declaring channel.policy is migrated in place at startup. |
| LLM Surface | llm.policy | data.llm.policy.allow | LLM Surfaces. |
| IDE front door + per-member Model Policy | ide.policy (front door) / llm.policy (each catalogue member) | data.ide.policy.allow / data.llm.policy.allow | The IDE catalogue’s front door is checked first. Once it passes, each catalogue member is checked under its own llm.policy. |
A policy definition’s type (appliance, llm, ide) determines the package its Rego must declare, and a mismatch is rejected at write time. The surface.policy / fabric layer is not one of these three creatable types; it is written directly onto a surface, not as a reusable definition.
A gateway or surface can enforce more than one policy definition at once. The set is evaluated deny-overrides: every member must independently return allow = true, and the first to deny (or error) blocks the request. An administrator can additionally mark one or more llm/ide-typed definitions as enforced globally across every surface of that type, ANDed with each surface’s own set. Each global assignment carries its own monitor-only flag so a would-be deny is evaluated and logged, but does not block the request, while the policy’s effect is observed before it goes live.
A missing or failed-to-compile engine for an enforced gateway or surface fails closed: the request is denied rather than let through. This is recorded in policy audit evidence as a missing_engine outcome.
Rego companion reason rules
Alongside allow, a policy’s package may declare allow_reason and deny_reason rules (derived from the query path by swapping the trailing .allow) to supply a human-readable message that flows into policy audit logs and observability:
package llm.policy
default allow = false
allow if { input.source_auth.claims.role == "admin" }
allow_reason := "Admin bypass" if allow
deny_reason := "Only admin callers may use this surface" if not allowOn an allow with no allow_reason rule, reason stays absent. On a deny with no deny_reason rule, the fixed fallback "Policy denied request" is used.
A handful of Rego builtins are rejected at write time regardless of layer:
| Builtin | Rejected because |
|---|---|
numbers.range( | Can allocate an attacker-sized collection. |
http.send( | No network access from a policy. |
opa.runtime( | No runtime metadata. |
print( | No policy-authored output. |
Compiled policies also run under a fixed evaluation time limit and a fixed source-size limit, both non-configurable.
Policy definitions
A reusable Rego policy is what you create and edit on the Policies page: write it once, then reference it from as many surfaces (or a global assignment) as need the same rule, and every enforcement point picks up the exact version you activate. Every save is kept as a numbered version behind the scenes, but the editor itself always shows you the current version.
- Type has to match where you’ll enforce the policy: Trust Fabric for appliance-to-appliance traffic, LLM for LLM Surfaces, IDE for the IDE catalogue front door and its per-model policies. The Rego package has to match too, or the save is rejected. Type is locked once you save; changing it later means creating a new definition.
- Turn Enabled off instead of deleting a policy you might reuse later. A disabled definition is simply skipped from any set that references it.
- Keep Sample Input current as you edit the policy, so the Test panel’s dry-run keeps reflecting the case you’re actually trying to catch.
| Field | Default | Description |
|---|---|---|
| ID | — | Unique identifier. |
| Name | — | Human-readable name. |
| Description | None | What the policy does. |
| Type | — | Which layer this definition may be applied to: Trust Fabric (appliance), LLM (llm), or IDE (ide). Its Rego must declare the matching package. Locked after creation. |
| Policy | — | The Rego source of the current version, edited directly as text. |
| Enabled | On | Whether this policy is compiled and enforced. A gateway or surface referencing a disabled definition is skipped from its set (and fails closed if that leaves the set empty). |
| Created At | — | Document creation timestamp. |
| Updated At | — | Last-updated timestamp (metadata edit or new version). |
| Version | — | The version number currently enforced. |
| Content Hash | — | A hash of the enforced revision’s content, used to prove which exact policy bytes produced a given audited decision. |
| Sample Input | — | Saved sample input JSON for the policy dry-run (Test panel), persisted with the policy across edits. See Sample Input below. |
A save that changes the Rego body appends a new version behind the current one and advances which version is enforced; a save that only changes metadata (name, description, enabled) updates the current version in place without adding a new one. Every past version is kept permanently, so renaming a policy never breaks anything that enforces it: bindings resolve a policy by its ID, not its name.
Sample Input
The Sample Input field feeds the Test panel’s dry-run, letting you re-run a stored request against your policy before activating an edit. A good sample:
- Is real JSON matching the input document shape below, not a partial fragment. A policy that reads
input.source_auth.claims.roleneeds a sample that actually populatessource_auth.claims, or the dry-run won’t reflect what a live request looks like. - Covers the specific caller and request shape your policy branches on. If your Rego has different rules for JWT-authenticated versus mTLS-authenticated callers, save (or keep swapping in) a sample for each path you need to check.
- Is updated whenever you add a new condition to the policy, so the dry-run keeps testing the case that condition is meant to catch, rather than a stale sample from an earlier version of the policy.
Global (appliance-wide) policy assignments
Held per policy type (appliance, llm, ide), enforced on every surface (or, for appliance, every gateway connection) of that type, independent of each surface’s own OPA Policy configuration and evaluated deny-overrides alongside it. It’s the right tool for an organization-wide requirement, such as always checking a trust registry, that shouldn’t depend on each surface owner remembering to add it.
There is no separate global-assignment screen: on the Policies page, switch to the tab matching the policy’s type, and each policy’s own row carries a Global switch. Turning it on opens a confirmation popover; confirming enforces that policy across every surface (or gateway connection) of that type.
- Turn on Monitor only when first rolling out a new global policy. It evaluates and logs the outcome without blocking anything, so you can confirm it behaves as expected before it’s allowed to deny real traffic.
- Turn it off once you’ve confirmed the policy’s decisions look right, so it actually enforces.
| Field | Default | Description |
|---|---|---|
| Global | Off | Toggle on the policy’s own row in the Policies list. Enforces this policy definition across every surface (or gateway connection) of its type. |
| Monitor only | Off | When on, the policy is evaluated and its outcome recorded, but a would-be deny does not block the request. This allows safely rolling out a new global policy before it enforces. |
Input document
Every policy layer evaluates against a single, versioned input document (schema version currently 1). Only the fields relevant to the current request are populated; everything else is omitted.
| Field | Populated when | Description |
|---|---|---|
| http | Always | Inbound HTTP method, path, and headers (sensitive headers containing authorization, cookie, or token substrings are stripped before injection). |
| request | IDE front door | purpose is discovery (GET /v1/models) or dispatch (POST /v1/chat/completions), letting a policy allow unauthenticated catalogue discovery while requiring auth to dispatch. forwarded_upstream_auth is true when the caller supplied a distinct upstream credential alongside the front-door token. |
| gateway | Gateway (Trust Fabric) layer | direction is inbound (self gateway) or outbound (remote gateway); source_id/target_id identify the connection points at each end of the exchange. |
| channel | Surface / fabric layer | config_id/name identify the surface configuration itself. variant_alias carries the active surface variant selected by the request URL (/route$alias/...); None for the default variant. The LLM and IDE layers don’t populate variant_alias. |
| source_auth | An authenticated caller | Exactly one of three shapes, depending on the surface’s authentication method: jwt_bearer (subject, claims), api_key (key_name), or mtls (principal, fingerprint, subject_dn, issuer_dn, sans). |
| mcp | MCP traffic | Normalized MCP request shape: method, tool_name, resource_uri, prompt_name, params. |
| extension_identity | Agent-identity extension present | The caller’s decentralized identifier and its hashed form, when the agent-identity extension resolved one. |
| payment | A payment extension is active | Whether the payment attached to this request was verified, and the response header used to communicate that back. |
| agent | Agent-card / A2A traffic | trust_verification is Some(true)/Some(false) when trust registry recognition ran, None when unavailable. did is the caller’s decentralized identifier and agent_dna its agent-card payload. |
| metadata | Arbitrary extension metadata | Free-form key/value data attached by whichever extension populated it; shape varies by extension. |
| identity_binding | VP evidence enabled and a VP was presented | The verified holder/issuer DIDs and VC claims from an upstream gateway’s request-path verifiable presentation. |
| resource | Always, though each field is populated only when relevant | Normalized request target: surface_type (llm/ide/api/mcp/webhook), model, reasoning_effort, reasoning_max_tokens, requested_models, requested_providers, tool, server_tools, requested_transforms, document_types, audio_formats, image_formats, route, modality. |
| action | Always when resolved | Normalized action verb (dispatch, discovery), consistent across surface types; supersedes the IDE-only request.purpose, kept as a compatibility alias. |
| environment | Always | now is the RFC 3339 evaluation timestamp, for time-window policies. |
| schema_version | Always | Version of this input contract (currently 1). |
Pipe-level policy block
The OPA Policy block is the surface-level block, available on an LLM Surface, that connects a surface to the policies you’ve written and turns Rego enforcement on for its inbound traffic. There is no separate enabled toggle: dragging the OPA Policy element onto the canvas is what turns it on, and removing the element turns it off. When present and a policy is referenced, requests are evaluated after source authentication, with source authentication’s identity, the request’s headers, and everything else in the input document available to the Rego rule, and denied (403) on allow = false.
- Reference a single Policy Definition to enforce it on this surface. To combine more than one policy on the same surface, enforce the additional policy or policies globally instead (see Global (appliance-wide) policy assignments); a global assignment is ANDed with this surface’s own policy, deny-overrides.
- Log OPA decisions logs the full
inputJSON, including request headers and caller identity, which makes it a debugging aid rather than something to run in production.
| Field | Default | Description |
|---|---|---|
| Policy Definition | — | The stored policy definition to evaluate against, chosen by name. |
| Log OPA decisions | Off | Emit a log line for every OPA evaluation containing the full input JSON and the resulting allow/deny. A debugging aid for policy authoring: leave off in production, since input carries request headers and caller identity. |
A fabric surface (the surface.policy layer, for A2A/MCP-style traffic) separately resolves its own response-side policy, independent of its request-side policy. This response-side mechanism is not available on an LLM Surface’s OPA Policy block.
VP evidence
The VP Evidence block governs per-surface verifiable-presentation evidence. When enabled, an inbound VP is verified and its holder/issuer/claims are injected as identity-binding data (above) for the surface’s OPA policy. That lets a Rego rule check input.identity_binding for a VP from a specific issuer (for example, “allow only if a VP from a known issuer is present”) without having to verify the presentation itself.
- Deny when no VP is presented denies the request outright, before the policy even runs, whenever a VP is mandatory for the surface: a belt-and-braces gate on top of whatever the policy decides.
- If a VP is only optional context that some callers provide, leave that toggle off and let your Rego decide what to do when
input.identity_bindingis absent. - VP carried in defaults to Request header; switch it to JSON body field only for callers that send the presentation there instead.
| Field | Default | Description |
|---|---|---|
| Evaluate VP evidence | Off | Whether VP evidence is evaluated for this surface. |
| Deny when no VP is presented | Off | Deny the request outright when no VP is presented: a belt-and-braces gate on top of whatever the policy decides. |
| VP carried in | Request header | Where the VP is carried on the inbound request: Request header or JSON body field. |
| Header name | X-Agent-Stream-Presentation | Header name to read when VP carried in is Request header. |
Versioning and immutability
Every save of a policy definition that changes its Rego body appends a new, immutable, monotonically numbered version. Prior versions are retained permanently and never rewritten (the sole exception is a one-time legacy channel.policy → surface.policy package migration, applied in place at startup). A metadata-only edit (name, description, enabled) updates the current version’s record without appending.
Every policy decision is recorded in audit evidence against the exact policy definition ID, resolved version, and content hash that was enforced, so a past decision can be proven against the precise policy bytes that produced it. The content hash binds both the policy’s type and its Rego body, so identical Rego stored under a different scope hashes differently.
A policy carries a saved dry-run/Test-panel fixture alongside its Rego, letting an operator re-run a stored sample against an edited policy before activating it. There is no separate “dry-run mode” flag on the definition itself, only the saved sample and the monitor-only rollout flag on a global assignment.
Related
- OPA policies concept: The embedded policy engine, why in-process evaluation adds no network hop, and the four-layer model.
- Core configuration: Where OPA Policy and VP Evidence sit among a surface’s other optional blocks.
- Control surface access with an OPA policy: Write a policy definition and attach it to a surface’s OPA Policy block.
- Roll out an OPA policy globally: Turn on a global assignment in monitor-only mode before letting it enforce.
- Verify caller credentials with VP evidence: Gate a policy on a cryptographically verified issuer and claims via the VP Evidence block.
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.