critical priority high complexity backend pending backend specialist Tier 2

Acceptance Criteria

`CertificationManagementService` class is implemented with `CertificationRepository`, `PauseManagementService`, and `HLFDynamicsSyncService` injected via constructor
`createCertification(CreateCertificationRequest request)` persists a new certification, sets initial status to `valid`, and returns the created record
`recordRenewal(String certificationId, DateTime newExpiryDate)` updates the certification's `renewed_at` and `expires_at`, recomputes expiry state, triggers Dynamics sync, and returns the updated record
`computeExpiryState(Certification cert)` returns `CertificationStatus.expired` if `expires_at` is in the past, `CertificationStatus.expiring_soon` if within 30 days, and `CertificationStatus.valid` otherwise
`initiateCourseEnrolment(String certificationId)` calls an external enrolment API or emits an enrolment event and returns an enrolment confirmation
All methods that mutate state execute their repository write, pause check, and Dynamics sync in a defined order with clear rollback semantics on partial failure
Service does not hold any BLoC, widget, or BuildContext references
All public methods are `async` and return typed Futures
Thrown exceptions are typed domain exceptions, not raw Supabase or Dart core exceptions
Unit tests pass with mocked collaborators for all happy-path and failure-path scenarios

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgreSQL 15
Microsoft Dynamics 365 REST API
data models
certification
performance requirements
Service methods must complete within 3 seconds under normal network conditions
No blocking synchronous calls — all I/O via async/await
security requirements
Service must validate that the calling user has permission to modify the target certification before delegating to the repository
Dynamics sync credentials handled server-side only — service calls an Edge Function, never Dynamics directly from mobile

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Place in `lib/domain/services/certification_management_service.dart`. The service is a pure domain-layer class — no Flutter or Supabase imports. Collaborator orchestration order in `recordRenewal` should be: (1) persist renewal to repository, (2) recompute expiry state, (3) if now valid, call Dynamics sync to restore visibility, (4) if still expired, call PauseManagementService. Never reverse this order or make Dynamics sync a precondition for local persistence.

For transactional semantics without a distributed transaction manager, use a compensating-action pattern: if Dynamics sync fails after a successful repository write, queue the sync for retry rather than rolling back the local write. Implement `initiateCourseEnrolment` as a thin delegation to a `CourseEnrolmentService` or a Supabase Edge Function — do not hardcode enrolment logic here. Register the service as a Riverpod `Provider` with all dependencies injected, keeping it lazily initialized.

Testing Requirements

Write unit tests using `mocktail` for all four public methods. For each method test: happy path with all collaborators returning success, repository failure path (assert exception propagation), pause service failure path (assert local state is not corrupted), and Dynamics sync failure path (assert local update is preserved and error is surfaced). Test `computeExpiryState` with boundary values: exactly at expiry date, one day before expiry, 30 days before expiry, 31 days before expiry. Verify that `recordRenewal` calls collaborators in the correct sequence.

Aim for 95% branch coverage on the service class. Integration test: execute `createCertification` followed by `recordRenewal` against local Supabase and assert database state is consistent.

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.