critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

ChapterAffiliation is an immutable value object with at minimum: affiliationId (String), contactId (String), chapterId (String), chapterName (String), assignedAt (DateTime), and isActive (bool) fields
ChapterMembershipResult is a sealed class or equivalent discriminated union covering success (with List<ChapterAffiliation> payload) and typed failure variants
MaxChaptersExceeded error type carries the current count and the enforced maximum (5) as fields so callers can surface human-readable messages
AlreadyAssignedToChapter error type carries the chapterId that triggered the conflict
ChapterNotFound error type carries the requested chapterId
AbstractMultiChapterMembershipService (or equivalent interface) declares addAffiliation(contactId, chapterId) → Future<ChapterMembershipResult>, removeAffiliation(contactId, chapterId) → Future<ChapterMembershipResult>, and getAffiliationsForContact(contactId) → Future<ChapterMembershipResult>
All domain types are placed in a dedicated domain layer directory (e.g., lib/domain/membership/) and have no imports from Flutter, Supabase, or any infrastructure layer
A Dart doc comment explaining business invariants is present on the abstract service interface
Unit tests covering equality and toString() of each value object compile and pass

Technical Requirements

frameworks
Flutter
Riverpod
data models
ChapterAffiliation
ChapterMembershipResult
ContactChapterRepository
performance requirements
Domain model instantiation must be allocation-light — prefer const constructors wherever possible
security requirements
No PII (contact names, addresses) should be stored directly on domain error types — use only IDs

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Model ChapterMembershipResult as a sealed class with two subclasses: ChapterMembershipSuccess(List affiliations) and ChapterMembershipFailure(ChapterDomainError error), where ChapterDomainError is itself a sealed class. This pattern allows exhaustive switch expressions at call sites and avoids nullable return types. Use @immutable and const constructors throughout. Keep this layer free of any async infrastructure — Future return types live only on the interface, not on the value objects themselves.

NHF's business rule of up to 5 chapter affiliations per contact is the primary invariant this domain models; encode the limit as a named constant (kMaxChapterAffiliations = 5) rather than a magic number.

Testing Requirements

Write unit tests (flutter_test, no widget pump needed) for: (1) ChapterAffiliation equality — two instances with identical fields must be equal; (2) ChapterMembershipResult pattern matching — ensure all variants are reachable; (3) each error type carries correct fields after construction. No mocks required at this stage — pure value-object tests only. Target 100% line coverage for the domain layer files introduced in this task.

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.