> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# CL-FW Event Automation Integration Contract

> Contract Version: 1.0 Created: 2026-03-24 Owning Cores: CL (Clinical & EHR), FW (Forms & Workflow) Status: Draft ADR Reference: ADR-004: CL-FW Event Patterns

**Contract Version:** 1.0
**Created:** 2026-03-24
**Owning Cores:** CL (Clinical & EHR), FW (Forms & Workflow)
**Status:** Draft
**ADR Reference:** [ADR-004: CL-FW Event Patterns](../decisions/ADR-004-cl-fw-event-patterns.md)

> **Purpose:** Define the event contracts for CL clinical events consumed by FW-16 (Event-Based Workflow Triggers) for automated task creation, notifications, and escalation workflows.

***

## Automated event publishers (system actor)

Cron jobs and other **non-human** publishers must set `metadata.user_id` to a **well-known platform system profile** so audit trails stay consistent (no `NULL` actor, no arbitrary strings).

| Item                          | Value                                                                                                                                                                                                                                                                                                                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Reserved `pf_profiles.id`** | `00000000-0000-0000-0000-000000000001`                                                                                                                                                                                                                                                                                                                                         |
| **Username**                  | `system` (stored in `pf_profiles.custom_fields.username` and auth `raw_user_meta_data`)                                                                                                                                                                                                                                                                                        |
| **Seeding**                   | **`supabase/migrations/20260325141500_pf_system_automation_profile.sql`** — inserts matching `auth.users` + `auth.identities` (non-login password) and `pf_profiles` with `email` = `system@encore.local`, `full_name` = `System Automation`, `first_name` / `last_name` = `System` / `Automation`, `custom_fields` = `{"platform_system_actor": true, "username": "system"}`. |
| **Consumers**                 | Edge functions such as `cl-assessment-expiry-checker`, `cl-moud-monitoring-checker`, `cl-care-gap-rules`, care-gap closure (`closed_by`), **`pf_notifications`** and any publisher that sets `metadata.user_id` on domain events — all MUST use this UUID.                                                                                                                     |

All automated events MUST use this UUID for `metadata.user_id` unless a spec explicitly documents a different service principal.

***

## Event Catalog

### Event 1: `cl_assessment_expired`

| Field       | Value                                                                          |
| ----------- | ------------------------------------------------------------------------------ |
| Channel     | `cl_events`                                                                    |
| Publisher   | CL (cron edge function: `cl-assessment-expiry-checker`)                        |
| Subscribers | FW (task creation)                                                             |
| Trigger     | Assessment `next_due_date` has passed without a new assessment being completed |
| FW Action   | Create reassessment reminder task assigned to treating clinician               |

**Payload:**

```typescript theme={null}
{
  event: 'cl_assessment_expired';
  publisher: 'CL';
  subscriber: ['FW'];
  payload: {
    entity_id: string;          // cl_assessments.id
    entity_type: 'assessment';
    chart_id: string;
    assessment_type: string;    // 'comprehensive', 'calocus', 'asam', 'phq9', etc.
    original_due_date: string;  // ISO date
    days_overdue: number;
    severity: 'warning';
    action_required: 'Schedule and complete reassessment';
    assigned_clinician_id: string;
  };
  metadata: {
    organization_id: string;
    user_id: string; // Use platform system profile UUID for cron (see "Automated event publishers" above)
    timestamp: string;
    correlation_id: string;
    event_id: string;
  };
}
```

**FW Automation Rule:**

* Template: `reassessment_reminder`
* Task: "Complete overdue {assessment_type} assessment"
* Assignee: `assigned_clinician_id`
* Priority: `high` if days\_overdue > 7, `medium` otherwise
* Due date: 3 business days from task creation

***

### Event 2: `cl_moud_monitoring_overdue`

| Field       | Value                                                                                 |
| ----------- | ------------------------------------------------------------------------------------- |
| Channel     | `cl_events`                                                                           |
| Publisher   | CL (cron edge function: `cl-moud-monitoring-checker`)                                 |
| Subscribers | FW (care team alert)                                                                  |
| Trigger     | MOUD monitoring checkpoint overdue (e.g., UDS not completed within monitoring window) |
| FW Action   | Alert care team; create follow-up task                                                |

**Payload:**

```typescript theme={null}
{
  event: 'cl_moud_monitoring_overdue';
  publisher: 'CL';
  subscriber: ['FW'];
  payload: {
    entity_id: string;          // cl_moud_enrollments.id
    entity_type: 'moud_enrollment';
    chart_id: string;
    monitoring_type: string;    // 'uds', 'vital_signs', 'lab_work', 'clinical_review'
    expected_date: string;
    days_overdue: number;
    medication: string;         // 'buprenorphine', 'naltrexone', 'methadone'
    severity: 'warning';
    action_required: 'Complete overdue MOUD monitoring';
    assigned_clinician_id: string;
    care_team_ids: string[];
  };
  metadata: { organization_id, user_id, timestamp, correlation_id, event_id };
}
```

**FW Automation Rule:**

* Template: `moud_care_team_alert`
* Alert: PF-10 notification to all `care_team_ids`
* Task: "Complete {monitoring_type} for MOUD patient" assigned to `assigned_clinician_id`
* Priority: `high`
* Escalation: If not resolved in 48 hours, escalate to supervisor (Event 3)

***

### Event 3: `cl_moud_adherence_risk`

| Field       | Value                                                                                           |
| ----------- | ----------------------------------------------------------------------------------------------- |
| Channel     | `cl_events`                                                                                     |
| Publisher   | CL (triggered by adherence scoring logic in CL-21)                                              |
| Subscribers | FW (escalation workflow)                                                                        |
| Trigger     | Patient's MOUD adherence score drops below threshold (e.g., 2+ missed UDS, missed appointments) |
| FW Action   | Escalate to clinical supervisor; create urgent review task                                      |

**Payload:**

```typescript theme={null}
{
  event: 'cl_moud_adherence_risk';
  publisher: 'CL';
  subscriber: ['FW'];
  payload: {
    entity_id: string;          // cl_moud_enrollments.id
    entity_type: 'moud_enrollment';
    chart_id: string;
    risk_factors: string[];     // ['missed_uds', 'missed_appointment', 'dose_reduction']
    adherence_score: number;    // 0-100 scale
    threshold: number;          // Score that triggered the alert
    severity: 'critical';
    action_required: 'Urgent clinical review — MOUD adherence risk';
    assigned_clinician_id: string;
    supervisor_id: string;
  };
  metadata: { organization_id, user_id, timestamp, correlation_id, event_id };
}
```

**FW Automation Rule:**

* Template: `moud_escalation`
* Alert: PF-10 critical notification to `supervisor_id`
* Task: "Urgent MOUD adherence review" assigned to `supervisor_id`
* Priority: `critical`
* Due date: Same business day

***

### Event 4: `cl_care_gap_identified`

| Field       | Value                                                                           |
| ----------- | ------------------------------------------------------------------------------- |
| Channel     | `cl_events`                                                                     |
| Publisher   | CL (cron edge function: `cl-care-gap-rules`)                                    |
| Subscribers | FW (task creation)                                                              |
| Trigger     | Care gap rule identifies an open gap for a patient (e.g., missed HEDIS measure) |
| FW Action   | Create task for care manager to close gap                                       |

**Payload:**

```typescript theme={null}
{
  event: 'cl_care_gap_identified';
  publisher: 'CL';
  subscriber: ['FW'];
  payload: {
    entity_id: string;          // cl_care_gaps.id
    entity_type: 'care_gap';
    chart_id: string;
    gap_type: string;           // 'hedis_amm', 'hedis_fuh', 'hedis_fum', 'hedis_iet', 'preventive_screening'
    measure_id: string;         // HEDIS measure identifier
    gap_description: string;    // Human-readable (e.g., "AMM: Antidepressant medication management — acute phase")
    due_date?: string;          // Measurement year end or clinical due date
    severity: 'info';
    action_required: 'Review and close care gap';
    care_manager_id?: string;   // If attributed, assigned care manager
  };
  metadata: { organization_id, user_id, timestamp, correlation_id, event_id };
}
```

**FW Automation Rule:**

* Template: `care_gap_task`
* Task: "Close care gap: {gap_description}" assigned to `care_manager_id` or unassigned pool
* Priority: `medium` (elevated to `high` if due\_date \< 30 days)
* Due date: 14 business days or `due_date`, whichever is earlier

***

### Event 5: `cl_crisis_episode_opened`

| Field       | Value                                                              |
| ----------- | ------------------------------------------------------------------ |
| Channel     | `cl_events`                                                        |
| Publisher   | CL (CL-13: Crisis Intervention)                                    |
| Subscribers | FW (supervisor notification), GR (incident awareness)              |
| Trigger     | New crisis episode created with risk\_level = 'high' or 'imminent' |
| FW Action   | Immediate notification to clinical supervisor and care team        |

**Payload:**

```typescript theme={null}
{
  event: 'cl_crisis_episode_opened';
  publisher: 'CL';
  subscriber: ['FW', 'GR'];
  payload: {
    entity_id: string;          // cl_crisis_episodes.id
    entity_type: 'crisis_episode';
    chart_id: string;
    crisis_type: string;        // 'walk_in', 'mobile', 'observation_23hr', 'phone', 'telehealth'
    risk_level: string;         // 'high' or 'imminent' (only these trigger FW)
    severity: 'critical';
    action_required: 'Crisis episode opened — immediate supervisor review';
    assigned_clinician_id: string;
    supervisor_id?: string;
  };
  metadata: { organization_id, site_id, user_id, timestamp, correlation_id, event_id };
}
```

**FW Automation Rule:**

* Template: `crisis_supervisor_notification`
* Alert: PF-10 critical notification to `supervisor_id` and site clinical lead
* No task created (crisis is actively managed by assigned clinician)
* If `crisis_type = 'observation_23hr'`, also create 6 interval assessment tasks (4-hour cadence)

***

### Event 6: `cl_restraint_event_initiated`

| Field       | Value                                                                                                 |
| ----------- | ----------------------------------------------------------------------------------------------------- |
| Channel     | `cl_events`                                                                                           |
| Publisher   | CL (CL-13: Crisis Intervention — restraint/seclusion workflow)                                        |
| Subscribers | GR (incident auto-create), FW (automation template `restraint_incident_creation`)                     |
| Trigger     | Restraint or seclusion physically initiated (`initiation_time` recorded per 42 CFR 482.13)            |
| FW Action   | Create incident-tracking workflow + PF-10 supervisor alert (critical)                                 |
| GR Action   | Create or update GR-09 incident record; map payload `entity_id` to `cl_restraint_seclusion_events.id` |

**Payload:**

```typescript theme={null}
{
  event: 'cl_restraint_event_initiated';
  publisher: 'CL';
  subscriber: ['GR', 'FW'];
  payload: {
    entity_id: string;              // cl_restraint_seclusion_events.id
    entity_type: 'restraint_seclusion_event';
    chart_id: string;
    event_type: string;             // e.g. 'restraint' | 'seclusion' | 'both'
    patient_age_group: string;    // e.g. 'adult' | 'adolescent' | 'child'
    max_duration_hours: number;    // Age-based regulatory limit
    initiation_time: string;       // ISO 8601
    severity: 'critical';
    action_required: string;        // e.g. 'Restraint initiated — verify monitoring and face-to-face compliance'
    crisis_episode_id?: string;
    supervisor_id?: string;
  };
  metadata: {
    organization_id: string;
    site_id?: string;
    user_id: string;
    timestamp: string;
    correlation_id: string;
    event_id: string;
  };
}
```

**FW automation:** Template name `restraint_incident_creation` — drives GR incident stub + PF-10 supervisor notification per org rules.

**GR / PF-10:** GR-09 consumes the event for regulatory incident creation; PF-10 delivers the critical alert to `supervisor_id` (or site escalation path).

**Canonical event name:** `cl_restraint_event_initiated` (physical start / `initiation_time`). Do not alias to `cl_restraint_event_documented` without updating **EVENT\_CONTRACTS.md**, **GR-09**, and **CL-GR-CLINICAL-INCIDENT-INTEGRATION.md** — those documents MUST stay aligned with this contract.

#### FW-16 consumer spec: `restraint_incident_creation` automation template

FW-16 MUST register a workflow template named **`restraint_incident_creation`** (referenced by `fw_workflow_events.auto_task_template` for `cl_restraint_event_initiated`). Implementation MUST satisfy GR-09 incident auto-create and compliance testing.

| Area                              | Requirement                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Payload validation**            | Validate required fields: `entity_id`, `entity_type === 'restraint_seclusion_event'`, `chart_id`, `initiation_time` (ISO 8601), `organization_id` from `metadata`. Reject or DLQ events missing any required field; do not create tasks on invalid payloads.                                                                                                                                                                                                                                              |
| **PHI / PII stripping**           | Follow [ADR-004](../decisions/ADR-004-cl-fw-event-patterns.md): payloads use **UUID identifiers only** for patients/charts; no free-text clinical content in FW task titles or notification bodies beyond org-approved templates. Strip or hash any unexpected string fields before persisting to FW tables or PF-10.                                                                                                                                                                                     |
| **Audit logging (42 CFR 482.13)** | Log every automation execution to **PF-04** `pf_audit_logs` (or append-only clinical audit per org policy) with: `organization_id`, `user_id` = reserved system profile UUID, `action` = e.g. `fw.restraint_incident_automation.executed`, `timestamp`, `new_values` JSONB containing `event_id`, `correlation_id`, `entity_id`, `template` = `restraint_incident_creation`, and outcome (`created_task_ids`, `gr_incident_id` if returned). Retention MUST meet restraint/seclusion record requirements. |
| **GR-09 integration**             | Template nodes SHALL create or idempotently update the GR-09 regulatory incident record linked to `cl_restraint_seclusion_events.id` = `entity_id`, then route PF-10 critical notification to `supervisor_id` or org escalation path.                                                                                                                                                                                                                                                                     |

**Seed note:** The template **`restraint_incident_creation`** MUST be defined in the **workflow templates seed** (or equivalent FW-16 registry) with nodes implementing the table above: (1) validate payload, (2) call GR-09 incident upsert, (3) enqueue PF-10 supervisor alert. Until the template exists in seed, deployments SHOULD treat `auto_task_template` as a **documentation contract** and implement the template before enabling production automation for this event.

***

### Event 7: `cl_restraint_event_terminated`

| Field       | Value                                                                                                          |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| Channel     | `cl_events`                                                                                                    |
| Publisher   | CL (CL-13)                                                                                                     |
| Subscribers | GR (GR-09 auto-close / escalation), FW (FW-16 debriefing task — e.g. template keyed to post-restraint debrief) |
| Trigger     | Restraint/seclusion ended (`termination_time` recorded)                                                        |
| FW Action   | Optional debriefing task trigger (FW-16); link to `cl_restraint_seclusion_events`                              |
| GR Action   | GR-09 may auto-close draft incident or escalate if `duration_exceeded` or regulatory flags set                 |

**Payload (minimum):**

```typescript theme={null}
{
  event: 'cl_restraint_event_terminated';
  publisher: 'CL';
  subscriber: ['GR', 'FW'];
  payload: {
    restraint_id: string;                 // same as entity_id / cl_restraint_seclusion_events.id
    patient_id: string;                   // chart-linked patient UUID (identifier only)
    initiation_time: string;
    termination_time: string;
    termination_reason: string;
    face_to_face_required_flag: boolean;  // true if post-termination face-to-face still required
    scheduled_face_to_face_deadline?: string; // ISO 8601 — when F2F must be completed if deferred
    duration_exceeded?: boolean;
    crisis_episode_id?: string;
  };
  metadata: { organization_id, site_id?, user_id, timestamp, correlation_id, event_id };
}
```

***

### Event 8: `cl_care_gap_closed`

| Field       | Value                                                             |
| ----------- | ----------------------------------------------------------------- |
| Channel     | `cl_events`                                                       |
| Publisher   | CL (CL-35 Population Health — auto-close cron or manual closure)  |
| Subscribers | FW (optional follow-up tasks), analytics                          |
| Trigger     | Care gap transitions to closed (`closed_at` set)                  |
| FW Action   | Optional workflow (e.g. close related tasks, notify care manager) |

**Payload:**

```typescript theme={null}
{
  event: 'cl_care_gap_closed';
  publisher: 'CL';
  subscriber: ['FW'];
  payload: {
    entity_id: string;          // cl_care_gaps.id
    entity_type: 'care_gap';
    chart_id: string;
    gap_type: string;
    closure_type: string;       // e.g. service_completed, clinician_override (see CL-35)
    closed_by: string;          // pf_profiles.id (use platform system UUID for cron)
    closed_at: string;          // ISO 8601
  };
  metadata: { organization_id, user_id, timestamp, correlation_id, event_id };
}
```

***

## Event Registration Seed Data

All CL→FW events must be registered in `fw_workflow_events` for FW-16 to consume them:

```sql theme={null}
-- Seed: CL-FW event automation registration
-- Run after FW-16 migration creates fw_workflow_events table

INSERT INTO fw_workflow_events (event_name, source_core, channel, description, auto_task_template, is_active)
VALUES
  ('cl_assessment_expired', 'CL', 'cl_events',
   'Assessment overdue — create reassessment task for clinician',
   'reassessment_reminder', true),

  ('cl_moud_monitoring_overdue', 'CL', 'cl_events',
   'MOUD monitoring overdue — alert care team and create follow-up',
   'moud_care_team_alert', true),

  ('cl_moud_adherence_risk', 'CL', 'cl_events',
   'MOUD adherence risk — escalate to supervisor with urgent review',
   'moud_escalation', true),

  ('cl_care_gap_identified', 'CL', 'cl_events',
   'Care gap identified — create task for care manager',
   'care_gap_task', true),

  ('cl_crisis_episode_opened', 'CL', 'cl_events',
   'Crisis episode opened — notify supervisor immediately',
   'crisis_supervisor_notification', true),

  ('cl_restraint_event_initiated', 'CL', 'cl_events',
   'Restraint initiated — notify GR and create incident tracking',
   'restraint_incident_creation', true),

  ('cl_restraint_event_terminated', 'CL', 'cl_events',
   'Restraint ended — update timers and close related FW tasks',
   NULL, true),

  ('cl_care_gap_closed', 'CL', 'cl_events',
   'Care gap closed — optional FW cleanup / analytics',
   NULL, true)
ON CONFLICT (event_name) DO UPDATE SET
  description = EXCLUDED.description,
  auto_task_template = EXCLUDED.auto_task_template,
  is_active = EXCLUDED.is_active;
```

***

## Drift Prevention

To ensure event contracts don't diverge between CL publishers and FW consumers:

### CI Check (Recommended)

Add to CI pipeline: validate that every `pg_notify('cl_events', ...)` call in CL code matches a registered event in `fw_workflow_events` seed data.

```bash theme={null}
# scripts/check-event-drift.sh
# Extract event names from pg_notify calls in CL migrations/functions
# Compare against fw_workflow_events seed INSERT statements
# Fail if any unregistered events found
```

### Manual Audit (Current)

* When adding a new CL event, update this contract and the seed SQL
* Update `EVENT_CONTRACTS.md` with the new event entry
* PR template includes checklist item: "Event contract updated if new events added"

***

## Idempotency & DLQ

Summary aligns with [ADR-004](../decisions/ADR-004-cl-fw-event-patterns.md). **Canonical handler pattern** (copy-paste for FW/edge consumers):

```typescript theme={null}
// import { sanitizeErrorMessage } from '@/shared/lib/error-utils';

async function handleEvent(event: DomainEvent): Promise<void> {
  // 1. Idempotency: 0 rows must NOT throw — use maybeSingle(), not single()
  const { data: existing } = await supabase
    .from('fw_domain_events')
    .select('id')
    .eq('event_id', event.metadata.event_id)
    .maybeSingle();

  if (existing) {
    // Structured log in production — do not treat as error
    logger.info(`Duplicate event ${event.metadata.event_id} — skipping`);
    return;
  }

  try {
    await processEvent(event);
    await supabase.from('fw_domain_events').insert({
      event_id: event.metadata.event_id,
      event_name: event.event,
      correlation_id: event.metadata.correlation_id,
      status: 'processed',
      processed_at: new Date().toISOString(),
      event_payload: event as unknown as Record<string, unknown>, // full payload for audit / replay
    });
  } catch (err) {
    await supabase.from('fw_domain_events').insert({
      event_id: event.metadata.event_id,
      event_name: event.event,
      correlation_id: event.metadata.correlation_id,
      status: 'failed',
      retry_count: 0,
      error_message: sanitizeErrorMessage(err),
      processed_at: null,
      event_payload: event as unknown as Record<string, unknown>,
    });
    throw err;
  }
}
```

**DLQ / retry:**

* On failure, row in `fw_domain_events` uses `status = 'failed'`, increment `retry_count` per retry attempt, populate `error_message`.
* Cron retries with exponential backoff (e.g. 1s, 2s, 4s) up to **3** additional attempts; after max retries: `retry_count = 3`, `processed_at` remains **`NULL`**, PF-10 notifies system admin.
* No retry on 4xx except **429** (rate limit).

***

## Related Documents

* [EVENT\_CONTRACTS.md](EVENT_CONTRACTS.md) — Canonical event schema and channel registry
* [ADR-004: CL-FW Event Patterns](../decisions/ADR-004-cl-fw-event-patterns.md) — Channel routing and payload conventions
* [CL-PM-EVENT-WIRING-INTEGRATION.md](CL-PM-EVENT-WIRING-INTEGRATION.md) — CL-PM event contracts (companion)
* [FW-16 Event-Based Workflow Triggers](./event-based-workflow-triggers-integration.md) — FW automation infrastructure
* [CL-13 Crisis Intervention](../../../specs/cl/specs/CL-13-crisis-intervention-documentation.md) — Crisis events source
* [CL-21 MOUD Tracking](../../../specs/cl/specs/CL-21-medication-assisted-treatment-moud-tracking.md) — MOUD events source
* [CL-35 Population Health](../../../specs/cl/specs/CL-35-population-health-care-gap-management.md) — Care gap events source
* [constitution.md](../../../constitution.md) §1.3 — No direct core-to-core imports
