The require() abstraction
An endpoint checks for the required permission, not
a specific role.
how
In v1, require() maps a permission to a role
via the static ROLE_PERMISSIONS table. The
fine-grained permissions from v2 plug in here too.
RBAC middleware
A FastAPI dependency — the single check point: it takes the
required permission, not a role. Roles are never
checked directly on endpoints.
role change
The role is taken from the access token's claim — its
freshness and DB reads for critical checks are described in
JWT claims.
Resource ownership check
RBAC answers “is this action allowed at all”, ownership —
“is it over your own object”. Personal agents and API keys
are accessible only to their owner: the endpoint checks the
resource's owner_id against the current
user_id on top of the permission.
IDOR protection.
v2 Team ownership — members'
access to their team's resources — arrives with the
Team Lead capability.
why on top of RBAC
Without an ownership check, one Member could
edit another's agent — a classic IDOR. Owner /
Admin bypass the ownership check only where it is their
normal remit (user management), never to access
source data.
Boundary of admin rights
Owner and Admin see data
strictly within source ACL, just like a Member. There is no
role-based blanket ACL bypass.
break-glass and audit
Emergency access that bypasses source ACL is
a separate, explicit setting with a mandatory
log entry.
Admin Panel.
Granular permissionsv2
The permissions and
user_permissions tables provide fine-grained
rights on top of roles.
details and precedence
They let an individual user's rights be extended or
trimmed. Assigned via
Admin Panel. Precedence is unambiguous:
an explicit deny outweighs
any grant — whether from a role or a targeted grant.
Grants (role + extensions) are summed first, then deny is
subtracted.