critical priority low complexity api pending api specialist Tier 3

Acceptance Criteria

The endpoint accepts a GET or POST request with mentorId and orgId parameters and returns a JSON ConsentStatusResponse with fields: mentor_id, org_id, status (enum: pending | granted | denied | revoked), granted_at (ISO 8601 or null), consent_version (string or null), requires_reconsent (boolean)
The endpoint validates the calling user's JWT using Supabase Auth's verify_jwt; requests with missing or invalid JWT return 401
The endpoint confirms that the caller is either the mentor themselves (JWT sub = mentorId) or a coordinator/admin in the same org (JWT org_id = orgId and role in coordinator|admin); otherwise returns 403
If no consent record exists for the given mentorId+orgId, the endpoint returns status: 'pending' rather than 404
The endpoint responds within 300ms at p95 under normal Supabase load (single row SELECT on consent_grants with index hit)
The endpoint is idempotent and read-only — no side effects
A Dart client wrapper class ConsentStatusClient exposes a Future<ConsentStatusResponse> checkStatus(mentorId, orgId) method that parses the response and surfaces typed exceptions for 401, 403, and network errors
The endpoint is deployed to the Supabase project and accessible at /functions/v1/check-consent-status
Integration test confirms correct responses for: active consent, revoked consent, no consent record, unauthorized caller

Technical Requirements

frameworks
Supabase Edge Functions (Deno)
Flutter (Dart client wrapper)
apis
Supabase Auth JWT verification
Supabase PostgreSQL 15 (consent_grants SELECT)
data models
consent_grants
performance requirements
Single indexed SELECT on consent_grants(mentor_id, org_id) — confirm composite index exists
No joins in the hot path — all required data in consent_grants row
Cold start budget: Edge Function cold start must not push p95 above 300ms; consider keeping function warm if called frequently
security requirements
JWT validated before any database query — fail fast on invalid token
Caller authorization check (mentor-self or coordinator-same-org) enforced in function logic, not just RLS, for defense in depth
Response must not include raw database row fields beyond the defined ConsentStatusResponse schema — no leaking of internal columns
Function uses anon key context for RLS-enforced read, not service role, to ensure RLS policies from task-005 are exercised
CORS headers restricted to the app's known origins — no wildcard CORS on consent endpoints

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Implement as a Supabase Edge Function in Deno (TypeScript). At the top of the handler, call createClient with the Authorization header from the request to get a user-scoped client — this ensures RLS from task-005 is applied. Use const { data, error } = await supabase.from('consent_grants').select('status, granted_at, consent_version, requires_reconsent').eq('mentor_id', mentorId).eq('org_id', orgId).maybeSingle() — maybeSingle() returns null instead of error when no row exists, allowing the 'pending' fallback. For the Dart client, use Supabase's functions.invoke('check-consent-status') method rather than raw http calls to get automatic JWT injection.

Define ConsentStatusResponse as an immutable Dart class with fromJson factory and copyWith. Keep the function stateless and free of any caching layer — consent status must always reflect the current database state.

Testing Requirements

Unit tests for the Dart ConsentStatusClient: mock HTTP responses for each status variant (granted, denied, revoked, pending), assert correct ConsentStatusResponse parsing, assert ConsentUnauthorizedException on 403, assert ConsentAuthException on 401, assert network error surfaced as ConsentNetworkException. Integration tests against local Supabase (supabase start): deploy function locally, call with valid JWT for each role, assert correct status returned for each consent state. Load test: 50 concurrent requests to confirm p95 < 300ms. Test cold start by restarting the function and measuring first-call latency.

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.