Implement consent enforcement layer in MentorLocationService
epic-geographic-peer-mentor-map-core-services-task-004 — Implement the consent status check within MentorLocationService that filters out any mentor whose location_consent field is not opted-in before results are returned to callers. This is the last line of defence before consented-only data reaches the UI. Enforce the check as a synchronous post-fetch filter and add integration tests verifying that opted-out mentors are never surfaced.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
The consent enforcement layer must be the last transformation applied to the raw Supabase result — after deserialization, after caching, but before returning to the BLoC. Define the order explicitly in a comment: fetch → deserialize → cache → consent filter → return. This order ensures the cache can store the full unfiltered result (for performance) while callers always receive the filtered set. Do not query LocationConsentService per-mentor in the filter loop — that would be O(n) network calls.
Instead, the consentStatus field on MentorLocation must already be populated from the Supabase join/select at fetch time. The Supabase query must include location_consent in its select clause. If the field is missing from the response, treat it as denied and log a warning with mentorId only.
Testing Requirements
Write integration-style tests in test/features/geographic_map/data/services/supabase_mentor_location_service_test.dart using a FakeSupabaseClient (mocktail stub). Test scenarios: (1) mixed consent seed → only granted returned, (2) all granted → all returned, (3) all denied/revoked → empty list returned (not an error), (4) null consentStatus in raw Supabase response → mentor excluded, (5) LocationConsentService throws → ConsentCheckFailure returned, (6) empty Supabase response → empty list returned successfully. For the _filterByConsent private method, test it via a package-private test or by making it internal to the test package. Assert that opted-out mentorIds never appear in any returned list.
The dual BLoC state machines (map view state + filter state) may introduce subtle synchronisation bugs where filter changes do not correctly re-trigger viewport queries, causing stale data to appear on the map.
Mitigation & Contingency
Mitigation: Define all BLoC state transitions in a state diagram before implementation. Use flutter_bloc's BlocObserver in development mode to log every state transition. Write explicit unit tests for filter-change → re-query transitions.
Contingency: If state synchronisation bugs appear in integration testing, refactor to a single unified BLoC that owns both map viewport state and filter state, eliminating cross-BLoC dependencies.
Cached mentor location data may become stale (mentors move, pause, or revoke consent) and coordinators in offline mode could be shown incorrect mentor information, leading to wasted outreach.
Mitigation & Contingency
Mitigation: Display a clear timestamp on cached data indicating when it was last synced. Set cache TTL to 24 hours and show an 'offline — data from [date]' banner. Revoked consent removes the mentor from the cache on next successful sync via contact-cache-sync-repository.
Contingency: If cache staleness causes user complaints, reduce TTL to 4 hours and implement background sync on app foreground. Accept that very-recently-revoked mentors may appear briefly in offline mode — document this as a known limitation in the privacy policy.