high priority low complexity infrastructure pending backend specialist Tier 0

Acceptance Criteria

PushNotificationService exposes a sendToUser(userId: String, title: String, body: String, data: Map<String, dynamic>) method that resolves the FCM token for the user and dispatches a push notification successfully
PushNotificationService exposes a sendToTopic(topic: String, payload: Map<String, dynamic>) method that dispatches a message to all subscribers of the given FCM topic
Android notification channels are registered on service initialization with appropriate name, description, and importance level; notifications appear in the correct channel on Android 8+
iOS notification categories and permissions are requested during initialization; notification payload includes apns configuration for correct banner/badge behavior
When FCM token is not found for a userId, the service throws a typed FcmTokenNotFoundException (not a raw platform exception) and logs a warning without crashing the caller
A MockPushNotificationService implementing the same abstract interface is provided, with in-memory call capture for sendToUser and sendToTopic so tests can assert dispatch without hitting FCM
The service does not expose any FCM-specific types (RemoteMessage, FirebaseMessaging) in its public API — all platform details are encapsulated behind the interface
PushNotificationService is registered as a singleton in the dependency injection layer (Riverpod provider or equivalent) with environment-based switching between real and mock implementations
All method calls are logged at debug level with userId/topic and notification title for traceability without logging sensitive body content

Technical Requirements

frameworks
Flutter
firebase_messaging
flutter_local_notifications
Riverpod
apis
FCM HTTP v1 API (via firebase_messaging SDK)
Android NotificationChannel API
iOS UNUserNotificationCenter
data models
FcmToken
NotificationPayload
NotificationChannel
performance requirements
sendToUser must resolve FCM token and dispatch within 500ms under normal network conditions
Service initialization (channel registration, permission request) must complete before the first frame renders to avoid missed notifications at cold start
Token lookup must use an in-memory cache to avoid redundant Supabase queries on repeated sends to the same user within a session
security requirements
FCM server key must never be embedded in the Flutter client — all server-side dispatch must go through a Supabase Edge Function or backend proxy
Notification data payload must not include PII (names, contact details) directly; use reference IDs only and let the client fetch details on tap
Android notification channel ID must not be predictable/guessable by third parties to prevent notification spoofing

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Define an abstract PushNotificationService interface first (abstract class in Dart), then provide two concrete implementations: FirebasePushNotificationService and MockPushNotificationService. This inversion of control is what makes task-011 testable. For FCM token resolution, call the existing FCM token manager (do not duplicate token storage logic). For Android channels, call flutter_local_notifications' AndroidFlutterLocalNotificationsPlugin.createNotificationChannel() during app startup — do this in main.dart or an AppInitializer, not lazily on first send.

For iOS, call FirebaseMessaging.instance.requestPermission() with alert: true, badge: true, sound: true. Wrap all FirebaseMessaging calls in try/catch and convert platform exceptions to domain exceptions before rethrowing. The sendToTopic method should only be used for broadcast scenarios (e.g., org-wide announcements); prefer sendToUser for targeted reminder notifications to maintain auditability.

Testing Requirements

Unit tests (task-011) cover the service in isolation using MockPushNotificationService. For this implementation task, verify manually on a physical Android device (channel creation) and iOS simulator (permission prompt) during development. Ensure the mock implements the identical abstract interface so no test can accidentally import the real service. Confirm the Riverpod provider switch between real/mock is driven by a compile-time or environment flag, not runtime branching that could ship the mock to production.

Component
Push Notification Service
infrastructure medium
Epic Risks (3)
high impact medium prob integration

Adding last_contact_date to the assignments table may conflict with existing RLS policies or trigger-based logic that monitors the assignments table. If the migration is not carefully reviewed, existing assignment management features could break in production.

Mitigation & Contingency

Mitigation: Review all existing triggers, policies, and foreign key constraints on the assignments table before writing the migration. Run the migration against a staging Supabase instance with production-like data and execute the full existing test suite before merging.

Contingency: Roll back the migration using Supabase's versioned migration history. Apply the schema change as an additive-only migration (nullable column with default) to ensure zero downtime and reversibility.

medium impact medium prob dependency

The PushNotificationService wraps an existing FCM integration whose internal API contract may have changed or may not expose the payload formatting required for deep-link CTAs. Misalignment discovered late delays the dispatch service epic.

Mitigation & Contingency

Mitigation: Before implementing the wrapper, read the existing push notification integration code and confirm the method signatures, payload structure, and token management model. Agree on a stable interface contract in a shared Dart abstract class.

Contingency: If the existing service is incompatible, implement a thin adapter layer that translates reminder payloads to the existing service's format, isolating the reminder feature from upstream changes.

high impact low prob security

Incorrect RLS policies on notification_log could allow coordinators to read reminder records belonging to peer mentors in other chapters, exposing sensitive assignment information across organisational boundaries.

Mitigation & Contingency

Mitigation: Write explicit RLS policies with integration tests that assert cross-chapter queries return zero rows. Use Supabase's built-in auth.uid() and join through the org membership tables to scope all queries.

Contingency: If a policy gap is discovered post-merge, immediately disable the affected table's SELECT policy, deploy a corrected policy, and audit recent queries in Supabase logs for any cross-boundary reads.