critical priority medium complexity database pending database specialist Tier 1

Acceptance Criteria

Migration file exists under supabase/migrations/ with a timestamped filename and applies cleanly with `supabase db push`
Table follow_up_notification_log is created with columns: log_id (UUID primary key, default gen_random_uuid()), peer_mentor_id (UUID NOT NULL, FK to users), chapter_id (UUID NOT NULL, FK to chapters), scenario_rule_id (UUID NOT NULL, FK to scenario_rules ON DELETE CASCADE), delivered_at (TIMESTAMPTZ NOT NULL), delivery_channel (TEXT NOT NULL, CHECK IN ('fcm','apns')), idempotency_key (TEXT NOT NULL), status (TEXT NOT NULL, CHECK IN ('delivered','failed','skipped'))
UNIQUE constraint exists on idempotency_key column
Indexes exist on (peer_mentor_id, chapter_id) and (scenario_rule_id) for query performance
created_at column (TIMESTAMPTZ DEFAULT now()) is present for auditing
Migration is idempotent — re-running does not produce errors (use IF NOT EXISTS guards)
Rollback (down) migration script exists and successfully drops the table
Migration passes `supabase db diff` without unexpected changes
Column nullability constraints are enforced: inserting a row with null idempotency_key raises a constraint violation
Inserting two rows with the same idempotency_key raises a unique constraint violation

Technical Requirements

frameworks
Supabase CLI
PostgreSQL 15+
apis
Supabase Database API
Supabase Migrations API
data models
follow_up_notification_log
scenario_rules
users
chapters
performance requirements
Index on (peer_mentor_id, chapter_id) to support coordinator read queries in <50ms
Index on scenario_rule_id to support FK join performance
idempotency_key uniqueness check must be enforced at DB level, not only application level
security requirements
Table must be created without default public access — RLS will be applied in task-004
FK to scenario_rules uses ON DELETE CASCADE to prevent orphaned log entries
No sensitive personal data stored beyond peer_mentor_id reference — no names or contact details

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Use the Supabase CLI (`supabase migration new follow_up_notification_log`) to generate the migration file shell. Write both an `up` and `down` migration. Use `gen_random_uuid()` as the default for log_id to avoid importing pgcrypto separately. The idempotency_key should be constructed by callers as a deterministic hash (e.g.

SHA-256 of `{peer_mentor_id}:{scenario_rule_id}:{date_window}`) — document this convention in a comment inside the migration file. Do NOT define RLS in this migration — RLS is handled in task-004. Ensure the FK on scenario_rule_id has `ON DELETE CASCADE` so that deleting a rule automatically clears its delivery history. Place composite index creation AFTER the table creation statement.

Avoid using SERIAL for primary key — UUID is required for distributed consistency across potential future multi-region setups.

Testing Requirements

Unit tests are not applicable for raw SQL migrations. Integration tests must: (1) apply the migration to a local Supabase instance and assert all columns exist with correct types using `information_schema.columns`; (2) attempt to insert a duplicate idempotency_key and assert a unique constraint violation is raised; (3) attempt to insert a row with null idempotency_key and assert a NOT NULL violation; (4) insert a valid row and verify all CHECK constraints accept valid enum values; (5) insert an invalid delivery_channel value (e.g. 'sms') and assert rejection; (6) run the rollback migration and verify the table no longer exists. All tests should run against a fresh local Supabase instance using `supabase start`.

Component
Prompt History Repository
data medium
Epic Risks (2)
high impact medium prob security

Supabase RLS policies for chapter-scoped rule access may interact unexpectedly with service-role keys used by the Edge Function, potentially blocking backend reads or leaking cross-chapter data.

Mitigation & Contingency

Mitigation: Write and review RLS policies in isolation with automated policy tests before merging; define a dedicated service-role bypass policy scoped to the edge function's Postgres role.

Contingency: If RLS blocks the edge function, temporarily use a bypass policy with audit logging while a permanent fix is implemented; escalate to a Supabase security review.

medium impact high prob integration

FCM device tokens become invalid when users reinstall the app or revoke permissions; stale tokens cause silent delivery failures that are hard to detect without explicit error handling.

Mitigation & Contingency

Mitigation: Implement token invalidation handling in PushNotificationDispatcher that removes stale tokens from the database on FCM 404/410 responses; log all delivery failures with structured output.

Contingency: If token hygiene proves unreliable, add a periodic token refresh job that re-registers all active users' tokens via the FCM registration endpoint.