high priority medium complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

Abstract class PushNotificationService is defined with method signatures: sendNotification(String recipientId, String title, String body, Map<String,String> payload) → Future<void> and sendBatchNotifications(List<NotificationRequest> requests) → Future<BatchNotificationResult>
FcmPushNotificationService implements PushNotificationService using firebase_messaging and sends notifications via the Supabase Edge Function (or FCM HTTP v1 API) — not directly from the Flutter client
NotificationRequest is a Dart data class with fields: recipientId, title, body, payload, idempotencyKey
BatchNotificationResult contains counts: sent, failed, skipped (deduplicated), and a list of failed recipientIds
FCM token is retrieved from a FcmTokenManager that stores the current device token in Supabase (fcm_tokens table or user profile) and listens to FirebaseMessaging.instance.onTokenRefresh to keep it current
NoOpPushNotificationService (stub) returns immediately without making any network calls, used in flutter_test environments
sendBatchNotifications processes recipients in chunks of 500 (FCM limit) and logs progress
All notification sends are logged to cert_notification_log (via the repository) using the provided idempotencyKey before the FCM call, and the log entry is updated with the result
Token refresh listener is registered on app startup and unregistered on dispose
Unit tests cover: successful send, FCM error handling, batch chunking, and no-op stub behavior

Technical Requirements

frameworks
Flutter
firebase_messaging
Riverpod (DI)
Supabase Edge Functions (for server-side FCM dispatch)
apis
FCM HTTP v1 API (via Edge Function)
Supabase Edge Functions REST endpoint
FirebaseMessaging Flutter SDK
data models
NotificationRequest
BatchNotificationResult
FcmToken (stored in Supabase)
performance requirements
sendBatchNotifications for 500 recipients must complete in under 30 seconds including network round-trips
FCM token refresh must not block the UI thread
Batch chunking must respect FCM's 500-message-per-request limit
security requirements
FCM server key must NEVER be stored in the Flutter app — dispatch must go through a Supabase Edge Function that holds the key as a secret
Notification payload must not contain PII; use recipientId references only
idempotencyKey must be validated before sending to prevent replay of notifications
Edge Function must authenticate callers using the Supabase service role JWT, not a user JWT

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Do not call FCM directly from the Flutter client — this exposes the server key. Instead, create a Supabase Edge Function (send-push-notification) that receives a list of NotificationRequest objects, calls FCM HTTP v1 API using the server key stored as a Supabase secret, and returns a BatchNotificationResult. The Flutter FcmPushNotificationService calls this Edge Function via http.post. Store FCM tokens in a supabase table (e.g.

user_fcm_tokens: user_id, token, platform, updated_at) updated on every token refresh. Use Riverpod to provide PushNotificationService so tests can override with NoOpPushNotificationService. Register the onTokenRefresh listener in an AppLifecycleObserver or in the Riverpod provider's build method. Log all notification attempts to cert_notification_log before the FCM call so failures are still recorded.

Testing Requirements

Unit tests (flutter_test): inject NoOpPushNotificationService and verify that sendNotification and sendBatchNotifications complete without error; test that FcmPushNotificationService correctly chunks a list of 1200 recipients into three batches of 500/500/200; mock the Edge Function HTTP call and assert the correct request body is sent; test that a failed FCM call for one recipient does not abort the rest of the batch. Integration tests: use a local Firebase emulator (if available) or a dedicated test FCM project to verify token registration and a successful test notification delivery to a test device. TestFlight builds should include a debug notification trigger screen for manual QA.

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

HLF Dynamics portal webhook API contract may be undocumented, subject to change, or require a separate authentication flow not yet agreed upon with HLF. If the contract changes post-implementation, the sync service silently fails and expired peer mentors remain on public listings.

Mitigation & Contingency

Mitigation: Obtain the official Dynamics webhook specification and test credentials from HLF before starting HLFDynamicsSyncService implementation. Agree on a versioned webhook contract and request a staging endpoint for integration testing.

Contingency: If the contract is unavailable, stub the sync service behind a feature flag and ship without Dynamics sync initially. Queue sync events locally and replay once the contract is confirmed.

high impact medium prob security

Supabase RLS policies for certifications must correctly scope data to the coordinator's chapter without leaking cross-organisation data, particularly complex in multi-chapter membership scenarios. A misconfigured policy could expose peer mentor PII to wrong coordinators.

Mitigation & Contingency

Mitigation: Write RLS policies against the established org-hierarchy schema used by other tables. Peer review all policies before migration deployment. Add integration tests that assert cross-organisation data isolation using test accounts with different org scopes.

Contingency: If a policy gap is discovered post-merge, immediately disable the affected query endpoint and apply a hotfix migration. Audit access logs in Supabase for any cross-org data access events.

medium impact low prob technical

Storing renewal history as a JSONB field rather than a normalised table simplifies queries but makes retrospective schema changes (adding fields to history entries) harder and could cause issues if history grows very large for long-tenured mentors.

Mitigation & Contingency

Mitigation: Define a versioned JSONB entry schema (include a schema_version field in each entry) so future migrations can transform old entries. Add a size guard in the repository to warn if renewal_history exceeds 500 entries.

Contingency: If JSONB approach proves limiting, add a normalised certification_renewal_events table and migrate history entries in a background job, keeping the JSONB field as a read cache.