critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

Org admin branch calls ContactRepository.fetchAllForOrganisation(orgId) and returns all contacts scoped to the authenticated user's organisation
Supabase RLS policies enforce org scoping server-side; the service does not apply manual org filtering on top of the RLS result
Peer mentor branch calls ContactRepository.fetchAssignedContacts(peerMentorId) and returns only contacts explicitly linked to that peer mentor
Role is resolved from the authenticated user context object passed into ContactListService — no direct Supabase auth calls inside the service
Passing an invalid or unrecognised role throws an UnknownRoleException (or equivalent sealed class variant) with a descriptive message
Passing a null or unauthenticated user context throws an UnauthenticatedException before any repository call is made
Both branches propagate repository errors (network, timeout, Supabase errors) without swallowing them
Unit tests cover: org admin happy path, peer mentor happy path, unrecognised role error, unauthenticated context error, repository error propagation

Technical Requirements

frameworks
Flutter
Riverpod
Supabase Dart client
apis
Supabase REST/PostgREST (contacts table, RLS-enforced)
Supabase Auth (user context)
data models
Contact
UserContext (authenticated user with role + orgId + userId)
UserRole (enum: orgAdmin, peerMentor, coordinator)
performance requirements
Repository calls must complete within 3 seconds on a standard mobile connection
No N+1 queries — a single Supabase query per branch fetch
ContactListService must be stateless so it can be instantiated per-request without overhead
security requirements
Role resolution MUST come from the server-verified user context, never from client-supplied parameters
Supabase RLS must be the authoritative org-scoping layer; service-level filtering is secondary validation only
Peer mentor branch must verify that peerMentorId matches the authenticated user's id to prevent IDOR
No contact PII is logged at the service layer

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use a sealed class or exhaustive switch on UserRole enum for the dispatch — this ensures the compiler enforces handling of all future roles. Keep the service a plain Dart class (no Riverpod coupling) so it is trivially unit-testable. Inject ContactRepository via constructor. The org admin branch should pass orgId (from UserContext) to the repository, not re-derive it inside the branch.

For peer mentor, the peerMentorId is always UserContext.userId — do not accept it as a parameter to avoid IDOR. Supabase RLS should already restrict the query, but the service should still validate that the returned contacts belong to the expected scope as a defence-in-depth check (assert count > 0 only if organisation is known to have contacts). Avoid async* generators here — return Future> for predictable error propagation.

Testing Requirements

Unit tests (flutter_test) for each role branch using mocked ContactRepository. Test org admin: returns full org contact list. Test peer mentor: returns only assigned contacts. Test unrecognised role: throws correct exception type.

Test null user context: throws before any repo call. Test repository throws: exception propagates unmodified. No integration tests at this layer — repository is mocked. Aim for 100% branch coverage of the role-dispatch logic.

Component
Contact List Service
service medium
Epic Risks (2)
medium impact medium prob technical

For organizations with large contact lists (NHF has 1,400 local chapters and potentially thousands of contacts), local in-memory filtering may be too slow and Supabase ILIKE queries without supporting indexes may exceed acceptable response times or accumulate excessive read costs, degrading search usability for power users.

Mitigation & Contingency

Mitigation: Define and document the list-size threshold in ContactSearchService before implementation. Confirm that indexes on name and notes columns exist in the Supabase schema before enabling server-side search. Profile ContactSearchService against realistic data volumes in the staging environment using the largest expected org.

Contingency: If response times are unacceptable in staging, introduce result-count pagination in ContactListService and add a user-visible 'showing top N results — refine your search' indicator, deferring full pagination to a follow-up task.

high impact low prob security

In NHF's multi-chapter context, when a user switches organization, Riverpod providers may emit a brief window of stale contact data scoped to the previous organization before the invalidation cycle completes, transiently exposing contacts from the wrong chapter.

Mitigation & Contingency

Mitigation: Model organization context as a Riverpod provider dependency so that any context change immediately marks contact providers as stale. Render a loading skeleton instead of the stale list during the re-fetch transition. Cover this scenario in integration tests with explicit org-switch sequences.

Contingency: If race conditions are observed during QA, add an explicit organization_id equality check in ContactListService that compares each fetched record's scope to the active session org, discarding any mismatched batch before returning results to the provider.