critical priority medium complexity database pending database specialist Tier 0

Acceptance Criteria

Migration creates consent_grants table with columns: id (uuid PK), mentor_id (uuid FK → profiles), organisation_id (uuid FK → organisations), granted_at (timestamptz NOT NULL), revoked_at (timestamptz nullable), consent_version (text NOT NULL), ip_hash (text NOT NULL), created_at (timestamptz default now())
Migration creates consent_audit_log table with columns: id (uuid PK), mentor_id (uuid), organisation_id (uuid), event_type (text CHECK IN ('granted','revoked','expired','checked')), event_at (timestamptz NOT NULL), consent_version (text), ip_hash (text nullable), actor_id (uuid nullable)
A UNIQUE constraint exists on consent_grants(mentor_id, organisation_id) so each mentor has at most one active consent record per org
A CHECK constraint ensures revoked_at > granted_at when revoked_at IS NOT NULL
RLS policy: mentors can SELECT and UPDATE (revoke) their own consent_grants row; coordinators can SELECT consent_grants for their org; service role has full access
RLS policy: consent_audit_log is INSERT-only for authenticated users (no UPDATE or DELETE), service role can SELECT all
A database function `delete_mentor_location_data(mentor_id uuid)` exists that atomically deletes the consent_grants row and all associated location data, satisfying GDPR Article 17
Migration is idempotent and includes a rollback (down migration)
ip_hash column stores SHA-256 hash of IP address — raw IP is never stored in the database
Migration applied successfully to local Supabase instance with `supabase db push`

Technical Requirements

frameworks
Supabase CLI
PostgreSQL
apis
Supabase PostgREST
Supabase Auth
data models
ConsentGrant
ConsentAuditLog
MentorProfile
Organisation
performance requirements
consent_grants table has composite index on (mentor_id, organisation_id) for O(1) consent status lookups
consent_audit_log table has index on mentor_id for efficient per-mentor audit retrieval
consent_audit_log table has index on event_at for time-range queries
security requirements
Raw IP addresses must never be stored — only SHA-256 hashes
RLS policies must prevent any authenticated user from reading another mentor's consent record across organisations
consent_audit_log must be append-only — no UPDATE or DELETE permitted via RLS for non-service roles
delete_mentor_location_data function must be callable only by service role or the mentor themselves
consent_version must reference a known policy version string — document valid values

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the Supabase CLI migration workflow: create migration file with `supabase migration new consent_schema`. Place the consent_grants and consent_audit_log table DDL in the up migration, and matching DROP TABLE statements in the down migration. The UNIQUE constraint on (mentor_id, organisation_id) in consent_grants means the application layer uses UPSERT semantics for granting consent — when a revoked mentor re-grants, set revoked_at back to NULL rather than inserting a new row. This preserves the original granted_at for audit purposes.

For the `delete_mentor_location_data` function, use `SECURITY DEFINER` so it runs with elevated privileges regardless of caller role — but add `REVOKE EXECUTE FROM PUBLIC` and `GRANT EXECUTE TO service_role, authenticated` with row-level guards inside the function body. Store the valid consent_version strings as a PostgreSQL ENUM or a reference table `consent_policy_versions` to enforce referential integrity. The ip_hash should be computed in the application layer (Dart) before sending to Supabase — never send the raw IP to the database. Consider adding a partial index `WHERE revoked_at IS NULL` on consent_grants(mentor_id, organisation_id) for the common 'is consent active?' query pattern.

Testing Requirements

Database-level tests using pgTAP via `supabase db test`. Test cases: (1) Insert a consent_grants row and verify all columns are persisted correctly; (2) Attempt to insert a second consent_grants row for the same (mentor_id, organisation_id) — assert unique constraint violation; (3) Set revoked_at to a timestamp before granted_at — assert CHECK constraint violation; (4) Verify RLS: authenticate as mentor A and attempt to SELECT mentor B's consent row — assert empty result; (5) Verify audit log is insert-only: attempt UPDATE on consent_audit_log as authenticated user — assert permission denied; (6) Call `delete_mentor_location_data()` for a mentor, then assert consent_grants row is deleted and all location rows referencing that mentor are deleted; (7) Verify ip_hash column rejects values that look like raw IP addresses (optional constraint or application-level validation). Run tests in CI with `supabase db test`.

Component
Location Consent Service
service medium
Epic Risks (2)
medium impact medium prob scope

If the privacy policy text or consent terms change after mentors have already opted in, existing consent records may become legally insufficient, requiring re-consent from all opted-in mentors which could temporarily reduce map coverage.

Mitigation & Contingency

Mitigation: Store a consent_version field on every consent record. Implement a consent version check in location-consent-service that compares the stored version against the current policy version from location-privacy-config and flags stale consents for re-consent prompting.

Contingency: If a policy update invalidates existing consents, suppress affected mentors from the map, queue them for re-consent notification via the existing in-app notification system, and restore map visibility only after new consent is recorded.

medium impact medium prob scope

A poorly designed consent dialog may lead to low opt-in rates, reducing map utility for coordinators to the point where the feature delivers insufficient value to justify maintenance cost.

Mitigation & Contingency

Mitigation: Follow plain-language writing guidelines from the cognitive accessibility feature. User-test the dialog with 2-3 peer mentors from Blindeforbundet before implementation is finalised. Ensure the dialog explains the benefit to the mentor, not just the data collection facts.

Contingency: If opt-in rate after launch is below 40%, conduct a targeted usability study and iterate on dialog copy and layout. The coordinator can also send a bulk opt-in invitation notification (per the user story) to non-consenting mentors.