Authorization model
The relationship graph that authorizes access to resources. Authority for this page isconfig/auth/openfga/model.fga; the deployable payload
config/auth/openfga/authorization-model.json is generated from it.
Synopsis
resource:<uuid>#reader@User:<id>.
Fields
Workspace
role
A role is a bag of independent bits. Each enabled bit must be written for both
User:* and Agent:*; a bit written only for User:* denies agent assignees.
role_assignment
Attach each assignment to exactly one object; attaching one assignment to several
objects amplifies its bits across all of them.
project
resource
Agents, skills, MCP servers and registered clients are all
resource:<uuid>.
Which kind a resource is lives in the database, not the graph.
Values
Permission bits
can_read, can_write and can_manage are independent. None implies another. A
role granting only can_write yields can_write: true with can_read: false
and can_manage: false.
Verb to bit — permission service
Applied byrequire_permission and the DI-resolved permission service.
A verb outside this map is denied and logged at warning level.
Verb to bit — relationship explorer
Applied by the/v1/access-control check endpoint. Note the absence of update.
A value that is neither a mapped verb nor one of the three bits returns
allowed: false without querying the graph.
Relation aliases accepted on write
The explorer write endpoint maps a submitted relation onto a resource grant.
Anything else is rejected. A
subject_set (group) body is rejected; bundled
access goes through role and role_assignment.
Namespaces accepted by the explorer
The namespace selects a repository for that check only. The tuple written or
checked is always against
resource:<object>.
Subject forms accepted on write
Seeded tuples
On workspace creation:
On governed resource creation:
The root project id is the workspace id suffixed with
-root. Both writers are
idempotent; an “already exists” response is treated as success.
Enforcement
Invariants the graph cannot express, enforced by the writer instead: one
workspace and one parent per project, one project per resource, same-workspace
edges, an acyclic project graph, one object per
role_assignment, and resource
kind resolved from the database.
Defaults and overrides
ACCESS_CONTROL_BACKEND accepts disabled, keto or openfga.
keto.enabled and openfga.enabled are mutually exclusive; the chart fails
rendering if both are set.
docker-compose.dev.yaml and the Helm chart both set AUTO_BOOTSTRAP and
AUTO_APPLY_MODEL to true. Model paths differ by deployment:
With
AUTO_APPLY_MODEL enabled the file wins and the returned model id is used
for the process lifetime. With it disabled,
ACCESS_CONTROL_OPENFGA_AUTHORIZATION_MODEL_ID is used and the file is ignored.
Bootstrap compares candidate models by normalized content ignoring ids, so an
unchanged model is reused rather than rewritten. When several processes race a
store create, all converge on the earliest store with that name.
Example
Grant a write-only role bundle on a project, and confirm the bits do not roll up:See also
- Policy rule syntax — the other authorization surface, which governs tool invocation.
- Limits — timeouts and pagination on the graph client.
- Errors — the 403 and 503 responses these checks produce.
- The AgentArea model — the reasoning behind the type set.