Implement app startup session restoration read path
epic-organization-selection-and-onboarding-data-layer-task-008 — Implement the startup session restoration flow in TenantSessionStore. On app launch, after authentication state is confirmed, call restoreSelection() to read the previously persisted TenantSessionData from SecureStorage. If a valid selection is found, immediately re-apply the Supabase session custom claim before any data queries are executed, restoring full tenant scope without requiring the user to re-select. Expose the restoration result as a Future that the app router can await before determining the initial route.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 4 - 323 tasks
Can start after Tier 3 completes
Implementation Notes
Implement `restoreAndApplySession()` as: (1) check `supabase.auth.currentUser != null` — return null immediately if unauthenticated, (2) call `restoreSelection()` to read SecureStorage, (3) if null return null, (4) call the same RPC claim-write path used in task-007 with `data.orgId`, (5) on RPC failure call `clearSelection()` and return null, (6) on success return `data`. In Riverpod, expose this as a `FutureProvider.autoDispose` named `tenantRestorationProvider`. In the app shell (root widget or go_router redirect), await this provider before rendering any authenticated routes. Use `ref.watch(tenantRestorationProvider)` with `.when(loading: () => AppLoadingScreen(), error: (e,s) => OrgSelectionRoute(), data: (session) => session != null ?
HomeRoute() : OrgSelectionRoute())`. Ensure the provider is invalidated on sign-out so the next sign-in triggers a fresh restoration.
Testing Requirements
Write widget tests in flutter_test: (1) with a FakeTenantSessionStore returning a valid TenantSessionData, assert the router redirects to the home route (not org selection), (2) with FakeTenantSessionStore returning null, assert the router redirects to the org selection route, (3) with FakeTenantSessionStore throwing on restoreAndApplySession(), assert the router falls back to org selection without crashing, (4) assert the loading screen is shown while the restoration Future is pending (using pumpAndSettle with a delayed fake). Write one unit test confirming restoreAndApplySession() clears stored session and returns null when the RPC call fails.
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.