critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

notifyCoordinatorOfPause(mentorId, pauseRecord) resolves all coordinator(s) for the mentor's chapter(s) and dispatches both an in-app notification row (persisted to Supabase notifications table) and a push notification (FCM) to each coordinator's registered device tokens
notifyCoordinatorOfReactivation(mentorId) dispatches in-app + push notifications to coordinator(s) indicating the mentor is active again
buildDeepLink(mentorId) returns a Uri string in the format 'app://coordinator/pause-roster?highlight=<mentorId>' that routes to the coordinator's pause roster screen with the mentor highlighted
When a mentor belongs to multiple chapters, all coordinators across all chapters are notified — no coordinator is missed
When a coordinator has no registered FCM device token, the push notification is silently skipped and only the in-app notification is created — no exception thrown
Push notification payload includes: title (e.g. 'Peer mentor paused'), body (mentor display name + reason if provided), and data map with deepLink and mentorId
In-app notification row includes: recipient_id, type ('mentor_paused' | 'mentor_reactivated'), payload (JSON with mentorId, deepLink), is_read (false), created_at
All notification dispatches complete within 5 seconds on p95
Service is unit-testable via injected repository and FCM client abstractions
Failed FCM dispatch (network error) is logged and does not throw — in-app notification still succeeds

Technical Requirements

frameworks
Flutter
firebase_messaging (FCM)
Riverpod
apis
Supabase PostgREST (notifications table, chapter_memberships table, device_tokens table)
Firebase Cloud Messaging (FCM) HTTP v1 API via Supabase Edge Function or direct HTTP call
data models
pause_records
notifications
chapter_memberships
device_tokens
user_profiles
performance requirements
Coordinator resolution query must use indexed chapter_memberships join — not a full table scan
FCM dispatch should be fire-and-forget (non-blocking) — use Future.unawaited or isolate if needed
In-app notification insert must complete before method returns
security requirements
FCM server key must NEVER be stored in Flutter client code — all FCM dispatch must go through a Supabase Edge Function or backend service
Notification payload must not include sensitive personal information beyond display name
Device tokens must be scoped to authenticated user and invalidated on logout

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Structure the service to first call a resolveCoordinators(mentorId) private method that queries chapter_memberships for the mentor, then fetches coordinator user_ids for each chapter. This separation makes testing easier. FCM dispatch should be implemented as a Supabase Edge Function (Deno) invoked via supabase.functions.invoke() — this keeps the FCM server key entirely server-side and avoids exposing it in client code. The Edge Function receives a list of device tokens and a notification payload.

Define an abstract NotificationDispatcher interface with a concrete FCMNotificationDispatcher and a MockNotificationDispatcher for tests. The in-app notifications table must already exist (check with database_specialist if schema needs extending). Deep links must match the router configuration — coordinate with the navigation/routing team to confirm the exact path schema before hardcoding.

Testing Requirements

Write unit tests using flutter_test with mocked repository, mocked FCM client, and mocked Supabase client. Test: (1) notifyCoordinatorOfPause resolves 1 coordinator and dispatches 1 in-app + 1 push notification. (2) notifyCoordinatorOfPause with 2 chapters dispatches notifications to both coordinators. (3) Coordinator with no device token receives in-app notification only, no exception thrown.

(4) FCM HTTP failure is caught, logged, and does not prevent in-app notification creation. (5) notifyCoordinatorOfReactivation dispatches correct notification type. (6) buildDeepLink returns correctly formatted URI with mentorId parameter. (7) Mentor with zero chapters results in no notifications and logs a warning.

Aim for 90%+ branch coverage on service class.

Component
Pause Notification Service
service medium
Epic Risks (3)
medium impact medium prob technical

Concurrent status transitions (e.g., coordinator and automated scheduler both attempting to update the same mentor's status simultaneously) may produce race conditions or inconsistent state in the database, leading to audit log gaps or incorrect notifications.

Mitigation & Contingency

Mitigation: Implement all status transitions as atomic Postgres RPC functions with optimistic locking (version column or updated_at check). Use database-level constraints rather than application-level guards as the final enforcement point.

Contingency: Add a compensation job that reconciles status and log table consistency on each nightly scheduler run, surfacing any discrepancies to coordinator dashboards.

medium impact medium prob integration

The coordinator-to-mentor assignment relationship may not always be 1:1 or may be stale (coordinator reassigned after a pause was set), causing notifications to be sent to the wrong coordinator or not sent at all.

Mitigation & Contingency

Mitigation: Query the assignment relationship at notification dispatch time rather than caching it at pause creation time. Add a fallback to notify the chapter administrator if no active coordinator assignment exists.

Contingency: Log all undeliverable notification attempts with the originating mentor ID so administrators can manually follow up, and surface undelivered notification counts on the coordinator dashboard.

medium impact low prob technical

The CoordinatorPauseRosterScreen may load slowly for coordinators managing large rosters with many concurrent certification expiry queries, degrading usability on low-bandwidth mobile connections.

Mitigation & Contingency

Mitigation: Use a single Supabase RPC that joins mentor status, certification expiry, and assignment data in one query rather than N+1 individual calls. Implement pagination with a configurable page size and skeleton loading states.

Contingency: Add an offline cache of the last-fetched roster state using Riverpod with SharedPreferences, ensuring coordinators can at minimum view stale data when connectivity is poor.