critical priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

Given a peer mentor received a notification for scenario type X at time T, when the trigger engine evaluates the same scenario type before T + cooldown_duration, then evaluation returns suppressed with reason 'cooldown_active'
Given a peer mentor received a notification for scenario type X at time T, when the trigger engine evaluates after T + cooldown_duration has elapsed, then cooldown check passes and evaluation continues
Given no prior notification exists for a peer mentor and scenario type combination, when the trigger engine evaluates, then cooldown check passes immediately
Cooldown duration is read from the scenario configuration object, not hardcoded — changing config values must take effect on the next evaluation run without code deployment
Cooldown check queries the scenario notification repository using (user_id, scenario_type) composite key and retrieves only the most recent dispatch timestamp for efficiency
When cooldown is active, the evaluation result is logged with peer_mentor_id, scenario_type, last_dispatch_at, cooldown_until, and suppression reason
Cooldown enforcement must be atomic with respect to concurrent evaluations — two simultaneous evaluations for the same user and scenario type must not both bypass cooldown
Edge case: if last_dispatch_at record exists but timestamp is in the future (clock skew), treat as cooldown active and log a warning

Technical Requirements

frameworks
Supabase Edge Functions (Deno)
Supabase PostgreSQL 15
apis
Supabase REST API
Supabase Edge Functions invocation
data models
activity
assignment
performance requirements
Cooldown query must use a composite index on (user_id, scenario_type, dispatched_at DESC) — single row lookup only
Cooldown check must complete within 50ms including database round-trip
No full-table scans: query must be bounded by user_id and scenario_type
security requirements
Service role key used only server-side within Edge Function environment — never exposed to mobile clients
RLS enforced on scenario_notification repository table — function accesses via service role bypass only where required
No PII in cooldown evaluation log payloads — use user_id (UUID) only, never names or contact details
Function input validated: user_id must be valid UUID, scenario_type must be member of known enum

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Implement cooldown check as a pure function `isCooldownActive(userId: string, scenarioType: ScenarioType, config: ScenarioConfig, repo: ScenarioNotificationRepository): Promise` returning a discriminated union `{ active: true; cooldownUntil: Date } | { active: false }`. This keeps the logic independently testable without the full trigger engine context. For atomicity under concurrent invocations, use a Postgres advisory lock or INSERT ... ON CONFLICT DO NOTHING pattern when writing the notification record (see task-009) combined with a SELECT FOR UPDATE on the cooldown check row.

Avoid using JavaScript Date arithmetic for the comparison — compute cooldown_until in SQL using `dispatched_at + interval '${cooldownMinutes} minutes' > now()` to avoid timezone edge cases in Deno's runtime. Cooldown duration config should be stored as integer minutes to avoid fractional second precision issues.

Testing Requirements

Unit tests in Deno test runner covering: (1) cooldown active when last dispatch within window, (2) cooldown expired when last dispatch beyond window, (3) no prior record — cooldown passes, (4) exact boundary condition (dispatch at exactly cooldown_duration ago — should pass), (5) future timestamp clock skew handled. Integration tests against a local Supabase instance: insert a notification record, call trigger engine, assert suppression; advance simulated time past cooldown, call again, assert dispatch proceeds. Test concurrent evaluation scenario using parallel Deno tasks to verify atomicity. Minimum 90% branch coverage on cooldown enforcement module.

Component
Scenario Trigger Engine
service high
Epic Risks (3)
high impact medium prob technical

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.

high impact low prob technical

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.

medium impact medium prob integration

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.