critical priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

When `computeExpiryState` returns `CertificationStatus.expired`, `PauseManagementService.autoPauseMentor(mentorId, reason: 'certification_expired')` is called
When `computeExpiryState` returns `CertificationStatus.valid` or `expiring_soon`, `autoPauseMentor` is NOT called
The auto-pause call is idempotent: calling it on an already-paused mentor does not create a duplicate pause record and does not throw an exception
A pause event log entry is created with `reason = 'certification_expired'`, `triggered_by = 'system'`, and a timestamp
If `PauseManagementService.autoPauseMentor` throws, the error is caught, logged, and re-thrown as a typed `CertificationPauseException` — the certification expiry status update is NOT rolled back
The integration is covered by a unit test that verifies `autoPauseMentor` is called exactly once for an expired certification regardless of how many times the expiry check is run
Coordinator is notified via the notification system after a successful auto-pause (delegate to NotificationService or emit an event — do not inline notification logic)
The auto-pause logic is triggered both from the mobile client (on foreground expiry check) and from the Supabase Edge Function batch job (server-side nightly check)

Technical Requirements

frameworks
Flutter
Dart
Supabase
apis
Supabase PostgreSQL 15
Supabase Edge Functions (Deno)
data models
certification
assignment
performance requirements
Idempotency check must use a database-level unique constraint on (mentor_id, reason, status) rather than a client-side read-before-write to handle concurrent triggers safely
security requirements
Auto-pause events must be audit-logged with actor_role = 'system' to distinguish from manual coordinator pauses
Only system-scoped Edge Function or authenticated coordinator roles may trigger auto-pause — peer mentors cannot self-pause via this path

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement idempotency at the `PauseManagementService` layer using an `INSERT ... ON CONFLICT DO NOTHING` upsert in Supabase rather than a check-then-insert pattern. In `CertificationManagementService`, the sequence after detecting expiry should be: (1) update certification status in repository, (2) call `autoPauseMentor`, (3) emit pause event for notification delivery. Steps 2 and 3 are fire-and-handle-error — a failure does not undo step 1.

For the Supabase Edge Function batch job, implement a scheduled Deno function (`check-certification-expiry`) that runs nightly via pg_cron or Supabase scheduled functions. The Edge Function queries all certifications where `expires_at < now()` and `status != 'expired'`, calls the same pause logic server-side, and logs each action to the audit table. This ensures certifications are caught even when the mobile client has not been opened.

Testing Requirements

Unit tests: (1) verify `autoPauseMentor` is called when status is `expired`, (2) verify it is NOT called for `valid` and `expiring_soon`, (3) verify idempotency — mock PauseManagementService to return a 'already paused' response and assert no exception is thrown and no duplicate record is created, (4) verify that a PauseManagementService failure does not roll back the expiry status update. Integration test: seed a mentor with an expired certification, run the expiry check twice, assert exactly one pause record exists in the database. Edge Function test: trigger the nightly batch via Supabase CLI and assert pause records are created for all expired mentors without duplicates.

Component
Certification Management Service
service high
Epic Risks (2)
high impact medium prob technical

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.

medium impact low prob technical

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.