Delegate reminder dispatch to PushNotificationService and log results
epic-certification-management-core-logic-task-011 — Complete CertificationReminderService by wiring the payload dispatch to PushNotificationService. After a successful send, write a record to cert_notification_log via CertificationRepository. Handle partial failures gracefully — log successes individually so failed sends are retried on next cron invocation without re-sending already-delivered notifications.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Implement the dispatch loop using Stream.fromIterable(candidates).asyncMap() with a throttle, or more practically, chunk the list into groups of 10 and use Future.wait() per chunk — this gives controlled concurrency without a full async stream overhead. The per-record log write (not batch) is critical for fault tolerance: if the Edge Function is killed at candidate 150 of 200, the first 150 are already logged as delivered and will be skipped on the next cron run via deduplication. Resolve all device tokens in a single SELECT WHERE user_id IN (...) before the loop and build a Map
The coordinator grouping: iterate the Map
Testing Requirements
Unit tests (flutter_test): (1) Mock PushNotificationService returning success for all candidates; assert cert_notification_log receives one 'delivered' INSERT per candidate. (2) Mock PushNotificationService failing for candidates 2 and 4 in a list of 5; assert loop completes all 5, candidates 2 and 4 logged as 'failed', others as 'delivered', DispatchSummary has successCount=3 and failureCount=2. (3) Mock cert_notification_log INSERT failing after a successful push; assert no re-dispatch occurs and error is logged. (4) Assert coordinator payload dispatched once per coordinator, not once per underlying mentor candidate.
(5) Simulate 200 candidates with 10-concurrent dispatch; assert all complete within test timeout. Integration tests against local Supabase: verify cert_notification_log entries persist correctly; verify idempotency — re-running with the same candidates after deduplication results in zero new dispatches. Coverage target: 90% branch coverage on dispatch loop.
The auto-pause workflow requires CertificationManagementService to call PauseManagementService and HLFDynamicsSyncService in the same logical transaction. If PauseManagementService succeeds but the Dynamics webhook fails, the mentor is paused locally but remains visible on the HLF portal.
Mitigation & Contingency
Mitigation: Implement a saga pattern: write a pending sync event to the database before calling Dynamics, and have a background retry job consume pending events. This guarantees eventual consistency even if the webhook fails transiently.
Contingency: If the Dynamics sync fails after auto-pause, surface an explicit coordinator alert in the dashboard indicating 'Dynamics sync pending — mentor may still be visible on portal'. Allow manual retry from coordinator UI.
If the nightly cron job runs concurrently (e.g., due to infra retry), CertificationReminderService could dispatch duplicate notifications to mentors before the cert_notification_log insert is visible to the second invocation.
Mitigation & Contingency
Mitigation: Use Supabase's upsert with a unique constraint on (mentor_id, threshold_days, cert_id) in cert_notification_log. The second concurrent insert will fail gracefully and the duplicate dispatch will be skipped.
Contingency: If duplicate notifications do reach mentors, add a post-dispatch dedup check and include a 'you may receive this notification again' disclaimer until the constraint is deployed.