high priority medium complexity infrastructure pending backend specialist Tier 3

Acceptance Criteria

terminologySyncServiceProvider is declared as an autoDispose provider — provider is disposed when its last listener is removed
Provider create callback calls TerminologySyncService.start() before returning the service instance
Provider onDispose callback calls TerminologySyncService.dispose() to cancel timers and subscriptions
When auth state transitions to unauthenticated, the provider is invalidated within one event loop tick
After invalidation on logout, a subsequent login causes the provider to be re-created fresh with a new TerminologySyncService instance
terminologySyncServiceProvider reads organizationLabelsNotifierProvider.notifier (not the state) to trigger reloads — no circular dependency at runtime
Provider graph analysis (e.g., via ProviderContainer) confirms no circular dependency between terminologySyncServiceProvider and organizationLabelsNotifierProvider
Disposing the provider while a sync is in-flight cancels the sync gracefully without throwing unhandled exceptions
The provider is not recreated on every auth state change — only invalidated on the unauthenticated transition

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase Auth (auth state stream)
data models
AuthState
TerminologySyncService
performance requirements
Service start and dispose must complete without blocking the UI thread
Auth state listener must be removed during dispose to prevent zombie subscriptions
security requirements
On logout, the sync service must be fully torn down before any new auth session begins — prevents cross-user data leakage
Invalidation must occur before any cached label data is cleared, maintaining UI stability during the logout transition

Execution Context

Execution Tier
Tier 3

Tier 3 - 413 tasks

Can start after Tier 2 completes

Implementation Notes

Pattern: use `ref.listen(authStateProvider, (prev, next) { if (next.isUnauthenticated) ref.invalidateSelf(); })` inside the provider create callback to wire logout invalidation without creating a separate side-effect provider. To avoid circular deps, pass `ref.read(organizationLabelsNotifierProvider.notifier)` as a constructor argument to TerminologySyncService at creation time rather than having the sync service read the provider itself. Use `ref.onDispose(() => service.dispose())` for cleanup. Consider wrapping the whole create body in a try/catch to ensure ref.onDispose is always registered even if service construction throws.

Testing Requirements

Unit tests using ProviderContainer: (1) verify service is started on provider creation; (2) verify service dispose is called when provider is disposed; (3) verify provider invalidation fires when auth state emits unauthenticated; (4) verify a new service instance is created after re-listen post-logout; (5) verify no circular dependency by inspecting the provider graph — use ProviderContainer.read() in topological order; (6) verify that disposing mid-sync does not throw. Use a fake/mock AuthStateStream and a mock TerminologySyncService to control timing. Integration test: simulate full login → sync → logout → re-login cycle and assert service lifecycle events in order.

Component
Terminology Riverpod Providers
infrastructure low
Epic Risks (3)
high impact medium prob technical

When a user switches organization context (e.g., a coordinator with multi-org access), a race condition between the outgoing organization's map disposal and the incoming organization's fetch could briefly expose the wrong organization's terminology to the widget tree.

Mitigation & Contingency

Mitigation: Implement an explicit loading state in OrganizationLabelsNotifier that widgets check before rendering any resolved labels. The provider graph should cancel the previous organization's fetch via Riverpod's ref.onDispose before initiating the next.

Contingency: If the race manifests in production, fall back to English defaults during the transition window and emit a Sentry error event for investigation; the UX impact is a brief English flash rather than wrong-org terminology.

high impact low prob security

Supabase Row Level Security policies on organization_configs may inadvertently restrict the authenticated user from reading their own organization's labels JSONB column, causing silent empty maps that appear as English fallbacks.

Mitigation & Contingency

Mitigation: Write and test explicit RLS policies that grant SELECT on the labels column to any authenticated user whose organization_id matches. Add an integration test that verifies label fetch succeeds for each role (peer mentor, coordinator, admin).

Contingency: If RLS blocks are discovered in production, temporarily escalate label fetch to a service-role edge function while the RLS policy is corrected, ensuring no labels are exposed cross-organization.

medium impact medium prob scope

A peer mentor who installs the app for the first time with no internet connection will have no cached terminology map and will see only English defaults, which may be confusing for organizations like NHF that use Norwegian-specific role names exclusively.

Mitigation & Contingency

Mitigation: Bundle a default fallback terminology map for each known organization as a compile-time asset (Dart asset file) so that even fresh installs without connectivity render correct organizational terminology immediately.

Contingency: If bundled assets are out of date, display a one-time informational banner noting that terminology will update on next connectivity restore, with no functional blocking of the app.