critical priority medium complexity infrastructure pending infrastructure specialist Tier 0

Acceptance Criteria

FcmPushNotificationSender class exists and implements the PushNotificationSender interface (or equivalent abstract class)
storeDeviceToken(userId, fcmToken) persists the token to a Supabase `fcm_device_tokens` table with columns: user_id, fcm_token, platform (ios/android), created_at, updated_at
Calling storeDeviceToken with a user_id that already has a token performs an upsert (no duplicate rows per user per device)
Token refresh: when FirebaseMessaging.onTokenRefresh fires, the new token replaces the old one in Supabase automatically
revokeDeviceToken(userId) deletes all FCM token rows for that user — called on logout and device change
getDeviceTokensForUser(userId) returns all active FCM tokens for a user (supports multi-device)
validateToken() returns false and triggers revocation if the Supabase row is stale (token was invalidated by FCM)
All Supabase writes are wrapped in try/catch; failures are logged and surfaced as typed exceptions (FcmTokenStorageException)
Class is injectable via Riverpod provider and mockable for testing
No FCM token is ever logged to console or crash reporting tools (security requirement)

Technical Requirements

frameworks
Flutter
firebase_messaging (Flutter Firebase plugin)
Riverpod
Supabase Flutter SDK
apis
Supabase REST/PostgREST for token upsert/delete
FirebaseMessaging.instance.getToken()
FirebaseMessaging.onTokenRefresh stream
data models
fcm_device_tokens (user_id, fcm_token, platform, created_at, updated_at)
UserProfile (for userId reference)
performance requirements
Token upsert must complete within 2 seconds on a normal mobile connection
onTokenRefresh listener must be registered at app startup, not lazily
Token revocation on logout must complete before the Supabase session is invalidated
security requirements
FCM tokens must never appear in application logs, Sentry, or crash reporting
Supabase RLS on fcm_device_tokens must restrict reads/writes to the owning user_id only
Token revocation must be atomic — partial revocation (some tokens deleted, others not) must trigger a retry
Platform field must be validated as enum (ios | android) before persistence

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Follow the repository pattern: FcmPushNotificationSender should delegate Supabase persistence to a thin FcmTokenRepository class, keeping the sender focused on FCM concerns. Register the FirebaseMessaging.onTokenRefresh listener inside the provider's initialization, not inside a widget lifecycle method, to avoid missed refresh events. Use Supabase's `upsert` with `onConflict: 'user_id,platform'` to handle multi-device correctly without duplicates. For token revocation on logout, ensure the Supabase delete completes before calling `supabase.auth.signOut()` — use `await` explicitly.

The `platform` field should be detected via `dart:io` Platform checks, not passed by the caller, to prevent spoofing. This class will be a dependency of task-008 (message delivery), so define the interface/abstract class in this task so task-008 can begin in parallel.

Testing Requirements

Write flutter_test unit tests using a mock Supabase client (mockito or mocktail). Test cases must cover: (1) storeDeviceToken inserts a new row when none exists, (2) storeDeviceToken performs upsert when a row already exists for the same user_id, (3) onTokenRefresh triggers a token update in Supabase, (4) revokeDeviceToken deletes all rows for the given user_id, (5) getDeviceTokensForUser returns an empty list when no tokens are stored, (6) Supabase write failure throws FcmTokenStorageException and does not swallow the error, (7) validateToken returns false for a known-invalid token format. Achieve minimum 85% method coverage. No integration tests against live Firebase or Supabase in CI.

Component
FCM Push Notification Sender
infrastructure 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.