high priority medium complexity backend pending integration specialist Tier 8

Acceptance Criteria

When a declaration status transitions to ACKNOWLEDGED, an in-app notification row is inserted into the notifications table for the responsible coordinator within 3 seconds
In-app notification body reads: '<driver_full_name> has acknowledged <declaration_title> at <acknowledged_at ISO 8601>'
In-app notification includes deep link: app://declarations/<declaration_id>/acknowledgements for the coordinator to view full acknowledgement details
If the coordinator has push notifications enabled (fcm_token present), a push notification is also dispatched with the same body
If the coordinator has push notifications disabled or no FCM token, only the in-app row is created — no error
The responsible coordinator is resolved from the declaration's coordinator_id foreign key — not inferred from org-level defaults
Coordinator notification is idempotent: re-triggering for the same declaration_id does not create duplicate notification rows
Coordinator can mark the in-app notification as read; unread count badge reflects this state on the Flutter Notifications tab
Driver's personal data (address, medical info) must NOT appear in any notification field

Technical Requirements

frameworks
Supabase Edge Functions (triggered by declarations status UPDATE webhook)
FCM HTTP v1 API (coordinator push, optional)
Flutter BLoC (NotificationsBloc polls or subscribes to Supabase Realtime for in-app notification updates)
apis
Supabase Realtime — coordinator subscribes to notifications table filtered by recipient_id = auth.uid()
FCM HTTP v1 API for coordinator push (conditional)
Supabase RPC or Edge Function: notify_coordinator_of_acknowledgement(declaration_id)
data models
Declaration (id, title, coordinator_id, driver_id, status, acknowledged_at)
DriverProfile (id, full_name)
CoordinatorProfile (id, fcm_token, push_enabled)
Notification (id, recipient_id, body, deep_link, type: DECLARATION_ACKNOWLEDGED, is_read, created_at, declaration_id)
performance requirements
In-app notification INSERT must complete within 1 second of acknowledgement event
Push dispatch is fire-and-forget: do not block the acknowledgement response on FCM latency
security requirements
RLS on notifications table: SELECT and UPDATE (is_read) restricted to recipient_id = auth.uid()
Edge Function must verify that the coordinator_id belongs to the same org_id as the declaration before inserting notification
Push notification body must be reviewed to ensure it contains no sensitive personal data beyond name and document title
ui components
Notifications tab badge (unread count)
NotificationListItem widget displaying body + deep link tap handler
NotificationDetailScreen reachable from deep link app://declarations/:id/acknowledgements

Execution Context

Execution Tier
Tier 8

Tier 8 - 48 tasks

Can start after Tier 7 completes

Implementation Notes

Trigger via Supabase Database Webhook on declarations UPDATE WHERE NEW.status = 'ACKNOWLEDGED' AND OLD.status != 'ACKNOWLEDGED'. Edge Function `notify-coordinator-on-acknowledgement`: (1) load declaration + driver full_name + coordinator FCM token in a single JOIN query; (2) INSERT into notifications with a unique constraint on (declaration_id, type) for idempotency; (3) if coordinator.push_enabled AND fcm_token IS NOT NULL, dispatch FCM asynchronously using Promise (do not await, return 200 immediately). On the Flutter side, use Supabase Realtime `supabase.from('notifications').stream(primaryKey: ['id']).eq('recipient_id', userId)` in the NotificationsBloc to receive live updates without polling.

Testing Requirements

Unit tests: (1) coordinator notification row is constructed with correct recipient_id, body, deep_link, and type; (2) push is dispatched when coordinator has FCM token and push_enabled=true; (3) push is skipped when push_enabled=false; (4) duplicate acknowledgement event does not insert duplicate notification row. Integration test: complete a declaration acknowledgement flow against local Supabase, assert notification row exists for coordinator, assert Supabase Realtime event is broadcast to coordinator subscriber. Flutter widget test: NotificationsBloc receives Realtime event and emits updated unread count.

Epic Risks (4)
high impact medium prob security

Org-scoped encryption key management is complex. If keys are not correctly isolated per organization, a breach in one org's key could expose another org's declarations. Additionally, key rotation is not specified but may be needed for compliance, and the current implementation may not support it.

Mitigation & Contingency

Mitigation: Use Supabase Vault or a dedicated secrets management approach for org-scoped key storage. Define the key derivation strategy (per-org master key) in a security design document reviewed before implementation begins. Include key isolation tests in the test suite.

Contingency: If a full per-org key management system cannot be safely implemented within the sprint, fall back to a single platform-level encryption key with strict RLS isolation as a temporary measure, flagging the key rotation gap as a security debt item with a defined resolution milestone.

medium impact medium prob integration

Push notification delivery to drivers depends on FCM token availability and device connectivity. If a driver has not granted notification permissions or has an expired FCM token, the declaration delivery notification will silently fail, leaving the coordinator unaware and the declaration unacknowledged.

Mitigation & Contingency

Mitigation: Implement delivery status tracking in declaration-notification-service. Fall back to in-app notification and SMS (if configured) when push delivery fails. Expose delivery failure status in the declaration status badge so coordinators can identify and manually follow up.

Contingency: If push delivery proves unreliable, implement a polling-based in-app notification fallback where drivers see pending declarations on next app open, ensuring the workflow can complete even without push notifications.

medium impact medium prob technical

The acknowledgement service is meant to validate that the driver has fully scrolled through the declaration before confirming. Implementing reliable scroll completion detection in Flutter across different screen sizes and font sizes is technically non-trivial and could be bypassed.

Mitigation & Contingency

Mitigation: Implement scroll position tracking using ScrollController with a threshold (e.g., 95% of content height reached) and record the validated state server-side before allowing acknowledgement submission. Document the approach in the legal sign-off checkpoint noted in the feature documentation.

Contingency: If reliable scroll detection cannot be implemented within the sprint, add a mandatory reading delay timer (e.g., estimated reading time based on word count) as an alternative validation mechanism, pending legal review of the approach.

medium impact low prob dependency

The driver assignment service must coordinate with the threshold-based expense approval workflow for fees above configured thresholds. If the expense approval workflow interface changes or is not yet stable, the integration point could break or produce incorrect routing behavior.

Mitigation & Contingency

Mitigation: Define a clear interface contract between driver-assignment-service and the expense approval workflow before implementation. Use dependency injection so the expense workflow client can be mocked in tests. Monitor the expense approval feature for interface changes.

Contingency: If the expense approval workflow interface is not stable, implement a direct database insert to the expense records table as a temporary bypass, with a flag indicating manual review is needed, until the stable interface is available.