critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

SupabaseMentorLocationService (the concrete implementation) applies a post-fetch consent filter: after receiving the raw list from Supabase, it removes any MentorLocation where consentStatus != ConsentStatus.granted before returning to the caller
The consent filter is applied in both fetchMentorsInBoundingBox and getMentorsSortedByDistance
If Supabase returns a mentor with null or unrecognised consent status, the mentor is treated as not consented and excluded — no unhandled null
Integration test: seed the mock Supabase client with 3 mentors (1 granted, 1 denied, 1 revoked); assert fetchMentorsInBoundingBox returns exactly 1 mentor with the correct mentorId
Integration test: seed with all-granted mentors; assert all are returned (filter does not incorrectly exclude)
Integration test: getMentorsSortedByDistance with mixed consent seed; assert only granted mentors in the sorted result
If the consent check itself throws (e.g., LocationConsentService is unavailable), the service returns ConsentCheckFailure — not a silent empty list
The filtering logic is extracted into a private _filterByConsent(List<MentorLocation>) method so it can be unit-tested in isolation
No location coordinate data appears in logs at any level — logging uses mentorId only

Technical Requirements

frameworks
Flutter
BLoC
Riverpod
flutter_test
mocktail
apis
Supabase PostgREST (mocked in tests)
LocationConsentService (injected dependency)
data models
MentorLocation
ConsentStatus
MentorLocationFailure
FilterCriteria
performance requirements
_filterByConsent must run in O(n) — single pass, no nested iteration
Post-fetch filter adds less than 2ms overhead for lists up to 200 mentors
security requirements
Consent enforcement is non-bypassable: the filter must run unconditionally — no feature flag, no debug bypass, no caller-controlled skip parameter
Do not log or expose raw Coordinates of non-consented mentors even in error paths
Consent status is read from the authoritative source (database field) — never from a client-side cache that could be stale

Execution Context

Execution Tier
Tier 2

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.

Component
Mentor Location Service
service high
Epic Risks (2)
medium impact medium prob technical

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.

low impact medium prob scope

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.