critical priority low complexity infrastructure pending backend specialist Tier 1

Acceptance Criteria

LocationPrivacyConfig Dart class exposes: privacyPolicyUrl (String), consentDialogTitle (String), consentDialogBody (String), dataRetentionDays (int), gdprLawfulBasis (enum: consent | legitimate_interest | legal_obligation), currentConsentVersion (String)
Configuration is fetched from a `organisation_privacy_settings` column or table in Supabase, scoped to the current user's organisation_id
Configuration is cached in-memory with a configurable TTL (default 15 minutes) — repeated calls within TTL return cached value without hitting Supabase
Cache is invalidated and refreshed when TTL expires or when `refresh()` is called explicitly
If Supabase fetch fails (network error, auth error), the loader falls back to a hardcoded safe default config and logs a warning
The loader is provided as a Riverpod provider so dependent widgets and services can watch for configuration changes
Configuration is read-only from the Flutter app — no write operations from the client side
Loading state, error state, and loaded state are all exposed via the Riverpod provider
Unit tests verify TTL caching logic and fallback behavior without a real Supabase connection

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase PostgREST (organisation_privacy_settings)
data models
LocationPrivacyConfig
Organisation
performance requirements
Cache hit (within TTL) returns in under 1ms — no async operation
Cache miss (first load or TTL expired) completes in under 2 seconds on a normal mobile connection
In-memory cache uses negligible memory — config object is small (under 1KB)
security requirements
Configuration is fetched using the authenticated user's JWT — RLS on organisation_privacy_settings ensures a user cannot read another org's config
privacyPolicyUrl must be validated as a well-formed HTTPS URL before being exposed — reject HTTP or non-URL values
consentDialogBody must be sanitised before display in Flutter Text widgets to prevent injection

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement as a Riverpod `AsyncNotifier` for clean loading/error/data state management. Store the last-fetched timestamp alongside the cached config — compare `DateTime.now().difference(lastFetched) > ttl` on each read to determine cache staleness. Use a `const Duration(minutes: 15)` default TTL, overridable via provider parameter for testing. The fallback config should contain hardcoded strings that are legally safe (generic GDPR consent language, a placeholder URL that opens the app's about screen).

For the Supabase query, use a `.select()` call filtered by `organisation_id` matching the current session's org claim — avoid fetching all organisations' configs. Model the GDPR lawful basis as a Dart enum `GdprLawfulBasis { consent, legitimateInterest, legalObligation }` with a `fromString` factory for JSON deserialization. This component will be consumed by the consent dialog UI (downstream task) and by the LocationConsentService, so keep the API surface minimal and focused on read-only access.

Testing Requirements

Unit tests using flutter_test and mocktail. Test cases: (1) First call fetches from Supabase mock and caches result — verify Supabase client called exactly once; (2) Second call within TTL returns cached result — verify Supabase client NOT called again; (3) Call after TTL expiry re-fetches — verify Supabase client called again; (4) Supabase error on fetch — verify fallback default config returned and no exception thrown; (5) refresh() call invalidates cache and triggers re-fetch; (6) privacyPolicyUrl with HTTP scheme is rejected and replaced with fallback URL; (7) Riverpod provider emits AsyncLoading, then AsyncData with correct config. Use `FakeAsync` from `fake_async` package to simulate TTL expiry without real time delays in tests.

Component
Location Privacy Configuration
infrastructure low
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.