Add key convention validation to LabelKeyResolver
epic-dynamic-terminology-and-labels-foundation-task-005 — Add a validateKey(String key) method to LabelKeyResolver that checks whether the provided key exists as a static constant in LabelKeyRegistry using a generated key set. In debug mode, throw an assertion error for unregistered keys. In release mode, log a warning and return the formatted key as fallback. Generate a Set<String> of all valid keys from LabelKeyRegistry using code generation or a static initializer to avoid reflection.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Since Dart/Flutter does not support runtime reflection in release mode, the valid key set must be statically defined. Two approaches: (1) Manual static initializer — add a `static final Set
(2) Code generation — write a `build_runner` builder that reads `label_key_registry.dart` and generates a `_validKeys` set. Approach (1) is recommended for the current scale (low complexity, few keys); approach (2) is better if the registry grows beyond ~100 keys. Integrate `validateKey` into the existing `resolve()` method as the first step — call `assert(validateKey(key), 'Unregistered label key: $key')` at the top of `resolve()`. The `assert` statement is a no-op in release mode, so add an explicit `if (kDebugMode)` block for the warning log to ensure visibility during development without impacting production.
Testing Requirements
Unit tests in `label_key_resolver_test.dart` (extend existing file). Test group 'validateKey': (1) known key returns true, (2) typo key returns false, (3) empty string returns false. Test group 'debug mode assertion': use `expect(() => LabelKeyResolver.resolve('invalid.key', {}), throwsAssertionError)` — this works because `flutter_test` runs in debug mode by default. Test group 'release mode behavior': mock `kDebugMode` to false using a test-injectable flag or test the `validateKey` method directly without calling `resolve` in release mode.
Alternatively, extract the warn-and-fallback logic into a separate method and test it directly. Ensure the valid key set test (`_validKeys` matches `LabelKeyRegistry` constants) runs in CI.
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.
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.