high priority medium complexity backend pending backend specialist Tier 5

Acceptance Criteria

When PauseManagementService.activatePause() completes successfully, PauseNotificationService persists a notification record with type='pause_activated' to the notifications table
When PauseManagementService.reactivateMentor() completes successfully, PauseNotificationService persists a notification record with type='mentor_reactivated' to the notifications table
Each notification record includes: notification_type, mentor_name, mentor_id, coordinator_id, created_at (server timestamp), and deep_link (route string to CoordinatorPauseRosterScreen filtered to the specific mentor)
Notifications are only visible to the coordinator assigned to the mentor — role-aware filtering via Supabase RLS policy on the notifications table
Notifications appear in the existing in-app notification centre UI without requiring changes to the notification centre renderer (i.e., the new notification types are handled by the existing generic renderer)
If the notification insert fails, the failure is logged but does NOT roll back or fail the pause/reactivation operation itself — notification creation is non-blocking
Notifications are marked as read when the coordinator taps them and navigates to the CoordinatorPauseRosterScreen
The notification centre shows an unread count badge that increments on new pause/reactivation notifications
Supabase RLS prevents one coordinator from reading another coordinator's pause notifications

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase REST (notifications table insert)
Supabase Realtime (for live notification badge updates)
data models
assignment
performance requirements
Notification insert must complete within 500ms to avoid blocking the pause/reactivation UX flow
Notification centre badge count must update within 2 seconds via Supabase Realtime subscription
security requirements
Supabase RLS policy on notifications table: coordinator_id must match auth.uid() for SELECT and UPDATE (mark-as-read)
INSERT must be restricted to service role or authenticated user acting on their own mentor relationship — prevent cross-org notification injection
deep_link values must be validated as internal app routes — no external URLs stored in notification records
No mentor PII beyond first/last name stored in the notification payload
ui components
Notification centre badge (unread count update)
Notification list item renderer (reuse existing generic renderer)

Execution Context

Execution Tier
Tier 5

Tier 5 - 253 tasks

Can start after Tier 4 completes

Implementation Notes

Implement notification persistence as a fire-and-forget call inside PauseNotificationService using a try/catch that logs failures to the app's error reporting service without rethrowing. Do not use a database transaction that couples notification creation to the pause state update — they should be independent operations. The deep_link should follow the app's existing internal routing convention (e.g., '/coordinator/pause-roster?mentorId=uuid'). For Supabase Realtime badge updates, subscribe to the notifications channel filtered by coordinator_id == auth.uid() AND read_at IS NULL in the notification centre BLoC — increment the unread count on INSERT events and decrement on UPDATE (read_at set).

Use server-side default for created_at (now()) — do not send a client timestamp.

Testing Requirements

Write unit tests for PauseNotificationService covering: (1) correct notification record structure built for pause_activated type, (2) correct structure for mentor_reactivated type, (3) notification insert failure is caught and does not throw or propagate to the caller, (4) deep_link string correctly encodes the mentor ID as a query parameter. Write integration tests verifying: (5) after activatePause() succeeds, a notification row exists in the notifications table with correct coordinator_id, (6) RLS prevents a different coordinator from querying the inserted notification. Test the mark-as-read flow by asserting the read_at column is set after the coordinator navigates via the deep link.

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.