Policies and Policy Sets
The platform decision contract decides whether the STS may issue a mandate. The contract is a signed, versioned Rego module that Caracal owns and embeds in the STS; it is the only thing that emits an authorization decision. You author policy data documents — grants, bindings, confinement, and restrictions — that the contract reads. Policies are versioned immutably, bundled into policy sets, and activated per zone.
Policy Objects
Section titled “Policy Objects”| Object | Purpose |
|---|---|
| Policy | Named policy data document with immutable versions. |
| Policy version | A specific content hash and schema version. |
| Policy set | A named bundle of policy versions. |
| Policy set version | A specific manifest of policy versions. |
| Active policy set version | The version the STS evaluates for a zone. |
Decision Contract
Section titled “Decision Contract”The platform decision contract owns the result object in package caracal.authz. It is deny by default and only allows an exchange that matches one of its vetted rules: a bootstrap mandate, a delegated mint narrowed to the delegation edge, or a mandate presented at the Gateway. You never write result; you supply the data the contract reads.
# caracal:data-documentpackage caracal.authz
import rego.v1
grants := { "resource://mercury-bank": { "application": "payments", "roles": {"payment-execution": ["payments:read", "payments:write"]}, },}The contract reads four adopter documents from package caracal.authz:
| Document | Shape | Effect |
|---|---|---|
app_ids | {binding_key: application_id} | Binds the application key used in grants to the control-plane id the STS sees as input.principal.id. |
grants | {resource: {application, roles: {role: [scopes]}}} | Declares which application owns a resource view and which scopes each role may hold. |
confinement | [{label_prefix, scopes}] | Caps every agent session whose principal carries a matching label prefix to a fixed scope set. Optional; omitting it confines nothing. |
restrict | set of reasons | Deny overlay. Any entry denies every exchange in the zone. Optional; keep it empty to authorize normally. |
A zone that supplies no data authorizes nothing: every allow rule collapses to the default deny.
Example input values the contract evaluates:
| Input | Example |
|---|---|
input.principal.id | app_lynx_control (the acting application, user, or agent) |
input.principal.registration_method | managed or dcr |
input.resource.identifier | resource://mercury-bank |
input.context.requested_scopes | ["payments:read", "payments:write"] |
input.delegation_edge | Present when authority was delegated from another agent session. |
Policy Input Contract
Section titled “Policy Input Contract”Every evaluation receives this exact input shape (schema_version 2026-05-20). There is no input.application or input.grant object — the acting application is the principal, and grant bounds are enforced by the STS before the contract runs. The platform decision contract evaluates the fields below; your data documents map applications, roles, and scopes onto them.
| Path | Type | Meaning |
|---|---|---|
input.principal.type | string | Application, user, service, or agent principal class. |
input.principal.id | string | Stable identifier of the acting application/user/agent. |
input.principal.zone_id | string | Zone the principal belongs to. |
input.principal.registration_method | string | managed or dcr; match dcr to scope dynamically registered workloads. |
input.principal.lifecycle | string | task or service for agent sessions. |
input.principal.labels | string[] | Role descriptors set at spawn(labels=[...]); drive least-privilege role checks. |
input.principal.agent_session_id | string | Agent session that initiated the exchange, when present. |
input.resource.type | string | Resource class, e.g. Resource. |
input.resource.id | string | Internal resource id. |
input.resource.identifier | string | Stable audience URI, e.g. resource://mercury-bank. |
input.resource.scopes | string[] | Scopes the resource defines (the full allowlist). |
input.action.id | string | The exchange action, currently TokenExchange. |
input.action.method | string | Upstream HTTP method (upper-cased) the gateway is authorizing. Present only on gateway-authenticated exchanges; absent on direct exchanges. Gate operation authority against this. |
input.action.path | string | Upstream request path the gateway is authorizing. Present only on gateway-authenticated exchanges; absent on direct exchanges. |
input.session.id | string | Present when the request is bound to a session. |
input.delegation_edge | object | Present only on delegated exchange (see below). |
input.context.requested_scopes | string[] | Scopes the caller is requesting now; gate these against the allowlist. |
input.context.actor_claims | object | Verified claims of the acting credential. |
input.context.subject_claims | object | Verified claims of the delegated subject, when present. |
input.context.challenge_resolved | bool | true after a step-up challenge is satisfied. |
input.context.trace_id | string | Request trace id for audit correlation. |
When input.delegation_edge is present it carries: id, source_session_id, target_session_id, issuer_application_id, receiver_application_id, resource_id, scopes (the narrowed grant), edge_version, path (the full delegation chain), graph_epoch, and constraints_json (typed delegation constraints such as a cost budget). The contract enforces hop membership and the scope-subset narrowing against these fields.
The contract reads your data documents through data.caracal.authz.* — grants, app_ids, confinement, and restrict. You supply that data; the platform contract reads it and decides.
Policy Outcomes
Section titled “Policy Outcomes”| Outcome | Effect |
|---|---|
allow | STS signs a mandate if token and session checks also pass. |
deny | STS refuses the exchange and records diagnostics. |
| step-up diagnostic | STS returns interaction_required with a challenge ID. |
Step-up is expressed as a diagnostic such as {"step_up_required": "mfa"}. The STS creates a challenge and expects a later exchange with the challenge proof.
Authoring Rules
Section titled “Authoring Rules”- Author data documents only; mark each with
# caracal:data-document. The platform decision contract owns everyresult. - Validate through the web console or Admin API before activation — a data document must define data and must not define
result. - Activate through a policy set version, not by editing active content in place.
- Keep
grants,app_ids,confinement, andrestrictin separate documents so ownership and review stay clear. - Tighten authority through
confinementandrestrict; neither can widen what the contract already allows.
Next Step
Section titled “Next Step”Read Step-Up Challenges to understand how policies pause sensitive exchanges for fresh proof.

