Build consent grant and status check service
epic-geographic-peer-mentor-map-consent-privacy-task-003 — Implement the core backend service methods on location-consent-service: grantConsent(mentorId, orgId, consentVersion), checkConsent(mentorId, orgId) returning ConsentStatus enum, and auditConsentEvent(event). All reads and writes must be transactional. checkConsent must be the single gating point enforced before any location data read or write by dependent services.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Define a `ConsentStatus` enum with three values: `granted`, `notGranted`, `revoked`. The distinction between `notGranted` and `revoked` is important: `revoked` triggers a different UI message ('You previously consented but have withdrawn — re-grant to use the map') vs `notGranted` ('Enable location sharing to appear on the peer mentor map'). For atomicity of grant + audit write, implement as a Supabase RPC function `grant_consent(p_mentor_id, p_org_id, p_version, p_ip_hash)` that performs both writes in a PostgreSQL transaction — do not rely on two sequential Supabase client calls from Flutter, as network interruption between them can leave data in an inconsistent state. The ConsentGatingMixin should be implemented as a Dart mixin with a single method `Future
This pattern makes it impossible to accidentally bypass consent checks in new location service methods. Expose `LocationConsentService` via a Riverpod `Provider` (not `AsyncNotifier`) since the service itself is synchronously constructible — only its async methods return futures.
Testing Requirements
Unit tests using flutter_test and mocktail for all service methods. Test cases: (1) grantConsent with matching version — verify upsert called with correct parameters and audit event written; (2) grantConsent with mismatched version — verify ConsentVersionMismatchException thrown and no database write attempted; (3) checkConsent when active row exists — verify returns ConsentStatus.granted; (4) checkConsent when no row exists — verify returns ConsentStatus.notGranted; (5) checkConsent when revoked_at IS NOT NULL — verify returns ConsentStatus.revoked; (6) revokeConsent — verify revoked_at set, audit event written, and delete_mentor_location_data RPC called; (7) Transaction rollback simulation — mock the audit write to throw, verify the grant/revoke was also rolled back (i.e., the mock upsert was called but the overall operation throws); (8) ConsentGatingMixin — verify a wrapped method is blocked with ConsentRequiredException when checkConsent returns notGranted. Integration tests (separate) should verify the actual RLS enforcement using a local Supabase instance.
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.
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.