Skip to content

Roles & Access

Every account in PLXY holds a set of three possible roles — the model of the AICC reference deployment (00-system_users_roles): category STUDIO, roles Admin, Project, Monitoring. A user may hold any combination of them, including all three and none.

Role (stored name)Shown asWhat it grants
STUDIO_ADMINAdminEverything. Users, roles, tenants, telephony, AI settings, the audit trail — and every project in the tenant, including taking over another editor's scenario lock (ADR-0042).
STUDIO_PROJECTProjectAuthor and operate content in the projects the user is a member of: callbots, scenarios, libraries, campaigns, contacts, and the reports that tell whether any of it works.
STUDIO_MONITORINGMonitoringObserve: read/list on calls, transcripts, recordings, queues, reports and usage. No mutation of any kind.

Roles combine as a union — the most permissive combination wins. Monitoring alongside Project still writes. An empty role set is valid: such a user can log in and do nothing, which is a state an administrator is allowed to put somebody in.

The mental model, in one picture

How a request is decided

Authorization is default-deny and evaluates in a fixed order — the first failure wins, and the denial names its reason so the studio can explain rather than shrug:

Notes on the axes:

  • Function level is the union of each held role's permissions. Only Admin holds the platform-level permissions (tenants, system readiness), so a platform resource — which belongs to no tenant — is reachable by admins alone.
  • Project scope: Admin spans every project; Project and Monitoring reach only projects they are members of. A resource with no project (a user, an inbound route) is governed by the matrix and tenant scope alone.

Beneath the axes sits a guarantee that no application code can bypass: PostgreSQL row-level security scopes every tenant-owned row at the database itself, so even a studio bug cannot leak one tenant's calls into another's list.

The live row decides — not the token

A JWT is an eight-hour bearer credential, and eight hours is too long for any of the levers an operator pulls during an incident. So authorization inputs come from the account's live database row, read once per request (ADR-0043 §11):

InputRead fromEffect
Rolesthe row's roles columnremoving a role takes effect on the next request, not at token expiry
enabledthe rowdisabling an account cuts it off immediately
must_change_passwordthe rowforcing a password change locks every route except the change screen
Project membershipsread per request, never baked into the tokenremoval from a project takes effect immediately

What each role sees in the studio

The studio hides dead ends, not secrets — the server enforces regardless. The icon rail's three areas map one-to-one onto the roles, with Admin seeing everything:

Rail areaWho sees it
Projects (workspace, libraries, reports)Admin, Project
MonitoringAdmin, Monitoring
AdminAdmin

Inside the rails the sidebar is not further role-gated, with one exception: the Activity Log (the audit trail) is an administrative record, offered to Admin only.

An account holding only role names the build does not recognise fails open in the UI — it sees the item and receives the server's honest refusal — because hiding an unknown vocabulary's pages would turn a version skew into an invisible wall.

Granting roles in practice

Roles are edited in Admin → User Roles (or per user in Admin → Users) and are replaced as a set — what you save is what the user holds. Any combination of the three is accepted; the one refusal is an unknown role name, because a stored typo would read back as a grant while granting nothing. Legacy persona names from before the three-role model fall in that bucket — the V32 migration mapped them, and any stragglers are shown in amber in the editor for removal.

Two guardrails apply to the caller: you may edit neither your own roles nor your own enabled flag — an administrator cannot accidentally (or deliberately) lock themselves out or promote themselves.

History: the eleven-persona model

Until V32 the product carried eleven "personas" (PLATFORM_ADMIN, TENANT_ADMIN, TELEPHONY_ADMIN, AI_BOT_DESIGNER, CAMPAIGN_MANAGER, CONTACT_CENTER_MANAGER, SUPERVISOR_QA, HUMAN_AGENT, COMPLIANCE_OFFICER, READ_ONLY_ANALYST, SERVICE_ACCOUNT) beside the three studio roles. V32 collapsed them onto the nearest role:

Legacy personaBecame
PLATFORM_ADMIN, TENANT_ADMIN, TELEPHONY_ADMINSTUDIO_ADMIN
AI_BOT_DESIGNER, CAMPAIGN_MANAGERSTUDIO_PROJECT
CONTACT_CENTER_MANAGER, SUPERVISOR_QA, HUMAN_AGENT, COMPLIANCE_OFFICER, READ_ONLY_ANALYST, SERVICE_ACCOUNTSTUDIO_MONITORING

Old audit rows keep the persona names they were written with — the trail is a record, not a view.

Where this is specified

RecordWhat it decides
ADR-0015Default-deny, cross-tenant property tests (matrix since simplified to three roles)
ADR-0020Projects, the studio roles, the project axis
ADR-0042Edit locks and the STUDIO_ADMIN takeover
ADR-0043The audit trail, the live-row rule, the incident levers

PLXY AI Call Center — built as five separated projects and one shared schema.