critical priority low complexity database pending database specialist Tier 0

Acceptance Criteria

Migration file exists in supabase/migrations/ with correct timestamped filename
Table has columns: id (uuid, PK, default gen_random_uuid()), user_id (uuid, NOT NULL, FK → auth.users.id ON DELETE CASCADE), token (text, NOT NULL), platform (text, NOT NULL, CHECK IN ('ios','android')), device_fingerprint (text, NOT NULL), is_active (boolean, NOT NULL, DEFAULT true), registered_at (timestamptz, NOT NULL, DEFAULT now()), last_refreshed_at (timestamptz, NULL), revoked_at (timestamptz, NULL)
UNIQUE constraint on (user_id, device_fingerprint) prevents duplicate device registrations per user
Index on token column for O(log n) lookup when FCM returns a token-refresh callback
Index on (user_id, is_active) for fast active-token queries used during notification dispatch
CHECK constraint on platform restricts values to 'ios' and 'android'
Foreign key to auth.users uses ON DELETE CASCADE
Migration applies cleanly with no errors on a fresh project
revoked_at being NULL implies the token is not revoked; is_active=false AND revoked_at IS NOT NULL indicates explicit revocation vs is_active=false AND revoked_at IS NULL for soft-deactivation

Technical Requirements

frameworks
Supabase
apis
Supabase Migrations CLI
FCM HTTP v1 API
data models
fcm_tokens
auth.users
performance requirements
Index on (user_id, is_active) must allow the dispatch service to retrieve all active tokens for a user in a single indexed scan
Token column index must support reverse-lookup (token → user) for FCM token refresh callbacks in < 5 ms
security requirements
FCM tokens are sensitive — RLS in task-003 must restrict read access to the owning user and service role only
device_fingerprint must not contain device serial numbers or IMEI — use a hash of installation ID only
revoked_at timestamp provides audit trail for token revocation events

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Keep revoked_at and last_refreshed_at as nullable timestamps rather than booleans — this gives a full audit timeline. The is_active flag is redundant with revoked_at but is kept for query simplicity (WHERE is_active = true is more readable than WHERE revoked_at IS NULL). The NotificationTriggerService (task-014) will query SELECT token FROM fcm_tokens WHERE user_id = $1 AND is_active = true — the (user_id, is_active) index is critical for this hot path. device_fingerprint should be populated from flutter_device_info hashed with SHA-256 to avoid storing raw device identifiers.

Do not store the FCM server key in this table or any client-accessible table.

Testing Requirements

pgTAP or Supabase test suite covering: (1) successful insert with platform='ios' and platform='android', (2) rejected insert with platform='web' confirming CHECK violation, (3) duplicate (user_id, device_fingerprint) insert confirms UNIQUE violation, (4) insert of two tokens for the same user on different devices succeeds, (5) cascade delete: remove user from auth.users, confirm all their fcm_tokens rows are deleted, (6) query by token column returns the correct row confirming index is used (EXPLAIN ANALYZE). Tests stored in supabase/tests/.

Component
FCM Token Manager
infrastructure medium
Epic Risks (3)
high impact medium prob scope

iOS only allows one system permission prompt per app install. If the rationale dialog timing or content is wrong the user may permanently deny permissions during onboarding, permanently blocking push delivery for that device with no recovery path short of manual system settings navigation.

Mitigation & Contingency

Mitigation: Design and user-test the rationale dialog content and trigger point (after onboarding value-demonstration step, not at first launch). Implement the settings-deep-link fallback in NotificationPermissionManager so the permission state screen always offers a path to system settings if denied.

Contingency: If denial rates are high in TestFlight testing, revise the rationale copy and trigger timing before production release. Ensure the in-app notification centre provides full value without push so denied users are not blocked from the feature.

medium impact medium prob technical

FCM token rotation callbacks can fire at any time, including during app termination or network outage. If the token rotation is not persisted reliably the backend trigger service will dispatch to a stale token, resulting in silent notification failures that are hard to diagnose.

Mitigation & Contingency

Mitigation: Persist token rotation updates with a local queue that retries on next app foreground if network is unavailable. Use Supabase upsert by (user_id, device_id) to prevent duplicate token rows and ensure the latest token always wins.

Contingency: If token staleness is observed in production, add a token validity check on each app foreground and force a re-registration if the stored token does not match the FCM-reported current token.

high impact low prob security

Incorrect RLS policies on notification_preferences or fcm_tokens could expose one user's preferences or device tokens to another user, or could block the backend Edge Function service role from reading token lists needed for dispatch, silently dropping all notifications.

Mitigation & Contingency

Mitigation: Write explicit RLS policy tests using the Supabase test harness covering user-scoped read/write, service-role read for dispatch, and cross-user access denial. Review policies during code review with a security checklist.

Contingency: Maintain a rollback migration that reverts the RLS changes, and add an integration test in CI that asserts the service role can query all tokens and that a normal user JWT cannot access another user's token rows.