Add session-level membership cache to MultiOrgMembershipResolver
epic-organization-selection-and-onboarding-core-logic-task-003 — Add an in-memory session cache layer to MultiOrgMembershipResolver so that repeated navigation events do not re-query Supabase. Cache should be invalidated on sign-out and on explicit org switch. Implement cache invalidation hook using Supabase auth state stream.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Use the decorator pattern: `CachedMembershipResolver` wraps `SupabaseMembershipResolver` and adds caching behavior without modifying the concrete resolver. This keeps both classes single-responsibility. Subscribe to `supabase.auth.onAuthStateChange` in the Riverpod provider's `build` method using `ref.onDispose` to cancel the subscription when the provider is disposed. Use `ref.listen` or a stream subscription — not a polling timer.
The `invalidateMembership` method should be exposed on the Riverpod notifier so OrgSelectionService (task-005) can call `ref.read(membershipResolverProvider.notifier).invalidate(userId)` after a successful org switch. Keep the cache as a plain `Map
Testing Requirements
Unit tests with `flutter_test`: (1) first `resolve` call hits the mock Supabase resolver — assert mock called once, (2) second `resolve` call for the same userId returns cached result — assert mock called still only once, (3) after `invalidateMembership(userId)` the next `resolve` call hits the resolver again — assert mock called twice total, (4) simulate `signedOut` auth event and verify cache map is empty afterward, (5) simulate `signedIn` event does NOT clear an existing cache entry (sign-in of the same user should not force a re-fetch). All tests are pure unit tests — no real Supabase connection needed.
TenantContextService must invalidate all downstream Riverpod providers when the org context changes (org switch scenario). If any provider caches org-specific data without subscribing to the tenant context, it will serve stale data from the previous org after a switch — which is both a UX failure and a potential GDPR violation.
Mitigation & Contingency
Mitigation: Define a single TenantContextProvider at the root of the Riverpod provider graph that all org-scoped providers depend on via ref.watch(). When TenantContextService.seedContext() runs, it invalidates TenantContextProvider which cascades invalidation to all dependents. Document this pattern in an architectural decision record so all developers follow it.
Contingency: Implement a post-switch integrity check that re-fetches a sample of each major data entity type and confirms the returned org_id matches the newly selected context; surface a reload prompt if any mismatch is detected.
MultiOrgMembershipResolver must query role assignments across potentially multiple tenant schemas. The anon or authenticated Supabase RLS policy may not permit cross-schema queries, making it impossible to return the full list of orgs a user belongs to in a single call.
Mitigation & Contingency
Mitigation: Design the membership query to use a dedicated Supabase edge function or a shared public schema view that aggregates role assignments across tenant schemas with a service-role key, returning only the org IDs the calling user is permitted to see. This keeps the client read-only.
Contingency: If cross-schema queries cannot be made safely, fall back to a per-org sequential membership check using the list of known org IDs and coalesce results client-side with appropriate timeout handling.
go_router redirect guards behave differently on web vs. mobile for deep links and browser back-button navigation. If the app is later deployed as a Progressive Web App (PWA) for admin use, the OrgRouteGuard may loop or fail to apply correctly on browser navigation events.
Mitigation & Contingency
Mitigation: Implement the guard as a GoRouter.redirect callback (not a ShellRoute redirect) following go_router best practices for platform-agnostic guards. Write widget tests that simulate navigation with and without auth/org context on both mobile and web target platforms in CI.
Contingency: If web-specific guard behaviour differs unacceptably, introduce a platform check in the guard and apply separate redirect logic branches for web vs. mobile until a unified solution is found.
In Phase 2 the OrgSelectionService will need to coordinate the handoff to BankID/Vipps authentication after the org is selected, storing the returned personnummer against the correct tenant's member record. If the service is designed too narrowly for Phase 1 email/password flow, retrofitting Phase 2 will require invasive changes to an already-tested component.
Mitigation & Contingency
Mitigation: Design OrgSelectionService with an AuthHandoffStrategy interface from the start (Phase 1 implementation: email/password, Phase 2: BankID/Vipps). The strategy pattern makes the Phase 2 swap an additive change rather than a rewrite. Stub the interface in Phase 1 with a TODO comment referencing the Phase 2 epic.
Contingency: If Phase 2 requirements diverge significantly from Phase 1 assumptions, create a dedicated Phase2OrgSelectionService subclass that extends the base and overrides the auth handoff step, preserving Phase 1 behaviour unchanged.