critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

addAffiliation returns ChapterMembershipFailure(MaxChaptersExceeded) when the contact already has 5 or more active affiliations, and no write is performed
MaxChaptersExceeded error carries both currentCount and maximumAllowed (5) fields
addAffiliation returns ChapterMembershipFailure(AlreadyAssignedToChapter) when the contact is already affiliated with the specified chapter, and no write is performed
addAffiliation returns ChapterMembershipFailure(ChapterNotFound) when the chapterId does not correspond to a known chapter
addAffiliation returns ChapterMembershipFailure(ChapterNotFound — contact variant) when the contactId does not exist
When all validations pass, exactly one insert is made to ContactChapterRepository and the method returns ChapterMembershipSuccess with the updated full affiliation list including the new entry
The 5-chapter check and duplicate-chapter check are both performed before any write — the order is: fetch → validate max → validate duplicate → insert
Repository exceptions during insert are caught and wrapped in a typed domain failure
The maximum chapter constant (kMaxChapterAffiliations = 5) is referenced from the shared domain constant, not hard-coded inline
Unit tests cover all five failure paths and the success path

Technical Requirements

frameworks
Flutter
Riverpod
apis
Supabase REST API (via ContactChapterRepository abstraction)
data models
ChapterAffiliation
ChapterMembershipResult
Contact
Chapter
performance requirements
All validation reads (affiliation fetch + chapter existence check) must complete before any write — no optimistic inserts
Total method execution must complete within 3 seconds on a standard mobile connection
security requirements
RLS policies on the contact_chapter_affiliations table must prevent a coordinator from adding a contact to a chapter outside their organisation
Validate that both contactId and chapterId are non-empty UUID-format strings before any repository call

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Reuse the getAffiliationsForContact implementation from task-003 to fetch the current affiliation set — do not duplicate the repository call pattern. Check affiliations.length >= kMaxChapterAffiliations first, then check affiliations.any((a) => a.chapterId == chapterId) for the duplicate check. Verify chapter existence via a ChapterRepository.exists(chapterId) call before attempting the insert. The atomicity requirement is a domain-level concern here (read-validate-write), not a database transaction — true database-level atomicity could be enforced later via a Supabase RPC/stored procedure if race conditions become a concern in production.

Document this caveat in code comments. The 5-chapter rule directly implements NHF's requirement for handling members in multiple local chapters while preventing double-reporting.

Testing Requirements

Unit tests with fake/mock repository: (1) contact has 5 affiliations → MaxChaptersExceeded, no insert called; (2) contact has 4 affiliations, chapter already present → AlreadyAssignedToChapter, no insert called; (3) contact has 4 affiliations, chapter not present, chapter exists → success, insert called once, returns 5-item list; (4) chapterId does not exist → ChapterNotFound; (5) contactId does not exist → appropriate failure; (6) repository insert throws exception → wrapped failure. Assert that the repository insert mock was never called in all failure scenarios using Mockito's verifyNever. Target 100% branch coverage of addAffiliation.

Component
Multi-Chapter Membership Service
service medium
Epic Risks (2)
medium impact medium prob scope

The ±1 day duplicate detection tolerance is specified in the acceptance criteria but timezone handling is not defined. A coordinator in UTC+2 submitting at 23:00 and another in UTC+0 submitting at 01:00 the next calendar day could trigger or miss a duplicate depending on which timezone the comparison uses.

Mitigation & Contingency

Mitigation: Define and document the authoritative timezone for all date comparisons (UTC stored in Supabase, all comparisons performed in UTC). Add timezone boundary unit tests covering the ambiguous ±1 day edges.

Contingency: If false positives or false negatives are reported in production, provide a coordinator-visible audit trail of duplicate detections so erroneous flags can be investigated and cleared manually.

medium impact medium prob technical

The Duplicate Activity Detection Service performs a cross-chapter join query synchronously during the activity submission flow. On slow mobile connections this could cause a perceptible stall on the submission confirmation step, degrading user experience.

Mitigation & Contingency

Mitigation: Pre-fetch the cross-chapter activity dataset for the selected contact immediately when the contact is selected in the activity wizard (not only at submit time), storing the result in the state manager for instant comparison at submission.

Contingency: If latency is still unacceptable, implement a loading indicator on the submit action and add a configurable server-side timeout with graceful degradation: if the check times out, allow submission with a logged 'check skipped' audit entry rather than blocking the user.