high priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

Reactivation method accepts a mentor ID, renewed certificate ID, and acting user ID (coordinator or system)
If the mentor's current suppression_reason is 'certificate_lapsed', their listing_status is restored to the value stored in previous_status — not hardcoded to 'active'
If the mentor's suppression_reason is NOT 'certificate_lapsed' (i.e. they were deactivated for a different reason), reactivation is rejected and returns a typed error indicating the suppression reason mismatch
The reactivation write (listing_status update + suppression_reason clear + previous_status clear) is performed as a single atomic Supabase transaction or RPC call
An audit log entry is written immediately after successful reactivation containing: mentor_id, certificate_id, acting_user_id, restored_status, ISO-8601 timestamp, and event_type 'certificate_renewal_reactivation'
If the audit log write fails, the visibility update is NOT rolled back — the audit failure is logged as a non-critical error
Attempting to reactivate a mentor who is not currently suppressed with suppression_reason 'certificate_lapsed' returns a typed ReactivationResult with status 'not_suppressed' — no error thrown
Unit tests cover: successful reactivation restores previous_status, rejection when suppression_reason mismatch, no-op when not suppressed, audit log written with correct fields

Technical Requirements

frameworks
Flutter (Dart)
Riverpod
apis
Supabase REST API (peer_mentor_visibility table)
Supabase RPC (atomic update if Supabase transactions used)
data models
PeerMentorVisibility
MentorCertificateRenewal
ReactivationAuditLog
ReactivationResult
performance requirements
Single mentor reactivation must complete within 2 seconds including audit log write
Reactivation must not trigger a full visibility table scan — query must filter by primary key (mentor_id)
security requirements
Only coordinator role or service role may invoke reactivation — enforce via Supabase RLS
Acting user ID must be validated against the authenticated session — do not accept arbitrary user IDs from client payload
Audit log table must be append-only for non-admin roles (no UPDATE or DELETE on audit_log rows)

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Add a reactivate(String mentorId, String certificateId, String actingUserId) method to the existing MentorVisibilitySuppressor class from task-007 — do not create a separate class. This keeps deactivation and reactivation logic co-located and shares the same repository dependency. For atomicity, prefer a Supabase RPC (PostgreSQL function) that performs the SELECT-then-UPDATE in a single transaction, especially to prevent a race condition where two coordinators attempt concurrent reactivation of the same mentor. The RPC should return the previous and new states so the Dart layer can construct the audit log without a second query.

If RPC is not feasible, use an optimistic update with a match filter on suppression_reason='certificate_lapsed' and check affected rows count — if 0 rows updated, the suppression_reason had changed. Clear previous_status and suppression_reason columns by setting them to NULL on reactivation, not empty string.

Testing Requirements

Unit tests (flutter_test): mock Supabase client responses for all branches — successful reactivation, suppression_reason mismatch (returns error), mentor not suppressed (returns not_suppressed), audit write failure (visibility update preserved, error logged). Integration test: seed a mentor with listing_status='deactivated', suppression_reason='certificate_lapsed', previous_status='active'; run reactivate; assert listing_status='active', suppression_reason=null, previous_status=null, audit row present. Second integration test: same seed but suppression_reason='admin_suspended'; assert reactivation rejected and row unchanged. Coverage target: 85%.

Component
Peer Mentor Visibility Suppressor
service medium
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.