high priority low complexity integration pending backend specialist Tier 4

Acceptance Criteria

RoleRepository.clearCache() is automatically called whenever Supabase emits AuthChangeEvent.signedOut or AuthChangeEvent.tokenRefreshed with a null session
Cache invalidation occurs before the app navigates away from authenticated screens — no stale role data is readable after logout event fires
The auth state subscription is established in RoleRepository constructor or init method, not in the UI layer
Subscription is properly disposed when RoleRepository is disposed to prevent memory leaks
If a second user logs in on the same device, the cache from the previous user's session is not accessible
Token expiry (session expiry without explicit logout) also triggers cache invalidation
No explicit clearCache() call is required from any widget or BLoC — invalidation is fully automatic
Edge case: if cache is already empty at logout time, invalidation completes silently without errors

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase Auth onAuthStateChange stream
GoTrueClient.onAuthStateChange
data models
assignment
performance requirements
Cache invalidation must complete synchronously within the auth state change handler — no async gaps that allow stale reads
Subscription overhead must be negligible — single StreamSubscription with no polling
security requirements
Cached role assignments must be wiped from memory on logout to prevent privilege escalation if the device is shared
Session expiry must be treated identically to explicit logout for cache invalidation purposes
No role data should be accessible after AuthChangeEvent.signedOut — validate with a post-logout read test

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Listen to `Supabase.instance.client.auth.onAuthStateChange` in the RoleRepository constructor. Store the StreamSubscription and cancel it in a dispose() method. The handler should check `event == AuthChangeEvent.signedOut || (event == AuthChangeEvent.tokenRefreshed && session == null)` before clearing. Avoid calling clearCache() on every AuthChangeEvent — only on session-ending events.

Since RoleRepository is typically a long-lived singleton in Riverpod, ensure the subscription lifetime matches the repository lifetime. Do not couple this to any UI lifecycle widget. Use a private `_authSubscription` field. Consider a thin `AuthSessionObserver` helper if multiple repositories need the same pattern — but only if it already exists or is needed by another task in the epic.

Testing Requirements

Unit tests: mock the Supabase auth stream and verify that emitting AuthChangeEvent.signedOut triggers RoleRepository.clearCache(). Verify that emitting AuthChangeEvent.tokenRefreshed with a null session also triggers clearCache(). Verify that valid token refresh (non-null session) does NOT clear cache. Integration test: simulate full login → cache population → logout cycle against local Supabase instance and assert cache is empty post-logout.

Test that a second login after logout does not serve stale data from the first session. Verify StreamSubscription is cancelled on repository dispose — use a subscription spy. Target 90%+ branch coverage on the auth-listener logic.

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.