critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Table peer_mentor_certifications exists in Supabase with columns: id (uuid, PK, default gen_random_uuid()), peer_mentor_id (uuid, FK → users.id, NOT NULL), certification_type (text, NOT NULL), issued_at (timestamptz, NOT NULL), expires_at (timestamptz, nullable), status (text, CHECK IN ('active','expired','suspended'), NOT NULL, default 'active'), renewal_history (jsonb, NOT NULL, default '[]'), created_at (timestamptz, default now()), updated_at (timestamptz, default now())
Index exists on (peer_mentor_id, status) for coordinator dashboard queries
Index exists on expires_at for nightly cron expiry lookups
RLS is enabled on the table with at least three policies: (1) peer mentors may SELECT their own rows, (2) coordinators may SELECT/INSERT/UPDATE rows belonging to their organisation, (3) org admins have full access within their organisation
An updated_at trigger automatically sets updated_at = now() on every UPDATE
renewal_history JSONB schema is validated via a check constraint or documented JSON schema: array of objects with keys {renewed_at, previous_expires_at, renewed_by, notes}
Migration script is idempotent (uses IF NOT EXISTS / DO $$ blocks) and runs without error on a clean Supabase project
No existing RLS policies from other tables are inadvertently modified
SQL migration file is committed under supabase/migrations/ with a timestamped filename

Technical Requirements

frameworks
Supabase CLI (supabase db push)
PostgreSQL 15+
apis
Supabase Management API (for RLS policy validation)
data models
peer_mentor_certifications
users
organisations
performance requirements
Index on expires_at supports sub-10ms lookups for nightly cron queries scanning up to 10 000 rows
Index on (peer_mentor_id, status) supports coordinator dashboard widget queries returning in under 50ms
JSONB renewal_history should not exceed ~50 entries per row to avoid bloat; document this limit
security requirements
RLS must be enabled before any data is inserted
peer_mentor_id must reference auth.users (or the profiles table) via FK with ON DELETE CASCADE
status CHECK constraint prevents arbitrary string values from being stored
Service-role key must never be used from the Flutter client — only anon/authenticated keys
Sensitive PII in renewal_history is protected by RLS; no public SELECT policy

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use supabase/migrations/ with a timestamped SQL file (e.g. 20260326000001_create_peer_mentor_certifications.sql). Define RLS policies using auth.uid() for peer mentor self-access and a helper function get_user_organisation_id() that is reused across tables. Store renewal_history as a JSONB array rather than a separate table to keep the full longitudinal record atomic and avoid N+1 queries on the nightly cron job.

Document the expected JSONB shape in a comment inside the migration file. Avoid using serial/integer PKs — use uuid throughout for consistency with Supabase auth.users. Add a database-level comment (COMMENT ON TABLE) describing the table purpose for future developers.

Testing Requirements

Write Supabase database tests using pgTAP or the Supabase test helpers: (1) unit test each RLS policy by authenticating as a peer mentor, coordinator, and org admin and asserting correct row visibility; (2) test that a peer mentor cannot read another mentor's certifications; (3) test that the CHECK constraint on status rejects invalid values; (4) test that the updated_at trigger fires on UPDATE; (5) test that the migration is idempotent by running it twice. Integration tests in Flutter (flutter_test) should confirm that the Supabase client returns only the authenticated user's certifications when queried with normal credentials.

Component
Certification Repository
data medium
Epic Risks (3)
high impact medium prob integration

HLF Dynamics portal webhook API contract may be undocumented, subject to change, or require a separate authentication flow not yet agreed upon with HLF. If the contract changes post-implementation, the sync service silently fails and expired peer mentors remain on public listings.

Mitigation & Contingency

Mitigation: Obtain the official Dynamics webhook specification and test credentials from HLF before starting HLFDynamicsSyncService implementation. Agree on a versioned webhook contract and request a staging endpoint for integration testing.

Contingency: If the contract is unavailable, stub the sync service behind a feature flag and ship without Dynamics sync initially. Queue sync events locally and replay once the contract is confirmed.

high impact medium prob security

Supabase RLS policies for certifications must correctly scope data to the coordinator's chapter without leaking cross-organisation data, particularly complex in multi-chapter membership scenarios. A misconfigured policy could expose peer mentor PII to wrong coordinators.

Mitigation & Contingency

Mitigation: Write RLS policies against the established org-hierarchy schema used by other tables. Peer review all policies before migration deployment. Add integration tests that assert cross-organisation data isolation using test accounts with different org scopes.

Contingency: If a policy gap is discovered post-merge, immediately disable the affected query endpoint and apply a hotfix migration. Audit access logs in Supabase for any cross-org data access events.

medium impact low prob technical

Storing renewal history as a JSONB field rather than a normalised table simplifies queries but makes retrospective schema changes (adding fields to history entries) harder and could cause issues if history grows very large for long-tenured mentors.

Mitigation & Contingency

Mitigation: Define a versioned JSONB entry schema (include a schema_version field in each entry) so future migrations can transform old entries. Add a size guard in the repository to warn if renewal_history exceeds 500 entries.

Contingency: If JSONB approach proves limiting, add a normalised certification_renewal_events table and migrate history entries in a background job, keeping the JSONB field as a read cache.