critical priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

RoleResolutionService.resolveRoles(userId) fetches all roles from RoleRepository and returns without throwing when the user has exactly one role
When user has both coordinator and peerMentor roles, coordinator is selected as the primary role via precedence logic
When user has only the peerMentor role, peerMentor is set as the active role
When user is a globalAdmin, the service immediately returns a GlobalAdminBlockedResult without calling setActiveRole
All available roles (not just primary) are stored in RoleStateManager so the role-switch widget can enumerate them
RoleStateManager.setActiveRole() is called exactly once per successful resolution with the correct primary role
Service handles empty roles list gracefully (no roles assigned) by returning a NoRoleAssignedResult
Service is a Riverpod Provider and can be injected into the auth flow
Resolution completes within 2 seconds under normal network conditions
Any Supabase error during role fetch is surfaced as a typed RoleResolutionError, not a raw exception

Technical Requirements

frameworks
Flutter
Riverpod
Dart
apis
Supabase PostgREST (user_roles table)
Supabase Auth (current session JWT)
data models
assignment
contact
performance requirements
Single Supabase query fetching all roles for user — no N+1 queries
Role resolution completes before home screen navigation (blocking but fast)
security requirements
JWT from Supabase Auth used to scope query — RLS enforces row visibility
globalAdmin users must be blocked before any navigation occurs
No role data cached beyond RoleStateManager in-memory state
Supabase service role key must never be used client-side

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Define a sealed class RoleResolutionResult with subtypes: RoleResolved(primaryRole, availableRoles), GlobalAdminBlocked, NoRoleAssigned, RoleResolutionError(message). Precedence map should be a const Map with lower number = higher precedence (coordinator: 0, peerMentor: 1). Use Riverpod's ref.read inside the service — do not use watch here since this is a one-shot operation triggered imperatively post-login. Org/chapter scoping for NHF's multi-chapter structure (up to 5 chapter memberships) should be stored alongside each role in availableRoles so downstream services can filter correctly.

Testing Requirements

Unit tests using flutter_test and mocktail: mock RoleRepository to return various role combinations (single peerMentor, single coordinator, both, globalAdmin only, empty). Assert correct primary role selection and that setActiveRole is called with expected argument. Assert globalAdmin path returns blocked result without calling setActiveRole. Integration test: spin up local Supabase emulator, seed a user with known roles, run resolveRoles and assert RoleStateManager state.

Target 90%+ line coverage of RoleResolutionService.

Component
Role Resolution Service
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.