critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

RoleStateManager is implemented as a Riverpod AsyncNotifier<RoleState> or StateNotifier<RoleState> registered at app root scope
On initialization, the manager calls RoleRepository.fetchAllRolesForUser() and transitions from RoleStateInitial → RoleStateLoaded or RoleStateError
setActiveRole(UserRole role) updates the active role and notifies all listeners — the change is reflected in the widget tree within one frame
setActiveRole throws StateError if the given role is not in the user's list of available roles (prevents privilege escalation via UI manipulation)
getAllAvailableRoles() returns all UserRole values present in the loaded RoleAssignments — not all possible enum values
resetOnLogout() transitions state back to RoleStateInitial and clears all cached role data — safe to call multiple times
Active role selection persists across screen navigations within the same session (not across app restarts unless explicitly specified)
For a user with exactly one role, setActiveRole is called automatically during initialization — no manual selection required
For a user with multiple roles, the initial active role defaults to the highest-privilege role as determined by fetchPrimaryRole()
The widget tree can read the active role via ref.watch(roleStateManagerProvider) without triggering redundant fetches

Technical Requirements

frameworks
Flutter
Riverpod
Dart
data models
assignment
performance requirements
State transitions must complete synchronously after RoleRepository returns data — no additional async gaps
getAllAvailableRoles() must be O(n) in the number of role assignments, not O(n²)
security requirements
setActiveRole must validate the requested role against the authenticated user's actual assignments — never trust client-side role selection without server-backed validation
resetOnLogout must be called before any logout-triggered navigation to ensure no role-gated screen is accessible after logout

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement as `class RoleStateManager extends AsyncNotifier` if using Riverpod code generation, or `StateNotifier` if following existing project patterns — check which pattern is used in other state managers before choosing. Store both `List _allAssignments` and `UserRole? _activeRole` as private fields. Expose `UserRole?

get activeRole` and `List get availableRoles` as computed getters on the notifier for convenient access in widgets. For the automatic single-role initialization path: after loading, check `availableRoles.length == 1` and call setActiveRole internally. For deduplicated available roles: use `_allAssignments.map((a) => a.role).toSet().toList()`. Register the provider with `ref.onDispose(() => resetOnLogout())` to ensure cleanup if the provider is ever torn down.

Consider whether BLoC or Riverpod is used for other state managers in the project and match that pattern strictly.

Testing Requirements

Unit tests using ProviderContainer: test that initialization calls RoleRepository.fetchAllRolesForUser exactly once. Test that a single-role user has their role set as active automatically. Test that a multi-role user's active role defaults to the highest-privilege role. Test setActiveRole with a valid role → state updates correctly.

Test setActiveRole with an invalid role → StateError thrown. Test resetOnLogout → state returns to RoleStateInitial and available roles list is empty. Test getAllAvailableRoles returns deduplicated role values (a user with coordinator role in two chapters should see coordinator only once). Test that RoleRepository error during init produces RoleStateError state.

Integration test: boot full Riverpod graph with real RoleRepository against local Supabase, verify end-to-end initialization flow. Target 85%+ branch 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.