05 Authorization action catalogue¶
Status: draft M0, integrated after the feedback of all chapters. Normative. This list is the
complete set of actions that authorize(principal, action, resource) -> Decision in the
module authz knows (B7).
Terms: 00-glossary.md. Tables: 03-data-model.md.
Sync and events: 04-sync-protocol.md. How authorize decides:
07-permissions.md.
1. Binding rules¶
- One decision point. Every route, every WebSocket op, every event delivery, every push,
every search, every file retrieval and every agent invocation calls
authorizewith an action from this catalogue. There is no permission logic outsideauthz. - No permission to see gives 404. If a principal may not read a resource, the answer is
404 not_found, whether it exists or not.403 forbiddenexists only when the principal may read the resource but lacks the required higher level or a capability. Lists, counters, search and suggestions filter in the query (P2) and reveal nothing. The exception is the list of cost estimates for the reconciliation (cost.reconcile.read): it evaluates the instance without a filter by visible channels and names neither channel nor thread nor content (R-AZ-28). - REST. Every operation in the OpenAPI document carries
x-authz-actionwith exactly one ID from this catalogue andx-rate-limit-classwith the class of this action. The API team enforces this with lint rules (authz-action-required: field missing;authz-action-known: ID not in the catalogue). In addition, the server checks at startup that every registered route has a known action, and a test counts all routes. The single source of this catalogue for docs, lint and code is actions.yaml (ADR 0049); the tables of sections 3, 4, 6 and 7 and the lists indocs/api/redocly.yamlare generated from it (make catalog), andmake catalog-checkkeeps them current in the gate. - WebSocket and events. Every op in the AsyncAPI document carries
x-authz-action; every durable and ephemeral event type carriesx-receive-action. Sections 6 and 7 are the normative tables. They are generated from the same file actions.yaml (rule 3). A test fails when a type or an op is registered without an entry. - The catalogue is closed. An unknown action is a program error:
authorizedenies, the request ends with500, an alert is raised. - Agent invocation and I1. Every triggering of an agent (message, follow-up, command,
interaction, session change, autocomplete) additionally checks
agent.invoke(section 5). The invariants I1 to I3 of the baseline depend on these actions:- I1 is checked transactionally on
grant.create,grant.revoke,grant.expire,group.member.add,group.member.remove,group.member.expire,group.member.sync,group.delete,owner.add,owner.remove,space.operator.set,channel.operator.set,agent.billing_mode.set,agent.billing_mode.set_all,agent.credential_holder.set,agent.enable,user.disableanduser.enable(check in 03-data-model.md, section 6.4). - Guided flow:
grant.create,group.member.add,owner.add,space.operator.setandchannel.operator.setoptionally acceptresolve_i1 = {switch_to_organization: [agent_ids], reason}. The server then additionally checksagent.billing_mode.setfor each named agent withauthorize, first switches them toorganizationin the same transaction, performs the actual change, checks I1 and writes one audit entry per action with the samecorrelation_id(10-backend-policy.md, section 11). Milestone: the field enters the request schemas with M2-21 (PLAN.md). Before that, the server rejects it like any unknown field with400 validation_failed(06-api.md, "Unknown"). - I2 is part of
agent.invokeand is checked again on every delivery of an invocation. - I3 is checked by the adapter; at run start (
run.start) the server checks the token again.
- I1 is checked transactionally on
- Permission version. Actions with column "Permissions" =
yesincrementpermission_versionand writeaccess.changedfor everyone affected (04-sync-protocol.md, section 8). - Read-only channels. If
channels.read_onlyis set,authorizedenies in this channel every action whose column "Permission" requires the levelwriteortrigger(messages, threads, reactions, interactions, commands, uploads, typing, agent invocations), for all principals including owners, with403and reasonchannel_read_only. Still allowed are reading and personal actions, actions with permissionmanageorowner(for example moderation,channel.update,channel.read_only.set) andimport.apply. Settingread_onlywritesrun.cancel_requestedwith reasonchannel_read_onlyfor active runs in the channel (R-AZ-12). - Maintenance mode. If
server.maintenance_modeis set toread_only(16-operations.md), a separate layer beforeauthorizeanswers all actions with effectwrite(section 2.7) with503 maintenanceandRetry-After, except for the exceptions from ADR 0065; reading actions and catch-up continue. The layer is not permission logic; it knows only the effect from the catalogue. - System actions. Actions with permission
systemare performed only by the server itself (a job or a server command on the host). They cannot be reached via API and gateway. They are in the catalogue so that audit entries name a known action and the registration test counts them. - Terminal and I6. The actions from section 4.14 additionally check the guard rules SR10,
SR11 and SR12 from 07-permissions.md. I6 is checked transactionally on
grant.createwith targethostand onhost.manage(03-data-model.md, section 6.8; 24-terminal.md, section 7.3) and again on everyterminal.openandterminal.attach. The terminal connections are separate WebSocket channels outside the gateway; their ops are in section 7.
2. Terms of the tables¶
2.1 Column "Permission"¶
| Value | Meaning |
|---|---|
public |
without login; still through authorize (principal anonymous) |
auth |
every active, logged-in principal |
human |
every active person |
self |
the principal owns the resource (own device, own setting) |
read, write, trigger |
effective minimum level on the target of the resource |
+upload, +approve_actions, +export, +manage |
additionally required capability on the target |
manage |
capability manage on the target (any level) |
owner |
the principal is an active owner and acts in a session (rule 2.3 item 1) |
agent-self |
the principal is the agent that owns the resource (own run, own session) |
app-self |
the principal is the app that owns the resource (own command, own component) |
participant |
the person is a participant of the DM channel |
+terminal |
capability terminal on the host (implicit for owners, unless I6 blocks; 07-permissions.md, section 2.12) |
assignee |
the person is the host assignee (hosts.assigned_user_id) |
host-self |
the principal is the host itself, logged in with its authentication key (24-terminal.md, section 4.1) |
system |
only the server itself (rule 1.10) |
Several values joined with "or" are alternatives. "and" means both.
2.2 Target of a resource¶
The target determines on which path grants apply:
| Resource | Path |
|---|---|
message, reaction, pin, run, run_step, component, interaction, agent_session |
thread -> channel -> space -> instance |
thread |
channel -> space -> instance |
file with purpose = 'attachment' |
channel of the message it is attached to; before attaching, scope_channel_id, and then only the uploader reads it |
file with purpose = 'avatar' |
every active, logged-in principal (auth) |
file with purpose = 'branding' |
public like instance.discovery.read (logo of the login page) |
file with purpose = 'gdpr_export' |
only via gdpr.export.download |
channel |
channel -> space -> instance |
space |
space -> instance |
instance |
instance |
| DM channel and everything in it | only dm_participants; grants do not apply |
host, terminal_session |
host (for the session: its host); a target of its own outside instance, space and channel, only grants on this host and the owner rule (07-permissions.md, section 2.12) |
An attached file always follows its message, also after the thread is moved;
files.scope_channel_id applies only before attaching.
2.3 Effective level¶
- Owner: level
triggerand all capabilities on all targets except DM channels, and only in a session of a person. A personal API token of an owner acts with the grants of this person, without owner permissions (SR5 in 07-permissions.md). Exception: the check without a request (section 5) takes the owner status from the current owner list without a session (R-AZ-29). - Otherwise: the highest level of all valid grants (not revoked, not expired) of the principal and its valid groups on the path. Capabilities: union of the capabilities of the same grants.
- DM participants: level
writeand capabilityuploadin the DM channel. - Operator rule: an agent has at least
writeon every channel whose effective operator it is, and on every thread withthreads.operator_agent_idequal to it, without a grant row. The rule follows the operator; on a change nothing remains (R-AZ-13). - Agents and apps: at most
writeand as a capability at mostupload(R-DM-08, R-DM-20). Apps additionally need the scope from 2.6. - If an agent acts within a run, the acting-for binding applies in addition: at most the permissions of the triggering person (SR9 in 07-permissions.md), computed as in the check without a request (section 5).
2.4 Columns "Owner", "Step-up", "Audit", "Permissions"¶
- Owner
yes: owners only, regardless of grants. - Step-up
yes: the session needs a fresh confirmation by the person (auth_sessions.step_up_atyounger thanauth.step_up_max_age): passkey, step-up device key with biometrics or device PIN, password with TOTP or a new OIDC login (08-identity.md, section 6). Otherwise401 step_up_required. Only people can perform step-up actions; agent and app tokens and personal tokens are rejected (R-AZ-03). - Step-up
conditional: likeyesas soon as the person already has credentials of the kind that the action sets up (the description names the condition); the first setup needs only the session of a person. Reason: the first setup must work without existing credentials, and a stolen session thereby gains only a login path whose new devices staypending(08-identity.md, section 5). - Step-up
fresh: likeyes, but the confirmation is younger thanterminal.step_up_max_age, comes from a passkey or a step-up device key (in the web console only a passkey withuserVerification = required) and is not yet consumed; the action consumes it (auth_sessions.step_up_consumed_at, 08-identity.md, section 6.3). Otherwise401 step_up_required. - Audit
yes: allow and deny decisions go into the audit log.denials: denials only. Every denial of every action in a request or op is logged; filters (receive check at delivery and catch-up, check per element of a list) write no denial (07-permissions.md, section 4.2). Identical denials (same principal or, without login, the same IP pseudonym, same action, same reason) are merged peraudit.deny_aggregate_windowinto one entry with a counter (R-AZ-04). Because the chain is append-only, every process collects the denials in memory and writes one row per key at the end of the window, at shutdown or as soon asaudit.deny_aggregate_max_keyskeys are open (then the oldest one). A hard crash loses at most the open aggregates of one window (19-security.md, section 12). - Permissions
yes: see rule 1.7.
2.5 Decision¶
authorize returns one of these values. The reason code per denial is given in
07-permissions.md, section 4.1; it is written to the audit log.
| Decision | HTTP | Meaning |
|---|---|---|
allow |
allowed | |
not_found |
404 |
no read permission on the resource, or the resource is missing |
forbidden |
403 |
read permission present, but level, capability or scope missing, or a guard rule blocks (for example channel_read_only, device_pending) |
step_up_required |
401 |
fresh confirmation missing |
invariant_violation |
409 |
the change would violate I1 or another invariant |
unauthenticated |
401 |
no valid principal |
A device in the state pending and a provisioned person in the state pending
(08-identity.md, section 3.1) have tokens, but every action except
self.read, device.list for its own device, auth.token.refresh and auth.logout ends with
403 device_pending (08-identity.md, section 5.2). The gateway rejects
identify of a pending device with 4016. Reason for the person case: with 401 the app would
have to delete its data and could not ask whether the owner has approved it.
Rate limits are checked by a separate layer before authorize with the class from the catalogue
(429); the maintenance mode by another one (rule 1.9).
2.6 Scopes for apps¶
Besides grants, an app needs a granted scope (app_installations.granted_scopes):
| Actions | Scope |
|---|---|
message.read, thread.read, channel.read, space.read, run.read, search.query |
messages:read |
message.create, message.update, message.delete, reaction.*, thread.create |
messages:write |
file.read |
files:read |
file.upload |
files:write |
command.register, command.remove, command.autocomplete.respond |
commands:write |
component.update, interaction.respond |
components:write |
user.read |
users:read |
Event subscriptions, gateway.connect, events.catch_up |
events:subscribe |
cost.reconcile.read, cost.reconcile.submit |
costs:reconcile |
self.read, instance.read, status.read, command.read, agent.read |
no scope; only metadata that every logged-in principal with matching grants sees |
webhook_in.post |
no scope; token of the incoming webhook |
Agents have all scopes except users:read and costs:reconcile implicitly; their limit is the
grants and the operator rule.
2.7 Effect (effect)¶
Every action has an effect. write means: the action can change domain state in the database
or writes an audit entry when allowed (column "Audit" yes). read means: it does
neither. The maintenance mode uses only this attribute (rule 1.9, ADR 0065); the
machine-readable catalogue carries it per action (ADR 0049). Exactly these have read:
- the actions whose ID ends in
.reador.list, pluscost.read.threadandcost.read.aggregate, exceptaudit.read(Audityes); access.explain,search.query,events.catch_up,gateway.connect,gateway.focus,typing.start,command.autocomplete,command.autocomplete.respond,client_metrics.submit,console.csp_report,push.deliver,webhook_out.deliverandterminal.read_own.
All others have write. Reason for the closed list: a new action without an entry is
write and is blocked in maintenance mode (fail closed); the column "Permission" is not suitable
for this, because, for example, settings.read and settings.update both require self.
3. Rate-limit classes¶
Every class has a key (what is counted) and two values
ratelimit.<class>.per_minute and ratelimit.<class>.burst (token bucket). Default values in
section 10.
| Class | Key | Purpose |
|---|---|---|
rl.public_auth |
client IP (IPv6: the /64 prefix) | login, refresh, public endpoints; additionally counts every failed check of presented credentials (02-architecture.md, section 9.1) |
rl.gateway_connect |
device or token, checked at identify and resume once the credentials are checked |
establish a WebSocket session |
rl.read |
principal | read queries |
rl.write |
principal | general writes |
rl.message |
principal | send and edit messages |
rl.trigger |
principal | trigger agents, commands, suggestions |
rl.interaction |
principal | use components |
rl.upload |
principal | upload files |
rl.search |
principal | search |
rl.typing |
connection | typing |
rl.admin |
principal | administration |
rl.export |
principal | export, GDPR packages |
rl.agent_runtime |
agent | heartbeat, run patches, acks |
rl.webhook_in |
incoming webhook | incoming webhooks |
rl.import |
principal | batches of a running import (import.apply) |
rl.terminal |
principal | open a terminal, attach, connect a stream |
rl.host |
host (after the check of the authentication key) | control and data connections of the host service |
System actions and the internal delivery checks have no class (none, the same value as
x-rate-limit-class in openapi.yaml).
Reasons for the keys: IPv6 clients usually get a whole /64 prefix; counted per address, a client
could fill any number of buckets by changing addresses. rl.gateway_connect counts after the
check of the credentials, so that nobody can drain the bucket of another person's device; before
identify, sync.identify_timeout limits an open connection (04-sync-protocol.md,
section 6.3).
4. Actions¶
4.1 Public and login¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
instance.discovery.read |
JWKS, protocol versions, minimal branding, branding logo | instance | public | no | no | no | no | rl.public_auth |
console.csp_report |
accept a Content Security Policy violation report of the web console, without content in the log (19-security.md, section 5, rule 6; R-AZ-33) | instance | public | no | no | no | no | rl.public_auth |
auth.login.begin |
start a login (OIDC, passkey challenge, header login, device pairing) | instance | public | no | no | denials | no | rl.public_auth |
auth.login.complete |
complete a login (OIDC server code, passkey, enrollment code, password with TOTP, pairing), register the device and device key, create a session | instance | public | no | no | yes | no | rl.public_auth |
auth.token.refresh |
rotate the refresh token (with grace auth.refresh_reuse_grace; in maintenance mode without rotation, ADR 0065); reuse revokes the family |
auth_session | public (valid refresh token and DPoP proof of the family) | no | no | denials | no | rl.public_auth |
auth.logout |
end one's own session; push registrations of this session are deactivated | auth_session | self | no | no | yes | no | rl.write |
auth.step_up |
provide a fresh confirmation (passkey, step-up device key, password with TOTP, new OIDC login) | auth_session | self | no | no | yes | no | rl.public_auth |
status.public.read |
status page without login, only if status.public_enabled |
instance | public | no | no | no | no | rl.public_auth |
4.2 Instance and administration¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
instance.read |
instance data for clients: branding, language, limits, feature flags evaluated for the requesting principal (including rollout per person and group) | instance | auth | no | no | no | no | rl.read |
instance.settings.read |
read all instance settings (console) | instance | owner | yes | no | no | no | rl.read |
instance.settings.update |
change an instance setting | instance | owner | yes | yes | yes | no | rl.admin |
instance.branding.update |
change the branding (name, logo, color from the category colors) | instance | owner | yes | yes | yes | no | rl.admin |
instance.bootstrap |
first owner via a server command on the host (08-identity.md, section 4) | instance | system | no | no | yes | yes | none |
feature_flag.read |
read all flags with their rules | instance | owner | yes | no | no | no | rl.read |
feature_flag.update |
change a flag | instance | owner | yes | yes | yes | no | rl.admin |
signing_key.rotate |
change server keys: mode = scheduled (announce the next key) or mode = emergency (all keys of the purpose retired at once) |
instance | owner | yes | yes | yes | no | rl.admin |
signing_key.transition |
scheduled state change of a signing key by the job instance.key_rotation (create next, make it active, set retired) |
instance | system | no | no | yes | no | none |
identity_provider.read |
read login drivers | instance | owner | yes | no | no | no | rl.read |
identity_provider.manage |
create, change, switch off login drivers | instance | owner | yes | yes | yes | no | rl.admin |
owner.add |
make a person an owner; optionally with resolve_i1 (rule 1.6) |
user | owner | yes | yes | yes | yes | rl.admin |
owner.remove |
withdraw the owner role; at least one active owner remains (SR8) | user | owner | yes | yes | yes | yes | rl.admin |
owner.recover |
enrollment code for recovery for an active owner via a server command on the host (bothy setup issue-enrollment-code, 08-identity.md, section 4) |
user | system | no | no | yes | no | none |
audit.read |
read and filter the audit log | instance | owner | yes | yes | yes | no | rl.admin |
audit.verify |
check the hash chain and checkpoints | instance | owner | yes | yes | yes | no | rl.admin |
stats.read |
read statistics, also the live frame, timeline, SLO traffic light and backend view and the subscription stats.subscribe (25-visualization.md); instance only owners, space with manage; always filtered to the visible channels, cost fields only with cost.read.aggregate |
instance, space | owner or manage | no | no | no | no | rl.read |
access.explain |
"Who has access?" with origin (direct, group, inherited, operator, indirectly via an agent) | space, channel | owner or manage | no | no | no | no | rl.read |
access.preview |
dry run of an action with column "Permissions" = yes: differences per principal, I1, SR8, hints in plain text; the transaction is rolled back; limits of delegation give allowed = false instead of a denial (07-permissions.md, section 9.3) |
as the target action | permission of the target action | as target action | no | no | no | rl.admin |
access.hints.read |
read the lists "Permission hints" and "Expiring soon" (grants, memberships, agent and app tokens) (07-permissions.md, sections 9.4 and 9.5; R-AZ-34); a space only within one's own manage scope |
instance, space | owner or manage | no | no | no | no | rl.read |
access.view_as |
"View as person", read-only | user | owner | yes | yes | yes | no | rl.admin |
access.view_as.end |
end "View as person" stored on one's own console session before its end time (07-permissions.md, section 9.2; R-AZ-32) | auth_session | self | no | no | yes | no | rl.admin |
4.3 People, identity, devices, tokens¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
self.read |
read one's own principal (GET /me), for people, agents and apps; also for a pending device |
principal | auth and self | no | no | no | no | rl.read |
user.read |
read the profile and directory of people | user | human; apps with scope | no | no | no | no | rl.read |
user.create |
create people (no self-registration), for a local account with an enrollment code | instance | owner | yes | yes | yes | no | rl.admin |
user.update |
change the profile of another person | user | owner | yes | yes | yes | no | rl.admin |
user.disable |
disable a person: revoke sessions and devices, cancel runs; never the last active owner (SR8) | user | owner | yes | yes | yes | yes | rl.admin |
user.enable |
lift the disabling or activate a provisioned account (pending -> active) |
user | owner | yes | yes | yes | yes | rl.admin |
self.profile.update |
change one's own profile (display name, avatar, language, time zone) | user | self | no | no | no | no | rl.write |
identity.read |
read the identities, passkeys and local credentials of a person, without secrets | user | self or owner | no | no | no | no | rl.read |
identity.link |
link a further identity | user | self | no | yes | yes | no | rl.admin |
identity.unlink |
unlink an identity; rejected if no login path remains | user | self or owner | no | yes | yes | no | rl.admin |
local_credential.set |
set a local password and set up TOTP; step-up as soon as a password path with TOTP is set up | user | self | no | conditional | yes | no | rl.admin |
passkey.register |
add a passkey; step-up as soon as an active passkey exists | user | self | no | conditional | yes | no | rl.admin |
passkey.revoke |
revoke a passkey | passkey | self or owner | no | yes | yes | no | rl.admin |
device.list |
read devices; a pending device reads itself | user, device | self or owner | no | no | no | no | rl.read |
device.approve |
approve a pending device; one's own device from an active own device, with a confirmation code or pairing code (08-identity.md, section 5.3) | device | owner or self | no | yes | yes | yes | rl.admin |
device.revoke |
revoke a device; connections close with 4012 |
device | self or owner | no | yes | yes | yes | rl.admin |
device.expire |
revoke a device without confirmation after auth.device_pending_ttl, an expired confirmed pairing or a device without contact after auth.device_inactive_revoke_after (job identity.auth_cleanup) |
device | system | no | no | yes | yes | none |
session.list |
read login sessions | user | self or owner | no | no | no | no | rl.read |
session.revoke |
end a session; its child sessions end as well | auth_session | self or owner | no | no | yes | yes | rl.admin |
session.extension.create |
create a child session for the share extension of one's own app with its own refresh family (the Notification Service Extension uses the preview token, not a session); replaces an active child session of the same component | auth_session | self | no | no | yes | no | rl.write |
token.list |
read API tokens (prefix only) | principal | owner; self for own tokens | no | no | no | no | rl.read |
token.create |
create an API token for an agent or app; a personal token only for oneself and only with the flag tokens.personal_enabled |
principal | owner; self for own token | no | yes | yes | no | rl.admin |
token.revoke |
revoke an API token | api_token | owner; self for own token | no | no | yes | yes | rl.admin |
4.4 Groups and grants¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
group.read |
read groups and members; one's own groups also without being owner | group | owner or self | no | no | no | no | rl.read |
group.create |
create a group | instance | owner | yes | yes | yes | no | rl.admin |
group.update |
rename a group | group | owner | yes | yes | yes | no | rl.admin |
group.delete |
delete a group (soft); its grants are revoked | group | owner | yes | yes | yes | yes | rl.admin |
group.member.add |
add a member, optionally with expiry and resolve_i1; not in groups with source = oidc_claim |
group | owner | yes | yes | yes | yes | rl.admin |
group.member.remove |
remove a member; not in groups with source = oidc_claim |
group | owner | yes | yes | yes | yes | rl.admin |
group.member.expire |
report an expired membership (job authz.expiry_sweep): permission version, access.changed, cancellation of affected runs |
group | system | no | no | yes | yes | none |
group.member.sync |
when an OIDC login completes, align a person's memberships in groups with source = oidc_claim of their provider with the group claim; one audit entry per login with the groups (08-identity.md, section 3.4) |
user | system | no | no | yes | yes | none |
grant.read |
read the grants of a target or subject; with manage only grants on targets within one's own manage scope (07-permissions.md, section 2.6); grants on hosts only owners |
space, channel, host, principal | owner or manage | no | no | no | no | rl.read |
grant.create |
create a grant. With manage only up to one's own level, only one's own capabilities, only to people and groups, never on the instance (R-AZ-05). Grants to agents and apps only owners; agents and apps never trigger, as a capability only upload. On a host (host) only owners, only to people and groups, without a level, exactly terminal; I6 (rule 1.11). Optionally resolve_i1 |
instance, space, channel, host | owner or manage (on host only owner) |
no | yes | yes | yes | rl.admin |
grant.revoke |
revoke a grant; with manage only grants that one could have created oneself under R-AZ-05 |
grant | owner or manage | no | yes | yes | yes | rl.admin |
grant.expire |
report an expired grant (job authz.expiry_sweep): permission version, access.changed, cancellation of affected runs |
grant | system | no | no | yes | yes | none |
Expiry takes effect in every authorize from the second of expiry; the two system actions only
make sure that clients clear their cache and runs end (R-AZ-15).
4.5 Spaces, channels, sidebar¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
space.read |
see a space; applies with read on the space or on at least one of its channels |
space | read | no | no | no | no | rl.read |
space.create |
create a space | instance | owner | yes | no | yes | no | rl.admin |
space.update |
change name, icon, color | space | manage | no | no | yes | no | rl.admin |
space.reorder |
global order of the spaces | instance | owner | yes | no | yes | no | rl.admin |
space.archive |
archive a space | space | owner | yes | yes | yes | yes | rl.admin |
space.operator.set |
set the default operator (I1); optionally resolve_i1 |
space | owner | yes | yes | yes | yes | rl.admin |
sidebar.update |
change one's own sidebar state (collapse, own order) | space | self and read | no | no | no | no | rl.write |
channel.read |
see a channel, read its header data | channel | read | no | no | no | no | rl.read |
channel.create |
create a channel | space | manage | no | no | yes | no | rl.admin |
channel.update |
change name, topic, the channel default for notifications | channel | manage | no | no | yes | no | rl.admin |
channel.reorder |
order of the channels in the space | space | manage | no | no | yes | no | rl.admin |
channel.behavior.read |
read prompt, skills, mention rules, backfill | channel | manage | no | no | no | no | rl.read |
channel.behavior.update |
change prompt, skills, mention rules, auto_thread, backfill |
channel | manage | no | yes | yes | no | rl.admin |
channel.operator.set |
set the operator or switch on inheritance (I1); optionally resolve_i1 |
channel | owner | yes | yes | yes | yes | rl.admin |
channel.project.bind |
set or change the project binding of a forum | channel | owner | yes | yes | yes | no | rl.admin |
channel.read_only.set |
set or lift read-only (rule 1.8), with a reason; when it is set, active runs in the channel are cancelled | channel | manage | no | yes | yes | no | rl.admin |
channel.archive |
archive a channel | channel | manage | no | yes | yes | yes | rl.admin |
forum_tag.manage |
create, change, delete the tags of a forum | channel | manage | no | no | yes | no | rl.admin |
dm.create |
create a direct message with other people; only with the flag dm.enabled |
instance | human | no | no | no | no | rl.write |
dm.leave |
leave a DM | channel | participant | no | no | no | no | rl.write |
4.6 Threads and agent sessions¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
thread.read |
read a thread and its history | thread | read | no | no | no | no | rl.read |
thread.create |
create a thread from a message or forum post; tags at creation are treated as with thread.tags.set (tags with restricted need manage) |
channel | write | no | no | no | no | rl.message |
thread.title.update |
change the title; sets title_source = manual. The operator agent may set it only with auto; the source then stays auto |
thread | write and (creator or manage or operator agent) | no | no | no | no | rl.write |
thread.close |
set a thread to "Done" | thread | write | no | no | no | no | rl.write |
thread.reopen |
reopen a thread (also implicitly through a new message) | thread | write | no | no | no | no | rl.write |
thread.move |
move to another channel; target with the same operator | thread | manage on source and target | no | no | yes | no | rl.admin |
thread.tags.set |
set tags; tags with restricted need manage |
thread | write | no | no | no | no | rl.write |
agent_session.read |
current and earlier session references, model, costs | thread | read | no | no | no | no | rl.read |
agent_session.reset |
start a new session in the thread (for example /new) |
thread | trigger and agent.invoke |
no | no | no | no | rl.trigger |
agent_session.bind |
bind an existing external session to the thread (resume) | thread | trigger and agent.invoke |
no | no | yes | no | rl.trigger |
agent_session.report |
the adapter reports the external session reference, model, started or bind_failed; the first report creates the row if run.start carried no reference (compare-and-swap) |
agent_session, run | agent-self | no | no | no | no | rl.agent_runtime |
4.7 Messages, reactions, pins, templates¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
message.read |
read a message | message | read | no | no | no | no | rl.read |
message.create |
send a message; with attachments additionally +upload. If it triggers an agent, agent.invoke applies in addition. |
thread | write | no | no | no | no | rl.message |
message.update |
edit one's own message, with If-Match |
message | write and author | no | no | no | no | rl.message |
message.delete |
delete one's own message; others' only with manage (moderation) |
message | (write and author) or manage | no | no | yes for others' | no | rl.message |
message.revisions.read |
read earlier versions | message | read | no | no | no | no | rl.read |
reaction.add |
add a reaction | message | write | no | no | no | no | rl.write |
reaction.remove |
remove one's own reaction | reaction | write and self | no | no | no | no | rl.write |
pin.create |
pin a message | message | manage | no | no | no | no | rl.write |
pin.delete |
unpin | message | manage | no | no | no | no | rl.write |
mention.list |
read one's own mentions (filtered by message.read) |
user | self | no | no | no | no | rl.read |
template.read |
read templates (one's own and those of the channel) | channel, user | read or self | no | no | no | no | rl.read |
template.manage |
maintain one's own templates (resource user); channel templates with manage (resource channel) |
user, channel | self or manage | no | no | no | no | rl.write |
4.8 Files and search¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
file.upload |
upload a file for a channel (upload session, parts, completion); avatar and logo go through self.profile.update, agent.update or instance.branding.update respectively |
channel | write and +upload | no | no | yes on malware detection | no | rl.upload |
file.read |
file, thumbnail, transcript or file ticket; only after the scan; path per purpose as in 2.2 |
file | read | no | no | no | no | rl.read |
file.delete |
delete one's own unattached file; attached ones via message.delete |
file | self or manage | no | no | no | no | rl.write |
file.transcribe |
start the transcription of a voice message by the server (flag voice.enabled) |
file | write | no | no | no | no | rl.upload |
file.transcript.submit |
save the transcript of a voice message (transcript_source = 'agent'), with If-Match |
file | agent-self: operator of the thread whose message the file is attached to, and read | no | no | no | no | rl.agent_runtime |
search.query |
full-text search; hits filtered by message.read, thread.read, file.read |
instance | auth | no | no | no | no | rl.search |
4.9 Personal data, notifications, sync¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
read_state.update |
advance the read state | thread | self and read | no | no | no | no | rl.write |
read_state.mark_unread |
reset the read state, with If-Match |
thread | self and read | no | no | no | no | rl.write |
draft.read |
read own drafts | user | self | no | no | no | no | rl.read |
draft.write |
save or delete a draft, also in read-only and archived channels (sending checks message.create) |
thread, channel | self and read | no | no | no | no | rl.write |
settings.read |
read own personal data: settings, read states, sidebar, notification levels, quiet hours | user | self | no | no | no | no | rl.read |
settings.update |
change an own setting | user | self | no | no | no | no | rl.write |
notification_pref.update |
notification level for a space, channel or thread | space, channel, thread | self and read | no | no | no | no | rl.write |
quiet_hours.update |
maintain quiet hours | user | self | no | no | no | no | rl.write |
push.register |
register the push target of one's own active device; returns the preview token once | device | self | no | no | no | no | rl.write |
push.unregister |
remove a push target | push_registration | self | no | no | no | no | rl.write |
push.deliver |
internal check before every push, every notification via the gateway and every retrieval via the preview endpoint (P3); hints without a thread (security, admin) use the recipient rule of R-AZ-30 instead |
thread | read | no | no | no | no | none |
client_metrics.submit |
send performance data of the app without contents and without IDs, only with privacy.client_metrics (15-client-architecture.md) |
instance | human | no | no | no | no | rl.write |
sync.snapshot.read |
snapshot for a resync | instance | auth | no | no | no | no | rl.read |
events.catch_up |
GET /api/v1/events; every event additionally with its receive action |
instance | auth | no | no | no | no | rl.read |
gateway.connect |
WebSocket identify or resume |
instance | auth (person with an active device, agent, app with events:subscribe) |
no | no | denials | no | rl.gateway_connect |
gateway.focus |
focus threads; unreadable ones are silently ignored | thread | read | no | no | no | no | rl.read |
typing.start |
show typing; only people, agents show typing through their run (04, section 12) | thread | human and write | no | no | no | no | rl.typing |
4.10 Agents, commands, runs, interactions¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
agent.read |
read the agent card (name, mode badge, tool class, presence); the agent reads itself, including the configured mode of its backend policy | agent | owner or agent-self or read on a channel that the agent operates | no | no | no | no | rl.read |
agent.create |
create an agent | instance | owner | yes | yes | yes | yes | rl.admin |
agent.update |
change name, description, avatar, operator_config |
agent | owner | yes | yes | yes | no | rl.admin |
agent.disable |
disable an agent; tokens and connections end, runs are cancelled | agent | owner | yes | yes | yes | yes | rl.admin |
agent.enable |
lift the disabling of an agent (I1) | agent | owner | yes | yes | yes | yes | rl.admin |
agent.billing_mode.set |
switch the backend policy of an agent, with a reason (I4, I1) | agent | owner | yes | yes | yes | yes | rl.admin |
agent.billing_mode.set_all |
switch the backend policy of several agents in one transaction, with a reason (I4, I1); to personal only with an explicit list |
instance | owner | yes | yes | yes | yes | rl.admin |
agent.credential_holder.set |
set credential_holder (I1) |
agent | owner | yes | yes | yes | yes | rl.admin |
agent.tool_class.set |
set the tool class (chat, scoped, host), with a reason (07-permissions.md, section 2.11) |
agent | owner | yes | yes | yes | yes | rl.admin |
agent.invoke |
trigger agents; check in section 5 | thread (with agent) | trigger and rules from 5 | no | no | denials | no | rl.trigger |
agent.heartbeat |
report state, reported mode, supported modes, tool class and active runs | agent | agent-self | no | no | yes on a change of the reported mode or the tool class | no | rl.agent_runtime |
agent.events.consume |
receive and acknowledge events as a consumer (ack) |
agent | agent-self | no | no | no | no | rl.agent_runtime |
agent.config.read |
read prompt, skills, project binding of the channels that the agent operates | channel | agent-self and effective operator | no | no | no | no | rl.read |
agent.triggerers.read |
set of the people who may trigger this agent, with permission_version (allow list in the adapter) |
agent | agent-self | no | no | no | no | rl.agent_runtime |
command.read |
see the command list and options (built-in commands, those of the operator, of allowed guests and of apps with write) |
channel | read | no | no | no | no | rl.read |
command.register |
register or change a command; it may apply only to targets that its owner operates or on which the owner has write |
command | agent-self or app-self with scope | no | no | yes | no | rl.admin |
command.remove |
remove a command | command | agent-self or app-self with scope | no | no | yes | no | rl.admin |
command.autocomplete |
fetch suggestions for an option from the owner of the command | channel | trigger and agent.invoke without a run (for app commands required_level) |
no | no | no | no | rl.trigger |
command.autocomplete.respond |
deliver suggestions for a request (gateway op or REST) | command | agent-self or app-self, owner of the command | no | no | no | no | rl.agent_runtime |
command.invoke |
run a command. Agent commands check required_level and agent.invoke; model commands during an active run: denial run_active. App commands check required_level and scope, without agent.invoke and I2. The control command stop additionally checks run.cancel |
channel, thread | required_level of the command and rules as described |
no | no | denials | no | rl.trigger |
run.read |
read a run, its steps and metrics | run | read | no | no | no | no | rl.read |
run.overview.read |
read "Running now", filtered by run.read |
instance | auth | no | no | no | no | rl.read |
run.context.read |
read the context of one's own active run (schema run_context.v1 with a trust mark per message, 09-agents.md, section 4.6) |
run | agent-self | no | no | no | no | rl.agent_runtime |
run.start |
start a run with a valid token: queued -> running and waiting_input -> running (CAS); the same token repeated returns the result again as long as the run is running; a consumed token does not resume a waiting run |
run | agent-self and token valid | no | no | denials | no | rl.agent_runtime |
run.token.renew |
fresh invocation token for one's own run in queued or waiting_input with the current unconsumed token, even an expired one (runs.started_jti NULL or not equal to jti, in SQL IS DISTINCT FROM), or the token before the last renewal (runs.renewed_from_jti, lost response); repeats the checks before a delivery (agent.invoke, I2, SR2, SR3) with current permissions (09-agents.md, section 4.5.1) |
run | agent-self and token current | no | no | denials | no | rl.agent_runtime |
run.update |
live box patch of one's own run | run | agent-self | no | no | no | no | rl.agent_runtime |
run.finish |
finish a run or let it wait: completed, failed, cancelled, waiting_input |
run | agent-self | no | no | no | no | rl.agent_runtime |
run.cancel |
cancel a run | run | trigger and (triggering person of the run or manage) | no | no | yes | no | rl.trigger |
run.followup.create |
follow-up to an active run of the same agent (via message.create) |
thread | write and agent.invoke |
no | no | denials | no | rl.message |
component.update |
deactivate or change own components | component | author of the message (agent-self or app-self) | no | no | no | no | rl.write |
interaction.create |
use a component (single-use rule per message); respect required_capability and restricted_to (any, run_starter, owner); for components of an agent additionally agent.invoke |
component | trigger | no | no | yes for approve_actions, otherwise denials |
no | rl.interaction |
interaction.respond |
the owner acknowledges or answers an interaction | interaction | agent-self or app-self | no | no | no | no | rl.agent_runtime |
4.11 Apps and webhooks¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
app.read |
read apps, manifests, installations | app | owner | yes | no | no | no | rl.read |
app.install |
install an app, grant scopes | instance | owner | yes | yes | yes | yes | rl.admin |
app.scopes.update |
change granted scopes | app | owner | yes | yes | yes | yes | rl.admin |
app.suspend |
switch an app off without losing its granted scopes: tokens blocked, deliveries paused, commands hidden, components disabled |
app | owner | yes | yes | yes | yes | rl.admin |
app.resume |
switch a suspended app back on | app | owner | yes | yes | yes | yes | rl.admin |
app.uninstall |
uninstall an app; tokens and subscriptions end | app | owner | yes | yes | yes | yes | rl.admin |
webhook_out.read |
read outgoing subscriptions and deliveries | app, agent | owner | yes | no | no | no | rl.read |
webhook_out.manage |
create, change, delete an outgoing subscription of an app or an agent, rotate the HMAC secret | app, agent | owner | yes | yes | yes | no | rl.admin |
webhook_out.redeliver |
repeat a delivery | webhook_delivery | owner | yes | no | yes | no | rl.admin |
webhook_out.deliver |
internal check before every delivery: receive action of the event for the app or the agent | app, agent | grants and scope of the receiver | no | no | no | no | none |
webhook_out.transition |
automatic state change of an outgoing subscription by the consumer webhooks or the job apps.webhook_deliver: disabled after 410, after webhook.max_consecutive_failures failures in a row, after a delivery that fails until webhook.retry_max_age, or on a gap in the event log; paused by the delivery quota webhook.max_pending_per_subscription (11-extensions.md, sections 6 and 7; webhooks.md, sections 4 and 5) |
app, agent | system | no | no | yes | no | none |
webhook_in.read |
read the incoming webhooks of a channel (without token) | channel | manage | no | no | no | no | rl.read |
webhook_in.create |
create an incoming webhook for a channel | channel | manage | no | yes | yes | no | rl.admin |
webhook_in.revoke |
revoke an incoming webhook | incoming_webhook | manage | no | yes | yes | no | rl.admin |
webhook_in.post |
message via an incoming webhook; writes as the app with origin = app |
channel | public with token; the app needs write | no | no | denials | no | rl.webhook_in |
4.12 Import, costs, privacy, status¶
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
import.read |
read import sources, progress, errors | import_source | owner | yes | no | no | no | rl.read |
import.manage |
create a source, start, pause, cancel, complete, ownership list per channel (the dry run only reads and needs import.read and read permissions) |
import_source | owner | yes | yes | yes | no | rl.admin |
import.apply |
write batches with origin keys of a running source (items of a thread, files, cursor); only in channels of this source, also read-only ones (22-import-migration.md, section 8) | import_source | owner | yes | no | denials | no | rl.import |
cost.read.thread |
costs of a run and a thread (header line, live box) | thread | read | no | no | no | no | rl.read |
cost.read.aggregate |
costs per space, agent, instance | instance, space, agent | owner; space with manage | no | no | no | no | rl.read |
price.read |
read the price table | instance | owner | yes | no | no | no | rl.read |
price.manage |
maintain prices with source and retrieval time | instance | owner | yes | yes | yes | no | rl.admin |
cost.reconcile |
reconcile estimates with the invoice by hand | instance | owner | yes | yes | yes | no | rl.admin |
cost.reconcile.read |
read the estimates per run of a period for the reconciliation: agent, provider, currency, amount of the estimate before the reconciliation and whether reconciled; only entries with billing_mode = 'organization', without channel, thread and content and without a filter by visible channels (10-backend-policy.md, section 12.4; R-AZ-28) |
instance | owner or app with scope costs:reconcile |
no | no | no | no | rl.read |
cost.reconcile.submit |
the reconciliation app submits reconciled entries for existing runs; deletes nothing (10-backend-policy.md, section 12.4; ADR 0055) |
instance | app with scope costs:reconcile |
no | no | yes | no | rl.admin |
export.channel |
export the channel history | channel | read and +export | no | yes | yes | no | rl.export |
gdpr.request.create |
export or deletion for oneself; for others only the owner | user | self or owner | no | yes | yes | no | rl.export |
gdpr.request.read |
read a request and its state | gdpr_request | self or owner | no | no | no | no | rl.read |
gdpr.request.decide |
approve or reject a request; the export of one's own data is approved by the system when privacy.export_requires_approval = false |
gdpr_request | owner | yes | yes | yes | no | rl.admin |
gdpr.export.download |
download the export package | gdpr_request | self (data subject) or owner | no | yes | yes | no | rl.export |
status.read |
read the status page | instance | auth | no | no | no | no | rl.read |
status.manage |
maintain components and incidents | instance | owner | yes | no | yes | no | rl.admin |
4.13 Dev environments (milestone)¶
These belong to the milestone "Multi-user dev environment"; their routes are created with it (21-dev-isolation.md).
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
dev_environment.read |
read an environment, its state and check results | dev_environment | owner or self | no | no | no | no | rl.read |
dev_environment.manage |
create, approve, suspend, retire an environment | dev_environment | owner | yes | yes | yes | yes | rl.admin |
dev_environment.report |
the runner reports check results and readiness | dev_environment | agent-self (runner of the environment) | no | no | yes | no | rl.agent_runtime |
4.14 Hosts and terminal¶
Normative together with 24-terminal.md. Milestone M2.
| ID | Description | Resource | Permission | Owner | Step-up | Audit | Permissions | RL class |
|---|---|---|---|---|---|---|---|---|
host.read |
read hosts: name, state, platform, profiles, fingerprint, LLM declaration, check results, valid assignments and reported names of the session users; owners all, others the hosts whose assignee they are or on which they have a terminal grant |
host | owner or assignee or +terminal | no | no | no | no | rl.read |
host.manage |
create hosts, change them (name, assignee), issue a pairing code (also re-pairing), confirm a fingerprint, declare the LLM login, assign session users and end assignments (T12; a name already taken yields 409 conflict with reason: session_user_taken, a name that the host does not report yields 400 validation_failed with errors[].code = session_user_not_on_host), disable, re-enable, retire; I6 (rule 1.11) |
instance, host | owner | yes | yes | yes | yes | rl.admin |
host.pair |
the host service redeems a pairing code and reports its public keys, profiles and the names of its session users | host | public (valid pairing code) | no | no | yes | no | rl.public_auth |
host.connect |
control connection and data connections of the host service; reports (profiles, names of the session users, check results, start and end of sessions) | host | host-self | no | no | denials; yes on a change of server host, check result or reported session users | no | rl.host |
terminal.open |
open a terminal session; SR10, SR11 (I6), SR12 (without a valid assignment of a session user whose name the host reports, 403 terminal_denied with reason: no_session_user), limits; for the profile claude only a working directory of one's own session user; issues the terminal session token with the session user |
host | human and +terminal | no | fresh | yes | no | rl.terminal |
terminal.attach |
attach to one's own session that is not closed, also from another device; new token. Step-up fresh, except from the same device in the same login session within terminal.quick_reattach_window after the detach; this exception never applies in the web console |
terminal_session | self and +terminal | no | fresh | yes | no | rl.terminal |
terminal.stream |
open the device's stream connection to one's own session; only the device of the current token | terminal_session | self | no | no | denials | no | rl.terminal |
terminal.read_own |
read own terminal sessions; owners read all | terminal_session, host | self or owner | no | no | no | no | rl.read |
terminal.close |
end a session; one's own, owners any | terminal_session | self or owner | no | no | yes | no | rl.write |
terminal.terminate |
end a session by the server: revocation, idle, maximum duration, detach beyond terminal.detach_grace, disabling, logout, state change of the host, end of the process |
terminal_session | system | no | no | yes | no | none |
Agents, apps and personal tokens reach none of these actions except host.pair and
host.connect, which have no principal from principals (subject_kind_not_allowed, SR5).
There is no scope for apps.
5. Agent invocation agent.invoke¶
authorize(P, agent.invoke, {thread T, agent A, trigger_message M | interaction I | command C})
is allow if and only if all items hold. It runs when the request is accepted and once more
immediately before every delivery to the adapter (I2). Which message triggers an agent at all is
decided by 09-agents.md, section 3.4.
- P is an active person. Agents, apps, imports and system messages never trigger
(
messages.origin = 'human'). - P effectively has
triggeron the channel of T. DM channels have no agents. The channel is not read-only (rule 1.8). - A is active and one of the two:
- A is the operator of T:
threads.operator_agent_idforkind = 'thread', the effective operator of the channel in the main thread and for new threads; the mention rule of the channel is met (03-data-model.md,channels); or - A is mentioned in M,
channels.allow_mentioned_agentsistrue, A itself has at leastwriteon the channel, and A hasbilling_mode = 'organization'(an agent withpersonalis never a guest agent, because I1 does not count guest channels among its targets).
- A is the operator of T:
- Backend policy: with
agents.billing_mode = 'personal',P = credential_holder_idapplies (I2). Otherwise denial with reasonbilling_mode_personal_not_holder, fail closed, audit. - Tool class (SR2): if the higher of
agents.tool_classandagents.reported_tool_classequalshost, P is an active owner, or SR3 proves a ready dev environment of P's own. Otherwisetool_class_owner_only. - For an interaction: the component is active and not expired; P has
required_capability(for exampleapprove_actions);restricted_to = 'run_starter': P is the triggering person of the run or an owner;restricted_to = 'owner': P is an owner. The single-use rule sets the component tousedin the same transaction and the othersingle_usecomponents of the message todisabled. - For forum channels with a project binding:
project_bindings.ready = true(otherwiseproject_not_ready) and SR3 from 07-permissions.md (dev environment, milestone). - The thread has at most one active run. If there is one of A, triggering by a message becomes
a follow-up (
run.followup.create); if it belongs to another agent, denialrun_active_other_agent. Control commands go to the agent immediately ascommand.invoked; model commands are denied during an active run (run_active).
App commands and components of apps do not check agent.invoke and I2, because no agent
runtime is involved; what is checked is required_level, required_capability,
restricted_to, the grants and the scope of the app (11-extensions.md,
section 9).
Check without a request. In these cases the server evaluates the permissions of a person without a request of this person (R-AZ-29):
agent.invokeimmediately before every delivery ofagent.invocation,agent.followup,interaction.createdandcommand.invokedto an agent (04-sync-protocol.md, section 15.3 step 2, section 15.4 item 2 and section 15.6), atrun.token.renew(04 section 15.3 step 3) and for the new run from deferred follow-ups (04 section 15.4 item 4);command.invokeandinteraction.create, when the server checks an app command or the use of a component of an app again with the permissions of that moment before handing it to the app (11-extensions.md, section 9; R-SY-02);- the permissions of the triggering person in the acting-for binding (rule 2.3 item 6, SR9 in 07-permissions.md);
push.deliverbefore each hint decision and each push send, and the recipient rule of R-AZ-30 for hints without a thread (13-push-notifications.md, sections 4.2 and 12).
The following applies:
- The person is a principal of kind
userwithout session, device, token,view_asandacting_for. Only the server itself builds this principal, never the authentication of a request. - Blocks come only from the person's status (SR7):
disabledordeletedgivesprincipal_inactive,pendinggivesprincipal_pending; in the acting-for binding such a person has no permissions. Sessions, devices and tokens of the person do not count. - The owner status comes from the current owner list (
ownerswithrevoked_at IS NULL), without a session (exception to rule 2.3 item 1 and R-AZ-14); everything else from rule 2.3 applies unchanged. A permission state computed this way is never the own permission state of the principal of a request, not even of a personal API token of the same person; in the acting-for binding it only narrows. authorizewith this principal and an action other thanagent.invoke,command.invoke,interaction.createorpush.deliveris a programming error and ends like an unknown action (rule 1.5).- If such a check writes an audit entry, this person is the actor, without device and session.
Consequences on allow: run queued, live box anchor, trigger_state, event to the agent,
invocation token on delivery (04-sync-protocol.md, section 15). The
token is a JWS with the header alg = EdDSA, kid and typ = agent-invocation+jwt (RFC 8725,
section 3.11). Claims:
| Claim | Content |
|---|---|
iss |
instance.instance_id |
aud |
principal ID of the agent |
sub |
principal ID of the triggering person |
jti, iat, exp |
UUIDv7 of the token, issuance, expiry (iat + agent.invocation_token_ttl) |
trigger_kind |
message, followup, interaction, command or autocomplete |
run_id |
run; missing for trigger_kind = autocomplete and for command.invoked when the owner of the control command has no active run in the thread at execution time (command.invoke); otherwise payload and token of command.invoked carry this run, never the run of another agent (R-AZ-25) |
message_id or interaction_id or command_id |
triggering object |
thread_id, channel_id, space_id |
location; for trigger_kind = autocomplete, thread_id is the thread of the request, without one the main timeline of the text channel; in a forum channel without a thread there is no token (04-sync-protocol.md, section 15.7; R-AZ-26) |
level |
effective level of sub on the channel at delivery time |
billing_mode |
configured mode from agents.billing_mode at delivery time (I3) |
tool_class |
configured tool class from agents.tool_class at delivery time |
dev_environment_id |
dev environment of the run, only in the dev environment milestone |
permission_version |
permission version at delivery time |
The adapter starts no run if its reported mode is not equal to billing_mode
(billing_mode_mismatch) or its actual tool class is higher than tool_class
(tool_class_mismatch).
Consequences on denial: the message is stored if message.create was allowed, but without a
run; trigger_state = failed and trigger_state_reason with the reason, so that the person sees
it. On a denial only before delivery: run failed with a reason, trigger_state = failed.
6. Durable event types and receive action¶
A receiver gets an event only if authorize(receiver, receive_action, resource)
allows it. Personal events (audience = self) go only to the named principal.
| Event type | Resource | Receive action | Note |
|---|---|---|---|
message.created |
message | message.read |
echo with client_msg_id to the author |
message.updated |
message | message.read |
edit, trigger_state, component state |
message.deleted |
message | message.read |
tombstone |
reaction.added |
message | message.read |
|
reaction.removed |
message | message.read |
|
pin.added |
message | thread.read |
|
pin.removed |
message | thread.read |
|
thread.created |
thread | thread.read |
|
thread.updated |
thread | thread.read |
title, tags, operator; during an import once per batch instead of message.created per message |
thread.closed |
thread | thread.read |
|
thread.reopened |
thread | thread.read |
|
thread.moved |
thread | thread.read |
applies to: target channel |
thread.moved_out |
channel | channel.read |
applies to: source channel; receivers without access to the target remove the thread |
agent_session.changed |
thread | thread.read |
new session, rebinding, binding after an import |
run.state_changed |
run | run.read |
with summary |
space.created |
space | space.read |
|
space.updated |
space | space.read |
name, icon, order, operator; if the default operator changes, a channel.updated with a new version follows for each inheriting, non-archived channel, because its effective operator changes |
space.archived |
space | space.read |
|
channel.created |
channel | channel.read |
|
channel.updated |
channel | channel.read |
name, topic, order, operator, read_only, default_notification_level; changes to forum tags (only the new version, tags via REST) |
channel.settings_changed |
channel | channel.read |
only settings_version; contents via channel.behavior.read or agent.config.read |
channel.archived |
channel | channel.read |
|
command.changed |
command | command.read |
registered, changed, removed |
file.updated |
file | file.read |
scan result, thumbnail, transcript |
user.updated |
user | user.read |
|
agent.updated |
agent | agent.read |
also on creation (there is no separate type); including a change of the backend policy and of the tool class; also reaches the agent itself (change of its configured values) |
instance.updated |
instance | instance.read |
branding, switches (keys only, values via REST) |
status.updated |
instance | status.read |
|
grant.changed |
grant | grant.read |
for administrators; the subject gets access.changed |
group.changed |
group | group.read |
|
access.changed |
principal | self | section 8 in 04 |
access.expiring |
principal | self | a grant or membership expires within authz.expiry_reminder_lead; to the subject (for groups, the person) and to the granter |
session.revoked |
auth_session | self | additionally close with 4012 |
device.pending |
device | self | a new device of the human waits for approval; written by identity in the transaction that registers the device with state pending; payload only device_id and platform (R-AZ-31) |
read_state.updated |
thread | self | |
draft.updated |
draft | self | |
draft.deleted |
draft | self | |
settings.updated |
user | self | |
sidebar.updated |
space | self | |
notification_pref.updated |
user | self | |
quiet_hours.updated |
user | self | |
gdpr.request_updated |
gdpr_request | gdpr.request.read |
|
gdpr.request_due |
principal | self | an open privacy request reaches the reminder lead before its due_at; to every active owner, once per request; payload only request_id, kind and due_at (R-AZ-35) |
import.progress |
import_source | import.read |
|
agent.invocation |
run | self (agent) and agent.events.consume |
before delivery, agent.invoke for the triggering person |
agent.followup |
run | self (agent) and agent.events.consume |
before delivery, agent.invoke for the triggering person |
run.cancel_requested |
run | self (agent) and agent.events.consume |
|
interaction.created |
interaction | self (agent or app, owner of the component); agent additionally agent.events.consume, app events:subscribe |
before delivery to agents, agent.invoke |
command.invoked |
command | self (agent or app, owner of the command); agent additionally agent.events.consume, app events:subscribe |
before delivery to agents, agent.invoke |
agent.config_changed |
channel | self (agent) and agent.config.read |
prompt, skills, project binding changed |
host.updated |
host | host.read |
also on creation; state, assignee, LLM declaration, profiles, check results, assignments of the session users; key_changed on re-pairing; never keys |
terminal_session.changed |
terminal_session | self | to the person of the session: state, device, close_reason; never contents |
Ephemeral events:
| Event type | Resource | Receive action | Additional condition |
|---|---|---|---|
typing.started |
thread | thread.read |
thread in focus |
run.updated |
run | run.read |
thread in focus |
agent.presence_changed |
agent | agent.read |
|
host.presence_changed |
host | host.read |
|
stats.live |
instance, space | stats.read |
only to connections with a stats.subscribe subscription; content filtered to the visible channels, costs only with cost.read.aggregate (25-visualization.md, section 5) |
notification.created |
thread; for a hint without a thread the recipient (user) |
self and push.deliver; for a hint without a thread: self and the recipient rule of R-AZ-30 |
only to connections of the receiver, instead of a push to this device (13-push-notifications.md, section 6.3) |
command.autocomplete_requested |
command | self (owner of the command); agent additionally agent.events.consume |
carries a token with trigger_kind = autocomplete; only to one gateway connection of the owner |
Outgoing webhooks and push use the same receive action (webhook_out.deliver,
push.deliver).
7. WebSocket ops and action¶
| Op | Direction | Action |
|---|---|---|
identify |
client to server | gateway.connect |
resume |
client to server | gateway.connect |
heartbeat |
client to server | gateway.connect (the session must be valid, consumes nothing from rl.gateway_connect); for agents additionally agent.heartbeat |
reauth |
client to server | gateway.connect (consumes nothing from rl.gateway_connect) |
focus |
client to server | gateway.focus per thread |
typing.start |
client to server | typing.start |
typing.stop |
client to server | typing.start |
ack |
agent to server | agent.events.consume |
run.update |
agent to server | run.update |
command.autocomplete_result |
agent or app to server | command.autocomplete.respond |
stats.subscribe, stats.unsubscribe |
client to server | stats.read on the instance or space respectively |
hello, ready, resumed, cursor, catchup.complete, resync_required, heartbeat_ack, reauth_required, error |
server to client | none of its own; part of the session from gateway.connect |
dispatch |
server to client | receive action of the event type (section 6) |
Terminal channels (24-terminal.md; asyncapi.yaml):
| Channel | Op | Direction | Action |
|---|---|---|---|
device stream /terminal/sessions/{session_id}/stream |
hello |
server to device | terminal.stream |
identify |
device to server | terminal.stream |
|
| binary frames (Noise) | both | terminal.stream (the session must be valid) |
|
error |
server to device | terminal.stream |
|
control /hosts/connect |
hello, ready, heartbeat_ack, error, session.open, session.close |
server to host | host.connect |
identify, heartbeat, report, session.started, session.ended |
host to server | host.connect |
|
data /hosts/sessions/{session_id}/stream |
hello, identify, binary frames (Noise) |
both | host.connect (the session belongs to the host) |
error |
server to host | host.connect |
8. Decisions of this chapter¶
| No. | Decision | Reason |
|---|---|---|
| R-AZ-01 | 403 only with an existing read permission, otherwise 404. |
Baseline B7; reveals no existence and stays understandable for those entitled. |
| R-AZ-02 | DM channels are readable only by participants, not even by owners. The only way in the product is the GDPR export of a person (gdpr.request.create for others, owner, step-up, audit); it contains only that person's own messages. Other ways (an order by an authority) lie outside the product. |
Confidentiality between people (ruling L21); a default that the owner confirms. |
| R-AZ-03 | Step-up actions only by people in a session. | A token cannot provide a confirmation by the person; administration by token would be a bypass. |
| R-AZ-04 | All denials of requests and ops go into the audit, identical denials aggregated per window; filters during delivery and in lists write none. | Study C requires a record of every denial; without aggregation, probing fills the log; a filter is not a request, otherwise every event would write one row per non-receiver. |
| R-AZ-05 | manage may assign grants only up to its own level, with its own capabilities (manage included), only to people and groups and not on the instance; revocation only within the same bounds. |
"delegable" according to the baseline, without an escalation of permissions beyond one's own grant; grants to agents and apps are data flows to processors and stay with the owner. |
| R-AZ-06 | Layout actions (create, sort, rename a space) without step-up; permissions, identity, agents, costs, export, deletion with step-up. | Step-up where damage arises; otherwise it gets clicked away out of habit. |
| R-AZ-07 | run.cancel only by the triggering person, manage or owner. |
With organization agents, people should not cancel each other's runs. |
| R-AZ-08 | pin.create needs manage. |
Everyone sees pinned items; that is moderation. |
| R-AZ-09 | Channel behavior (prompt, skills) is readable only with manage and never in events. |
Prompts can contain internal notes; read permission on the channel is not enough. |
| R-AZ-10 | Only owners set the operator, the project binding and the tool class. | All three affect I1 or the tool power of the agent. |
| R-AZ-11 | Internal delivery checks (push.deliver, webhook_out.deliver) are actions of their own. |
Rule 1.1: deliveries too run through the catalogue and are testable. |
| R-AZ-12 | The read-only setting of a channel blocks all write and trigger actions for all principals, including owners; only import.apply writes. |
A read mirror has exactly one leading location; no agent may be triggered in the mirror. Grants cannot restrict owners. |
| R-AZ-13 | Operator rule: the effective operator has write implicitly; space.operator.set and channel.operator.set require no grant of its own for the agent. |
Follows the operator without a second, easily forgotten grant; on a change no remnant stays. Replaces the proposal to require a grant when setting the operator. |
| R-AZ-14 | Owner permissions only in a session, never through a personal API token; the exception is the check without a request (R-AZ-29). | A stolen token without device binding must never open the whole instance. |
| R-AZ-15 | Expirations, the first installation, the recovery on the host, the sync of claim groups, the automatic revocation of devices and scheduled key rotations are system actions in the catalogue (grant.expire, group.member.expire, group.member.sync, instance.bootstrap, owner.recover, device.expire, signing_key.transition). |
Every audit entry names a known action; expirations reach clients and runs via access.changed; these operations are security relevant and belong in the audit, without faking an action by a person. |
| R-AZ-16 | A waiting device gets tokens, but may only read itself (403 device_pending). |
The app can wait for the approval without a new login; it reaches contents only after that. |
| R-AZ-17 | resolve_i1 carries out "switch first, then grant" in one transaction, with both checks. |
The guided flow of the console needs no intermediate state in which I1 would be violated. |
| R-AZ-18 | App commands and app components without agent.invoke and I2. |
No agent runtime, no subscription; the limit is the grants, the scope and required_level. |
| R-AZ-19 | Guest agents only with billing_mode = 'organization'. |
Guest channels do not count among the I1 targets; a personal guest could otherwise be triggered by strangers. |
| R-AZ-20 | Triggering another agent during an active run is denied (run_active_other_agent), not turned into a follow-up. |
A follow-up belongs to the run of its agent; a foreign agent would otherwise get text that was not meant for it. |
| R-AZ-21 | settings.read covers all of one's own personal data; no separate read action per kind. |
All are self, with no difference in permissions; fewer actions, same effect. |
| R-AZ-22 | Health has no action; liveness and readiness are on the internal listener outside the public API (ruling L2, 16-operations.md). | An operations contract, not a product API; no auth bypass in the public listener. |
| R-AZ-23 | Terminal actions only for people in a session, with step-up fresh per opening; the host acts only through host.pair and host.connect. |
The owner requires a passkey when opening; a terminal is remote access to a host (24-terminal.md, R-TM-07). |
| R-AZ-24 | Hosts are a target of their own outside the tree; grants there only by owners. | A grant on the instance or a space must never silently open a host; manage is meaningless on hosts. |
| R-AZ-25 | The token of command.invoked carries run_id only if the owner of the control command has an active run in the thread at execution time, and then exactly this run, like the payload; otherwise the claim is missing (section 5). |
Control commands run without a run and still go to the agent with a token (09-agents.md, section 10.4, D-AG-12); the adapter learns which of its own runs the command concerns, and never a foreign one. The token starts, resumes and renews no run (run.start, run.token.renew), because its jti is never in runs.invocation_jti: only deliveries of agent.invocation, agent.followup and interaction.created and their renewal enter a token there (04-sync-protocol.md, section 15.3). |
| R-AZ-26 | Autocomplete of an agent command without a thread in the request: in a text channel, token and payload name the main timeline as thread_id. In a forum channel the server checks as for a new thread of the channel; if the check passes, it does not ask the agent, issues no token and returns an empty list at once (04-sync-protocol.md, section 15.7). |
Every token names a thread as its location (section 5), and a forum has no main timeline (R-DM-11). A token with only a channel would be a new form with another location that every check of a token would have to know (adapter, server, acting-for binding); without a token the surface stays smaller. The check still runs, so that the empty list reveals nothing and denials go into the audit (R-AZ-01, R-AZ-04). Typing costs nothing and never hangs (R-AG-14); the client then shows the static choice values, if there are any (09-agents.md, section 10.5). |
| R-AZ-27 | Automatic state changes of outgoing subscriptions (disabled after 410, after webhook.max_consecutive_failures, after webhook.retry_max_age and on a gap; paused by the delivery quota) are the system action webhook_out.transition: per change one audit entry without an actor in the transaction of the change, with the subscription, the old and new state and the cause as a code. Changes by the owner stay webhook_out.manage; the disabling at uninstallation is covered by the entry of app.uninstall. |
Rule 1.10: every audit entry names a known action, and webhooks.md, section 4, requires the disabling in the audit log; as with R-AZ-15, a system action does not pretend to be an action of a person. One action for all causes, because the cause is in the details (D-W-104). |
| R-AZ-28 | cost.reconcile.read gives the reconciliation app with scope costs:reconcile and the owner in a session the estimates per run in the mode organization for the requested agents and the period: run, agent, provider, currency, amount of the estimate before the reconciliation and whether reconciled. Without step-up, with the effect read (section 2.7); allows without audit, denials with audit (R-AZ-04). The list evaluates the instance and does not filter by visible channels (exception to rule 1.2); it never names channel, thread or content, and the instance is the only check, without a check per entry; a run ID opens nothing, because whoever wants to read the run still needs run.read on its path. Agents, personal tokens and people without owner status do not reach it (sections 2.3 and 2.6). |
The app distributes the daily amount in proportion to the estimates and submits it per run (10-backend-policy.md, section 12.4, items 3 and 4; ADR 0055); without a read action it knows neither the estimates nor the run IDs, and cost.read.aggregate returns only sums and reaches no app (section 2.3 item 5). Reading is weaker than cost.reconcile.submit, which the same scope already allows for every run of the instance; a filter by visible channels would leave an app without grants only an empty list. Personal amounts stay out, because they are never an invoice (03 cost_entries); DM channels have no agents and therefore no runs (07-permissions.md, section 2.8). Like cost.read.aggregate and price.read, reading needs no step-up; R-AZ-06 requires it for changes to costs, and an app cannot perform it (R-AZ-03; D-W-104). |
| R-AZ-29 | Check without a request (section 5): if the server evaluates the permissions of a person without the person's request (agent.invoke before every delivery to an agent, at run.token.renew and for the run from deferred follow-ups; command.invoke and interaction.create before handing over to an app; the permissions of the triggering person in the acting-for binding), this person is a principal without session, device and token: blocks come only from the person's status, the owner status from the current owner list (exception to R-AZ-14). Only the server builds this principal, only for these cases, and the permission state computed this way is never the own permission state of the principal of a request. |
None of these cases has a request, session, device or token of the person; with R-AZ-14 alone no invocation of an owner would pass the repeated check (AZ-M19, every agent with personal or host), and no run of an owner would read its own context in the acting-for binding. The acceptance has already applied R-AZ-14 to the real request; afterwards the server evaluates the same person with the permissions of the moment, and a block or the loss of the owner status takes effect at once. A request with a personal token still gets no owner permissions. Remainder: an owner who triggered through grants with a personal token is evaluated as an owner afterwards; in the acting-for binding the run then reaches as far as the permissions of the agent. A permission of another person never arises from this (D-W-104). |
| R-AZ-30 | Hints without a thread (kinds security and admin, 13-push-notifications.md, section 2) are not checked with push.deliver on a thread. Instead the server evaluates, at the time of each decision and each send, the recipient rule of 13 section 2 with the rights of that moment: the recipient is an active human; security goes only to the other active devices of the human whose new device waits for approval; admin goes only to the owners or administrators that 07-permissions.md and 20-privacy.md name for the reminder. notification.created for such a hint has the recipient (user) as its resource and goes only to connections of that recipient (self). The payload names only the kind and the target ID, as for every hint. |
push.deliver has the resource type thread, and these kinds have no thread, so without a rule no check would be defined for them and the push path could not be built (M3-17, M3-19, M3-23). The recipient rule is the same check the chapter already states, evaluated at send time like push.deliver (P3), so a revoked device or a lost owner status stops the hint at once; it grants no read right on any content (D-W-108). |
| R-AZ-31 | The durable personal event device.pending (resource device, receive action self, audience the human who owns the device) marks a device that waits for approval; identity writes it in the transaction that registers the device with state pending, with only device_id and platform. The check of push.deliver and the recipient rule of R-AZ-30 are checks without a request (section 5, R-AZ-29). |
Hints of the kind security (13-push-notifications.md, section 2) arise in the worker consumer push from the event log (13 section 12 point 1, ADR 0051), and no event marked a waiting device, so the kind had no source. The hint check evaluates a person who sent no request, exactly like the cases of R-AZ-29 (D-W-108). |
| R-AZ-32 | New action access.view_as.end on the person's own console session (auth_session, self, without step-up, audit always, rl.admin): it ends "View as person" stored on the console session (03 auth_sessions, R-DM-49) before authz.view_as_max_duration; access.view_as from a console session starts the stored view. While a view is stored, the actions access.view_as.end, auth.logout, auth.step_up and self.read on the person's own session are decided without view_as (07-permissions.md section 4.2 steps 4 and 7 would otherwise deny them); every other decision of the session follows 07 section 9.2, and the first request after the end time gets step_up_required. The module identity owns the action, because the state lives on auth_sessions. |
07-permissions.md section 9.2 requires audit at start and end and operations to start and end the view; ending must not need a fresh step-up, which access.view_as requires, so that an owner can always leave the view (D-W-100 extended to M5 and M6). |
| R-AZ-33 | New public action console.csp_report on the instance (no login, without step-up and audit, rl.public_auth, owned by instance): the endpoint of the web console's report-to accepts violation reports, logs only the directive, the disposition and the blocked origin and stores nothing; its effect is read (05 2.7), so reports are accepted in maintenance mode as well (19-security.md, section 5, rule 6). |
Browsers send reports without the session cookie in many cases, so the endpoint cannot require a session; every operation needs an action from the catalogue (05 section 1), and the rate class of public endpoints bounds abuse (D-W-123). |
| R-AZ-34 | New read action access.hints.read on the instance (owner) or a space (manage, within one's own manage scope; without step-up, audit no, rl.read, owned by authz): the lists "Permission hints" (H1, grants to disabled principals, trigger blocked by SR2 or SR3, agents in mode personal with their trigger set) and "Expiring soon" (grants, memberships, and for owners only agent and app tokens, within authz.expiry_reminder_lead) of 07-permissions.md, sections 9.4 and 9.5. |
07 section 9.5 requires the list "Permission hints" in the console, and access.explain and grant.read do not have the instance as a target; a read action of its own keeps the hints out of the audit and limits a manager to the targets they administer (D-W-123). |
| R-AZ-35 | The durable personal event gdpr.request_due (resource principal, receive action self, audience the owner): when an open privacy request (gdpr_requests.state in requested, approved, running) reaches the reminder lead before its statutory due_at (20-privacy.md, section 5.1), the module privacy appends one event to every active owner, once per request; payload only request_id, kind and due_at. The consumer push maps it to a hint of kind admin without a thread (13-push-notifications.md, section 2; R-AZ-30). |
The console warning of 20 5.1 needs a durable signal that reaches owners on every device; a personal event fits the hint path of R-AZ-30 and carries no content. |
9. Tests that hold this catalogue¶
- Route test: every registered route has a known
x-authz-actionand its rate-limit class. - Event test: every registered event type and every op has an entry in 6 or 7 respectively.
- Case table: principal x target x action x expected decision, including expired, revoked and overlapping grants, groups with expiry, DM, owner, owner with a personal token, agent with the operator rule, app, waiting device, read-only channel.
- 404 test: for every reading route with a foreign ID, the response equals the one for a non-existent ID (status, body, timing within bounds).
- I1 test: every action from rule 1.6 is checked with a change that would violate I1,
and with
resolve_i1, which makes it permissible. - I2 test: a permission revocation between acceptance and delivery leads to run
failed, no invocation. - System actions: no route and no op names an action with permission
system. - Terminal: every op of the terminal channels has an entry in section 7; the cases AZ-M54 to AZ-M61 from 07-permissions.md and TM-T07 to TM-T10, TM-T17 and TM-T28 (a) from 24-terminal.md run in the case table.
10. Configuration values of this chapter¶
| Name | Default | Meaning |
|---|---|---|
audit.deny_aggregate_window |
60s |
aggregation window for identical denials |
audit.deny_aggregate_max_keys |
10000 |
at most this many open aggregations per process; limits the memory during floods without login |
status.public_enabled |
false |
status page without login |
ratelimit.rl.public_auth.per_minute / .burst |
30 / 10 |
per client IP |
ratelimit.rl.gateway_connect.per_minute / .burst |
12 / 4 |
per device or token |
ratelimit.rl.read.per_minute / .burst |
600 / 100 |
per principal |
ratelimit.rl.write.per_minute / .burst |
120 / 30 |
per principal |
ratelimit.rl.message.per_minute / .burst |
60 / 20 |
per principal |
ratelimit.rl.trigger.per_minute / .burst |
30 / 10 |
per principal |
ratelimit.rl.interaction.per_minute / .burst |
60 / 20 |
per principal |
ratelimit.rl.upload.per_minute / .burst |
30 / 10 |
per principal |
ratelimit.rl.search.per_minute / .burst |
60 / 20 |
per principal |
ratelimit.rl.typing.per_minute / .burst |
30 / 5 |
per connection |
ratelimit.rl.admin.per_minute / .burst |
60 / 20 |
per principal |
ratelimit.rl.export.per_minute / .burst |
2 / 2 |
per principal |
ratelimit.rl.agent_runtime.per_minute / .burst |
1200 / 200 |
per agent |
ratelimit.rl.webhook_in.per_minute / .burst |
60 / 20 |
per incoming webhook |
ratelimit.rl.import.per_minute / .burst |
120 / 30 |
per owner for import.apply |
ratelimit.rl.terminal.per_minute / .burst |
20 / 5 |
per person |
ratelimit.rl.host.per_minute / .burst |
120 / 30 |
per host |