critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

ConcreteOrgSelectionService implements OrgSelectionService and satisfies all interface contracts defined in task-008
selectOrg() calls OrganizationRepository.fetchById(orgId) and validates the org is_active field before proceeding
A shallow profile check queries the user's profile in the target org's tenant context — it must NOT fetch full profile data, only confirm existence and active status
If the org is found but is_active is false at validation time, selectOrg() returns OrgSelectionResult.deactivated
If the org was active at fetch time but deactivates before the profile check completes (race condition), selectOrg() returns OrgSelectionResult.deactivated with OrgDeactivatedMidFlowException populated
If the user has no profile in the target org, selectOrg() returns OrgSelectionResult.unavailable
Network or Supabase errors are caught and mapped to OrgSelectionResult.networkError with retryable: true for transient errors
All Supabase queries use RLS-compliant patterns — no service role key in mobile client
selectOrg() completes within 3 seconds on a 4G connection (p95)
The implementation is registered as a Riverpod provider and can be overridden in tests

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Flutter SDK
apis
Supabase PostgreSQL 15 (organizations table, profiles/user table)
Supabase Auth (JWT for RLS context)
data models
assignment
contact
performance requirements
Org fetch and profile check must execute sequentially — profile check only runs after org validation passes
Shallow profile check must use SELECT id, is_active LIMIT 1 — no full row fetch
Total selectOrg() latency target: under 3 seconds p95 on 4G
security requirements
All Supabase queries scoped via RLS using the authenticated user's JWT — no service role key on mobile
Org deactivation check must be server-enforced (RLS policy), not just client-side logic
Profile existence check must not leak information about other users' profiles
No PII logged in error messages — use org_id (UUID) only in exception fields

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

The deactivated-mid-flow race condition is the trickiest case: after fetching and validating the org as active, there is a window before the profile check where the org could be deactivated by an admin. To detect this, re-check the org's is_active field as part of the same database transaction as the profile check (use a Supabase RPC or a single query joining organizations and profiles). This eliminates the TOCTOU gap. For the shallow profile check, query only SELECT id FROM user_profiles WHERE user_id = $userId AND organization_id = $orgId LIMIT 1 — do not fetch roles, settings, or any PII.

Register ConcreteOrgSelectionService as a Riverpod Provider so the interface type is what consumers depend on, not the concrete class. This enables clean test overrides via ProviderContainer.overrides.

Testing Requirements

Unit tests with mocked OrganizationRepository: (1) active org + existing profile → success, (2) inactive org → deactivated, (3) active org + missing profile → unavailable, (4) active org deactivates between fetch and profile check → deactivated with OrgDeactivatedMidFlowException, (5) Supabase timeout → networkError with retryable:true, (6) non-retryable auth error → networkError with retryable:false. Integration tests against a local Supabase instance: verify RLS prevents cross-org profile leakage. Performance test: measure selectOrg() latency under simulated 4G throttling. Target 95%+ branch coverage on the concrete implementation.

Component
Organization Selection Service
service low
Epic Risks (4)
high impact medium prob technical

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.

medium impact medium prob security

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.

medium impact low prob technical

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.

medium impact medium prob scope

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.