high priority medium complexity integration pending integration specialist Tier 3

Acceptance Criteria

A `CachingFeatureFlagRepository` class implements `IFeatureFlagRepository` and wraps the base `FeatureFlagRepository` using the decorator pattern
`getFlag` checks `FeatureFlagCache.get` first; returns the cached value immediately on hit; calls through to Supabase only on cache miss, then stores the result via `FeatureFlagCache.put`
`getAllFlags` checks the cache for all known flag keys; on partial or full miss, fetches from Supabase and calls `FeatureFlagCache.putAll` to populate the cache
`setFlag` calls the base repository to persist to Supabase, then immediately invalidates the affected flag in the cache to prevent stale read-your-own-writes
A `warmUp(String orgId)` method fetches all flags from Supabase and populates both cache layers — intended for app startup; is a no-op if the cache is already populated and fresh
`warmUp` handles network failures gracefully: if Supabase is unreachable, the method completes without throwing, relying on any existing persistent cache entries for offline resilience
On organization switch (different orgId), `warmUp` triggers a cache invalidation for the previous org before fetching the new org's flags
The Riverpod provider for `IFeatureFlagRepository` is updated to return `CachingFeatureFlagRepository` (with base repository and cache injected), so all consumers automatically benefit from caching without code changes
Logging at DEBUG level shows cache hit/miss ratio per `getFlag` call for observability during development

Technical Requirements

frameworks
Dart (latest)
Flutter
supabase_flutter
Riverpod
apis
Supabase PostgREST REST API (via base FeatureFlagRepository)
FeatureFlagCache (task-005)
data models
organization_configs
performance requirements
Cache-hit path for `getFlag` must be synchronous or resolve within a single microtask — no network latency on hit
`warmUp` should be called during app initialization (before the home screen renders) to ensure flags are available with no latency during normal use
Batch fetch in `getAllFlags` must use a single Supabase query — never N individual `getFlag` calls
security requirements
Cache warm-up must use the authenticated user's JWT — do not use the service role key in the Flutter client
On logout, the Riverpod provider must call `FeatureFlagCache.clear()` to purge all cached flag state before session teardown
Cache warm-up network failure must not be silently swallowed in production — log the error to the app's error reporting system even if the method does not rethrow

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Use the decorator pattern rather than modifying `FeatureFlagRepository` directly — `CachingFeatureFlagRepository` holds a reference to `IFeatureFlagRepository` (the base implementation) and `FeatureFlagCache`, delegating non-cached operations to the base. This preserves the Single Responsibility Principle and keeps the base repository testable in isolation. In the Riverpod provider setup, compose the providers: `cachingRepositoryProvider = Provider((ref) => CachingFeatureFlagRepository(ref.watch(baseRepositoryProvider), ref.watch(featureFlagCacheProvider)))`. For the warm-up freshness check, store a `lastWarmUpAt` timestamp in memory and skip fetching if it is within the TTL window.

Call `warmUp` from the app initialization sequence (e.g., in the root `ConsumerWidget.initState` or in a splash screen), awaiting its completion before navigating to the home screen. Ensure `warmUp` is idempotent — safe to call multiple times without duplicate Supabase requests.

Testing Requirements

Unit tests using `mocktail` to mock both `IFeatureFlagRepository` (base) and `FeatureFlagCache`. Test cases: (1) `getFlag` with cache hit — base repository is NOT called; (2) `getFlag` with cache miss — base repository IS called and result is stored in cache; (3) `setFlag` calls base repository then calls cache invalidate for the specific key; (4) `warmUp` calls `getAllFlags` on base repository then calls `putAll` on cache; (5) `warmUp` with network failure (base repository throws) — method completes without rethrowing; (6) `warmUp` when cache is fresh — base repository is not called (no redundant fetch); (7) org switch triggers invalidation of old org. Integration test: full warm-up → getFlag (cache hit) → setFlag → getFlag (cache miss, Supabase fetched) sequence against local Supabase. Run via `flutter_test`.

Component
Feature Flag Repository
data medium
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for organization_configs may have gaps that allow cross-organization reads if the JWT claim for organization_id is absent or malformed, leading to data leakage between tenants.

Mitigation & Contingency

Mitigation: Implement RLS policies using auth.uid() joined against a memberships table to derive organization_id rather than trusting a client-supplied claim. Write integration tests that simulate a cross-org read attempt and assert it returns zero rows.

Contingency: If a gap is discovered post-launch, immediately disable the affected RLS policy, roll back the migration, and re-implement with a parameterized policy tested against all organization fixture data.

medium impact medium prob technical

Dart does not have a built-in semantic version comparison library; a naive string comparison (e.g., '2.10.0' < '2.9.0' lexicographically) would cause rollout evaluator to produce incorrect eligibility results for organizations on different app versions.

Mitigation & Contingency

Mitigation: Use the pub.dev `pub_semver` package or implement a proper three-segment integer comparison. Add parameterized unit tests covering 20+ version pairs including double-digit minor/patch segments.

Contingency: If incorrect comparison is discovered in production, push a hotfix with corrected comparison logic and temporarily disable phase-gated flags until all affected organizations have updated to the corrected version.

medium impact low prob technical

Persistent local cache written to shared_preferences or Hive could become corrupted or deserialized incorrectly after an app update changes the FeatureFlag schema, causing startup crashes or all flags defaulting to disabled.

Mitigation & Contingency

Mitigation: Wrap all cache reads in try/catch with explicit fallback to the all-disabled default map. Version the cache key (e.g., `feature_flags_v2_{orgId}`) so schema changes automatically invalidate old entries.

Contingency: If cache corruption is detected in a release, publish an app update that clears the versioned cache key on first launch and re-fetches from Supabase.