RBAC
Three roles
Section titled “Three roles”| Role | Summary |
|---|---|
| admin | Everything below, plus mint and revoke agents, invite users, manage memberships, change retention, manage rate-limit rules, delete the project. |
| operator | Everything below, plus issue commands (retry, cancel, bulk-retry, purge a queue, restart a worker), plus schedule CRUD. |
| viewer | Read-only: list tasks, view events, read schedules, read agents, read audit (audit reads require admin, see below). |
Roles are project-scoped: you can be admin on one project and viewer on another. There is no separate owner tier; admin is the highest role and the last-admin protection (see below) prevents the project from being orphaned.
Enforcement model
Section titled “Enforcement model”Every API route resolves (user, project) -> role and calls into the policy engine:
await policy.require_member( memberships, user=user, project=project, min_role=ProjectRole.OPERATOR,)Insufficient role returns 403 with error: "insufficient_role". The frontend hides actions the user cannot perform, but UI hiding is polish only — the backend is authoritative.
Audit endpoint reads require admin, not viewer: audit data can reveal who did what when, which is itself sensitive.
Last-admin protection
Section titled “Last-admin protection”The brain refuses to:
- Demote the last
adminof a project (PATCH membership). - Delete the membership row of the last
admin(DELETE membership).
Both return 422 with error: "last_admin_protection". To rotate the last admin, add a new admin first.
Invitations
Section titled “Invitations”Admins can invite users to a project at a specific role:
- Dashboard, Memberships, Invite (or
POST /api/v1/projects/{slug}/invitations). - If the project has an active email notification channel, the link is sent automatically; otherwise the response surfaces the link for out-of-band delivery.
- Invitee opens the link, fills in display name and password, and
POST /api/v1/invitations/acceptmaterialises their user + membership in one step. - The token TTL defaults to 7 days (
ttl_days, server-bounded). Single-use; reuse returns410 invitation_consumed_or_expired.
Multi-project users
Section titled “Multi-project users”A user can belong to multiple projects. The UI shows a project switcher in the top bar. Each agent token is bound to one project; an agent only ever sees the project it was minted for.
Audit trail
Section titled “Audit trail”Every membership change writes an audit log entry (membership.invited, membership.accepted, membership.role_changed, membership.removed). The chain is HMAC-signed and append-only; see audit log.