Status: Proposed Date: 2026-03-24 Decision Makers: CL Team, FW Team, Platform ArchitectDocumentation Index
Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
Use this file to discover all available pages before exploring further.
Context
The Clinical (CL) core produces domain events that need to flow through FW-16fw_domain_events infrastructure for workflow automation. Currently:
cl_pm_eventschannel is active and handles cross-core CL/PM events (lab orders, note signing, prescriptions)cl_eventschannel is reserved but unused — intended for CL-internal and CL-to-other-core events- FW-16 (Event-Based Workflow Triggers) provides
fw_process_domain_event()for consuming events and creating automated tasks/notifications - CL-13 (Crisis), CL-10 (Outcomes), CL-21 (MOUD), and CL-35 (Population Health) all need to publish events that FW consumes
docs/architecture/integrations/EVENT_CONTRACTS.md
Decision
1. Channel Routing Strategy
CL events use two channels based on consumer scope:| Channel | When to Use | Examples |
|---|---|---|
cl_pm_events | Event has PM as a primary consumer (charge capture, scheduling, eligibility) | progress_note_signed, cl_lab_order_created, prescription_sent |
cl_events | Event is consumed by FW, GR, PF, or CL-internal automation (no PM charge/scheduling impact) | cl_crisis_episode_opened, cl_assessment_expired, cl_moud_adherence_risk |
cl_pm_events. FW subscribes to both channels.
Primary vs secondary PM consumer (explicit definition):
- PM is primary when the event directly triggers PM operations that create or modify PM billing, scheduling, or eligibility records — e.g. charge capture (
pm_charge_suggestions/pm_charges), appointment create/update tied to the event, or eligibility checks that persist PM state. - PM is secondary when the event only informs PM-side workflows (notifications, dashboards, manual follow-up) without creating or updating those PM records.
| Scenario | Channel | Rationale |
|---|---|---|
| Progress note signed → charge suggestion / auto-post | cl_pm_events | PM is primary (billing records). |
| Crisis episode opened → supervisor alert, GR awareness | cl_events | No PM record mutation; informational/automation only. |
| Lab order created → PM billing line / encounter link | cl_pm_events | PM is primary when scheduling/billing rows are created or updated. |
| Assessment expired → FW reassessment task | cl_events | PM not primary unless the handler also writes PM appointments/charges. |
2. Event Naming Convention
All new CL events follow the canonical format from EVENT_CONTRACTS.md:| Entity | Actions | Examples |
|---|---|---|
crisis_episode | opened, resolved, followup_overdue | cl_crisis_episode_opened |
restraint_event | initiated, terminated, overdue | cl_restraint_event_initiated |
assessment | completed, expired, interval_due | cl_assessment_expired |
moud_monitoring | overdue, adherence_risk, phase_changed | cl_moud_monitoring_overdue |
care_gap | identified, closed, escalated | cl_care_gap_identified |
note | finalized, cosigned | cl_note_finalized |
safety_plan | activated, updated | cl_safety_plan_activated |
3. Payload Schema Convention
All CL events conform to theDomainEvent interface from EVENT_CONTRACTS.md:
4. Idempotency Pattern
- Every event includes a unique
event_id(UUID v4) in metadata fw_process_domain_event()checks for duplicateevent_idinfw_domain_eventsbefore processing- Duplicate events are logged but not re-processed
correlation_idgroups related events (e.g., all events from a single crisis episode share a correlation_id)
5. Dead-Letter Queue Pattern
- Failed event processing (consumer throws after max retries) writes to
fw_domain_eventswithstatus = 'failed' - Failed events include error message and retry count
- Edge function cron (
cl-event-dlq-processor, runs every 15 minutes) retries failed events up to 3 additional times - After final failure: creates PF-10 notification to system admin with event details
- Retry strategy: exponential backoff (1s, 2s, 4s) on 5xx errors; no retry on 4xx except 429 (rate limit)
6. FW Subscription Registration
CL events that FW consumes are registered infw_workflow_events (column auto_task_template references the FW task template key).
Seed ordering and idempotency:
- Insert or upsert rows in
fw_workflow_eventsonly after the corresponding CL event names are implemented (publishers exist) and each referencedauto_task_templateexists in FW (e.g.pf_task_templatesor equivalent). Otherwise migrations fail or produce orphaned registrations that never resolve at runtime. - Prefer idempotent seeds: use
ON CONFLICT (event_name) DO NOTHINGorON CONFLICT ... DO UPDATEso re-running migrations or seed scripts does not fail on duplicate keys.
ON CONFLICT target columns to match the actual unique constraint on fw_workflow_events in the FW-16 migration.)
Rationale
Options Considered
Option A: Singlecl_events channel for all CL events
- Pro: Simple routing, one consumer per channel
- Con: PM charge capture events mixed with FW automation events; PM team must filter irrelevant events; higher latency for time-sensitive billing events
- Rejected: Violates separation of concerns; PM consumers shouldn’t process crisis/MOUD events
- Pro: PM consumers only see billing-relevant events; FW consumes both channels but primarily
cl_events; clear ownership - Con: Publisher must decide which channel; slight complexity
- Chosen: Best balance of performance and clarity. Aligns with existing
cl_pm_eventsusage.
domain_events channel for all
- Pro: Simplest; one channel for everything
- Con: Extremely noisy; all cores’ events in one channel; consumer filtering overhead; violates EVENT_CONTRACTS.md channel ownership model
- Rejected: Doesn’t scale; contradicts established channel-per-core pattern
Why not a new cl_fw_events channel?
Adding a third CL channel creates confusion about which channel to publish to. Two channels with clear rules (PM-impacting vs. non-PM) is sufficient. FW already subscribes to multiple channels.
Consequences
Positive
- Consistent event naming and payload schema across all CL specs
- FW automation rules can be authored against a known, stable contract
- Idempotency and DLQ prevent lost events and duplicate processing
- PHI-safe payloads by design (UUIDs only)
- Clear channel routing reduces consumer-side filtering
Negative
- Publishers must make a routing decision (cl_pm_events vs cl_events) — adds minor cognitive overhead
- FW must subscribe to two CL channels (already subscribes to multiple channels for other cores)
- Seed data in
fw_workflow_eventsmust stay in sync with actual published events
Mitigations
- Document routing decision tree in this ADR (section 1 above)
- CI drift check: validate that all
pg_notify('cl_events', ...)calls match registered events infw_workflow_events - EVENT_CONTRACTS.md updated whenever a new CL event is added (PR template checklist item)
Related Documents
- EVENT_CONTRACTS.md — Canonical event schema and channel registry
- FW-16 Event-Based Workflow Triggers — FW automation infrastructure
- CL-13 Crisis Intervention — Primary driver for crisis events
- CL-21 MOUD Tracking — MOUD monitoring events
- CL-35 Population Health — Care gap events
- ADR-002 CL-PM Cross-Core Foreign Keys — Related CL-PM integration decision
- constitution.md §1.3 — Integration pattern exceptions require ADR
Constitution Reference: This ADR documents the standard integration pattern for CL events flowing to FW and other consumers, per
constitution.md §1.3 and §5.2.7. No exception to architecture rules is required — this ADR establishes the canonical pattern.