critical priority low complexity database pending backend specialist Tier 0

Acceptance Criteria

AssignmentRepository class is located at lib/data/repositories/assignment_repository.dart and follows the established repository pattern in the codebase
`fetchByContactId(String contactId)` returns `Future<List<Assignment>>` for all assignments where contact_id matches, ordered by created_at descending
`fetchByOrganisation(String organisationId)` returns `Future<List<Assignment>>` scoped to the calling user's organisation, enforced by Supabase RLS (no client-side filter bypass possible)
`updateStatus(String assignmentId, AssignmentStatus status)` updates the status field and returns the updated `Assignment` record
All methods throw a typed `AssignmentRepositoryException` (with originalError field) on Supabase errors — never rethrow raw PostgrestException to callers
AssignmentStatus is a Dart enum with values: open, pending, completed, matching the database CHECK constraint values
Repository is injectable via Riverpod provider defined in lib/data/providers/assignment_repository_provider.dart
All Supabase queries include `.eq('organisation_id', organisationId)` where applicable to cooperate with RLS — never query across organisations
Unit tests pass with a mocked Supabase client covering all three public methods

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase PostgreSQL 15
data models
assignment
contact
performance requirements
fetchByContactId must return results within 500ms for up to 100 assignment records
updateStatus must complete within 300ms under normal network conditions
security requirements
All queries must cooperate with Supabase Row Level Security — never use service role key on the client
organisation_id must be derived from the authenticated user's JWT claims, never passed as a raw client parameter
AssignmentRepository must be scoped to the authenticated session — dispose and recreate on logout
No assignment data from other organisations must be accessible, even if RLS is misconfigured — add organisation_id filter on all reads as defence in depth

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Follow the existing repository pattern in the codebase — look at existing repositories (e.g., contact_repository.dart) for the established structure before writing from scratch. Use Supabase's `.from('assignments')` client — do not use raw SQL. Map the Supabase JSON response to the Assignment model using `Assignment.fromJson(map)` — ensure the fromJson factory handles nullable fields gracefully. For AssignmentStatus, define a `toSupabaseString()` extension to convert enum values to the exact strings expected by the database CHECK constraint (avoid `.name` if the Dart enum name differs from the DB value).

The Riverpod provider should be a `Provider` (not StateNotifier) since the repository is stateless. Register it as `assignmentRepositoryProvider` to follow the naming convention.

Testing Requirements

Unit tests using flutter_test with a mocked SupabaseClient (use Mockito or a hand-written fake). Test fetchByContactId: assert correct table name, eq filter on contact_id, and correct mapping to Assignment model. Test fetchByOrganisation: assert organisation_id filter is present in the query. Test updateStatus: assert the correct status string is sent for each AssignmentStatus enum value.

Test error path: simulate a PostgrestException from the mock and assert AssignmentRepositoryException is thrown with originalError populated. Test that AssignmentStatus.values covers exactly open, pending, completed. Minimum 80% line coverage on the repository class.

Component
Assignment Repository
data 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.