critical priority high complexity backend pending backend specialist Tier 2

Acceptance Criteria

Orchestrator accepts a typed list of expiring-certificate mentor records and processes each record without throwing unhandled exceptions
Each mentor record is classified into exactly one threshold tier: 60-day advance, 30-day warning, 7-day urgent, or lapsed — based on days until certificate expiry at invocation time
Before creating any notification, the orchestrator queries the notification record repository for an existing record with the same mentor_id, certificate_type, and threshold_tier within the current notification window; a duplicate is skipped, not written
For each non-duplicate record, an in-app notification entry is written to the notifications table via the notification record repository with status 'pending'
For each non-duplicate record, an FCM push message is queued via the push sender service with the correct payload for the resolved threshold tier
If the same batch is submitted a second time (re-run), zero additional notification records or FCM messages are produced (idempotency guarantee)
Batch processing is transactional per record: a failure on one mentor record is caught, logged, and does not abort processing of subsequent records
Orchestrator exposes a structured result object listing counts of: processed, skipped (duplicates), created (notifications), queued (FCM), and failed records
All processing steps emit structured log entries at info level; errors emit at error level with mentor_id and certificate_type context
Unit tests cover: correct tier classification for each boundary (60, 30, 7 days and past expiry), duplicate suppression, FCM queue invocation, repository write invocation, and partial-failure resilience

Technical Requirements

frameworks
Flutter (Dart)
BLoC
Riverpod
apis
Supabase Edge Functions (caller)
Supabase REST API (notification record repository)
Firebase Cloud Messaging (FCM) HTTP v1 API (push sender)
data models
PeerMentorCertificate
CertificateExpiryNotification
NotificationThresholdTier
MentorRecord
FCMPushQueue
performance requirements
Batch of 200 mentor records must complete processing within 10 seconds under normal Supabase latency
Duplicate-check query must use an index on (mentor_id, certificate_type, threshold_tier, notification_window_start) — confirm index exists before deploying
All Supabase writes must use bulk insert where possible to minimise round-trips
security requirements
Service must execute under a Supabase service-role key stored in Supabase Vault — never in client-side code
Mentor PII (name, certificate details) must not appear in application logs; log only mentor_id (UUID) and tier
FCM server key must be stored in Supabase Vault and retrieved at runtime, never hardcoded
Row-level security on notifications table must allow service-role writes but deny direct client writes

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Model the orchestrator as a pure Dart service class (no Flutter dependency) so it can be invoked from both the Supabase Edge Function runtime and unit tests. Use a repository interface for notification records and a sender interface for FCM to enable mock injection. Threshold tier calculation should be a pure function accepting a DateTime expiry and DateTime now — never use DateTime.now() inside business logic; inject the clock. Idempotency key: composite of (mentor_id, certificate_type, threshold_tier, notification_window_date) where notification_window_date is the calendar date of the scheduled run — this prevents cross-day re-runs from treating records as duplicates while still blocking same-day re-runs.

Use Dart's Future.wait with error zones or a try/catch per record in a for-loop (not Future.wait for all — one failure must not cancel others). Return a typed OrchestrationResult value object. Avoid using Riverpod providers in this service layer; Riverpod is for UI state — inject dependencies via constructor.

Testing Requirements

Unit tests (flutter_test): tier classification logic for boundary values (61, 60, 31, 30, 8, 7, 1, 0, -1 days), idempotency with mocked repository returning existing records, partial-failure resilience (one record throws, rest complete), result object accuracy. Integration tests: full orchestrator run against a Supabase local emulator with seed data covering all four tiers; verify notification rows created, FCM mock called with correct tier payloads, re-run produces no additional rows. Coverage target: 90% of orchestrator core logic. All tests must pass in CI without real FCM or Supabase credentials using dependency-injected mocks.

Epic Risks (4)
high impact medium prob technical

If the daily edge function runs more than once in a 24-hour window due to a Supabase scheduling anomaly or manual re-trigger, the orchestrator could dispatch duplicate push notifications to the same mentor and coordinator for the same threshold, eroding user trust.

Mitigation & Contingency

Mitigation: Implement idempotency at the notification record level using a unique constraint on (mentor_id, threshold_days, certification_id). The orchestrator checks for an existing record before dispatching. Use a database-level upsert with ON CONFLICT DO NOTHING.

Contingency: If duplicate notifications are reported in production, add a rate-limiting guard in the edge function that aborts if a notification for the same mentor and threshold was created within the last 20 hours, and add an alerting rule to Supabase logs for duplicate dispatch attempts.

medium impact medium prob scope

The mentor visibility suppressor relies on the daily edge function to detect expiry and update suppression_status. A mentor whose certificate expires at midnight may remain visible for up to 24 hours if the cron runs at a fixed time, violating HLF's requirement that expired mentors disappear promptly.

Mitigation & Contingency

Mitigation: Schedule the edge function to run at 00:05 UTC to minimise lag after midnight transitions. Additionally, the RLS policy can include a direct date comparison (certification_expiry_date < now()) as a secondary predicate that does not rely on suppression_status, providing real-time enforcement at the database level.

Contingency: If the cron lag is unacceptable after launch, implement a Supabase database trigger on the certifications table that fires on UPDATE of expiry_date and calls the suppressor immediately, reducing lag to near-zero for renewal and expiry events.

medium impact low prob integration

The orchestrator needs to resolve the coordinator assigned to a specific peer mentor to dispatch coordinator-side notifications. If the assignment relationship is not normalised or is missing for some mentors, coordinator notifications will silently fail.

Mitigation & Contingency

Mitigation: Query the coordinator assignment from the existing assignments or user_roles table before dispatch. Log a structured warning (missing_coordinator_assignment: mentor_id) when no coordinator is found. Add a data quality check in the edge function that reports mentors without coordinators.

Contingency: If coordinator assignments are missing at scale, fall back to notifying the chapter-level admin role for the mentor's chapter, and surface a data quality report to the admin dashboard showing mentors without assigned coordinators.

medium impact low prob dependency

The course enrollment prompt service generates deep-link URLs targeting the course administration feature. If the course administration feature changes its deep-link schema or the Dynamics portal URL structure changes, enrollment prompts will navigate to broken destinations.

Mitigation & Contingency

Mitigation: Define the deep-link contract between the certificate expiry feature and the course administration feature as a shared constant in a cross-feature navigation config. Version the deep-link schema and validate the generated URL format in unit tests.

Contingency: If the deep-link breaks in production, the course enrollment prompt service should gracefully fall back to opening the course administration feature root screen with a query parameter indicating the notification context, allowing the user to manually locate the correct course.