Integrate push dispatcher and notification record write
epic-scenario-push-engagement-core-engine-task-009 — Wire the trigger engine to call the push notification dispatcher (582-push-notification-dispatcher) when a scenario evaluation produces a dispatch decision. After a successful dispatch, write a record to the scenario notification repository (scenario type, user_id, timestamp) so cooldown and deduplication logic has an accurate history.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 5 - 253 tasks
Can start after Tier 4 completes
Implementation Notes
Structure the integration as: `evaluateAndDispatch(mentorId, scenarioType, config, deps)` where `deps` is an injectable dependency object containing the push dispatcher client and notification repo — this enables clean unit testing without network calls. The write-after-dispatch ordering is critical: use a try/catch around the push dispatcher call, only enter the record write block on success. For the idempotency key, use `crypto.subtle.digest('SHA-256', encode(userId + scenarioType + isoDate))` — this is available natively in Deno without additional dependencies. The 582 dispatcher should be called via Supabase Edge Function invocation using the service role key from environment variables.
Design the notification record schema with columns: id (uuid), user_id (uuid), scenario_type (text), dispatched_at (timestamptz), idempotency_key (text UNIQUE) — the UNIQUE constraint on idempotency_key provides the ON CONFLICT target.
Testing Requirements
Integration tests with mock push dispatcher and mock scenario notification repository: (1) successful dispatch path — assert dispatcher called with correct payload and record written, (2) dispatcher failure — assert no record written and error logged, (3) record write failure after successful dispatch — assert critical error logged with dispatch details, (4) no device token — assert skip with correct log reason, (5) concurrent duplicate dispatch — assert ON CONFLICT prevents duplicate records. End-to-end test against local Supabase with FCM sandbox: trigger a complete evaluation cycle and verify exactly one notification record exists afterward. Test idempotency: run same evaluation twice and assert dispatcher called once (second blocked by cooldown or deduplication).
The scenario-edge-function-scheduler must evaluate all active peer mentors within the 30-second Supabase Edge Function timeout. For large organisations, a sequential evaluation loop may exceed this limit, causing partial runs and missed notifications.
Mitigation & Contingency
Mitigation: Design the trigger engine to batch mentor evaluations using database-side SQL queries (bulk inactivity check via a single query rather than per-mentor calls), and add a performance test against 500 mentors during development. Document the evaluated mentor count per scenario type in scenario-evaluation-config to allow selective scenario execution per run.
Contingency: If single-run execution is insufficient, split evaluation into per-scenario-type scheduled functions (inactivity check, milestone check, expiry check) on separate cron schedules, dividing the computational load across multiple invocations.
A race condition between concurrent scheduler invocations or retried cron triggers could cause the same scenario notification to be dispatched multiple times to a mentor, severely degrading trust in the feature.
Mitigation & Contingency
Mitigation: Implement cooldown enforcement using a database-level upsert with a unique constraint on (user_id, scenario_type, cooldown_window_start) so that a second invocation within the same window is rejected at the persistence layer rather than the application layer.
Contingency: Add an idempotency key derived from (user_id, scenario_type, evaluation_date) to the notification record insert; if a duplicate key violation is caught, log it as a warning and skip dispatch without error.
The trigger engine queries peer mentor activity history across potentially multiple organisations and chapters. RLS policies configured for app-user roles may block the Edge Function's service-role queries, or query performance may degrade on large activity tables.
Mitigation & Contingency
Mitigation: Confirm the Edge Function runs with the Supabase service role key (bypassing RLS) and add composite indexes on (user_id, activity_date) to the activity tables before implementing the inactivity detection query.
Contingency: If service-role access is restricted by organisational policy, implement a dedicated database function (SECURITY DEFINER) that performs the inactivity aggregation and is callable by the Edge Function with limited scope.