critical priority medium complexity backend pending backend specialist Tier 3

Acceptance Criteria

RoleRepository exposes a single public method: Future<List<RoleAssignment>> getRolesForCurrentUser()
On first call for a session, getRolesForCurrentUser() calls SupabaseRoleProvider.fetchRolesForCurrentUser() and maps results to List<RoleAssignment>
Subsequent calls within the same session return the cached list without calling SupabaseRoleProvider again
Cache is keyed by session ID (obtained from the Supabase auth session) so different sessions never share cached data
When the Supabase auth session changes (sign-in, sign-out, token refresh with new session ID), the cache for the old session ID is invalidated
After cache invalidation, the next call to getRolesForCurrentUser() fetches fresh data from SupabaseRoleProvider
RoleRepository accepts SupabaseRoleProvider and a Supabase auth stream (or SupabaseClient) via constructor injection
RoleRepository implements an abstract IRoleRepository interface to allow mocking in higher-layer tests
getRolesForCurrentUser() returns an empty list (not throws) when the user has no active role assignments
Unit tests cover: cache hit, cache miss, cache invalidation on session change, empty role list, provider error propagation

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
Riverpod
apis
Supabase Auth (onAuthStateChange stream)
SupabaseRoleProvider (internal)
data models
RoleAssignment
RoleName
RawRoleRecord
performance requirements
Cache hit must return in under 1ms (synchronous map lookup)
Cache miss must complete within 2 seconds (network-bound)
Memory footprint of the cache is bounded — at most one session's roles are cached at a time (old sessions are evicted on change)
security requirements
Cache must be invalidated on sign-out to prevent role data from a previous user session being accessible to a newly signed-in user
Session ID used as cache key must come from the Supabase session object — never from user-controlled input
Cached data must not be persisted to disk — in-memory only to prevent sensitive role data leaking via device storage

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use a Map> as the in-memory cache where the key is the session ID string. Subscribe to supabaseClient.auth.onAuthStateChange in the constructor using a StreamSubscription stored as a field, and call _cache.clear() (or remove the old session key) in the listener. Dispose the subscription in a dispose() method. Expose IRoleRepository as an abstract class with getRolesForCurrentUser() and dispose() to make Riverpod provider overrides straightforward.

Register as a Riverpod Provider (not StateNotifier) since it has its own internal state management. Place in lib/data/role/role_repository.dart. Map RawRoleRecord → RoleAssignment using RoleAssignment.fromJson(rawRecord.toJson()) to avoid duplicating mapping logic.

Testing Requirements

Write flutter_test unit tests with a mock SupabaseRoleProvider. Test cases: (1) first call fetches from provider and caches — verify provider called once; (2) second call hits cache — verify provider still called only once total; (3) emit a session change event on the auth stream — verify cache is cleared and next call fetches from provider again (provider now called twice total); (4) provider returns empty list — verify getRolesForCurrentUser() returns empty list without throwing; (5) provider throws RoleProviderException — verify exception propagates to caller. Use a StreamController to simulate Supabase auth state changes in tests.

Component
Role Repository
data medium
Epic Risks (2)
medium impact medium prob technical

The get_my_roles RPC call adds a network round-trip immediately after login, potentially increasing the time before the home screen renders. If Supabase RPC is slow or the roles table lacks proper indexing, users with multiple org affiliations could experience noticeable delays.

Mitigation & Contingency

Mitigation: Index user_roles on user_id and org_unit_id. Use JWT claim extraction as the primary fast path; fall back to the RPC only when claims are absent or stale. Set a 3-second timeout with a fallback to cached roles.

Contingency: If RPC latency exceeds acceptable thresholds in production, pre-fetch and embed roles into the session JWT at login time via a Supabase Auth hook, eliminating the post-login RPC entirely.

high impact medium prob integration

Users who belong to multiple organizations (e.g., a coordinator in one NHF chapter who is also a peer mentor in another) may have conflicting role assignments. The repository layer must correctly scope roles to the active organization context set during the organization selection step, or it could return roles from the wrong org.

Mitigation & Contingency

Mitigation: Always filter role queries by the active org_unit_id stored in the tenant session. Write integration tests that simulate multi-org users and verify only the correct org's roles are returned.

Contingency: If org-scoping logic is found to be incorrect during QA, add an explicit org_unit_id parameter to get_my_roles RPC and require the client to always pass the active org context, making the scoping explicit rather than inferred.