Capability map

Bearer tokens carry a JSON array of capability strings. Routes and MCP tools check the required capability before handling the request.

Canonical machine-readable maps:

CI enforces map ↔ OpenAPI parity via http_openapi_capability_map_contract, table-driven HTTP denial via http_capability_matrix_e2e, exhaustive event-surface classification via event_surface_disposition_contract, and scripts/check-agent-contract.sh.

This page itself is covered by none of them. They guard the JSON contracts above; nothing compares this table to capability::all(). That is why five live capabilities — channel:admin, secret:read, secret:admin, audit:read-global, operator:global — were missing here until 2026-09-23 while being correct in the contracts the entire time. A guard is scheduled in Cluster 411.1; until it lands, add new capabilities here by hand.

Delegation lends work, never authority. A delegation grant may carry only workspace:read, workspace:write, message:post, thread:transition, artifact:upload, search:query and event:subscribe. Every other capability is refused at grant creation and stripped from any borrowed token regardless of how its grant was created. capability::tests::every_capability_is_classified_as_work_or_authority fails if a new capability is added without deciding which it is.

HTTP (member bearer)

CapabilityRoutes / behavior
workspace:readGET workspaces, channels, threads, messages, artifacts, search, events (member), GET /members/:id/manager-digest, GET /workspaces/:wid/events/verify (hash-chain integrity), GET /workspaces/:wid/snapshot (header + graph_hash; include_graph=true needs token:admin), GET /workspaces/:id/audit, GET /workspaces/:id/context, GET /workspaces/:id/tombstones, GET /workspaces/:id/kind-census, GET /messages/:id/backlinks, GET /workspaces/:wid/mention-webhook, GET /workspaces/:id/room, GET /workspaces/:id/handle, GET /capability-sets, POST /tokens/attenuate and POST /tokens/{id}/rotate of the caller's own token (holder-side; no token:admin), POST /auth/session/from-token (the caller's own token becomes a browser session with its authority), group-DM list/get, automation list/DLQ/get, MCP notifications SSE, POST /mcp/streamable
workspace:writePOST channels, threads, messages (mentions, votes), references; automation replay; slash/FSM hook CRUD; PUT /workspaces/:wid/mention-webhook; PUT /workspaces/:id/handle
message:postPOST thread messages, A2A SendMessage; PATCH /messages/:id and DELETE /messages/:id on your own message — only the author can edit a message (no capability lets anyone rewrite another member's words), and tombstoning someone else's needs channel:admin
thread:transitionPOST thread FSM transitions; claim the next ready thread in a channel (POST /channels/:cid/threads/claim-next) or anywhere in the workspace the caller may read (POST /workspaces/:wid/threads/claim-next); experimental POST /threads/:id/land-gate/advice when enabled; MCP transition_thread
artifact:uploadPOST /artifacts, multipart artifact routes
search:queryGET workspace search
event:subscribeWebSocket /ws/subscribe (token in subscribe frame)
channel:adminChannel membership (GET/POST /channels/:cid/members, DELETE …/members/:mid); granting and revoking governance skills (land_gate, review) on another member via /members/:id/skills; per-thread review controls (DELETE /threads/:id/land-gate, …/review-requirement, …/reviewers/:member_id); and moderation — tombstoning another member's message (DELETE /messages/:id)
secret:readGET /workspaces/:wid/secrets, POST /workspaces/:wid/secrets/:name/resolve
secret:adminPOST /workspaces/:wid/secrets, DELETE /workspaces/:wid/secrets/:name; the secret-egress allowlist: GET /workspaces/:wid/secret-egress-hosts, DELETE …/secret-egress-hosts/:host, and POST /workspaces/:wid/secret-egress-hosts, which also needs secret:read (a listed host receives the values, so a token that may rotate secrets but not read them cannot read them by listing a host)
audit:read-globalGET /operator/audit — cross-workspace audit read
operator:globalGET /operator/legal-holds, GET /operator/status; an instance-wide POST /operator/reindex-embeddings (no workspace_id) and reading its job (a workspace-scoped reindex is workspace:write)
approval:grantAccept an approval gate (POST /approval-gates/:id/answer with accept) with a token, or a session made from one. Without it, accepting needs a browser session a person signed in to through the identity provider, sent from the console page (POST /ui/api/approval-gates/:id/answer); a plain bearer token whose member is a human does not accept. Declining and cancelling need only workspace:write. In no preset and never delegatable: an admin grants it deliberately to an automated approver the workspace trusts. Through MCP approval_decide it accepts only a gate whose risk is below the workspace's approval-policy threshold (default low, so none); every other accept the tool asks for returns a one-time link the bound person confirms at POST /auth/approval-confirmations/confirm with their signed-in session
token:adminMint/revoke/list API tokens (GET/POST .../members/:mid/tokens, DELETE /tokens/:id); rotate another member's token (POST /tokens/:id/rotate); create/list/revoke /workspaces/:wid/delegation-grants; PUT /workspaces/:wid/delegation-policy (the grant-lifetime ceiling); PUT /workspaces/:wid/approval-policy (the risk at which a model's accept needs a person, and how long its confirmation link lives, audited; GET is workspace:read); PUT /workspaces/:wid/retention (the workspace's own retention, audited; GET is workspace:read); issue/list/revoke /workspaces/:wid/share-tickets; signed workspace export / verify / import; snapshot include_graph=true; destroying the record — POST /workspaces/:id/purge, DELETE /workspaces/:id (erase), DELETE /messages/:id/purge, DELETE /artifacts/:sha (erase this workspace's reference); legal holds — POST/GET /workspaces/:id/legal-holds, DELETE /workspaces/:id/legal-holds/:hold_id, and GET /workspaces/:id/legal-holds/preserved (audited); GET /workspaces/:wid/events/catch-up (the whole log as one chain, every private channel and DM included; or a registered federation peer); SCIM 2.0 provisioning at /scim/v2/ — Users (including a userName rename) and Groups, confined to the token's workspace. The SCIM routes answer in SCIM's own error envelope and sit outside OpenAPI and http-capability-map.json; each handler checks token:admin itself

MCP (POST /mcp tools/call)

CapabilityTools
workspace:readlist_channels, list_threads, list_messages, list_dm_conversations, list_reactions, list_pins, get_artifact_metadata, list_slash_commands, list_fsm_hooks, get_thread_context, get_workspace_context, get_manager_digest, get_log_snapshot, verify_event_chain, list_tombstones, list_message_backlinks, get_kind_census, list_capability_sets, parse_maidan_uri, get_room, attenuate_token, rotate_token; the long-poll waits wait_for_mention, wait_for_notification, wait_for_result, wait_for_ready, wait_for_claim_expired, wait_for_claim_failed, wait_for_blocked_resolved, wait_for_landed, wait_for_memory_block
workspace:writerecord_mention, cast_vote, add_reaction, remove_reaction, pin_message, unpin_message, add_reference, register_slash_command, register_fsm_hook, set_workspace_handle, approval_decide (declines on this alone; accepts directly only with approval:grant below the workspace threshold, else returns a confirmation link)
message:postopen_dm_conversation, post_dm_message, post_message, edit_message (author only)
artifact:uploadupload_artifact, begin_artifact_multipart, upload_artifact_multipart_part, complete_artifact_multipart, abort_artifact_multipart
search:querysearch_messages
thread:transitiontransition_thread, claim_next_thread, claim_next_workspace_thread
channel:adminadd_channel_member, list_channel_members, remove_channel_member, clear_land_gate
secret:readlist_secrets, resolve_secret
secret:adminlist_secret_egress_hosts, revoke_secret_egress_host, and allow_secret_egress_host (also needs secret:read)
token:admincreate_share_ticket, list_share_tickets, revoke_share_ticket, create_delegation_grant, list_delegation_grants, revoke_delegation_grant, set_delegation_policy, set_retention_policy, freeze_member, unfreeze_member, list_frozen_members, export_workspace, verify_workspace_export, import_workspace, catch_up_events (the whole log as one chain, every private channel and DM included)

MCP protocol methods (not tools):

CapabilityMethods
workspace:readresources/read, resources/subscribe, prompts/get

Federation (peer bearer)

CapabilityRoutes
federation:ingestPOST /a2a/v1/events
federation:adminPeer CRUD

A2A protocol (POST /a2a/v1/rpc)

The HTTP+JSON binding (/a2a/v1/...) checks the same capabilities.

CapabilityJSON-RPC methods
message:postSendMessage, SendStreamingMessage, GetTask, ListTasks, CancelTask, SubscribeToTask, GetExtendedAgentCard
workspace:writethe four push-config methods; a SendMessage that opens a new context thread

Tests

SuiteCoverage
capability_matrix_e2e.rsHTTP search/artifacts, MCP post_message, A2A, WS subscribe
mcp_capability_matrix_e2e.rsEvery MCP tool: deny without cap + pass capability gate with cap
http_capability_map_contract.rsHTTP contract uses known capability strings