How authorization works
Everything that decides who may do what in your product, on one page: the organization tree, memberships, profiles, grants, groups, denies, field policy, the node each request acts at, and elevation. Each section links to the guide that puts it to work.
The short version: a request acts at one node of your product's organization tree, with the authority its membership holds there, and the platform reads that authority on every request. Your end user's token says only who they are; what they may do comes from the tree, never from the token and never from your code. A change to a member's authority takes effect within seconds, on the requests that follow, not at the next sign-in.
The picture
/ the root: your product's base profiles live here (config/profiles.toml)
└── /org Ada's membership is homed here: roles admin, staff, customer
├── /org/customers Bea signed up here as a customer, and owns her own orders
└── /org/acme a client node; deny: entity.delete on orders, binding Acme and below
├── /org/acme/sales Carl: membership at /org/acme, grant "editor" here until 31 Jan
└── /org/acme/ops a group "night-shift" holds a grant of "viewer" here
a request ─► acts at ONE node ─► denies there and above ─► roles there ─► owner / shared ─► field policy
(its membership's (refuse first, (home roles (which records) (which fields)
home, or the entry's whatever grants + grants at
declared org_path) confer) or above it)
The pieces
- Nodes. Your product's end users and their records live in a tree of nodes such as
/org,/org/customersand/org/acme. A record is stored at the node its writer acts at. A session reaches the records at its node and below; an entity can narrow that to each user's own records or open an ancestor's records for reading (Owner and shared entities). You create client nodes withaircloud org nodes create(Members, roles and registration). - Memberships. A person holds one or more memberships in your product. Each membership is homed at one node
and holds roles there. One person's memberships never sit one inside another. Every request acts through one
membership: a session switches it at
/_air/memberships; the CLI takes--membershipwhen you hold several (Members, roles and registration). - Roles are the keys of your
[authenticate] rolesvocabulary. A route admits a session holding any of its roles (Routes); an entity'swrite_rolesdecide who may write it (Owner and shared entities). - Profiles are named sets of roles. The base profiles are declared in
config/profiles.toml; runtime profiles are created at a node below the root (Profiles and grants). - Grants confer a profile, by reference, at a node at or below a membership's home, optionally until a time. Changing a profile changes every grant that references it.
- Groups are memberships that share grants: a group's grant reaches each member at or below both the grant's node and the member's own home.
- Denies remove roles or refuse operations at and below a node, whatever any grant confers, and they bind every actor there: members, functions acting there, and you.
- Field policy decides, per record, which fields are visible, writable and required (Field policy).
- The acting node. A function with no end user acts at the
org_pathits route or trigger declares, and may narrow to a node below it, never above (The acting node and elevation). - Elevation. A declared aggregate may count or sum an owned entity across its owners without ever naming a record or an owner (The acting node and elevation).
- Workspaces. A product may let any signed-in end user create a company of their own: a node and a new membership homed there (Workspaces).
Who decides, in what order
The platform evaluates a request in this order. The first step that refuses ends it; aircloud authz explain
prints which step decided and the rule it names.
| step | what it reads | refuses with | the rule explain names |
|---|---|---|---|
| the membership | the selected membership, against the projection | session_membership_required (a session), membership_required (a direct caller) |
— |
| the node is active | the acting node and every ancestor | org_node_archived |
— |
| denies | every deny at the acting node and above | an op-deny refuses the operation; a role-deny removes the role before the next steps | deny.op, deny.role |
| roles | the membership's home roles, its grants and its groups' grants at or above the record's node | session_role_required (a route's roles), flex.role_required (write_roles) |
home, grant.membership, grant.group, write_roles |
| records | owner, shared and the tree |
a record outside the scope is not found | owner |
| fields | the [[policy]] rules at the record's node |
a hidden field is omitted; a non-writable change is flex.forbidden |
field_policy |
When the authority cannot be read in time, the request is refused (session_authz_unavailable on a session), never
treated as "no restriction".
Asking why
aircloud authz explain --sub 2891… --plane end_user --node /org/acme --action entity.delete --entity orders
aircloud authz who --node /org/acme
aircloud members review --window 720h
authz check answers the decision and exits 1 unless it is an allow; authz explain prints the whole answer: the
membership, its roles and scopes at the node, the step that decided and the record layer's detail. authz who
lists every membership whose authority reaches a node, with its roles there. Each is answered by the same
evaluator enforcement uses, over the nodes at or below yours. Your coding agent asks the same question through the
dev MCP server's authz_check (The dev MCP server).