> ## Documentation Index
> Fetch the complete documentation index at: https://ctrlrun.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Authority YAML reference

> Every key of the authority: section: max_delegation_depth, grants and break_glass, and on a grant subject, actions, resources and constraints.

The `authority:` section says which principal may propose which action. It lives at the top of
`ctrlrun.yaml` from `ctrlrun.policy/v3`, or in a standalone document passed with `--authority`
whose only keys are `schema` and `authority`. It is opt-in and then fail-closed: absent, nothing
changes; present, every principal needs a matching grant and no grant means denied. A test
asserts this page names every key the loader accepts.

## The section

| Key | Type | Default | Notes |
| - | - | - | - |
| `max_delegation_depth` | non-negative integer | `3` | how long a chain of delegations may be; recomputed on every evaluation by walking to the root, never read from the stored row |
| `grants` | list of grants | **error** if missing | the whole of who may ask |
| `break_glass` | map of envelope id → envelope | none | since v6: the widest authority an incident may reach, declared in advance. **Never consulted when deciding an action** |

## Break-glass envelopes

An incident needs authority nobody was granted in advance. The wrong answer is a flag: a flag
leaves no record, expires never, cannot be revoked and cannot be narrowed. What an envelope
declares is the **ceiling** for a grant opened beneath it during an incident.

```yaml theme={null}
authority:
  grants: []
  break_glass:
    incident-payments:
      subject: { agent: "oncall-*" }       # who a grant opened here may be FOR
      actions: ["payments.*"]
      resources: ["payment:*"]
      constraints: { amount_lte: 50000 }
      environments: ["prod"]
      max_ttl: PT4H                        # the longest expiry a grant beneath it may carry
      controls: [incident-response]        # whose approver_role gates who may OPEN it
```

| Key | Type | Notes |
| - | - | - |
| `max_ttl` | ISO-8601 duration, positive | **required**. `PT4H`, `PT30M`, `PT90S`, `P2D`; months and years are refused, because they are not durations a clock can add without a calendar. Re-checked on every evaluation, so narrowing it cuts grants already open |
| `controls` | list of control ids | which controls' `approver_role` gates who may open one. A citation that resolves to nothing, or to a control naming no role, is refused: here the citation **is** the gate, so an omission that gates nobody fails closed, which is the opposite of what the same omission means on an action |
| everything a grant has | | minus `id`, which is the mapping key, and minus `delegable` and `expires_at`, which are **refused by name** |

**The envelope decides nothing, by construction.** It lives in a mapping separate from `grants:`,
and the candidate set an evaluation walks is `grants:` unconditionally — so an envelope cannot
authorize an action even by accident. A deployment with an envelope and nothing opened beneath it
evaluates exactly as one without it.

**It carries no `delegable` and no `expires_at`, and both are load errors.** An envelope exists
only to be a parent, so "delegable" is what it means rather than a key it sets; and what bounds it
in time is `max_ttl`, which every grant beneath it obeys. It is covered by the policy hash,
`max_ttl` included, so widening the widest authority an incident can reach moves every receipt.

**Opening one is an act with its own command**, `ctrlrun break-glass --envelope <id> --file
grant.yaml --reason "<incident>"`. There is no `--as`: the opener is the principal the deployment's
approver identity resolves, and a deployment that names none cannot open one at all. A grant opened
beneath an envelope **must** carry an `expires_at` inside `max_ttl`, is contained on every
dimension, is revocable, attenuates, and is named on the receipt of every action taken under it.

An id declared in both `grants:` and `break_glass:` is a load error naming both, and a standalone
`--authority` document may not declare `break_glass` at all: it carries no control registry to
resolve the gate against, so the only envelope it could express is an ungated one.

## A grant

| Key | Type | When omitted | Notes |
| - | - | - | - |
| `id` | string, unique | **error** | what a delegation names as its parent and what receipts cite |
| `subject` | `{agent, user}` | **error** | who the grant is for; at least one of `agent` and `user`. Both absent is refused, because a subject matching every principal is not one anybody writes on purpose. *Any agent* is spelled `agent: "*"`, which greps |
| `actions` | list of action-name patterns | **error** | `stripe.refund`, or `stripe.*` |
| `resources` | list of resource patterns | any resource | `payment:*`, `payment:EU-*`; matched against the action's resolved `resource` template |
| `constraints` | map of condition → operand | no value limit | the same seven operators as a policy rule, over the action's arguments; `amount_lte: 10000000` |
| `environments` | list of environment names | any environment | `production`, `staging` |
| `delegable` | `true` or `false` | `false` | whether the holder may narrow this grant at runtime with `ctrlrun delegate`; `true` **requires** `expires_at` |
| `expires_at` | ISO-8601 timestamp with an offset | never expires | checked at every evaluation, and refused as a naive datetime |
| `tasks` | list of task-id patterns | **any task** | since v7: the unit of work this authority is for. `refund-run:*`. A grant that omits it authorises any task, so every grant written before v7 keeps its meaning |
| `budgets` | list of budgets | **bounds no aggregate** | since v7: how much, over how long. Each is `{metric, limit, window}` |

### `budgets`

`constraints` bounds **one action**; a budget bounds the **aggregate**. They are different
questions, and a grant that answers only the first permits a thousand actions that each pass it.

| Key | Type | Means |
| - | - | - |
| `metric` | the name of an action argument | which number is summed. The action must carry it, as an integer: a missing value is refused rather than counted as zero, because absence-as-zero turns a missing field into unlimited authority |
| `limit` | non-negative integer | what the sum over the window may **reach**. The comparison is inclusive |
| `window` | ISO-8601 duration, whole seconds | how far back the sum runs, rolling. `PT24H`, `P30D`. Sub-second windows are refused |

```yaml theme={null}
budgets:
  - { metric: amount, limit: 100000, window: PT24H }   # 1,000.00 a day
  - { metric: amount, limit: 2000000, window: P30D }   # 20,000.00 a month
```

Two budgets on one metric over two windows is the ordinary shape, and it is one spend measured
against two windows rather than two spends.

**A budget is consumed when the effect is reserved, in the same transaction**, so two processes
cannot both pass a check and then both spend. It is released only when the effect reaches
`FAILED`, which is the one state where the executor proved nothing happened. An `AMBIGUOUS`
effect **holds** its consumption until a human or a `reconcile` hook resolves it: ambiguity is
not a refund, because otherwise an agent able to manufacture ambiguity is able to manufacture
authority. `ctrlrun inspect --grant` shows what is held and what holds it.

A delegation's budget must be **contained** by its parent's: no higher limit, and no *shorter*
window, because a shorter window over the same limit is a higher rate and therefore more
authority.

**An action a budgeted grant covers must resolve an effect key.** Without one there is nothing
to charge against, so the action is refused rather than spending nothing for ever.

A grant carries no `decision:`. How much autonomy an action has is the policy's answer, the same
for everyone; the grant answers whether this principal may ask at all.

```yaml runnable theme={null}
schema: ctrlrun.policy/v3

authority:
  max_delegation_depth: 3
  grants:
    - id: head-of-support
      subject: { agent: "head-of-support", user: "dana@example.com" }
      actions: ["stripe.refund", "stripe.refund.partial"]
      resources: ["payment:*"]
      constraints: { amount_gte: 0, amount_lte: 10000000 }
      environments: ["production"]
      delegable: true
      expires_at: "2027-01-01T00:00:00Z"
    - id: reconciliation
      subject: { agent: "reconciliation-agent" }
      actions: ["stripe.charge.read", "stripe.refund.read"]
      resources: ["payment:*"]
      environments: ["production", "staging"]

actions:
  stripe.refund:
    effect: "refund:{payment_id}"
    resource: "payment:{payment_id}"
    rules:
      - when: { amount_gte: 0, amount_lte: 100000 }
        decision: allow
      - decision: approve
  stripe.refund.partial:
    effect: "refund:{payment_id}:{line_item}"
    resource: "payment:{payment_id}"
    decision: approve
  stripe.charge.read:
    decision: allow
  stripe.refund.read:
    decision: allow
```

## How a grant matches

A grant covers an action when the subject matches the principal, the action name matches one
of `actions`, the resolved resource matches one of `resources`, the environment is in
`environments`, every constraint holds over the arguments, and `expires_at` has not passed.
Every failing reason is collected and the reported one follows a fixed order, so the evidence
for a configuration does not depend on the order grants appear in the file. Where several
grants match, the receipt names the lowest id.

## Delegation

`ctrlrun delegate --parent <id> --file grant.yaml --as AGENT[/USER]` writes a delegated grant
beneath a `delegable` one. The file has the keys above minus `id`. It is accepted only if it is
provably a subset of its parent on every dimension, at creation and again at every evaluation:

* a narrower or equal subject (never a wildcard, never dropping the parent's `user`);
* `actions`, `resources`, `environments` each a subset;
* `constraints` no looser on any argument;
* `expires_at` no later than the parent's.

**Omitting a dimension the parent constrains is rejected, not inherited.** A child that leaves
out `resources:` would authorize resources its parent never could. `ctrlrun revoke <id>` cuts a
chain of any depth with one write and is not reversible.

## What the section does not do

It does not authenticate anybody: the principal comes from the identity provider, and `--as` on
the command line is an assertion recorded as `created_via: cli`. It does not match on a token
claim, does not propagate across an agent-to-agent hop, and does not hot-reload: revocation and
expiry are live, an edited file takes effect when the process next loads it.

## Next

* [Authority and delegation](/docs/concepts/authority-and-delegation): the concept.
* [Policy YAML reference](/docs/reference/policy-yaml): the other axis.
* [Get started](/docs/get-started/quickstart) · [Why](/docs/why).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.