Skip to content

RBAC

RoleSummary
adminEverything below, plus mint and revoke agents, invite users, manage memberships, change retention, manage rate-limit rules, delete the project.
operatorEverything below, plus issue commands (retry, cancel, bulk-retry, purge a queue, restart a worker), plus schedule CRUD.
viewerRead-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.

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.

The brain refuses to:

  • Demote the last admin of 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.

Admins can invite users to a project at a specific role:

  1. Dashboard, Memberships, Invite (or POST /api/v1/projects/{slug}/invitations).
  2. 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.
  3. Invitee opens the link, fills in display name and password, and POST /api/v1/invitations/accept materialises their user + membership in one step.
  4. The token TTL defaults to 7 days (ttl_days, server-bounded). Single-use; reuse returns 410 invitation_consumed_or_expired.

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.

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.