high priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

switchRole(UserRole newRole) validates newRole is present in the availableRoles list before switching
If newRole is not in availableRoles, switchRole returns a SwitchRoleResult.unauthorized without changing state
If newRole equals the current active role, switchRole is a no-op returning SwitchRoleResult.alreadyActive
On successful switch, RoleStateManager emits a new RoleState with the updated active role
availableRolesStream emits the full list of roles the user holds, updated whenever roles change
The role-switch widget reads availableRolesStream and shows only roles the user actually holds
When switching from coordinator to peerMentor for an NHF user with multiple chapters, the chapter scoping for the peerMentor role is preserved (not reset to a default)
Role switch triggers downstream reactive updates in all Riverpod providers that watch RoleStateManager
The previous active role is stored in RoleState to enable undo/back navigation
switchRole is safe to call concurrently — second call while first is pending queues correctly

Technical Requirements

frameworks
Flutter
Riverpod
Dart
data models
assignment
contact_chapter
performance requirements
switchRole is synchronous (pure state mutation) — no network call required
Stream emission must occur within one event loop tick after switchRole completes
security requirements
Available roles list sourced from server-validated resolution — not user-provided input
Role switch does not bypass PermissionCheckerService — permissions re-evaluated for new role automatically via reactive dependency
No elevation of privileges: cannot switch to a role not in the server-authorised list
ui components
RoleSwitchWidget (reads availableRolesStream)
RoleBadge (shows current active role in app bar)

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

RoleState should be a data class: RoleState({required UserRole activeRole, required List availableRoles, UserRole? previousRole, String? activeChapterId}). RoleWithScope pairs a UserRole with its associated organizationUnitId for NHF chapter scoping.

Use Riverpod's StateNotifier or AsyncNotifier pattern. The availableRolesStream is simply the StateNotifier's stream mapped to state.availableRoles. For NHF users with up to 5 chapter memberships, availableRoles may contain multiple RoleWithScope entries for the same UserRole — the switch UI should display chapter names, not just role names, to disambiguate.

Testing Requirements

Unit tests with flutter_test: test switchRole with valid role → assert state emission. Test switchRole with role not in availableRoles → assert unauthorized result and no state change. Test no-op for same role. Test availableRolesStream emits correct list for single-role and multi-role users.

Test chapter scoping preserved after switch by asserting activeChapterId unchanged. Test concurrent calls with delayed mock to ensure no race condition. Widget test: mount RoleSwitchWidget with a mocked stream, assert only available roles are rendered. Target 90%+ coverage.

Component
Role State Manager
service medium
Epic Risks (3)
high impact low prob security

A coordinator's permissions could be revoked by an admin while they are actively using the app. If the permission checker relies solely on the cached role state from login, the coordinator could continue performing actions they are no longer authorized for until the next login.

Mitigation & Contingency

Mitigation: The Permission Checker Service must re-validate against the Role Repository (not just in-memory state) before high-impact actions. Implement a configurable staleness window (e.g., 15 minutes) after which role data is refreshed from Supabase in the background.

Contingency: If a revoked permission is detected during a pre-action check, immediately clear the cached role state, force a re-resolution from Supabase, and display an inline error explaining the permission change rather than crashing or silently failing.

medium impact medium prob technical

Using both BLoC and Riverpod in the same state management layer for roles risks state synchronization bugs where one system updates before the other, causing widgets to render with stale role data during the switch transition.

Mitigation & Contingency

Mitigation: Choose a single primary state management approach (Riverpod StateNotifier is recommended) for role state and wrap the BLoC pattern within it if legacy code requires BLoC interfaces. Establish a single source-of-truth provider that all consumers read from.

Contingency: If synchronization bugs appear during integration testing, introduce a RoleStateReady gate widget that delays rendering of role-dependent UI until the state notifier emits a confirmed resolved state, preventing partial renders.

medium impact high prob scope

Hardcoded permission constants per role can become a maintenance burden as new features are added across 61 total features, leading to permission definitions that are scattered, stale, or inconsistent.

Mitigation & Contingency

Mitigation: Centralize all role-permission mappings in a single RolePermissions constants file with named action keys. Enforce that no widget or service directly checks role type strings; all checks must go through the Permission Checker Service.

Contingency: If permission definitions drift out of sync, introduce a validation test suite that cross-references all registered permission constants against their usage sites and fails the CI build if an undefined permission key is referenced.