Implement Supabase query layer for organization fetching
epic-organization-selection-and-onboarding-data-layer-task-002 — Implement the concrete OrganizationRepository backed by the Supabase client. Write the query to fetch all organizations the authenticated user is a member of, applying RLS policies automatically. Map raw Supabase JSON rows to typed Organization domain models. Handle network errors with typed exceptions and implement retry logic for transient failures.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Place the implementation in `lib/data/repositories/supabase_organization_repository.dart`. Register it as a Riverpod `Provider
Wrap the stream in a StreamController and add a finalizer/dispose callback in the Riverpod provider's `ref.onDispose` to cancel the Realtime subscription — memory leaks from undisposed Realtime channels are a common Flutter/Supabase bug. For error mapping, create a private `_mapSupabaseError(Object e)` method that converts PostgrestException, SocketException, and TimeoutException to the appropriate typed domain exceptions — this keeps the query methods clean.
Testing Requirements
Unit tests using mocktail to mock the Supabase client and query builder chain. Test: fetchAllActive() calls the correct query with is_active filter; fetchById() throws OrgNotFoundException on empty result; retry logic fires 3 times before throwing OrgNetworkException; empty result returns empty list not null. Integration test (optional, CI-gated): against a local Supabase instance via Docker, verify RLS correctly filters organizations by authenticated user. Use flutter_test for all tests.
Mock the Supabase Realtime channel for watchAll() tests — verify subscribe() and unsubscribe() are called at correct lifecycle points. Minimum 85% branch coverage on the repository implementation.
TenantSessionStore must write to both SecureStorageAdapter and Supabase session synchronously. If the Supabase write succeeds but the local secure storage write fails (or vice versa), the system ends up in an inconsistent state: local app thinks org A is selected but Supabase queries are scoped to org B (or unscoped), causing silent data leakage or empty result sets.
Mitigation & Contingency
Mitigation: Implement the dual-write with compensating rollback: if the second write fails, undo the first write and surface a typed DualWriteFailureError to callers. Add a startup integrity check in restoreSession() that validates local storage and Supabase session agree on the current org_id.
Contingency: If integrity check fails on startup, clear both stores and redirect the user to the org selection screen rather than proceeding with potentially mismatched state.
An organization could be deactivated in Supabase between the time the org list is cached and the time the user taps to select it. If the repository serves stale cached data the org-selection-service will attempt to seed a context for an inactive org, potentially causing RLS scope issues or confusing error states.
Mitigation & Contingency
Mitigation: The OrganizationRepository.getOrganizationById() path used during selection validation always performs a fresh network fetch (bypassing cache) to confirm the org is still active before the TenantSessionStore writes anything.
Contingency: If a freshness check finds the org is inactive, surface a localized error message on the selection screen ('This organization is no longer available') and refresh the org list to show only currently active partners.