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

  1. Open the catalogue. The administrator opens Roles. The system lists the tenant's roles with their names, descriptions and permission counts.
  2. 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.
  3. 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".
  4. 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.
  5. 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 needs iam::roles:update, and it answers with the bundle as stored so the screen renders what was saved rather than what it hoped to save.
  6. 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.

wireframe_shape_the_role_catalogue.png

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.

  1. The screen is not built. The operations exist and the journey runs at the protocol, but nothing in ores.web draws 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.
  2. iam::roles:create is enforced by nothing. The generated role write checks iam::roles:write, which covers a create and a modify, so the seeded iam::roles:create is 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.
  3. 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_event for an assignment change and no caller passes it an event bus, so nothing is published for a bundle change either.
  4. 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

9. Related knowledge

10. See also

Emacs 29.3 (Org mode 9.6.15)