critical priority medium complexity backend pending fullstack developer Tier 4

Acceptance Criteria

TenantSessionStore exposes a `Future<TenantSessionData?> restoreAndApplySession()` method that reads SecureStorage and re-applies the Supabase custom claim in one atomic operation
On successful restoration, the method returns the TenantSessionData so the app router can navigate directly to the home screen without showing the organization selection screen
If SecureStorage returns null (no prior selection), the method returns null and the app router navigates to the organization selection screen
If the stored org_id claim re-application (RPC call) fails during restoration, the method clears the stored selection and returns null — the user is sent to org selection, never left in a partially-scoped state
The restoration Future completes before the app router's `redirect` callback returns a route — no route is rendered until tenant scope is known
A Riverpod FutureProvider (e.g., `tenantRestorationProvider`) wraps `restoreAndApplySession()` and is awaited at the app shell level before any data-fetching providers are activated
The restoration flow is exercised with a widget test confirming that with a stored session the router navigates to home, and without a stored session it navigates to org selection
Restoration is performed at most once per app launch; subsequent route navigations do not re-invoke the RPC claim write

Technical Requirements

frameworks
Flutter
Riverpod
flutter_test
go_router (or equivalent Flutter navigator)
apis
Supabase Auth (confirm authenticated state before reading storage)
Supabase RPC: `set_active_organization(org_id uuid)` (re-apply claim)
flutter_secure_storage (read persisted TenantSessionData)
data models
assignment
accessibility_preferences
performance requirements
Total restoration time (SecureStorage read + RPC call + session refresh) must complete within 3 seconds on a cold start under normal network — display a loading indicator while awaiting
SecureStorage read must not block the UI thread; use async/await pattern throughout
Supabase RPC timeout of 5 seconds must be enforced to prevent indefinite loading on poor connectivity
security requirements
Restoration must only proceed after Supabase Auth confirms the user is authenticated (non-null currentUser) — never apply a stored org claim to an unauthenticated session
If the stored orgId does not match any organization the current user belongs to (RPC validation failure), clear the stored session and force org re-selection
The restoration flow must not log TenantSessionData fields containing orgId or userRole to the console in production builds
On app backgrounding during restoration, the flow must complete or be safely cancelled — do not leave orphaned RPC calls
ui components
AppLoadingScreen (full-screen loading indicator shown while restoration Future is pending)
OrganizationSelectionScreen (navigated to when restoration returns null)

Execution Context

Execution Tier
Tier 4

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.

Component
Tenant Session Store
data medium
Epic Risks (2)
high impact low prob technical

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.

medium impact low prob integration

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.