critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Concrete class `SupabaseMembershipResolver` implements `MultiOrgMembershipResolver` and is injectable via Riverpod
Queries a Supabase table (e.g., `org_memberships`) using the authenticated user's id from `supabase.auth.currentUser`
RLS policies on the `org_memberships` table must be respected — the query must not use service role key; it must use the user's session token
Returns `SingleOrgResult` when the user belongs to exactly one active organization
Returns `MultiOrgResult` with full list when the user belongs to two or more active organizations
Returns a typed error (e.g., `MembershipResolutionError`) when the user has zero active memberships, distinguishing 'no memberships' from 'network error'
Riverpod provider is updated to `AsyncNotifierProvider` returning `AsyncValue<MembershipResolutionResult>`
The resolver handles Supabase `PostgrestException` and network timeouts gracefully, surfacing them as Riverpod `AsyncError` states
Cross-schema federation (if organizations use separate Supabase schemas) is handled via a single federated view or RPC function — not N separate queries
Query response is mapped to `OrgMembership` model using a typed `fromJson` factory

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase PostgREST API
Supabase Auth API
data models
OrgMembership
MembershipResolutionResult
MembershipResolutionError
performance requirements
Membership query must complete within 2 seconds on a 4G connection
Use a single SQL query or RPC call — not multiple sequential queries per tenant
Query must be indexed on user_id column in org_memberships table
security requirements
Must use Row Level Security (RLS) — the user can only read their own memberships
Service role key must never be used client-side
User session token must be passed via Supabase client auth headers, not manually injected
Log query errors without logging user IDs or personal data

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

If the project uses separate Supabase schemas per organization, the recommended approach is a Postgres function (RPC) called via `supabase.rpc('get_user_memberships')` that performs the cross-schema union internally — this keeps client code clean and puts federation logic in a single versioned DB function. If all orgs share one schema, a simple `.from('org_memberships').select('*').eq('user_id', uid).eq('is_active', true)` suffices. Use `AsyncNotifierProvider` so the UI can react to loading/error states without extra state management. Wire `ref.watch(supabaseClientProvider)` rather than using `Supabase.instance.client` directly — this makes the resolver testable.

Handle the case where `supabase.auth.currentUser` is null by throwing an `UnauthenticatedException` before the query.

Testing Requirements

Unit tests with `flutter_test` using a mocked `SupabaseClient` (via interface or manual mock): (1) single membership returns `SingleOrgResult`, (2) two or more memberships returns `MultiOrgResult` with all entries, (3) empty result returns typed `MembershipResolutionError.noMemberships`, (4) `PostgrestException` maps to `MembershipResolutionError.networkError`. Integration test against a local Supabase instance (Docker or hosted test project): verify RLS prevents user A from seeing user B's memberships. Riverpod provider test: confirm provider transitions through `AsyncLoading` → `AsyncData` on successful resolve. Target 90% coverage on the resolver implementation file.

Component
Multi-Organization Membership Resolver
service medium
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.