Agency RBAC Model
Production V1 contract
Agency access answers two separate questions:
- What can this person do? Their organization role and its capabilities.
- Which clients can they do it for? All clients or selected clients.
Both checks apply. Client access never grants an action that the role does not allow, and a capability never grants access to a client outside the member's client scope.
Roles and capabilities
Active orgUsers memberships use four roles:
owneradminaccount_managerstaff
Organization role policies define the default capabilities for admin, account manager, and staff. Owners always retain full organization oversight. Settings → Role permissions is the place to change those defaults, and is shown only to members with role-management authority.
Per-member capability overrides remain supported in persistence for compatibility, but they are not part of the normal V1 Team workflow. Changing a person's role from Team returns that person to the selected role's defaults. This keeps the visible model predictable.
Client access
Every non-owner membership has one client-access mode:
- All clients: the member can access every current and future client in the organization.
- Selected clients: the member can access only the clients selected for them in Settings → Team.
Owners are always set to All clients. A non-owner in Selected clients mode with no selected clients has no client-scoped data access. The UI normally requires at least one client when selecting this mode, while the backend remains fail-closed for legacy or transitional records.
The internal persisted value for Selected clients is assigned. That is an implementation detail and must not be presented as a second product concept.
Client scope follows the client across the app. It filters client accounts and client-owned records such as projects, tasks and delivery data, time, files and requests, contracts, quotes, billing records, and relevant search/workspace projections. Individual surfaces may impose additional capability checks.
Team is the access-management home
Settings → Team is the sole V1 place to invite staff and manage each person's:
- role
- All clients or Selected clients access
- selected client list
- invitation lifecycle
- membership removal
Client list and client detail do not expose a parallel “Assigned” workflow. This avoids making access appear to be client metadata and gives owners one place to answer who can see what.
Access is selected when an invitation is created, so an invited member receives the intended scope on acceptance. Revoking an unaccepted invitation cleans up its provisional selected-client rows. Expired invitations fail closed; consuming an expired token also performs that cleanup.
Client access is not project staffing
Client access is an authorization boundary. Project team membership, project ownership, task assignment, and workload planning are delivery concepts.
A person may have access to a client without being on every project. Conversely, being named on a project must never bypass the person's client access or role capabilities. Project staffing should stay in project workflows and should not be used as an access-control shortcut.
Delegated administration guardrails
Team management is capability-gated rather than owner-page-only, but delegated administrators cannot use it to escalate privileges:
- non-owners cannot change their own role, client scope, or custom capabilities
- non-owners cannot manage a peer or a higher-ranked member
- non-owners cannot promote someone to their own rank or above
- non-owners cannot grant capabilities they do not hold
- a Selected clients manager cannot inspect or grant client scope beyond their own; only an All clients manager can grant All clients
- owners cannot be removed or demoted through normal member editing
- ownership changes use the explicit ownership-transfer flow
- users without team-view capabilities receive an access-denied state and no team data query
Backend enforcement is authoritative; the UI mirrors these restrictions by hiding or disabling unavailable controls.
Read-only Team access is deliberately narrow. It may expose the member or invitation directory a viewer is allowed to read, but it does not return client names, selected-client scope, capability overrides, or effective-capability detail unless the viewer can manage staff access. A delegated manager also sees only client-scope detail fully contained by their own scope; broader target scope is redacted rather than partially represented.
Access states and navigation
Settings remains the coherent home for organization access because Team, Role permissions, billing, domains, integrations, and personal settings are configuration rather than everyday client work. The Settings navigation only lists destinations the viewer can use, and the Settings entry route sends them to their first available destination. Direct links still render a clear access-denied page and never rely on hidden navigation as enforcement.
A member in Selected clients mode with no active selections sees a single explanatory no-client state on client-scoped workspaces. Client-work navigation remains visible so the state is understandable, while client-dependent create shortcuts are hidden until access exists. A direct link to a missing or inaccessible client-owned record uses the same neutral “unavailable” treatment, so the app does not reveal whether an out-of-scope record exists.
Product rules
- Default new invitations to All clients for managers who can grant global scope, so a member does not appear broken immediately after joining. A Selected clients delegated inviter defaults to Selected clients and can grant only their own subset.
- Use Selected clients when an agency intentionally needs separation between accounts.
- Keep role labels human-readable and keep raw capability keys out of the normal Team workflow.
- Keep billing protected by both billing capabilities and client scope.
- Keep destructive and ownership operations explicit and auditable.
- Prefer simple role templates and occasional exceptions over custom-role builders, inheritance trees, or policy languages in V1.