arizuko

arizuko › concepts › grants

grants

A grant decides what a principal can do. Every permission check asks one question: can principal P do action A on scope S? For example, can google:114alice send a message in eng/oncall? Two tables hold the answer. acl, the access control list, holds the permission rows. acl_membership records that one id belongs to another, such as a role. One auth.Authorize() call reads both tables.

Three functions read the same rows

The figure shows the caller on the left and the rows on the right. Three functions read the same rows for three questions. Can this call run? Which tools does this turn see? Can this person pass a grant on?

┌─────────────────────────────────┐   ┌───────────────────────────────────┐
│ caller                          │   │ acl row                           │
│ operator REST: the JWT sub      │   │ principal, action, scope,         │
│ agent socket: the turn's sender │   │ params, predicate, effect,        │
│ and folder:<turn folder>        │   │ grant_option                      │
└────────────────┬────────────────┘   └─────────────────┬─────────────────┘
                 │ expanded through acl_membership,     │
                 │ transitive: role:member,             │
                 │ role:operator, roles, the account a  │
                 │ paired JID acts as                   │
                 ▼                                      ▼
┌───────────────────────────────────────────────────────────────────────────┐
│ the rows that reach the expanded principal set, read on every call        │
└──────┬─────────────────────────┬─────────────────────────────┬────────────┘
       ▼                         ▼                             ▼
┌──────────────────────┐ ┌──────────────────────┐ ┌───────────────────────┐
│ Authorize            │ │ EffectiveActions     │ │ Delegate              │
│ the per-call gate:   │ │ tools/list: the      │ │ add_acl: each new row │
│ scope glob, params,  │ │ action held at any   │ │ a subset of the rows  │
│ predicate; deny      │ │ scope; a deny row    │ │ held WITH GRANT       │
│ wins, no row = deny  │ │ hides no tool        │ │ OPTION; never a deny, │
└──────────────────────┘ └──────────────────────┘ │ never a wildcard      │
                                                  │ principal             │
                                                  └───────────────────────┘
┌───────────────────────────────────────────────────────────────────────────┐
│ AuthorizeTurn, the agent socket's two axes. Route: folder:<turn folder>   │
│ and its roles confer in full. Identity: the sender and its roles add an   │
│ allow only inside the turn folder's subtree, on the exact action, with no │
│ lattice. A deny counts on both axes, everywhere.                          │
└───────────────────────────────────────────────────────────────────────────┘

Four words name every part of the question

Principal
Who asks: a person such as google:114alice, the agent of a folder such as folder:atlas/eng, an id from a channel, or a named role.
Action
What the principal wants: interact, admin, mcp:<tool> or *.
Scope
Where: a folder path such as atlas/eng, or a glob such as atlas/**.
Effect
allow, the default, or deny. A deny wins.

A broader action includes the narrower ones

Actions include each other. * includes admin, and admin includes interact and every mcp:<tool>. So one admin row on a folder covers every tool call inside it, and you do not list the tools one by one. One matching deny stops the check, even when other rows allow it. reference / grants has the tables of principal shapes, the action lattice and the two seeded roles. The rest of the page uses the four words and real rows.

Three rows cover three common cases

Each row below is a row you would write. The CLI form is arizuko group -i <instance> grant <principal> <scope> [action], and the action defaults to admin.

Alice can talk in her own folder

principal: google:114alice   action: interact   scope: alice

The eng admin manages the whole subtree

The glob eng/** matches eng, eng/sre, eng/sre/oncall and every deeper folder.

principal: google:114alice   action: admin   scope: eng/**

One user is banned everywhere

A deny row at scope ** wins over every allow.

principal: discord:user/badguy   action: *   scope: **   effect: deny

The same shape grants a whole channel at once. Make the room the principal, for example discord:837412095138652180/1504288810112233445 with interact on main/lab. Everyone in the room then gets the grant. reference / grants has the full syntax.

A role gives one permission set to many members

When several people need the same permissions, create a role, grant the role and add members: one acl row for the role and one acl_membership edge for each member. Create both through the acl and acl_membership resources.

acl:             principal: role:editor   action: admin   scope: docs/**
acl_membership:  child: google:114alice   parent: role:editor

A second editor is one more edge. A new scope for editors is one more acl row. A role can contain roles: role:senior-editor as a child of role:editor works in the same way. The store refuses a write that would make a principal its own ancestor, so the walk always ends.

A pairing edge links a channel id to a person

Alice posts in Discord as discord:user/811270095431860224 and has a Google login. She confirms a pairing link in a browser where she is logged in with Google. Onboarding follows the flow. The confirmation writes one membership edge:

acl_membership:  child: discord:user/811270095431860224   parent: google:114alice   added_by: pairing

Her next Discord message expands to include the canonical sub, so every grant on google:114alice applies. No acl row changes.

The sender does not lend the agent their rights

On the agent socket, a turn has two principals: the folder it runs in and the person who sent the message. The grants of the folder count everywhere they reach. The grants of the sender count only inside the subtree of that folder, and only on the exact action. Without this limit, an operator who chats in a team room would give the agent of that room the reach of the operator. Anyone who could make the operator say something there could then use that reach. A deny counts on both principals, so a deny on the person still refuses the turn that the person started.

Agents and operators pass the same check

The REST and MCP faces share the handler, the folder-containment rule and the check. A REST call from an operator and an MCP call from an agent both go to auth.Authorize, over the same acl rows. REST uses the JWT subject. MCP supplies both the attested sender and the turn folder. reference / grants has the full call signature.

A subagent holds exactly what its parent holds

When an agent starts a Claude Code subagent inside its turn, the subagent uses the same unix socket. So the gate sees the same principal and gives the same answer. A narrower job gets no narrower set. A subagent that summarises one file can still call every tool of the parent, secrets and outbound requests included. Limit the parent folder to what you let a subagent do, not only to what the agent itself needs.

Five patterns cause problems

Do not add deny rows to take back access
Remove the allow row or the membership edge. A deny is for a real exception, such as one banned user in an open channel, not for undoing a grant.
Do not grant admin with a params predicate
admin means full write in the scope. For a narrow capability, use mcp:<tool> with params.
Do not put routing into the ACL
The routes table answers where a message goes. acl answers whether a principal can act there. The two tables share the names of room ids but never merge.
Do not skip roles for operators
arizuko group -i <instance> grant <sub> '**' makes someone an operator: it adds the sub to role:operator. Another operator is another membership edge, not a new wildcard grant.
Do not put a predicate on a folder: principal
The agent socket carries no login claims, so such a row never matches. Put the predicate on a user principal.

auth/README.md has every rule

[auth/README.md] has every rule behind the figure. reference / grants has the rule syntax. auth explains where principals come from.