Skip to content

Policy layers ​

Grants authorise; policy restricts. Every action is evaluated against several layers, and a layer can only tighten the result: a deny from any layer blocks, and every applicable approval requirement must be satisfied.

LayerWho writes itWhere it lives
emergencyIncident responders (Control)Emergency denies published by an incident
enterpriseOrg security policy admins (Control)Org policies, applied to projects enrolled in the org
projectProject owners and developers (Gateway)Project policies
grantThe customer's tenant adminThe rules of the grant being used

Before any layer runs, the request must pass the grant check (Tenants and grants).

Rules ​

A policy document is axiom.policy.v1: a list of rules.

json
{
  "id": "finance-approval-over-500",
  "description": "Refunds above USD 500 need a finance approver",
  "effect": "require_approval",
  "operations": ["billing.refund.create"],
  "tenants": ["ten_…"],
  "environments": ["production"],
  "when": [{ "field": "params.amount", "op": "gt", "value": "500" }],
  "reason_code": "finance_approval_required",
  "approval": {
    "approver_role": "finance_approver",
    "count": 1,
    "separation_of_duties": true,
    "decision_ttl_seconds": 14400,
    "resume_ttl_seconds": 3600,
    "connector_id": "con_…"
  }
}
  • effect is allow, deny or require_approval. An allow rule never grants anything the grant does not; it only records a match in the trace.
  • Scope: operations, tenants and environments are glob lists; omitted means "all".
  • Conditions (when, all must hold) read paths in the evaluation context: params.*, resource.type, resource.id, tenant, operation, environment, audience, delegated, agent.id, agent.owner, agent.verification. Operators: eq, ne, gt, gte, lt, lte, in, not_in, exists, not_exists. Decimal strings compare exactly as decimals, never as floating point: "25000.00" is greater than "500".
  • approval (required for require_approval): the role whose holders in the action's tenant may approve, how many distinct approvers (1 to 5), separation of duties, the decision deadline, the resume window, and optionally a connector that carries the request. Without a connector, the built-in approval page is used.
  • hard: true makes a rule immune to overrides. Ceilings are hard.

How layers combine ​

  1. Emergency denies first. If any emergency deny targets the agent, its credential, the grant (or any ancestor grant), the tenant, the service or the project, the decision is deny with emergency_deny. Nothing else is evaluated.
  2. Every other rule is evaluated in order: enterprise, then project, then grant. Each match is recorded in a trace.
  3. Any matching deny blocks. The decision is deny with that rule's reason_code (default policy_deny).
  4. Otherwise, every matching require_approval contributes a requirement. Requirements for the same role merge to the strictest: the most approvers, both separation-of-duties flags, the shortest decision and resume deadlines. The decision's reason code is the first requirement's.
  5. Otherwise the decision is allow with allowed_by_grant.

So a project can add an approval step or a lower limit to an enterprise policy, but cannot remove one. An absolute ceiling denies a refund even when someone offers to approve it: deny beats approval.

Ceilings, overrides and their limits ​

An override lifts one non-hard rule for a bounded time:

json
POST /v1/projects/{project_id}/overrides    (or /v1/orgs/{org_id}/overrides)
{ "rule_id": "finance-approval-over-500", "reason": "Quarter-end backlog, approved by CFO", "expires_at": "2026-11-20T00:00:00Z", "tenant_id": "ten_…", "operation": "billing.refund.create" }

Overrides require a recorded owner (the caller), a reason, an optional tenant and operation scope, and an expiry at most 30 days ahead. A hard rule cannot be overridden. An overridden rule appears in the trace with the override ID.

An override carries the authority of the scope it was created at: enterprise for an organisation override, project for a project override. It can lift only a rule at or below that layer (grant, then project, then enterprise). A project override therefore cannot relax an enterprise rule; only an organisation override can.

Lifecycle: draft, staged, active ​

Policies are versioned. POST /v1/{projects|orgs}/{id}/policies saves a validated draft (with policy_id, a new version of an existing policy in the same scope); …/policies/{policy_id}/versions/{v}/publish activates it (or stages it with "stage": true); …/rollback re-activates an earlier version with a recorded reason. Publish, rollback and new versions are refused with 404 when the policy belongs to a different project or organisation. Publishing signs the version with Axiom's policy key, and each action records the policy IDs and versions in force, which the receipt carries.

Before publishing, preview a candidate against recent traffic (POST /v1/{projects|orgs}/{id}/policies/preview): it re-evaluates up to 200 recent actions from their stored material fields and lists every action whose outcome would change. The project simulator (POST /v1/projects/{project_id}/simulate) predicts allow, deny or approval for a hypothetical request, optionally with a draft in place of the active version, and returns the full rule trace. Neither creates an action.

Staged rollout is a preview, not partial enforcement

A staged version, and its rollout_percent, is stored and appears in the policy list and as the fleet view's expected impact, but evaluation and gateway bundles use only active versions. Nothing is enforced for a percentage of traffic.

A policy author cannot satisfy an approval their own rule requires: with separation of duties, the author of the policy version is excluded from the eligible approvers (see Approvals).

Re-evaluation on resume ​

Policy is evaluated again when the original agent resumes an approved action, against the policies in force at that moment. A new deny, a stricter approval requirement than the one satisfied, or an approver who no longer holds the role stops the resume. See Action state machine.

Developer preview. Contract version 2026-11-14.