Roles and assignments
System and custom roles, assignments with a scope and a validity window, IdP group mappings, break-glass and the built-in admin role.
Roles
A role is a named set of permissions, each with a mode. There are two kinds:
- System roles come from the product manifest and are seeded into every org. Admins assign them but can't edit or delete them; they change with the code.
- Custom roles are made by admins from the permission catalog, for needs the product didn't anticipate. An admin can only put a permission into a role if they hold it globally.
systemRoles:
employee:
name: Employee
permissions:
leave.request:create: self
leave.request:read: self
line_manager:
name: Line manager
description: Assign with scope team.
permissions:
leave.request:read: inherit
leave.request:approve: inherit"*": global in a role means every permission that may be granted globally; it's for break-glass roles.
A role in use (assigned, or mapped to a group) can't be deleted. Deleting an unused one soft-deletes it, so the change log and revoked assignments keep pointing at it.
Assignments
An assignment gives one user one role at one scope:
| Field | |
|---|---|
userId, roleId | who, and which role |
scopeType, scopeIds | global, self, a prefix scope type, or a set scope type with IDs |
validFrom, validUntil | optional window; an assignment outside it grants nothing |
reason | why; required for sensitive global grants and break-glass |
Assignments are revoked, never deleted. Revoking one keeps it in history with who revoked it and why.
Group mappings
A group mapping gives a role at a scope to everyone whose token carries an IdP group: (idp, groupName) -> role @ scope. Members hold it while their verified token has the group (Subject.IdP and Subject.Groups). It's how "everyone in all-staff is an employee" works without onboarding code.
Mappings with a prefix scope need the person creating them to hold those permissions globally, because the members aren't known in advance. Break-glass roles can't be mapped to groups: that would make them permanent for a whole group.
Break-glass
A role marked breakGlass: true is for emergencies: "*" at global, for a few hours. Assigning it needs a reason and a validUntil at most 24 hours after validFrom, and is logged with high severity.
break_glass:
name: Break glass
breakGlass: true
permissions:
"*": globalThe built-in admin role
access_admin is built in and can't be redeclared. It holds every access.* permission:
| Permission | Lets you |
|---|---|
access.role:read | see roles and the permission catalog |
access.role:manage | create, edit and delete custom roles |
access.assignment:read | list assignments |
access.assignment:manage | assign and revoke roles |
access.group_mapping:manage | map IdP groups to roles |
access.change_log:read | read the change log |
access.decision:explain | explain other people's decisions |
access.delegation:any | grant beyond your own reach (sensitive, global only) |
access.delegation:any is what lets the first admin hand out product roles they don't hold themselves. It lifts the no-escalation rule and nothing else.
The rules the admin service enforces
- No escalation. To grant a permission at a scope you must hold it at that scope or wider: global covers everything; self needs self or global; a prefix scope needs the same scope with the target's path inside yours; a set scope needs every expanded ID. Revoking follows the same rule.
access.delegation:anylifts this one. - No self-assignment. Nobody assigns roles to themselves, admins included.
- Role editing needs each added permission held globally. System roles can't be edited.
- Reasons for sensitive global grants and break-glass.
- Break-glass limits: at most 24 hours, never mapped to groups.
- The last admin: the last active
access_adminof an org can't be revoked. - The change log is append-only; the database refuses
UPDATE,DELETEandTRUNCATEon it.
They hold for every caller, the admin screens, scripts and service accounts alike, because only the admin service writes to the access tables. See Administering access.