critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

A `ContactChapter` immutable Dart class (or `@freezed` data class if freezed is used in the project) is defined with fields: `contactId` (String/UUID), `chapterId` (String/UUID), `roleInChapter` (String?), `joinedAt` (DateTime), `createdAt` (DateTime), `updatedAt` (DateTime)
`ContactChapter` overrides `==` and `hashCode` based on the composite key `(contactId, chapterId)`
A `ContactChapterMapper` class (or extension) provides `fromJson(Map<String, dynamic>)` that correctly parses all fields from Supabase PostgREST JSON, including ISO 8601 DateTime parsing
The mapper's `fromJson` handles null values for optional fields (`roleInChapter`) without throwing
`toJson()` produces a `Map<String, dynamic>` matching the column names in the `contact_chapters` database table (snake_case keys)
`toJson()` excludes `createdAt` and `updatedAt` from insert payloads (these are server-set) — implement an `toInsertJson()` variant or use a parameter to control inclusion
The domain class is located in the domain layer directory (e.g., `lib/domain/models/contact_chapter.dart`) following the project's layer structure
Mapper is located in the data layer directory (e.g., `lib/data/mappers/contact_chapter_mapper.dart`)
Unit tests for the mapper cover: round-trip serialisation, missing optional field returns null without error, malformed DateTime string throws a clear domain exception

Technical Requirements

frameworks
Flutter
Dart
apis
Supabase PostgREST JSON response format
data models
contact_chapter
performance requirements
Mapper must not perform any I/O operations — pure synchronous transformation only
security requirements
No PII beyond contactId and chapterId is stored in the domain class — no names or personal details
UUID fields must be typed as String and validated as non-empty before use in queries

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Check whether the project uses `freezed` for data classes — if so, annotate `ContactChapter` with `@freezed` and generate via `build_runner` for `copyWith`, `==`, `hashCode`, and `toString` for free. If not, implement manually and keep it consistent with other domain models. For DateTime parsing, use `DateTime.parse(json['joined_at'] as String)` and wrap in a try/catch that throws a domain-level `MappingException` with context rather than a raw `FormatException`. Use `_` prefixed private constructor pattern if `freezed` is not in use, exposing a factory constructor `ContactChapter({...})` for clarity.

Snake_case to camelCase key mapping must be explicit in the mapper — do not rely on `json_serializable` auto-naming unless it is already the project standard.

Testing Requirements

Unit tests in `test/domain/models/contact_chapter_test.dart` and `test/data/mappers/contact_chapter_mapper_test.dart`. Cover: (1) `fromJson` with a complete valid JSON map returns a correctly populated `ContactChapter`; (2) `fromJson` with `role_in_chapter: null` returns a `ContactChapter` with `roleInChapter == null`; (3) `toInsertJson` does not include `created_at` or `updated_at` keys; (4) two `ContactChapter` instances with the same `contactId` and `chapterId` are equal (`==` returns true). Aim for 100% line coverage of both files.

Component
Supabase Contact Chapter Adapter
infrastructure medium
Epic Risks (2)
high impact medium prob security

Supabase RLS policies for a junction table that spans organisations are non-trivial. An incorrectly scoped policy could expose chapter affiliations from other organisations to coordinators, constituting a data breach.

Mitigation & Contingency

Mitigation: Draft RLS policies in a staging environment and run an explicit cross-organisation isolation test suite before merging. Use Supabase policy testing tools and peer review all policy definitions.

Contingency: If a policy error reaches review, roll back the migration and apply a corrective patch. Ensure no production data has been exposed by auditing Supabase logs for cross-organisation query results.

medium impact medium prob technical

The contact_chapters table migration may conflict with existing foreign key structures or require a backfill for contacts already assigned to a single chapter, causing migration failures in production.

Mitigation & Contingency

Mitigation: Write the migration as an additive, non-destructive operation. Backfill existing single-chapter assignments by deriving them from the existing contact records. Test the full migration on a production-sized dataset clone before release.

Contingency: Provide a rollback migration script that removes the new table without touching existing contact records. Coordinate with operations for a maintenance window if a re-run is needed.