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 via `supabase db push` without errors
Table `certification_expiry_tracking` exists with columns: id (uuid, PK, default gen_random_uuid()), peer_mentor_id (uuid, NOT NULL, FK → auth.users), certificate_type (text, NOT NULL), expiry_date (date, NOT NULL), last_notified_at (timestamptz, nullable), notification_thresholds_sent (int[], NOT NULL, default '{}')
B-tree index exists on expiry_date for efficient date-range scans
B-tree index exists on peer_mentor_id for per-mentor lookups
Composite index exists on (expiry_date, peer_mentor_id) to support the 60/30/7-day threshold queries without full table scans
Row-Level Security (RLS) is enabled on the table; service-role key bypasses RLS for edge function access; peer mentors can only SELECT their own rows
A GIN index is added on notification_thresholds_sent to support `@>` (contains) queries for threshold deduplication
Rollback migration (down.sql) is provided and tested to cleanly drop the table and indexes
Schema is documented with inline SQL comments describing each column's business purpose
certificate_type column has a CHECK constraint limiting values to known types (e.g., 'hlf_peer_mentor', 'nhf_peer_mentor', 'blindeforbundet_peer_mentor') or is extensible via a lookup table

Technical Requirements

frameworks
Supabase CLI (supabase db push, supabase migration new)
apis
Supabase Management API (for applying migrations in CI)
data models
certification_expiry_tracking
peer_mentor (auth.users reference)
performance requirements
Date-range queries for 60/30/7-day windows must complete in < 100ms on a table with 10,000 rows
Per-mentor lookups must use index scan (not sequential scan) — verify with EXPLAIN ANALYZE
GIN index on notification_thresholds_sent must make `@> ARRAY[60]` queries index-bound
security requirements
RLS policy: peer mentors SELECT only their own rows (auth.uid() = peer_mentor_id)
RLS policy: only service-role (edge functions) can INSERT/UPDATE/DELETE
peer_mentor_id must be a valid FK reference to auth.users to prevent orphaned records
No PII beyond peer_mentor_id UUID stored in this table — certificate_type is a code, not a name

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use `supabase migration new create_certification_expiry_tracking` to generate the timestamped file. The `notification_thresholds_sent` column as `int[]` is intentional — it acts as a denormalized set of already-sent thresholds (e.g., `{60, 30}` means 60-day and 30-day notifications have been dispatched). This avoids a separate join table for simple threshold tracking and supports GIN-indexed `@>` containment queries. For certificate_type, prefer a CHECK constraint over a lookup table at this stage to keep the schema simple; it can be migrated to a FK later.

Ensure the migration sets `default '{}'::int[]` so the array is never NULL, simplifying application-layer logic. The composite index (expiry_date, peer_mentor_id) is the primary query path for edge function scans — order matters, put expiry_date first since it is the range predicate.

Testing Requirements

Apply the migration to a local Supabase instance and verify with `psql` or the Supabase Studio UI that all columns, types, constraints, and indexes exist as specified. Write SQL test scripts that: (1) insert a sample row and verify defaults; (2) attempt to insert an invalid certificate_type and confirm the CHECK constraint rejects it; (3) run EXPLAIN ANALYZE on a date-range query and confirm index usage; (4) verify RLS blocks a peer mentor from reading another mentor's row using a JWT with a different sub claim; (5) apply the rollback migration and confirm the table is dropped cleanly. These SQL tests should be committed alongside the migration file.

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.