User Journey: Shape the role catalogue
Table of Contents
This page documents a user journey: A tenant administrator defines roles and the permissions each one bundles.
A user journey describes what one person is trying to do, before anything is built. It is a design document that outlives any sprint, so it names the screens, the entity fields each screen shows, and the operations and messages each step needs.
1. Goal
"I want to decide what a role grants." The tenant administrator defines a role and chooses the permissions it bundles. The journey is done when the role's permission list is exactly what the administrator intended, and an account that holds the role can do exactly those things.
2. Actor and trigger
The tenant administrator, an account inside one tenant that holds the role write permission. The trigger is a new desk, a new capability that needs a home, or an audit finding that a role is too broad. It starts at Roles, and ends when the catalogue and the role's permission list read correctly. This is an administrator acting inside their tenant, not a member.
The Qt client listed a role's permissions read-only and shipped no editor for them, so this journey is new ground. The screen must be designed, not reproduced.
3. Steps
- Open the catalogue. The administrator opens Roles. The system lists the tenant's roles with their names, descriptions and permission counts.
- Create or pick a role. The administrator names a role and writes a description. The system writes a new role version and records the change reason.
- Read the permission catalogue. The system lists every permission the platform defines, with its code and description. Permissions are seeded constants, so the screen offers no "new permission".
- Choose the bundle. The administrator ticks the permissions the role should bundle and unticks the ones it should not. The request carries the whole ticked set rather than an increment, so a permission the role held and the administrator unticked is removed by the same call that adds the others.
- Save the bundle. The administrator saves, and the role bundles exactly the
permissions that were ticked. The call is
iam.v1.roles_permissions.put, it needsiam::roles:update, and it answers with the bundle as stored so the screen renders what was saved rather than what it hoped to save. - Check the result. The system re-reads the role and its permissions. The administrator compares them with the intent.
The permission catalogue is system-defined. A code the administrator invents would not be enforced anywhere, and would drift from the seed data, so the screen picks from the seeded list and never creates a permission.
4. Screens and wireframes
One screen, three panels: the role catalogue, the selected role's identity, and the permission bundle. The administrator moves between the three while shaping one role, so they belong together.
Figure 1: Role catalogue: the tenant's roles, the selected role, and the permissions it bundles.
5. Entities composed
| Entity | What it contributes | Model |
|---|---|---|
role |
Name and description: the role being shaped | ores.iam.role |
permission |
The atomic codes and descriptions the administrator picks from | ores.iam.permission |
role_permission |
The junction row that records which permissions a role bundles | no entity model — junction table |
There is no module entity and no module table. The component segment of the
code — iam in iam::accounts:read — is what plays the part a module would
play, and the catalogue is grouped by that segment.
The role_permission row carries the same tail as account_role:
assigned_by, assigned_at, change_reason_code and change_commentary. A
bundle change is attributable to the account that made it and to the reason it
gave, and the store stamps the time.
The tail belongs to the grant rather than to the change. An addition writes it. A removal closes the row and leaves the tail the grant wrote, so a bundle reads as its live rows plus the record of when each permission ended; the caller's reason covers what the call adds, and a removal has no reason of its own. The alternative, restamping the tail on removal, would erase who granted the permission, which is the record this journey exists for. A reason per removal needs somewhere to put it that is not the grant's own row.
No credential field appears on this screen. password_hash, password_salt,
service_password_hash, totp_secret, cleartext passwords and JWTs stay on
the server.
6. Operations and messages
| Step | Operation | Subject | Status |
|---|---|---|---|
| Open the catalogue | List the roles in the tenant | iam.v1.roles.list |
exists |
| Create or pick a role | Write a role | iam.v1.roles.put |
exists |
| Read the permission catalogue | List the permissions the platform defines | iam.v1.permissions.list |
exists |
| Choose the bundle | Read the permissions a role bundles | iam.v1.ops.get_role_permissions |
exists |
| Choose the bundle | Write the permissions a role bundles | iam.v1.roles_permissions.put |
exists |
| Remove a role | Delete a role | iam.v1.roles.delete |
exists |
| Check the result | Read a role and its permissions | iam.v1.roles.get, iam.v1.ops.get_role_permissions |
exists |
The role subjects are defined in
ores.iam.authorization_messages.
iam.v1.ops.get_role_permissions is a read and iam.v1.roles_permissions.put is the
write, and the write answers with the read's own response: the codes as stored,
so the two cannot disagree about the bundle.
Replacing the bundle is one subject rather than an add and a remove. The screen knows the set it wants, not the difference, and two verbs would make it compute the difference and then depend on both landing. The write is idempotent: sending the same set twice leaves the role bundling it once.
Writes are checked against a permission the seed defines:
iam::roles:write for a role's own fields, which the generated role write
checks, and iam::roles:update for its permission bundle, which is what the
seeded description of that code already says. The two are separate because they
are separate capabilities: an administrator can be trusted to shape what a role
grants without being able to rename or delete the role itself.
A write that is refused answers at the operation rather than in the envelope:
the response carries a result whose outcome is denied, invalid or
missing, the same contract the
authorised reads page states. An
unknown permission code is invalid and writes nothing, because the whole
bundle resolves before any row lands.
7. What is left
The screen runs in ores.web as Roles: the catalogue with service roles
hidden unless asked for, and a role's page that ticks what it allows by area
and saves the whole set after showing what it adds, takes away and whose
access changes (Implement the access journeys in ores.web). Two guards are the screen's alone: it does not
offer to delete a role somebody holds, and it does not edit a role that
grants everything or a service's role. The server enforces neither.
- The screen is not built. The operations exist and the journey runs at the
protocol, but nothing in
ores.webdraws the catalogue, the selected role and the permission bundle yet, and nothing reaches them from the shell's menus. A journey a person cannot reach from a screen is not finished. iam::roles:createis enforced by nothing. The generated role write checksiam::roles:write, which covers a create and a modify, so the seedediam::roles:createis a code no handler reaches. Either the write grows a create precondition that checks it or the code is retired; until then it is a grant that means nothing.- Nothing tells a holder that the bundle moved. A change to a role's
permissions changes what every account holding the role may do, and the
session's permission claim is a snapshot taken at sign-in. The database-backed
check reads the change immediately, but a caller relying on the claim does
not. The service declares an
account_permissions_changed_eventfor an assignment change and no caller passes it an event bus, so nothing is published for a bundle change either. - A permission code an administrator invents is not enforced anywhere. This is by design rather than a gap: the catalogue is seeded, the screen picks from it, and creating a code would grant a capability no handler checks. The permission write subjects exist for the seed's purposes and are hidden from the journey.
8. Related journeys
- Change someone's access — a role is only useful once it is assigned to an account
- Know what I may do — the member's view of the roles they hold and the permissions they carry
- Register a service account — non-human accounts consume roles too
- See who has access — the roster that shows which accounts hold the role you are shaping
9. Related knowledge
- Identity and Access Management
- Role-Based Access Control
- Accounts and Roles — the user guide chapter
- How do I add a PlantUML diagram?
- Access journeys — the group hub