critical priority low complexity infrastructure pending backend specialist Tier 2

Acceptance Criteria

CoordinatorRoleGuard is implemented as a Riverpod Provider (or family) and is accessible throughout the app widget tree
requireCoordinator() synchronously checks the current user's role and throws InsufficientPermissionsException with a descriptive message when the role is not 'coordinator'
isCoordinator exposes a bool stream (or AsyncValue<bool>) that emits true only when the authenticated user holds the coordinator role
Guard is wired into all route guards for coordinator-only screens so that non-coordinators are redirected to a no-access screen automatically
Guard is called as the first operation at every repository call site that requires coordinator privileges, before any DB or network call
InsufficientPermissionsException is a typed, catchable exception class with a human-readable message field
When the authenticated user's role changes (e.g., session refresh), isCoordinator stream emits the updated value within one event loop tick
Unit tests pass for coordinator role (returns true / does not throw) and non-coordinator roles (returns false / throws)
No raw role string comparisons exist outside the guard — all role checks are delegated to CoordinatorRoleGuard

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase Auth (user metadata / JWT claims for role)
data models
UserRole enum (coordinator, peer_mentor, org_admin)
InsufficientPermissionsException
performance requirements
Role check must complete synchronously from in-memory state — no async DB call per guard invocation
isCoordinator stream must not cause widget rebuilds on unrelated state changes
security requirements
Role must be read from a trusted source (Supabase JWT claim or server-side verified session), never from mutable local state alone
Guard must be stateless with respect to caching — always reads latest session role to prevent stale permission elevation
InsufficientPermissionsException must not leak internal role details to UI layer beyond 'access denied' messaging

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a Riverpod Provider backed by a watchable auth state provider (already established in task-003). Keep CoordinatorRoleGuard a plain Dart class injected via Riverpod — do not make it a widget. The isCoordinator stream can be derived with StreamProvider.family or a select() on the auth state provider to avoid unnecessary rebuilds. For requireCoordinator(), prefer a synchronous read (ref.read) since it is called inside service/repository methods, not inside build().

Define InsufficientPermissionsException in a shared exceptions file to keep it reusable across guards. When wiring route guards, use GoRouter's redirect callback with a ref.read(coordinatorRoleGuardProvider).isCoordinatorSync getter to keep routing logic simple. Avoid duplicating the guard logic in the UI layer — the BLoC/service should throw and the UI should catch.

Testing Requirements

Unit tests using flutter_test with a ProviderContainer and mock auth state notifier. Test matrix: (1) user with coordinator role — requireCoordinator() does not throw, isCoordinator emits true; (2) user with peer_mentor role — requireCoordinator() throws InsufficientPermissionsException, isCoordinator emits false; (3) user with org_admin role — same as peer_mentor; (4) unauthenticated / null session — requireCoordinator() throws, isCoordinator emits false; (5) role change mid-session — isCoordinator stream emits correct new value. Route guard wiring covered by widget tests that attempt navigation to a coordinator-only route as a non-coordinator and assert redirect to no-access screen.

Component
Coordinator Role Guard
infrastructure low
Epic Risks (3)
high impact medium prob technical

The activities table migration adding registered_by and attributed_to columns may conflict with existing RLS policies or FK constraints if the user profile table structure differs from assumptions, blocking all subsequent epics.

Mitigation & Contingency

Mitigation: Review existing activities table schema and RLS policies before writing the migration. Run the migration against a staging database clone first. Write rollback scripts alongside the migration.

Contingency: If migration fails in staging, isolate the conflict with a targeted schema audit, adjust FK references or RLS policy scope, and re-run before touching production.

high impact medium prob security

The RLS policy must filter proxy inserts to the coordinator's chapter scope. If the chapter-scope resolver pattern differs between organisations (multi-chapter coordinators in NHF vs single-chapter in HLF), the policy may be too broad or too restrictive.

Mitigation & Contingency

Mitigation: Design the RLS policy to accept a coordinator's full set of assigned chapter IDs (array) rather than a single chapter_id. Validate the policy against NHF multi-chapter test fixtures during the integration test phase.

Contingency: If the policy is found to be incorrect after deployment, introduce a server-side validation edge function as a safety net while the RLS policy is corrected.

medium impact low prob technical

The bulk_register_activities RPC function may time out or cause lock contention when inserting large participant batches (e.g. 40+ peer mentors in a single group session), degrading the user experience.

Mitigation & Contingency

Mitigation: Benchmark the RPC function with 50-participant batches during development. Use unnest-based bulk insert rather than row-by-row PL/pgSQL loops. Set a reasonable statement_timeout.

Contingency: If performance is insufficient, split the client-side submission into chunks of 20 participants with progress feedback, rather than a single RPC call.