Define CertificationRepository interface and data contracts
epic-certification-management-core-logic-task-001 — Define the abstract CertificationRepository interface that CertificationManagementService will depend on. Specify method signatures for fetching certifications by mentor, saving renewals, updating expiry status, and querying certs due for expiry within a given date window. Ensure data models align with the Supabase schema for certifications.
Acceptance Criteria
Technical Requirements
Implementation Notes
Place the interface in `lib/domain/repositories/certification_repository.dart` following the clean architecture layer convention used in the codebase. The Certification domain model should be a plain Dart class (not a Supabase-specific type) to keep the domain layer portable. Use `sealed class` or `enum` for CertificationStatus so exhaustive switch handling is enforced by the Dart compiler. Avoid including `summaries`, UI hints, or presentation logic in the domain model.
The `getCertificationsExpiringWithin` method should accept a Duration rather than a raw DateTime to keep the caller logic clean — the repository implementation converts to absolute timestamps internally. Define `CertificationRepositoryException` as a base class with subclasses for `NotFound`, `PermissionDenied`, and `NetworkFailure` to enable typed error handling in the service layer.
Testing Requirements
No runtime tests are needed at this stage as this is a pure abstract interface. Write a compile-time contract test that creates a mock implementation of CertificationRepository using `mockito` or `mocktail` and asserts that all required methods are present and return correct types. Include a unit test confirming CertificationStatus enum values match the expected set (valid, expiring_soon, expired). Verify Certification domain model serialization round-trip: fromJson → toJson produces identical output.
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.