critical priority medium complexity integration pending integration specialist Tier 4

Acceptance Criteria

OrgSelectionService.selectOrg() calls TenantContextService.load(orgId) only after org validation and shallow profile check both pass
selectOrg() does not return OrgSelectionResult.success until TenantContextService.load() resolves without error
If TenantContextService.load() throws or returns an error state, selectOrg() returns OrgSelectionResult.unavailable (not a silent failure)
The full seeding sequence (labels → branding → feature flags → session persistence) completes before success is returned
Navigation triggered by OrgSelectionResult.success is guaranteed to have a fully populated currentTenant stream available
If selectOrg() is cancelled (e.g., user presses back), TenantContextService.load() is also cancelled and no partial state is persisted
The integration does not create a circular dependency between OrgSelectionService and TenantContextService
Integration is covered by an end-to-end test that verifies the full selectOrg() → TenantContextService.load() → currentTenant emission chain
Timeout of 10 seconds applied to the full selectOrg() call — if exceeded, returns networkError with retryable:true

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase Edge Functions (labels, branding, feature flags endpoints)
data models
assignment
accessibility_preferences
performance requirements
Full selectOrg() including tenant seeding must complete within 10 seconds p95 on 4G
Individual seeding steps (labels, branding, flags) should be parallelized where there are no data dependencies
security requirements
TenantContextService.load() must be called with the org_id from the validated org — not from user input directly
No tenant state should be persisted if TenantContextService.load() fails partway through
Cancellation must trigger a secure clear of any partially written secure storage values

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Inject TenantContextService into ConcreteOrgSelectionService via Riverpod constructor injection (not via a global read) to keep the dependency explicit and testable. The seeding steps within TenantContextService.load() can be parallelized using Future.wait() where there are no data dependencies (e.g., labels and branding may be independent), but feature flags must come after branding if they influence UI theme. Use a CancelToken pattern (or Dart's CancellationToken equivalent) to allow the caller to cancel the entire selectOrg() operation cleanly. Document the contract: callers MUST check OrgSelectionResult before attempting any navigation — this is the single synchronization point that guarantees the app is configured.

Consider adding a timeout wrapper around TenantContextService.load() using Future.timeout() with a typed TimeoutException mapped to OrgSelectionResult.networkError.

Testing Requirements

Integration tests: (1) happy path — selectOrg() triggers load(), all seeding steps succeed, OrgSelectionResult.success returned, currentTenant stream emits active state; (2) load() fails midway — selectOrg() returns unavailable, no org_id persisted in secure storage; (3) cancellation mid-load — verify TenantContextService.clear() is called and currentTenant reverts to none; (4) timeout after 10s — networkError with retryable:true returned; (5) dependency order — labels available before branding step runs. Use a mock TenantContextService to simulate each failure mode in unit tests. One end-to-end test with a real Supabase test project verifying the full chain.

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.