critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

PauseNotificationPayload sealed class defined with two subclasses: CoordinatorPausePayload and PeerMentorPausePayload
PauseNotificationRecipientType enum defined with values: coordinator, peerMentor
Both payload subclasses have: title (String), body (String), recipientType (PauseNotificationRecipientType), localisationKey (String), metadata (Map<String, dynamic>)
title field enforces ≤ 65 characters via assertion in constructor and a static const titleMaxLength = 65
body field enforces ≤ 240 characters via assertion in constructor and a static const bodyMaxLength = 240
Localisation key contract documented as a const String in each subclass: CoordinatorPausePayload.localisationKeyContract = 'notifications.pause.coordinator.{mentorName}.{returnDate}' and PeerMentorPausePayload.localisationKeyContract = 'notifications.pause.peer_mentor.{coordinatorName}'
All models implement toFcmPayloadMap() returning a Map<String, dynamic> formatted for FCM data payload (keys: title, body, type, recipientType, localisationKey)
All models implement fromJson(Map<String, dynamic>) factory constructor
AssertionError is thrown (in debug mode) and a custom PayloadValidationException is thrown (in all modes) when character constraints are violated
Models are immutable (all fields final, const constructors where applicable)
File is located at lib/features/notifications/models/pause_notification_payload.dart

Technical Requirements

frameworks
Flutter
Dart
data models
PauseNotificationPayload
CoordinatorPausePayload
PeerMentorPausePayload
PauseNotificationRecipientType
performance requirements
Model construction and serialization must be synchronous and O(1)
security requirements
metadata map must not contain sensitive user data (no SSN, no health data, no full addresses) — only IDs and display names
PayloadValidationException message must not echo back user-supplied content to prevent log injection

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use Dart 3 sealed classes so callers are forced to handle both coordinator and peerMentor cases exhaustively in switch expressions. Define the character limit constants as static const at the class level (not as magic numbers in validation logic) so they can be referenced in tests and in UI validation layers without duplication. Implement PayloadValidationException as a custom exception class with a field violatedField (String) and actualLength (int) so callers can produce user-facing error messages without string parsing. The toFcmPayloadMap() method should produce the FCM data payload format (not the notification object format) so the app can handle display logic client-side — this is important for WCAG 2.2 AA compliance where notification display may need to be customised.

Use const constructors on enum and on models where all fields can be const.

Testing Requirements

Unit tests using flutter_test. Required test cases: (1) valid CoordinatorPausePayload constructed successfully, (2) title exceeding 65 chars throws PayloadValidationException, (3) body exceeding 240 chars throws PayloadValidationException, (4) toFcmPayloadMap() returns correct keys and values for each subclass, (5) fromJson round-trips correctly for both subclasses, (6) recipientType is serialised correctly in FCM map. All tests are pure (no async, no network).

Component
Pause Notification Payload Builder
service low
Epic Risks (3)
high impact medium prob integration

The org membership table structure used to resolve coordinator relationships may differ from what the repository assumes, causing incorrect coordinator lookup or missing rows for mentors in multi-chapter scenarios.

Mitigation & Contingency

Mitigation: Review the existing org membership table schema and RLS policies before writing repository queries. Align query logic with the patterns already used by peer-mentor-status-repository and multi-chapter-membership-service.

Contingency: If schema differs, add an adapter layer in the repository that normalises the membership resolution and document the discrepancy for the data team. Fall back to coordinator lookup via the feature's own stored coordinator_id field if org membership join fails.

high impact medium prob technical

Device tokens stored in the database may be stale or unregistered, causing FCM dispatch failures that silently drop coordinator notifications — the primary coordination safeguard of this feature.

Mitigation & Contingency

Mitigation: Implement token validation on every dispatch call and handle FCM's NOT_REGISTERED error by flagging the token as invalid in the database. Reuse the token refresh pattern already established by fcm-token-manager.

Contingency: If push delivery fails after retry, ensure the in-app notification record is always written regardless of push outcome so coordinators can still see the event in the notification centre.

medium impact low prob technical

The optional reason field may contain special characters, emoji, or non-Latin scripts that exceed the 200-character byte limit when FCM encodes the payload, causing delivery failures.

Mitigation & Contingency

Mitigation: Enforce the 200-character limit on Unicode code point count, not byte count, in the payload builder. Add a unit test with multi-byte input strings.

Contingency: If an oversized payload is detected at dispatch time, strip the reason field from the push notification body and note 'See in-app notification for full reason' to preserve delivery.