high priority low complexity backend pending backend specialist Tier 4

Acceptance Criteria

Every dispatch attempt emits a structured log entry containing: assignment_id, notification_type, and timestamp
Successful push delivery emits a log entry at INFO level with assignment_id and delivery confirmation token/result
Successful in-app record persistence emits a log entry at INFO level with record ID and assignment_id
Idempotency skips emit a log entry at INFO level with assignment_id, notification_type, and original reminder_sent_at
Dispatch errors emit a log entry at ERROR level with assignment_id, notification_type, error message, and stack trace
No personally identifiable information (name, contact details) appears in any log entry
Log entries use a consistent field naming convention (snake_case keys) across all log points
Logger is injected as a dependency and can be replaced with a no-op logger in tests

Technical Requirements

frameworks
Flutter
Dart
Riverpod
data models
ReminderContactTracking
performance requirements
Logging must be synchronous fire-and-forget and must not block the dispatch pipeline
security requirements
Log entries must never include FCM tokens, personal names, phone numbers, or email addresses
Assignment IDs (UUIDs) are the only identifiers permitted in logs

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Define a ReminderLogger abstract class with methods: logDispatchAttempt, logPushSuccess, logInAppPersisted, logIdempotencySkip, logDispatchError. Inject it via Riverpod Provider so production code uses a real logger (e.g., wrapping dart:developer log or a package like logger) and tests inject a ListLogger that collects entries for assertions. Use Map as the log payload type so entries are structured and serialisable. Keep log levels consistent: DEBUG for attempt, INFO for success/skip, ERROR for failures.

This pattern keeps logging testable without coupling to a specific logging package.

Testing Requirements

Unit tests using flutter_test with a captured/mock logger. Verify: (1) dispatch attempt log entry emitted with correct fields; (2) success log emitted after PushNotificationService returns success; (3) in-app persistence confirmation log emitted after repository write; (4) idempotency skip log includes original reminder_sent_at; (5) error log includes stack trace when PushNotificationService throws; (6) no PII present in any captured log entry. Tests should capture log output via a TestLogger implementation injected into the service.

Component
Reminder Dispatch Service
service medium
Epic Risks (3)
medium impact high prob scope

The idempotency window (how long after a reminder is sent before another can be sent for the same assignment) is not explicitly specified. An incorrect window — too short, duplicate reminders appear; too long, a resolved and re-opened situation is not re-notified. This ambiguity could result in user-visible bugs post-launch.

Mitigation & Contingency

Mitigation: Before implementation, define the idempotency window explicitly with stakeholders: a reminder is suppressed if a same-type notification record exists with sent_at within the last (reminder_days - 1) days. Document this rule as a named constant in the service with a comment referencing the decision.

Contingency: If the window is wrong in production, it is a single constant change with a hotfix deployment. The notification_log table allows re-processing without data migration.

high impact medium prob technical

For organisations with thousands of open assignments (e.g., NHF with 1,400 chapters), the daily scheduler query over all open assignments could time out or consume excessive Supabase compute units, especially if the contact tracking query lacks proper indexing.

Mitigation & Contingency

Mitigation: Add a composite index on assignments(status, last_contact_date) before running performance tests. Use cursor-based pagination in the scheduler (query 500 rows at a time). Run a load test with 10,000 synthetic assignments as described in the feature documentation before merging.

Contingency: If the query is too slow for synchronous execution, move the evaluation to the Edge Function (cron trigger epic) and use Supabase's built-in parallelism. The service interface does not change, only the execution context.

medium impact medium prob integration

If the push notification service fails (FCM outage, invalid device token) during dispatch, the in-app notification may already be persisted but the push is silently lost. Inconsistent state makes it impossible to report accurate delivery status.

Mitigation & Contingency

Mitigation: Implement push dispatch and in-app persistence as separate operations with independent error handling. Record delivery_status as 'pending', 'delivered', or 'failed' on the notification_log row. Retry failed push deliveries up to 3 times with exponential backoff.

Contingency: If FCM is consistently unavailable, the in-app notification is still visible to the user, providing a degraded but functional fallback. Alert on consecutive push failures via the cron trigger's error logging.