critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

Abstract interface declares `Future<List<CertificationExpiryRecord>> getMentorsExpiringWithinDays(int days)` as a single parameterized method (not three separate methods)
Concrete implementation queries only records where `expiry_date <= now() + interval 'N days'` AND `expiry_date >= now()` (i.e., not already expired)
The query additionally filters out records where the threshold is already present in `notification_thresholds_sent` using a Supabase RPC or PostgREST `not.cs` (not-contains) filter on the int[] column
Method returns an empty list (not null, not throws) when no mentors match the criteria
Method accepts any positive integer for `days` — not hardcoded to 60/30/7 — enabling future threshold addition without code changes
Calling `getMentorsExpiringWithinDays(7)` does NOT return mentors already returned by a previous `getMentorsExpiringWithinDays(7)` call in the same day (deduplication via DB state)
An integration test against a local Supabase instance (or seeded test DB) confirms correct filtering for each threshold
The abstract interface is updated in the base class task and the Riverpod provider does not need changes

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase PostgREST filter API (`lte`, `gte`, `not.cs`)
Supabase RPC (optional, for complex array containment filtering)
data models
CertificationExpiryRecord
certification_expiry_tracking (DB table)
performance requirements
Query must complete in < 200ms for up to 10,000 records using the composite index (expiry_date, peer_mentor_id)
Must not perform N+1 queries — single DB round-trip per `getMentorsExpiringWithinDays` call
Array containment filter must leverage the GIN index on notification_thresholds_sent
security requirements
This query is intended to run in a Supabase Edge Function using the service-role key — ensure the method signature supports both authenticated and service-role client injection
Never expose all peer mentor records to a regular peer mentor user — RLS handles this at DB level

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

PostgREST supports array containment via the `cs` (contains) and `not.cs` (not contains) operators. In Supabase Dart client: `.not('notification_thresholds_sent', 'cs', '{$days}')`. However, verify this syntax against the current supabase_flutter package version — if `not.cs` is not supported directly, use a Supabase RPC function (`select get_expiring_mentors(days int)`) that encapsulates the array containment logic in SQL for reliability.

The RPC approach is preferred for complex array logic as it avoids client-side PostgREST operator gymnastics. Regardless of approach, the date range must compute `now()` server-side (in SQL), not client-side, to avoid timezone/clock skew issues in edge functions. Do not add `limit` without pagination — the edge function may need all results; let the caller paginate if needed.

Testing Requirements

Unit tests with mocked Supabase client: (1) verify correct PostgREST filter parameters are passed (lte expiry_date, gte expiry_date, array not-contains threshold); (2) verify empty list returned when mock returns empty; (3) verify list is correctly deserialized from fixture JSON. Integration tests against local Supabase: seed records with expiry dates at now+5, now+25, now+55, now+90 days and notification_thresholds_sent of {60} for the 55-day record; assert getMentorsExpiringWithinDays(60) returns now+5, now+25, now+55 records; assert getMentorsExpiringWithinDays(30) returns now+5 and now+25 only; assert getMentorsExpiringWithinDays(7) returns only now+5; assert the 55-day record with {60} in thresholds_sent is excluded from the 60-day query. All tests in flutter_test.

Component
Certification Expiry Repository
data medium
Epic Risks (3)
high impact medium prob technical

The RLS policy predicate that checks certification_expiry_date and suppression_status on every coordinator list query could cause full table scans at scale, degrading response time for coordinator contact list screens across all chapters.

Mitigation & Contingency

Mitigation: Add a partial index on (certification_expiry_date, suppression_status) filtered to active mentors. Benchmark the policy predicate against a representative data set (500+ mentors) during development using EXPLAIN ANALYZE on Supabase staging.

Contingency: If the index does not resolve the performance issue, introduce a computed boolean column is_publicly_visible that is updated by the mentor_visibility_suppressor service and indexed separately, shifting the predicate cost to write time rather than read time.

medium impact medium prob integration

FCM device tokens become invalid when users reinstall the app or switch devices. If the token management strategy does not handle token refresh reliably, notification delivery will silently fail for a significant portion of the user base without surfacing errors.

Mitigation & Contingency

Mitigation: Implement the FCM token refresh callback in the Flutter client to upsert the latest token to Supabase on every app launch. Store token with a last_refreshed_at timestamp. The FCM sender should handle UNREGISTERED error codes by deleting stale tokens.

Contingency: If token staleness becomes widespread, add a token health check that forces re-registration during the expiry check edge function run by querying mentors whose token was last refreshed more than 30 days ago and triggering a silent push to prompt re-registration.

medium impact low prob integration

The certification expiry and notification record tables may have column naming or constraint conflicts with existing tables in the peer mentor status and certification management features, causing migration failures in shared Supabase environments.

Mitigation & Contingency

Mitigation: Audit existing table schemas for user_roles, certifications, and notification tables before writing migrations. Prefix new columns with expiry_ to avoid collisions. Run migrations against a clean Supabase branch environment before merging.

Contingency: If a conflict is found post-merge, apply ALTER TABLE migrations to rename conflicting columns and issue a hotfix migration. Communicate schema changes to all dependent feature teams via a shared migration changelog.