critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Migration file created under supabase/migrations/ with timestamped filename for both tables in a single migration
peer_mentor_status table exists with columns: id (uuid, PK, gen_random_uuid()), mentor_id (uuid, FK to profiles/auth.users, NOT NULL, UNIQUE — one active status record per mentor), chapter_id (uuid, FK to chapters, NOT NULL), status (text, NOT NULL, CHECK IN ('active','paused','expired_cert','deactivated')), pause_reason (text, nullable), expected_return_date (date, nullable), activated_by (uuid, FK to profiles, nullable — coordinator or system), paused_at (timestamptz, nullable), reactivated_at (timestamptz, nullable), created_at (timestamptz, default now()), updated_at (timestamptz, default now())
peer_mentor_status_log table exists with columns: id (uuid, PK), mentor_id (uuid, NOT NULL), chapter_id (uuid, NOT NULL), previous_status (text), new_status (text, NOT NULL), changed_by (uuid, nullable), change_reason (text, nullable), changed_at (timestamptz, default now()) — append-only audit log with no UPDATE/DELETE allowed via policy
Index on peer_mentor_status(mentor_id) — unique, already implied by UNIQUE constraint
Index on peer_mentor_status(chapter_id, status) — composite index for coordinator roster queries filtering by chapter and status
Index on peer_mentor_status_log(mentor_id, changed_at DESC) — for per-mentor audit history queries
updated_at on peer_mentor_status auto-updates via trigger
A DB trigger or application-level convention inserts a row into peer_mentor_status_log on every UPDATE to peer_mentor_status.status — at minimum document this requirement in the migration comment
Migration applies cleanly with zero errors, rollback migration provided
CHECK constraint on status column rejects any value not in the allowed enum set

Technical Requirements

frameworks
Supabase CLI
PostgreSQL
apis
Supabase Database
data models
PeerMentorStatus
PeerMentorStatusLog
performance requirements
Coordinator roster query (SELECT * FROM peer_mentor_status WHERE chapter_id = $1 AND status = $2) uses the composite index — confirmed via EXPLAIN ANALYZE
Audit log query (SELECT * FROM peer_mentor_status_log WHERE mentor_id = $1 ORDER BY changed_at DESC LIMIT 20) uses the composite index on (mentor_id, changed_at DESC)
security requirements
peer_mentor_status_log should be append-only — define a RLS policy (or DB-level rule) that blocks UPDATE and DELETE even for admin roles, preserving audit integrity
activated_by FK must reference a real user — use FK constraint to prevent orphaned audit references
RLS will be added in a follow-up task — table created without RLS at this step, consistent with the cert table pattern

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

The UNIQUE constraint on peer_mentor_status.mentor_id means there is exactly one status record per mentor, updated in-place on each transition — the full history lives in peer_mentor_status_log. This is a deliberate design choice that simplifies lookups (no need to query for the latest record) but requires a trigger to write log entries. Consider implementing the audit trigger in this same migration using a PostgreSQL trigger function: CREATE OR REPLACE FUNCTION log_mentor_status_change() RETURNS TRIGGER AS $$ BEGIN INSERT INTO peer_mentor_status_log (mentor_id, chapter_id, previous_status, new_status, changed_by, changed_at) VALUES (OLD.mentor_id, OLD.chapter_id, OLD.status, NEW.status, NEW.activated_by, now()); RETURN NEW; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trg_mentor_status_log AFTER UPDATE OF status ON peer_mentor_status FOR EACH ROW EXECUTE FUNCTION log_mentor_status_change(); This ensures the audit log is always in sync regardless of which service performs the update.

Testing Requirements

After applying the migration to the local Supabase instance: (1) verify all columns on both tables with \d+ peer_mentor_status and \d+ peer_mentor_status_log; (2) insert a row with status='active' and confirm it succeeds; (3) attempt to insert status='unknown_value' and confirm CHECK constraint rejects it; (4) insert two rows with the same mentor_id in peer_mentor_status and confirm UNIQUE constraint rejects the second; (5) run EXPLAIN ANALYZE on the composite index query (chapter_id + status filter) to confirm index scan; (6) insert a log row and attempt to UPDATE it, confirming it is blocked if the append-only policy/rule is already in place. Document results in a test_schema_verification.sql file.

Component
Peer Mentor Status Repository
data medium
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for coordinator-scoped status queries may be difficult to express correctly, especially for peer mentors assigned to multiple coordinators or chapters, leading to data leakage or overly restrictive access blocking valid queries.

Mitigation & Contingency

Mitigation: Design RLS policies using security-definer RPCs rather than table-level policies for complex multi-coordinator scenarios. Write a comprehensive RLS test matrix covering all role and assignment permutations before marking complete.

Contingency: Fall back to application-level filtering in the repository layer with explicit coordinator_id parameter checks if RLS proves intractable, and document the trade-off for security review.

high impact medium prob dependency

The HLF Dynamics portal API contract may be undocumented or subject to change, causing the DynamicsPortalClient to break during development or production rollout.

Mitigation & Contingency

Mitigation: Obtain the full Dynamics portal API specification and credentials early in the sprint. Build the client behind a well-defined interface so the HLF-specific implementation can be swapped without affecting upstream services.

Contingency: If the Dynamics API is unavailable or unstable, stub the client with a feature-flag-guarded no-op implementation so all other epics can proceed to completion independently.

medium impact low prob technical

Supabase Edge Functions used as the nightly scheduler host may have cold-start latency or execution time limits that prevent reliable nightly certification checks on large mentor rosters.

Mitigation & Contingency

Mitigation: Benchmark Edge Function execution time against the expected roster size. Design the expiry check to process in paginated batches to stay within execution limits. Use pg_cron with a direct database function as an alternative trigger if Edge Functions prove unreliable.

Contingency: Migrate the scheduler trigger to pg_cron invoking a Postgres function directly, removing the Edge Function dependency entirely for the scheduling layer.