critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

Concrete `SupabaseTenantContextService` partially implements `TenantContextService`, completing the `load(orgId)` method up to and including terminology label injection
Terminology labels are fetched from a Supabase table (e.g., `org_labels`) filtered by orgId, returning a list of `{key: string, value: string}` rows
Fetched labels are stored as `Map<String, String>` and applied to `OrganizationLabelsNotifier` via `ref.read(organizationLabelsProvider.notifier).setLabels(labels)`
All `TerminologyAwareTextWidget` instances across the app re-render with the new labels within one frame after `setLabels` is called (verified by widget test)
If no label overrides are found for the org, the app falls back to default labels defined in `LabelKeys` constants — the app must remain fully functional
TenantContext state transitions correctly: `empty` → `loading` (start of load) → `ready` (after labels set) in the StateNotifier
Network errors during label fetch surface as `TenantContextStatus.error` in the notifier state, with an error message accessible to the UI
Supabase query uses user's session RLS — labels are only returned for orgs the user belongs to
Dart analyzer reports zero errors; no console warnings in debug mode after label injection

Technical Requirements

frameworks
Flutter
Riverpod
Supabase
apis
Supabase PostgREST API
data models
TenantContext
OrganizationLabels
LabelKeys
performance requirements
Label fetch and injection must complete within 1.5 seconds on a 4G connection
Label map must be populated before any UI route dependent on org context is rendered
Single Supabase query must retrieve all label overrides — no per-key queries
security requirements
RLS on org_labels table must restrict rows to the authenticated user's orgs
Label values must be HTML-escaped before use in web contexts — Flutter Text widgets handle this natively
No label key or value may contain executable code or deep link URIs
ui components
TerminologyAwareTextWidget

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

The `OrganizationLabelsNotifier` is likely already defined elsewhere in the project (referenced in CLAUDE.md). Do not duplicate it — read its existing API and call it from within `load()`. Use `ref.read` (not `ref.watch`) inside the service implementation to avoid circular dependency loops. The sequence within `load(orgId)` should be: (1) emit `loading` state, (2) fetch labels from Supabase, (3) call `OrganizationLabelsNotifier.setLabels()`, (4) update TenantContext with labels and status `ready`, (5) emit new state.

Wrap the entire body in try/catch to ensure `error` state is always emitted on failure. For the fallback label mechanism, define a `DefaultLabels` const map keyed by `LabelKeys` constants so it can be used both as the initial state and as the fallback when the Supabase query returns no rows.

Testing Requirements

Unit tests with `flutter_test` using a mocked Supabase client: (1) successful fetch returns correct Map from rows, (2) empty result (org with no overrides) results in fallback labels being applied — assert OrganizationLabelsNotifier holds default values, (3) Supabase exception maps to TenantContextStatus.error with non-empty error message, (4) state transitions in order: empty → loading → ready. Widget test: render a tree containing several `TerminologyAwareTextWidget` instances with a real `OrganizationLabelsNotifier`, call `setLabels`, and assert each widget displays the updated label text within one pump cycle. Integration test (optional): against a local Supabase instance with seeded org_labels data, verify the full load path end-to-end.

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.