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.
| Layer | Who writes it | Where it lives |
|---|---|---|
emergency | Incident responders (Control) | Emergency denies published by an incident |
enterprise | Org security policy admins (Control) | Org policies, applied to projects enrolled in the org |
project | Project owners and developers (Gateway) | Project policies |
grant | The customer's tenant admin | The 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.
{
"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_…"
}
}effectisallow,denyorrequire_approval. Anallowrule never grants anything the grant does not; it only records a match in the trace.- Scope:
operations,tenantsandenvironmentsare 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 forrequire_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: truemakes a rule immune to overrides. Ceilings are hard.
How layers combine
- 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
denywithemergency_deny. Nothing else is evaluated. - Every other rule is evaluated in order: enterprise, then project, then grant. Each match is recorded in a trace.
- Any matching deny blocks. The decision is
denywith that rule'sreason_code(defaultpolicy_deny). - Otherwise, every matching
require_approvalcontributes 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. - Otherwise the decision is
allowwithallowed_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:
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.