critical priority low complexity frontend pending frontend specialist Tier 0

Acceptance Criteria

Events file (`certification_event.dart`) defines an abstract sealed base class `CertificationEvent extends Equatable` and three concrete subclasses: `LoadCertification({required String certificationId})`, `RecordRenewal({required String certificationId, required DateTime renewalDate})`, `EnrolInCourse({required String certificationId, required String courseId})`
States file (`certification_state.dart`) defines an abstract sealed base class `CertificationState extends Equatable` and exactly six concrete subclasses: `CertificationInitial`, `CertificationLoading`, `CertificationLoaded({required Certification certification})`, `CertificationError({required String message})`, `RenewalSuccess({required Certification updatedCertification})`, `EnrolmentSuccess({required String courseId, required Certification updatedCertification})`
Every event and state class overrides `get props` returning all meaningful fields so that Equatable value equality works correctly in tests
All classes use `const` constructors where no mutable fields are present
`CertificationLoaded`, `RenewalSuccess`, and `EnrolmentSuccess` carry a typed `Certification` domain model — not raw Maps
A barrel file (`certification_bloc.dart` or `certification_bloc_exports.dart`) exports all events, states, and the BLoC class (stub acceptable at this stage)
The file compiles with zero warnings under `dart analyze` with strict mode enabled
No business logic or service calls exist in this task — pure data class definitions only

Technical Requirements

frameworks
Flutter
BLoC (flutter_bloc package)
Equatable
data models
Certification (domain model — id, status, expiry_date, course_id, peer_mentor_id)
performance requirements
All event and state classes must be immutable — no mutable fields
Equatable props lists must not include computed or derived values to avoid unintended equality mismatches
security requirements
State classes must not expose raw Supabase row maps — always use typed domain models to prevent accidental PII leakage via toString()

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart's `sealed` keyword (available from Dart 3.0) on the base event and state classes — this enables exhaustive pattern matching in the BLoC and produces compiler warnings if a new event/state is added without being handled. File structure convention: `lib/features/certification/bloc/certification_event.dart`, `certification_state.dart`, `certification_bloc.dart`. The `Certification` domain model should be defined in `lib/features/certification/models/certification.dart` separately — do not inline it in the state file, as it will be reused by the repository and UI layers. If the `Certification` model does not yet exist, create a minimal version with the fields needed by the states and add a TODO comment marking it for enrichment by the service layer task.

Follow the project's existing BLoC naming conventions — check adjacent BLoC modules for file and class naming patterns before writing.

Testing Requirements

Write pure Dart unit tests (no widget tree needed). Test 1: assert `LoadCertification('id-1') == LoadCertification('id-1')` returns true. Test 2: assert `LoadCertification('id-1') != LoadCertification('id-2')` returns true. Test 3: assert `CertificationLoaded(certification: c1) == CertificationLoaded(certification: c1)` where c1 uses Equatable.

Test 4: assert `CertificationError(message: 'err') != CertificationLoading()`. Test 5: assert `RenewalSuccess.props` contains `updatedCertification`. These tests run in under 100ms and require no mocking. Place in `test/certification/certification_bloc_definitions_test.dart`.

Component
Certification BLoC
service medium
Epic Risks (2)
medium impact low prob technical

Supabase Edge Functions can have cold-start latency that causes the nightly cron to time out when processing large cohorts of expiring certifications, resulting in partial reminder dispatches.

Mitigation & Contingency

Mitigation: Batch the cron processing in chunks of 50 mentors per iteration. Use pagination with a cursor to resume processing if the function is re-invoked. Keep total invocation time well under the Edge Function timeout limit.

Contingency: If timeouts occur in production, split the cron into two separate functions: one for reminders and one for auto-pauses, each with its own schedule offset to reduce peak load.

low impact medium prob technical

Certification BLoC covers three distinct workflows (view, renew, enrol) which may lead to an overly complex state machine that is hard to test and maintain, particularly when error states from multiple concurrent operations need to be differentiated in the UI.

Mitigation & Contingency

Mitigation: Use separate sealed state classes per workflow (CertificationViewState, RenewalState, EnrolmentState) composed into a single BLoC state wrapper. Follow the existing BLoC patterns established in the codebase for consistency.

Contingency: If the BLoC grows too complex, split into two BLoCs: CertificationBLoC (view/load) and CertificationActionBLoC (mutations), connected via a shared stream.