critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Migration script runs successfully on a clean Supabase project without errors
notification_log table exists with columns: notification_id (UUID PK default gen_random_uuid()), assignment_id (UUID FK → assignments.id), org_id (UUID FK → organizations.id NOT NULL), notification_type (TEXT NOT NULL, e.g. 'reminder', 'escalation'), sent_at (TIMESTAMPTZ NOT NULL default now()), channel (TEXT NOT NULL CHECK IN ('push','in-app')), status (TEXT NOT NULL CHECK IN ('sent','delivered','read','failed')), metadata (JSONB default '{}')
RLS is enabled on notification_log table
RLS policy 'coordinators_read_own_org' restricts SELECT to rows where org_id matches the authenticated user's org_id
RLS policy 'system_insert' allows INSERT only from service role (Edge Functions / backend)
Coordinators cannot read notifications from other orgs (verified via Supabase policy test)
Foreign key constraints are in place and ON DELETE CASCADE is applied for assignment_id and org_id
Migration is idempotent (can be re-run with IF NOT EXISTS guards)
Index on (org_id, sent_at DESC) exists for efficient coordinator feed queries
Index on (assignment_id) exists for per-assignment notification lookups
Migration script is version-controlled and follows project naming convention (e.g. YYYYMMDD_HHmm_add_notification_log.sql)

Technical Requirements

frameworks
Supabase migrations (SQL)
apis
Supabase REST API
Supabase RLS policies
data models
notification_log
assignments
organizations
user_roles
performance requirements
Composite index on (org_id, sent_at DESC) to support paginated coordinator feeds
Index on (assignment_id) to support O(log n) lookup of notifications per assignment
JSONB metadata column must not be indexed by default — add GIN index only if query patterns require it
security requirements
RLS enabled and enforced — no row accessible without matching org_id in JWT claims
INSERT restricted to service role only — client SDK cannot insert directly
notification_id generated server-side (gen_random_uuid()) to prevent ID enumeration
metadata JSONB must not store PII beyond what is strictly necessary
Audit trail: sent_at is immutable (no UPDATE policy on sent_at column)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Supabase CLI (`supabase migration new`) to generate the migration file. Place RLS policies in the same migration file immediately after table creation for atomicity. The service role bypass for INSERT is critical — Edge Functions that dispatch notifications must use the service role key, never the anon key. Use `auth.jwt() ->> 'org_id'` or a helper function `get_my_org_id()` in RLS policies for cleaner policy definitions.

Avoid using `current_user` for RLS — always rely on JWT claims. The metadata JSONB column should store structured context like `{"reminder_day_threshold": 10, "triggered_by": "scheduled_job"}` for debugging without exposing PII. Coordinate with the Edge Function developer (task-005 dependency) to agree on the exact notification_type enum values before finalizing the CHECK constraint, or use TEXT without CHECK to allow future extension via application-level validation.

Testing Requirements

Write SQL-level integration tests using Supabase's pgTAP or a dedicated test schema: (1) Unit: verify table DDL matches expected schema using information_schema queries; (2) RLS policy tests: create two test orgs and two coordinator users, insert notifications for each org, assert each coordinator can only SELECT their own org's rows; (3) Constraint tests: attempt INSERT with invalid channel/status values and assert rejection; attempt INSERT with non-existent assignment_id and assert FK violation; (4) Index existence tests: query pg_indexes to assert all three expected indexes exist; (5) Idempotency test: run migration twice and assert no errors. All tests should run in CI before promotion to staging.

Component
In-App Notification Repository
data low
Epic Risks (3)
high impact medium prob integration

Adding last_contact_date to the assignments table may conflict with existing RLS policies or trigger-based logic that monitors the assignments table. If the migration is not carefully reviewed, existing assignment management features could break in production.

Mitigation & Contingency

Mitigation: Review all existing triggers, policies, and foreign key constraints on the assignments table before writing the migration. Run the migration against a staging Supabase instance with production-like data and execute the full existing test suite before merging.

Contingency: Roll back the migration using Supabase's versioned migration history. Apply the schema change as an additive-only migration (nullable column with default) to ensure zero downtime and reversibility.

medium impact medium prob dependency

The PushNotificationService wraps an existing FCM integration whose internal API contract may have changed or may not expose the payload formatting required for deep-link CTAs. Misalignment discovered late delays the dispatch service epic.

Mitigation & Contingency

Mitigation: Before implementing the wrapper, read the existing push notification integration code and confirm the method signatures, payload structure, and token management model. Agree on a stable interface contract in a shared Dart abstract class.

Contingency: If the existing service is incompatible, implement a thin adapter layer that translates reminder payloads to the existing service's format, isolating the reminder feature from upstream changes.

high impact low prob security

Incorrect RLS policies on notification_log could allow coordinators to read reminder records belonging to peer mentors in other chapters, exposing sensitive assignment information across organisational boundaries.

Mitigation & Contingency

Mitigation: Write explicit RLS policies with integration tests that assert cross-chapter queries return zero rows. Use Supabase's built-in auth.uid() and join through the org membership tables to scope all queries.

Contingency: If a policy gap is discovered post-merge, immediately disable the affected table's SELECT policy, deploy a corrected policy, and audit recent queries in Supabase logs for any cross-boundary reads.