critical priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

isFeatureEnabled(orgId, featureKey) returns true only when a matching enabled record exists in org_feature_flags
isFeatureEnabled returns false when the record is missing, has is_enabled = false, or any Supabase error occurs
First call for a given (orgId, featureKey) pair executes exactly one Supabase query; subsequent calls within TTL return from in-memory cache without a DB round-trip
invalidateCache(orgId) clears all cached entries for that org, causing the next call to re-fetch from Supabase
invalidateCache() with no arguments clears the entire cache
The service is exposed via a Riverpod Provider (or AsyncNotifierProvider) and can be overridden in tests
The service compiles with no analyzer warnings and passes dart analyze
All public methods are documented with dartdoc comments explaining fail-safe default behavior
Cache TTL is configurable via a constructor parameter with a sensible default (e.g., 5 minutes)
Concurrent calls for the same key while a fetch is in-flight do not trigger duplicate Supabase queries (request deduplication)

Technical Requirements

frameworks
Flutter
Riverpod
Dart
apis
Supabase PostgREST REST API — org_feature_flags table SELECT
data models
OrgFeatureFlag (org_id, feature_key, is_enabled, updated_at)
DriverFeatureFlagConfig constants
performance requirements
Cache hit latency < 1ms (in-memory HashMap lookup)
Cache miss (Supabase query) must complete within 2 seconds on a normal mobile connection
Cache must not grow unbounded — evict entries older than TTL on next access or via a periodic sweep
security requirements
Service must never expose a flag as enabled when the Supabase response is ambiguous or errored — default to false
RLS on org_feature_flags must restrict reads to rows matching the authenticated user's org_id; the service must not attempt to bypass RLS
Feature key strings must be validated against DriverFeatureFlagConfig constants before query to prevent injection of arbitrary key names

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define an abstract class OrgFeatureFlagService with isFeatureEnabled, invalidateCache, and dispose methods. Implement OrgFeatureFlagServiceImpl backed by a Map where _CacheEntry holds the bool value and a DateTime expiry. Use a Completer-based deduplication map (Map>) to coalesce concurrent in-flight fetches for the same key. In the Supabase query use .maybeSingle() so a missing row returns null rather than throwing.

Wrap the entire fetch in try/catch and return false on any exception — log the error via the app logger but do not rethrow. Register via Riverpod as a Provider backed by the impl; use ref.onDispose to call service.dispose() for cleanup. Store DriverFeatureFlagConfig constants as static const String fields (e.g., driverManagement = 'driver_management') so callers never use raw strings.

Testing Requirements

Unit tests (flutter_test) are covered in task-013. This task must deliver the production implementation with a clean abstract interface (OrgFeatureFlagServiceBase or equivalent) that allows mock injection. Ensure the Riverpod provider is defined in a dedicated provider file (feature_flag_providers.dart) so it can be overridden with ProviderContainer.overrides in tests. Manual smoke test: run against a real Supabase dev project with a row for the driver feature flag and verify the correct boolean is returned.

Component
Organization Feature Flag Service
service low
Epic Risks (3)
high impact medium prob security

Row-level security policies for driver assignments and declarations must correctly scope data to the coordinator's chapter without leaking records across organizations. An incorrect RLS predicate could silently return empty result sets or, worse, expose cross-org data, both of which are difficult to detect in unit tests.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration test scenarios with multiple org fixtures asserting both data isolation and correct data visibility. Use Supabase's built-in policy testing utilities and review policies with a second developer.

Contingency: If RLS policies prove too complex to get right quickly, implement application-layer org scoping as a temporary guard while RLS is fixed in a follow-up, with an explicit security review gate before production deployment.

high impact medium prob security

The declaration audit logger must produce tamper-evident records. If the database allows updates or deletes on audit rows, the compliance guarantee is broken. Supabase does not natively prevent row deletion by default.

Mitigation & Contingency

Mitigation: Implement an insert-only RLS policy on the audit table that denies UPDATE and DELETE for all roles including the service role. Add a database trigger that rejects mutation attempts and logs the attempt itself.

Contingency: If immutability cannot be enforced at the database level within the sprint, store audit entries in an append-only Supabase Edge Function log stream as a temporary alternative, with a migration plan to the proper table once constraints are implemented.

medium impact low prob technical

The org-feature-flag-service caches flag values to avoid repeated database reads. If the cache is not invalidated promptly after an admin toggles the flag, coordinators may see stale UI state — either seeing driver features when they should not, or not seeing them when they should.

Mitigation & Contingency

Mitigation: Use a Supabase Realtime subscription to listen for changes on the driver_feature_flag_config table and invalidate the in-memory cache immediately on change. Set a short TTL (60 seconds) as a safety net.

Contingency: If Realtime subscription proves unreliable, expose a manual cache-bust endpoint accessible from the admin toggle action, ensuring the cache is cleared synchronously on every flag change.