high priority medium complexity testing pending testing specialist Tier 4

Acceptance Criteria

Test case: successful activation — verify labels are fetched before branding is applied (mock call order enforced via mocktail's `verifyInOrder`)
Test case: successful activation — verify branding is applied before feature flags are loaded
Test case: successful activation — verify feature flags are loaded before the session is persisted to TenantSessionStore
Test case: labels fetch fails — the service rolls back to no-tenant state; branding, feature flags, and session persist are never called
Test case: branding fetch fails — the service rolls back to no-tenant state; feature flags and session persist are never called
Test case: feature flags fetch fails — the service rolls back to no-tenant state; session persist is never called
Test case: after any failure, currentTenant stream emits null (no-tenant state) and does not retain a partial state
Test case: successful activation — currentTenant stream emits the correct Tenant object after all steps complete
Test case: TenantContextService.restore(orgId) replays the same ordered sequence and emits tenant on success
Test case: concurrent calls to activate() for the same org are deduplicated (second call does not trigger a second sequence)
All typed errors from each step are propagated as specific error subtypes (not generic Exception)

Technical Requirements

frameworks
Flutter
flutter_test
mocktail
Riverpod (if TenantContextService is a Notifier)
apis
LabelRepository (mocked)
BrandingRepository (mocked)
FeatureFlagRepository (mocked)
TenantSessionStore (mocked)
TenantContextService.currentTenant stream (under test)
data models
contact (tenant identity context)
performance requirements
All unit tests complete in under 15 seconds total
No real async delays — use Completer-based mocks for controlled async sequencing
security requirements
Test fixtures use clearly fake org IDs and tenant data
Verify session is only persisted after all steps succeed — no partial session state written

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement the orchestration sequence as a sequential chain of awaited Futures inside a single `try/catch` block — do NOT use Future.wait() for these steps as they must run in order, not in parallel. Use a `_rollback()` private method that clears all in-memory state and emits null on currentTenant — call it in the catch block to ensure atomicity. Model the no-tenant state explicitly as `null` (not an empty Tenant object) on the currentTenant stream so consumers can reliably distinguish loading from loaded from unloaded. For the concurrent-call deduplication, use a `_activationCompleter` field: if it is non-null, return its Future instead of starting a new sequence.

Use a BehaviorSubject (rxdart) or StateNotifier for currentTenant so late subscribers receive the current value immediately without waiting for the next emission. Sealed class for TenantActivationError with subtypes: LabelLoadError, BrandingLoadError, FeatureFlagLoadError, SessionPersistError — makes catch clauses exhaustive.

Testing Requirements

Pure unit tests using `test()` and `StreamMatcher` / `expectLater` for stream assertions. Use mocktail's `verifyInOrder([...])` to assert that mock methods are called in the correct sequence. For rollback tests, use `verifyNever()` to confirm downstream steps were not called after an upstream failure. Use `StreamController` to model currentTenant as a testable stream.

For the concurrent call deduplication test, fire two `activate()` calls within the same microtask and verify the mock repositories are only called once. Group tests by: ordering guarantees, rollback on each step failure, stream emission correctness, restore path. Achieve 100% branch coverage on the orchestration method.

Component
Tenant Context Service
service high
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.