09 — User Behaviour (RBAC Module)
- 1. Core state machine (all lists)
- 2. Data freshness rules
- 3. Permission propagation (the critical behaviour)
- 4. Destructive actions
- 5. Offline behaviour
- 6. Concurrency / multi-admin
- 7. Empty / zero states
- 8. Rate limiting
- 9. Session expiry mid-edit
- 10. Behaviour by persona (summary)
Behavioural rules for the RBAC surface: state machines, refresh, caching, offline, destructive paths, and the permission-change propagation story. Server rules quoted from
rbac.service.ts/rbac.guard.ts; client conventions from 00-shared/06.
1. Core state machine (all lists)
Initial → Loading → Success(Empty) ─┐
│ ├→ Content
└→ Error(ApiException) ←┘
Error → Retry → Loading
Success → PullToRefresh → Loading(background, keep content)
Per 00-shared/06 §3.1; screens never render blank.
2. Data freshness rules
| List | Server cache | Client cache (proposed, 00-shared/06 §3.3) | Refresh triggers |
|---|---|---|---|
Roles (GET /rbac/roles) | none (rbac.service.ts:75-77 raw) | 5 min, stale-while-revalidate | pull-to-refresh; return from editor; after delete |
Permissions (GET /rbac/permissions) | none (static constant) | 24 h — catalog only changes with deploys (permissions.constants.ts:1-97) | app foreground if > 24 h |
Members (GET /rbac/members) | none | 5 min | pull-to-refresh; after add/edit/remove |
| Users (profile merge) | none | 24 h reference cache (00-shared/06 §3.3) | — |
Audit (GET /audit-logs) | none (paginated) | none beyond current page | infinite scroll; pull-to-refresh |
3. Permission propagation (the critical behaviour)
Derived from source — three different latencies:
| Change | Takes effect | Source |
|---|---|---|
| Role permission edit | ≤ 300 s (Redis cache TTL sl:{tenantId}:perm:{userId} EX 300) | rbac.service.ts:48,68 |
| Member roles change | next login (JWT roles claim minted at login, auth.service.ts:145-154; refresh reuses old claim auth.service.ts:192-196) | OQ-R4 |
| Member removed | immediate for new requests IF the permission check runs (guard resolves via member doc, rbac.service.ts:56-65); JWT roles claim still valid until re-login — role-checked endpoints (@Roles, e.g. rbac.controller.ts:21) stay open until token expiry | rbac.guard.ts:39-41 |
UI behaviour:
- After saving a role's permissions, the editor shows: "Permissions apply to holders within 5 minutes; role claims refresh at next login." (info banner).
- Route-tree rebuild (
05 §9) happens on login/permission refresh — not on RBAC writes by a different admin (no server push event exists; OQ-R9). - Removing/deleting while own token holds the affected role → post-save hint + offer "Refresh session" (re-login) — else the UI may show screens the server now denies.
4. Destructive actions
| Action | Confirm | Server behaviour (source) |
|---|---|---|
| Delete role | AppDialog — lists holder count (proposed); copy: "N member(s) hold this role. They lose its permissions." | soft delete (rbac.service.ts:106); system → 400 (blocked in UI) |
| Remove member | AppDialog destructive | soft delete, silent 200 even if absent (rbac.service.ts:138-140) |
Remove own org_admin | typed confirm (type org_admin slug, 05 §5) + "You will lose access" warning | same as above; JWT keeps access until expiry (see §3) |
No optimistic deletes with undo: deletes are irreversible-enough (soft-delete but no
restore endpoint — rbac.service.ts:106,139) → server-confirm then remove row
(00-shared/03 F rule: irreversible ops are never optimistic).
5. Offline behaviour
- Reads: last-good cache renders +
AppOfflineBanner; all write CTAs disabled with tooltip "Connect to retry" (no offline write queues for RBAC —00-shared/12C3). - Stale matrix: if editor opened offline, matrix renders from 24 h permission cache and 5 min role cache; Save blocked; on reconnect → refetch + compare (banner "Permission catalog updated" if diff).
6. Concurrency / multi-admin
- Two admins editing the same role: last-writer-wins (
$setwhole doc,rbac.service.ts:96;versionfield exists inbase.schema.ts:30-31but no optimistic lock is enforced on role update — OQ-R10). Mitigation: client shows "edited elsewhere" on conflict only if the fetched doc differs at save time — refresh detail before save when the screen has been backgrounded > 5 min. - Duplicate member add across two admins: server 500 (E11000, OQ-R5) — client shows conflict copy + refreshes list.
7. Empty / zero states
- Roles: system section always renders (7 seeded roles,
role.schema.ts:8-65); custom section empty-state. - Members: "No members yet — add your first member." (org_admin always exists in
practice — created at registration
auth.service.ts:88-91). - Matrix search: "No permissions match 'xyz'".
- Audit: "No activity recorded" (empty
meta.totalItems === 0).
8. Rate limiting
/rbac/*uses the defaultapitier, 100/min (rate-limit.guard.ts:36-37); audit default too. Enforcement only in production (rate-limit.guard.ts:30).- 429 →
AppBannercountdown + backoff, no auto-retry (00-shared/06 §5).
9. Session expiry mid-edit
- 401 during Save → silent refresh → retry once → failure: preserve form state in
memory, route to login with "re-login to continue" snackbar; matrix selections survive
re-login (state held by cubit,
13 §2).
10. Behaviour by persona (summary)
| Persona | What they see | What they can do |
|---|---|---|
| org_admin | full | everything |
| role manager (planned) | roles/members/audit | per OQ-R1 grants; edit matrix |
| member viewer | members list read-only | search, view |
| platform_admin | tenant picker shell (planned) | cross-tenant reads via repo bypass (base.repository.ts:21) — never inside tenant RBAC UI without org_admin token |
| teacher etc. | nothing | 403 screen if route reached |