Skip to main content
The relationship graph that authorizes access to resources. Authority for this page is config/auth/openfga/model.fga; the deployable payload config/auth/openfga/authorization-model.json is generated from it.

Synopsis

Identity types are PascalCase; governance types are lowercase. Object references are literal, so a tuple reads 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 by require_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:

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
Last modified on September 17, 2026