high priority medium complexity integration pending integration specialist Tier 6

Acceptance Criteria

When a pause or reactivation event occurs, a push notification is dispatched to all active FCM tokens registered for the assigned coordinator
FCM token resolution queries the device_tokens table filtered by user_id = coordinator_id and is_active = true
Push notification payload includes: title ('Mentor paused' or 'Mentor reactivated'), body (mentor full name), and data payload with deep_link route to CoordinatorPauseRosterScreen filtered to the specific mentor
FCM dispatch is executed server-side via a Supabase Edge Function — the FCM service account key is never present in the Flutter mobile app
The Edge Function is invoked via Supabase's REST API from PauseNotificationService after in-app notification creation (task-011) completes
If a token returns a 'token not found' or 'invalid registration' error from FCM, that token is marked inactive in the device_tokens table (is_active = false)
If FCM returns a rate-limit (429) response, the dispatch is retried once after a 5-second delay; if the retry also fails, the error is logged and the operation completes without throwing
If the coordinator has no registered FCM tokens, the operation completes silently without error
FCM dispatch failure does NOT fail or roll back the pause/reactivation operation
The Edge Function validates the invoking JWT's organisation claim before dispatching to ensure cross-org token leakage is impossible
Push notification tapping on the device navigates the app to the correct deep link route

Technical Requirements

frameworks
Flutter
Supabase Edge Functions (Deno)
apis
Firebase Cloud Messaging (FCM) HTTP v1 API
Supabase Edge Functions REST invocation
Supabase REST (device_tokens table read/update)
data models
device_token
performance requirements
Edge Function cold start + FCM dispatch must complete within 3 seconds for a single coordinator token
Bulk dispatch to multiple coordinator tokens must be parallelised (Promise.all in Deno) — not sequential
security requirements
FCM service account JSON key stored only in Supabase Edge Function environment secrets — never in mobile app binary or Supabase database
Edge Function validates JWT claims (organisation_id) before resolving coordinator tokens to prevent cross-org token access
Device tokens are treated as sensitive — not returned to mobile clients, only used server-side
Notification data payload must contain only non-sensitive data (route string + mentor UUID) — no PII in the FCM payload
TLS enforced for all FCM HTTP v1 API calls from the Edge Function

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Create a Supabase Edge Function named 'dispatch-pause-notification' that accepts a JSON body with { event_type, mentor_id, mentor_name, coordinator_id, organisation_id }. The function should: (1) validate the caller's JWT org claim matches organisation_id, (2) query device_tokens for active tokens for coordinator_id, (3) build FCM messages using the HTTP v1 API format with the service account JWT (obtained via Google's OAuth2 token endpoint using the service account key), (4) dispatch all tokens in parallel using Promise.all, (5) process results and deactivate invalid tokens. In the Flutter PauseNotificationService, invoke the Edge Function using Supabase's functions.invoke() method — this automatically attaches the user's JWT. For deep link routing on notification tap, use the flutter_local_notifications or firebase_messaging onMessageOpenedApp handler to parse the data.deep_link field and push the appropriate named route.

Ensure the retry logic in the Edge Function uses Deno's built-in setTimeout wrapped in a Promise — do not use a blocking sleep.

Testing Requirements

Write unit tests for the Supabase Edge Function covering: (1) correct FCM payload structure built for pause_activated event, (2) correct payload for mentor_reactivated event, (3) invalid/not-found FCM token triggers device_tokens deactivation, (4) rate-limit 429 triggers one retry with delay, (5) second retry failure logs error without throwing, (6) JWT with wrong organisation claim is rejected before token resolution. Write a Flutter unit test for PauseNotificationService asserting: (7) Edge Function is invoked with the correct coordinator_id and event type after pause/reactivation. Test the no-tokens case asserting the function returns without error. Use a mock FCM endpoint in tests — do not call the real FCM API.

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.