critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Migration file exists under supabase/migrations/ with a timestamped filename and applies cleanly without errors
Table `notification_records` exists with columns: id (uuid, PK, default gen_random_uuid()), recipient_id (uuid, NOT NULL, FK → auth.users), notification_type (text, NOT NULL), threshold_days (int, nullable — only set for expiry notifications), title (text, NOT NULL), body (text, NOT NULL), is_read (boolean, NOT NULL, default false), created_at (timestamptz, NOT NULL, default now()), reference_id (uuid, nullable — FK to the source entity, e.g., certification_expiry_tracking.id)
Index on (recipient_id, is_read) exists to support unread-count badge queries
Index on (recipient_id, created_at DESC) exists for efficient paginated inbox queries
Unique constraint on (recipient_id, notification_type, threshold_days, reference_id) prevents duplicate notification records for the same threshold event
RLS is enabled: recipients can SELECT, UPDATE (is_read only) their own rows; service-role can INSERT/DELETE
notification_type CHECK constraint limits values to known types (e.g., 'certificate_expiry_60', 'certificate_expiry_30', 'certificate_expiry_7')
Rollback migration (down.sql) drops table and indexes cleanly
created_at is indexed for time-based cleanup/archival queries

Technical Requirements

frameworks
Supabase CLI
apis
Supabase Management API
data models
notification_records
certification_expiry_tracking (reference_id FK)
peer_mentor (auth.users reference)
performance requirements
Unread count query (SELECT COUNT(*) WHERE recipient_id=? AND is_read=false) must use index scan — verify with EXPLAIN ANALYZE
Inbox listing query (SELECT * WHERE recipient_id=? ORDER BY created_at DESC LIMIT 20) must use index scan
Duplicate check query on unique constraint must be sub-10ms
security requirements
RLS UPDATE policy must restrict peers to only toggling is_read — prevent body/title tampering
recipient_id FK to auth.users enforced at DB level
reference_id is nullable and not FK-enforced to allow flexibility (source entity may be deleted)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

The unique constraint on (recipient_id, notification_type, threshold_days, reference_id) is the database-level deduplication guard — it complements the application-layer check in NotificationRecordRepository and protects against concurrent edge function invocations. Make threshold_days nullable so the table can store non-expiry notification types in the future without requiring a sentinel value. The RLS UPDATE policy should use a WITH CHECK clause: `WITH CHECK (is_read = true)` to ensure peers can only mark as read, never revert to unread or modify other fields. Consider adding a partial index on (recipient_id) WHERE is_read = false for maximum badge-count performance since unread rows are a small fraction of total rows.

Testing Requirements

Write SQL test scripts that: (1) insert a notification record and verify default values for is_read and created_at; (2) attempt to insert a duplicate (same recipient_id, notification_type, threshold_days, reference_id) and confirm the unique constraint rejects it; (3) run EXPLAIN ANALYZE on unread count and inbox listing queries to confirm index usage; (4) verify RLS: a peer mentor cannot read another recipient's notifications; (5) verify RLS UPDATE: a peer mentor cannot change the `body` column (should be rejected by a column-level restriction or trigger); (6) apply rollback and confirm clean drop. Commit SQL test scripts alongside the migration.

Component
Notification Record 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.