critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

A sealed class `NotificationPayload` (or `@freezed` union) is defined in Dart with exactly five concrete subtypes: ReminderPayload, ExpiryPayload, ScenarioPayload, PausePayload, SystemPayload
ReminderPayload contains non-nullable fields: contact_id (String), due_date (DateTime), activity_type (String)
ExpiryPayload contains non-nullable fields: certification_id (String), expiry_date (DateTime), course_name (String)
ScenarioPayload contains non-nullable fields: scenario_id (String), prompt_text (String), deep_link (String)
PausePayload contains non-nullable fields: mentor_id (String), pause_reason (String), expected_return (DateTime?)
SystemPayload contains non-nullable fields: message (String), action_url (String?)
Each subtype implements `fromJson(Map<String, dynamic>)` factory constructor that correctly parses JSONB maps from Supabase
Each subtype implements `toJson()` method returning a `Map<String, dynamic>` compatible with Supabase JSONB storage
DateTime fields are correctly serialized to/from ISO 8601 strings
All subtypes support value equality and hashCode (via freezed or manual implementation)
All subtypes support `copyWith` for immutable updates
Unit tests cover `fromJson`/`toJson` round-trip for all five subtypes with representative test data
Unit tests cover edge cases: null optional fields, malformed/missing keys throw `FormatException` or return sensible defaults
No dynamic or untyped `Map` access without null-safety guards

Technical Requirements

frameworks
Flutter
freezed
json_annotation
build_runner
data models
NotificationPayload
ReminderPayload
ExpiryPayload
ScenarioPayload
PausePayload
SystemPayload
performance requirements
fromJson deserialization must complete in under 1ms per payload on a mid-range device
No heap allocations beyond the final object (avoid intermediate maps where possible)
security requirements
No sensitive data (contact names, health info) stored directly in payload fields; only IDs are stored
deep_link in ScenarioPayload must be validated to an allowlist of internal URI schemes before use

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the `freezed` package with `@freezed` annotation and `@JsonSerializable` for code generation — this eliminates boilerplate and gives copyWith, equality, and hashCode for free. Define a shared `NotificationPayload` sealed base with `const factory` constructors for each subtype. The `fromJson` dispatch logic (selecting the correct subtype from a `type` string key) belongs in task-003's `Notification.fromJson`, not here — these classes only need to parse their own fields from a pre-selected map. For DateTime fields, use `DateTime.parse(json['field'] as String)` and `.toIso8601String()` in toJson.

Register a custom JsonConverter if needed. Keep all model files under `lib/features/notifications/domain/models/`. Run `flutter pub run build_runner build --delete-conflicting-outputs` after annotating. Avoid dynamic casting; use `as String`, `as int`, etc.

with explicit null checks.

Testing Requirements

Unit tests using `flutter_test`. One test file per payload subtype (e.g., `reminder_payload_test.dart`). Each file must contain: (1) valid fromJson round-trip test, (2) toJson output shape test, (3) copyWith immutability test, (4) equality test for two identical instances, (5) at least one malformed-input test verifying graceful error or default. Target 100% line coverage for all payload model files.

No integration or widget tests needed for this task.

Component
Notification Domain Model
data low
Epic Risks (3)
high impact medium prob technical

Supabase Realtime channels on mobile networks can drop silently. If reconnection logic is flawed, users miss notifications without knowing it, undermining the audit-trail guarantee.

Mitigation & Contingency

Mitigation: Implement exponential-backoff reconnection with a maximum of 5 retries; expose a channel-status stream to the BLoC so it can trigger a full-fetch fallback when the channel reconnects after a gap.

Contingency: If Realtime reliability proves insufficient in production, fall back to polling the repository every 60 seconds as a background supplement to the Realtime channel.

high impact medium prob security

Coordinator and org-admin RLS expansions require joining user_roles and org_memberships tables. An incorrect policy could expose notifications to wrong users or block legitimate access entirely.

Mitigation & Contingency

Mitigation: Write dedicated RLS integration tests for each role (peer mentor, coordinator, org admin) using separate Supabase test projects. Review policies with the security checklist before merging.

Contingency: If an RLS defect is discovered post-deployment, disable the expanded-scope policy and revert to user-scoped-only access while a corrected migration is prepared and tested.

medium impact medium prob integration

JSONB payload structure may vary across notification types created by different Edge Functions (reminder, expiry, scenario, pause). Missing or renamed fields will cause runtime parse failures.

Mitigation & Contingency

Mitigation: Define a canonical NotificationPayload union type in a shared schema document. Each Edge Function must validate its payload against this schema before inserting. Add fallback parsing with default values in the domain model.

Contingency: Wrap all payload parsing in try/catch and log malformed payloads to a monitoring channel; render a generic notification item rather than crashing when the payload cannot be parsed.