critical priority low complexity infrastructure pending infrastructure specialist Tier 1

Acceptance Criteria

FlutterSecureStorageAdapter implements SecureStorageAdapter exactly — no extra public methods
Android configuration uses AndroidOptions with encryptedSharedPreferences: true (AES-256 via Android Keystore)
iOS configuration uses IOSOptions with accessibility set to IOSAccessibility.firstUnlockThisDeviceOnly (allows background reads after first unlock)
write() maps SecureStorageKey to a stable string key (e.g., key.name) before delegating to flutter_secure_storage
read() returns null for missing keys — confirmed by testing that FlutterSecureStorage.read returns null for unknown keys
Encryption failures and platform exceptions are caught and re-thrown as the appropriate SecureStorageException subtype
A Riverpod Provider<SecureStorageAdapter> named secureStorageAdapterProvider is declared and returns FlutterSecureStorageAdapter
In tests the provider can be overridden with FakeSecureStorageAdapter using ProviderContainer.override
No platform channel calls occur during provider construction — initialization is lazy
App compiles and runs on both iOS (Keychain) and Android (Keystore) without runtime errors

Technical Requirements

frameworks
Flutter
flutter_secure_storage
Riverpod
apis
iOS Keychain (via flutter_secure_storage)
Android Keystore (via flutter_secure_storage)
data models
SecureStorageKey
SecureStorageException
performance requirements
Read/write operations complete within 100ms on a mid-range device
Provider construction must be synchronous — no await at provider creation time
security requirements
Android: encryptedSharedPreferences must be true — plain SharedPreferences is not acceptable
iOS: accessibility must not be set to IOSAccessibility.always — must require at least first device unlock
The string representation of SecureStorageKey must be stable across app versions — renaming an enum value breaks existing storage

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The key mapping from SecureStorageKey enum to String must be centralized — use a const Map or key.name (Dart 2.15+). Never concatenate or manipulate keys at call sites. For the Riverpod provider, use a simple `Provider` (not StateProvider or FutureProvider) since the adapter is stateless. If the app later needs to support Android API < 23 (no Keystore), add a runtime check and throw SecureStoragePlatformUnavailableException rather than silently falling back to plaintext.

Document the iOS accessibility setting choice in a comment — future developers must not change it without understanding the background-fetch implications for session restoration.

Testing Requirements

Unit tests for the concrete implementation are handled in the dedicated test task (task-003). For this task: verify compilation on both platforms, and manually confirm on a physical or emulator device that a value written survives an app restart (hot restart is insufficient — cold start required). Write one smoke test using FakeAsync that confirms the Riverpod provider resolves to a FlutterSecureStorageAdapter instance. Verify the provider override pattern works in a ProviderContainer test so downstream tests can substitute the fake.

Component
Secure Storage Adapter
infrastructure low
Epic Risks (3)
high impact medium prob technical

iOS Keychain and Android Keystore have meaningfully different failure modes and permission models. The secure storage plugin may throw platform-specific exceptions (e.g., biometric enrollment required, Keystore wipe after device re-enrolment) that crash higher-level flows if not caught at the adapter boundary.

Mitigation & Contingency

Mitigation: Wrap all storage plugin calls in try/catch at the adapter layer and expose a typed StorageResult<T> instead of throwing. Write integration tests on real device simulators for both platforms in CI using Fastlane. Document the exception matrix during spike.

Contingency: If a platform-specific failure cannot be handled gracefully, fall back to in-memory-only storage for the current session and surface a non-blocking warning to the user; log the event for investigation.

high impact medium prob integration

Setting a session-level Postgres variable (app.current_org_id) via a Supabase RPC requires that RLS policies on every table reference this variable. If the Supabase project schema has not yet defined these policies, the configurator will set the variable but queries will return unfiltered data, giving a false sense of security.

Mitigation & Contingency

Mitigation: Include a smoke-test RPC in the SupabaseRLSTenantConfigurator that verifies the variable is readable from a policy-scoped query before marking setup as complete. Coordinate with the database migration task to ensure RLS policies reference app.current_org_id before the configurator is shipped.

Contingency: If RLS policies are not in place at integration time, gate all data-fetching components behind a runtime check in SupabaseRLSTenantConfigurator.isRlsScopeVerified(); block data access and surface a developer warning until policies are confirmed.

medium impact medium prob technical

Fetching feature flags from Supabase on every cold start adds network latency before the first branded screen renders. On slow connections this may cause a perceptible blank-screen gap or cause the app to render with default (unflagged) state before flags arrive.

Mitigation & Contingency

Mitigation: Persist the last-known flag set to disk in the FeatureFlagProvider and serve stale-while-revalidate on startup. Gate flag refresh behind a configurable TTL (default 15 minutes) so network calls are not made on every launch.

Contingency: If stale flags cause a feature to appear that should be hidden, add a post-load re-evaluation pass that reconciles the live flag set with the rendered widget tree and triggers a targeted rebuild where needed.