Implement background sync version check logic
epic-dynamic-terminology-and-labels-service-layer-task-005 — Implement the version comparison step inside TerminologySyncService: read the cached map's updatedAt, fetch the updated_at timestamp from Supabase (lightweight query, no full map download), compare values, and return a SyncDecision (upToDate or refreshRequired). This step must be cheap enough to run on every app foreground event without noticeable latency.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 1 - 540 tasks
Can start after Tier 0 completes
Implementation Notes
Implement as a private method _checkVersion on the concrete TerminologySyncServiceImpl class. Use Supabase's .from('organization_terminology').select('updated_at').eq('org_id', orgId.value).maybeSingle() to get a nullable result. Treat a null result the same as a cache miss (refreshRequired). Parse the returned updated_at string to DateTime.parse(...).toUtc() for comparison.
Store and compare all timestamps in UTC to avoid DST edge cases. Wrap the entire Supabase call in try/catch; map PostgrestException and SocketException to TerminologySyncException with a bool isNetworkError flag so the retry logic can distinguish permanent from transient failures. Keep this method pure and stateless — no side effects — so it can be unit tested in isolation.
Testing Requirements
Unit tests using flutter_test with mocked Supabase client (via mockito or manual stub). Test all four decision paths: (1) no cache entry → refreshRequired; (2) cache.updatedAt == server.updated_at → upToDate; (3) server.updated_at is 1 ms newer → refreshRequired; (4) Supabase throws → TerminologySyncException propagated. Verify the Supabase query string contains only 'updated_at' column and a WHERE clause (using argument capture on the mock). Add a performance smoke test that asserts mock-backed call completes in under 10 ms.
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.
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.
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.