high priority medium complexity infrastructure pending backend specialist Tier 6

Acceptance Criteria

A single file `lib/providers/contact_data_layer_providers.dart` exports all four providers (contactDetailRepositoryProvider, assignmentRepositoryProvider, fieldEncryptionUtilsProvider, contactFormValidatorProvider)
contactDetailRepositoryProvider and assignmentRepositoryProvider are `family` providers parameterized by `orgId` (String)
fieldEncryptionUtilsProvider is a `family` provider parameterized by `orgId` and internally reads the org encryption key — not exposed as a raw parameter
contactFormValidatorProvider is a plain `Provider` (no family) since validation rules are org-type-scoped via the org configuration, not per-org-instance
No provider directly instantiates a SupabaseClient — all providers read from a shared `supabaseClientProvider` defined at app level
Dependency graph is linear: supabaseClientProvider → repository providers → fieldEncryptionUtilsProvider → service providers (no cycles)
All providers are `riverpod` `Provider` or `AsyncNotifierProvider` — no deprecated `StateProvider` usage
Provider overrides for testing are documented with at least one example in the provider file's doc comment
Running `flutter analyze` reports zero warnings or errors after this task
Service layer developers can inject `contactDetailRepositoryProvider(orgId)` without needing to know about Supabase or encryption internals

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase (via existing supabaseClientProvider)
data models
ContactDetail
Assignment
EncryptedFieldMetadata
ValidationResult
OrgId
performance requirements
Providers must use `keepAlive: false` by default — allow disposal when no widget references them
Family provider instances are cached by Riverpod per orgId — no manual caching needed
security requirements
Org encryption key must be read from a secure provider (e.g., secureStorageProvider) — never passed as a plain string family parameter
fieldEncryptionUtilsProvider must NOT expose the raw key through its public interface

Execution Context

Execution Tier
Tier 6

Tier 6 - 158 tasks

Can start after Tier 5 completes

Implementation Notes

Use `riverpod` annotation-free style (not `@riverpod` codegen) unless the project already uses codegen — check existing provider files before choosing. Define all providers in one file to make the dependency graph visually obvious and easy to audit. Use `ref.watch` inside provider bodies (not `ref.read`) so Riverpod can track dependencies correctly. For the `fieldEncryptionUtilsProvider`, the family param should be `orgId`; internally, use `ref.watch(orgEncryptionKeyProvider(orgId))` to get the key, then construct the utils instance — this keeps the key hidden behind the provider boundary.

Add a `// DEPENDENCY GRAPH` comment block at the top of the file listing the provider chain: supabaseClientProvider → contactDetailRepositoryProvider → fieldEncryptionUtilsProvider. This serves as living documentation. Follow the existing provider naming conventions in the codebase.

Testing Requirements

Write unit tests in `test/providers/contact_data_layer_providers_test.dart` using `ProviderContainer` with overrides. Test that `contactDetailRepositoryProvider('org-a')` and `contactDetailRepositoryProvider('org-b')` return different instances. Test that overriding `supabaseClientProvider` with a fake client propagates correctly to repository providers. Test that `contactFormValidatorProvider` returns the same instance across multiple reads (no unnecessary re-creation).

Use `flutter_test`; no integration test setup required for this task.

Component
Contact Form Validator
infrastructure low
Epic Risks (3)
high impact medium prob security

Blindeforbundet's encryption key retrieval mechanism may not be finalised at implementation time, or session key availability via Supabase RLS may be inconsistent, causing decryption failures that expose masked placeholders to users and degrade the experience.

Mitigation & Contingency

Mitigation: Agree with Blindeforbundet on key storage and retrieval contract before implementation starts. Prototype key retrieval in a spike against the staging Supabase instance and validate the full decrypt/verify cycle with real test data before committing to the implementation.

Contingency: Implement a fallback that shows a 'field temporarily unavailable' state with a retry affordance. Log decryption failures server-side for audit. Escalate to Blindeforbundet stakeholders to unblock key management before the service tier epic begins.

medium impact medium prob technical

NHF contacts may belong to up to 5 chapters, each governed by separate RLS policies. A coordinator's chapter scope may not cover all affiliations, causing partial profile reads or silent data omissions that are difficult to detect in tests.

Mitigation & Contingency

Mitigation: Map all RLS policy combinations for multi-chapter contacts early. Write integration tests that create contacts with 5 affiliations and query them from coordinators with varying chapter scopes. Use Supabase's RLS test utilities to verify row visibility per role.

Contingency: Add an explicit 'affiliation partially visible' state in the repository response model so the UI can communicate scope limitations to the coordinator rather than silently showing incomplete data.

low impact medium prob scope

Organisation-specific validation rules (e.g., NHF chapter limit, Blindeforbundet encrypted field edit flow) may expand in scope during implementation as edge cases are discovered, causing the validator to grow beyond the planned complexity.

Mitigation & Contingency

Mitigation: Define the complete validation rule set with product and org stakeholders before coding begins. Document each rule with its source organisation and acceptance test. Use a rule registry pattern so new rules can be added without modifying core validator logic.

Contingency: Timebox validator enhancements to 2 hours per additional rule. Defer non-blocking rules to a follow-on maintenance task rather than blocking the epic delivery.