critical priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

TerminologyRepository.fetchLabels(orgId) returns Either<TerminologyFailure, Map<String, String>> — never throws
On successful Supabase query, the returned label map matches the JSONB `labels` column from organization_configs for the given orgId
On successful fetch, TerminologyCacheAdapter.write(orgId, labelMap) is called before the result is returned
On SupabaseException or network failure, TerminologyCacheAdapter.read(orgId) is called and its result is returned as Right if non-null
On network failure with empty cache, a typed Left(TerminologyFailure.networkAndCacheEmpty()) is returned
Repository class is final, constructor-injected with SupabaseClient and TerminologyCacheAdapter interfaces (no hard dependencies on concrete classes)
Supabase query selects only the `labels` column with a `.eq('org_id', orgId)` filter — no unbounded table scans
Null or missing `labels` column in DB row is treated as an empty map (not a failure), and the empty map is cached
All Supabase client exceptions (timeout, auth error, malformed JSON) are caught and mapped to typed TerminologyFailure subtypes
Repository compiles without warnings and passes `dart analyze`

Technical Requirements

frameworks
Flutter
Riverpod
supabase_flutter
apis
Supabase PostgREST — organization_configs table, labels JSONB column, filtered by org_id
data models
organization_configs (orgId, labels: Map<String, String>)
TerminologyFailure (sealed class hierarchy)
Either<L, R> or Result<T, E> (fpdart or custom)
performance requirements
Supabase query must select only the `labels` column — avoid SELECT *
Repository method must complete within 3 seconds on a 3G connection before falling back to cache
No synchronous blocking operations on the main isolate
security requirements
Supabase Row Level Security (RLS) must enforce that authenticated users can only read organization_configs rows for their own org
No label data logged at INFO level or above — org labels may contain PII-adjacent terminology
SupabaseClient must be obtained via the authenticated session — anonymous reads are not permitted

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a sealed class or freezed union for TerminologyFailure (e.g. networkError, cacheEmpty, parseError, authError) so callers are forced to handle all cases via exhaustive switch. Prefer fpdart's Either> as the return type for consistency with functional Dart patterns; if the team has not adopted fpdart, a simple Result custom class is acceptable but must be documented. The Supabase call should be: `await _supabase.from('organization_configs').select('labels').eq('org_id', orgId).single()`.

Wrap the entire method body in a try-catch that catches Object (not just Exception) to guarantee no leakage. Do not use `maybeSingle()` unless the schema guarantees at most one row — use `.single()` and map the empty-result exception to an empty map. Inject dependencies via constructor so the class is testable without a live Supabase instance.

Testing Requirements

Unit tests must mock both SupabaseClient and TerminologyCacheAdapter. Cover: (1) successful fetch → cache write → returns Right(labelMap); (2) network exception → cache hit → returns Right(cachedMap); (3) network exception → cache miss → returns Left(networkAndCacheEmpty); (4) Supabase returns null labels → treated as empty map, cached, returned as Right({}); (5) Supabase returns malformed JSON → mapped to Left(TerminologyFailure.parseError). Use flutter_test with mockito or mocktail. Minimum 90% branch coverage on the repository class.

Component
Terminology Repository
data low
Epic Risks (2)
high impact medium prob integration

The labels JSONB column in organization_configs may lack a consistent schema across organizations, causing deserialization failures or silent missing keys when a new organization is onboarded with a differently structured map.

Mitigation & Contingency

Mitigation: Define and enforce a canonical JSONB schema via a Supabase check constraint and a migration script. Validate the schema in TerminologyRepository at parse time and emit structured errors for any key-type mismatches.

Contingency: If schema drift is discovered post-deployment, LabelKeyResolver's fallback logic ensures the app continues to function with English defaults while a data migration is prepared to normalize the offending organization's config.

medium impact low prob technical

Device local storage corruption or platform-specific SharedPreferences serialization bugs could render a cached terminology map unreadable, causing the app to fall back to English defaults unexpectedly for an organization with custom terminology.

Mitigation & Contingency

Mitigation: Wrap all cache reads in try/catch, validate the deserialized map against a minimum-required-keys check, and evict corrupted entries automatically before re-fetching from Supabase.

Contingency: Surface a non-blocking in-app warning to the coordinator that terminology may be in default English until the next sync completes; trigger an immediate background sync.