critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Abstract RoleRepository defines fetchRolesForUser(String userId) → Future<List<RoleAssignment>>, fetchPrimaryRole(String userId) → Future<UserRole?>, and fetchAllRolesForUser(String userId) → Future<List<RoleAssignment>>
fetchPrimaryRole returns the highest-privilege role following the hierarchy: globalAdmin > orgAdmin > coordinator > peerMentor; returns null if the user has no roles
SupabaseRoleProvider implements RoleRepository by calling the get_my_roles Supabase RPC or querying user_roles table with RLS-enforced SELECT
A user assigned to multiple chapters (up to 1,400 NHF chapters) receives all their RoleAssignments without truncation — no hardcoded result limits
SupabaseRoleProvider surfaces typed exceptions (RoleFetchException) rather than raw PostgrestException or dynamic errors
fetchAllRolesForUser returns all role assignments across all orgs and chapters for a given user ID
RLS ensures a user cannot fetch roles for a different userId — the Supabase query is scoped to auth.uid() server-side
SupabaseRoleProvider correctly parses the chapterId field as nullable — rows without a chapter assignment do not cause parse errors

Technical Requirements

frameworks
Flutter
Supabase Flutter SDK
Dart
apis
Supabase RPC get_my_roles or user_roles table SELECT
Supabase Auth (for RLS JWT context)
data models
assignment
performance requirements
A single RPC call must handle all role assignments for a user with membership in up to 1,400 chapters — no N+1 queries per chapter
Query must complete in under 2 seconds on a 4G mobile connection for a user with 10 role assignments
security requirements
RLS on user_roles table must prevent cross-user access — server enforces auth.uid() = user_id constraint
No userId value from the client should override the RLS filter — use auth.uid() in the RPC, not a client-supplied parameter
RoleFetchException must not expose raw SQL or internal Supabase error details to the UI layer

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Prefer a Supabase RPC function (`get_my_roles`) over a direct table query — the RPC can enforce auth.uid() server-side and return a well-typed structure. If the RPC does not yet exist, use `supabase.from('user_roles').select().eq('user_id', supabase.auth.currentUser!.uid)` as a fallback, but document the RPC requirement. For priority resolution in fetchPrimaryRole, define a static `_rolePriority` map in SupabaseRoleProvider rather than a switch statement for O(1) lookup. To handle NHF's 1,400-chapter scale, ensure the Supabase query does not use `.limit()` — the default Supabase Flutter SDK returns up to 1,000 rows; use `.select().range(0, 9999)` or configure the server-side function to return all rows.

Define `RoleFetchException` with a `message` field and optional `code` field for structured error handling downstream.

Testing Requirements

Unit tests with a mocked Supabase client: test that fetchRolesForUser parses a valid multi-row JSON response into a list of RoleAssignment. Test that a response with a null chapterId is parsed correctly. Test that fetchPrimaryRole returns globalAdmin when the user has both coordinator and globalAdmin assignments. Test that fetchPrimaryRole returns null for an empty assignment list.

Test that a PostgrestException from Supabase is caught and rethrown as RoleFetchException with a sanitized message. Integration tests against local Supabase: seed a user with 3 role assignments across different chapters and verify all 3 are returned. Verify RLS: user A cannot retrieve user B's roles even if user B's userId is passed as a parameter.

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.