> ## 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.

# FUA/FUI DS4P Security-Label Propagation

> DS4P security-label propagation trace for the out-of-network ED substance-use ADT path feeding the FUA/FUI follow-up measures.

**Spec:** CL-29-EN-66 — FUA & FUI Follow-Up Measures · **Task:** T4.2 (Phase 4) · **Status:** spec `draft`, human-merge
**Boundary owner:** CL-63 (DS4P Data Segmentation & Privacy) — `specs/cl/specs/CL-63-ds4p-data-segmentation-privacy.md`
**Regulatory:** 42 CFR Part 2 (SUD), HL7 DS4P IG

This is the focused trace doc for DS4P label propagation on the FUA/FUI out-of-network ED-SUD ADT
path. The broader FUA/FUI integration doc (PF-108 consumer contract + CL-51 registration) is a
separate task (T4.4).

***

## 1. Propagation chain: ADT source → `cl_fua_adt_events`

Out-of-network ED-SUD ADT events arrive (from the PF-108 / Contexture HIE feed) and land in
`cl_fua_adt_events`. Two DS4P facets are fail-closed at the **DB chokepoint** — two BEFORE
INSERT/UPDATE triggers that fire on **every** write path (direct PostgREST, the service-role feed,
and the sanctioned `cl_ingest_fua_adt_event` RPC), so no writer can persist an unlabeled SUD row:

| Facet                    | Column                      | Default for SUD rows                    | Set by                                                                    | Migration                               |
| ------------------------ | --------------------------- | --------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------- |
| Confidentiality **code** | `ds4p_confidentiality_code` | `'R'` (HL7 Confidentiality, Restricted) | `cl_fua_adt_events_derive_biu()` / `trg_cl_fua_adt_events_derive`         | P3b — `20260701000017`                  |
| Security **label**       | `ds4p_security_label`       | `'42CFRPart2,ETH\|NORDSLCD'`            | `cl_fua_adt_events_ds4p_label_biu()` / `trg_cl_fua_adt_events_ds4p_label` | **T4.2 — `20260701000021`** (this task) |

T4.2 closes the substrate invariant declared in `20260701000016_cl29en66_fua_fui_substrate.sql:16-17`
("SUD ADT data … requires `ds4p_confidentiality_code` / `ds4p_security_label` when
`sud_indicated=true`") that P3b had left to Phase 4 (P3b fail-closed only the *code*; the *label* was
a passthrough). A feed-supplied non-blank label is **preserved** (passthrough); non-SUD rows are
**never** forced.

### SSOT and the DB↔TS drift trap

The label is **not** a hand-typed string. It is the canonical serialization of the SSOT
`deriveSecurityLabels({ sensitivityClass: 'part2_sud' })`:

* TS SSOT: `src/cores/cl/lib/ds4p/security-labels.ts` →
  `PART2_ADT_SECURITY_LABEL = serializeDs4pSecurityLabel(deriveSecurityLabels({ sensitivityClass: 'part2_sud' }))`.
* `serializeDs4pSecurityLabel` sorts each HL7 ActCode group ascending + comma-joins, then
  pipe-delimits the two groups: `"<sensitivity>|<obligation>"`. The Part 2 floor
  (sensitivity `ETH` + `42CFRPart2`, obligation `NORDSLCD`) therefore serializes deterministically
  and order-independently to **`42CFRPart2,ETH|NORDSLCD`** (`'4'` \< `'E'` by codepoint).
* The DB trigger writes that exact literal. The integration test
  (`tests/integration/cl/cl-29-en-66-ds4p-label.integration.test.ts`, D1/D4) **imports the TS
  constant and asserts the persisted DB value is byte-equal to it** — so any future drift between the
  derivation matrix and the DB literal fails the suite loudly.

### Trigger fire-order (load-bearing)

Both triggers are BEFORE-ROW on the same table/events, so PostgreSQL fires them in **trigger-name
ascending order**. `trg_cl_fua_adt_events_derive` \< `trg_cl_fua_adt_events_ds4p_label`
(`'derive'` \< `'ds4p_label'`), so the P3b derive trigger sets `NEW.sud_indicated` **before** the T4.2
trigger reads it. The label fail-close is therefore evaluated against the authoritatively-derived
SUD flag, never a client-supplied one. This ordering is documented in both migrations; a rename that
reorders the names would silently break the fail-close.

`sud_indicated` itself is server-derived fail-closed (D-7): indeterminate/unparseable ⇒ `true`
(presumed SUD, maximum Part 2 protection).

***

## 2. `cl_fua_adt_events` → export: DS4P propagation is **BLOCKING before payer export**

The label carries the `redisclosureProhibited = true` / `NORDSLCD` obligation. That obligation is
**operationalized at runtime** by the T4.1 payer-export consent gate
(`cl_fua_payer_export_gate()` BEFORE-INSERT trigger on `cl_fua_payer_export_requests`,
`20260701000020`): a payer-facing export of any SUD chart's FUA/FUI denominator membership is
**fail-closed at the DB** unless (a) active 42 CFR Part 2 consent exists (via
`cl_check_sud_consent()`), and (b) the org has explicitly enabled payer export — and every permitted
payer redisclosure writes a `cl_redisclosure_log` row.

**Plainly: DS4P propagation is BLOCKING before payer export — a SUD row cannot reach a payer
destination without consent.** The label marks the data; the consent gate enforces the prohibition
the label asserts. (This task does **not** modify the T4.1 gate; it is referenced here only to show
the end-to-end blocking relationship.)

***

## 3. CL-63 boundary

DS4P segmentation is owned by **CL-63** (`specs/cl/specs/CL-63-ds4p-data-segmentation-privacy.md`);
its implementation surface is the shared `src/cores/cl/lib/ds4p/` module (`security-labels.ts`,
`confidentiality.ts`) plus the platform DS4P types (`@/platform/clinical` →
`src/platform/clinical/ds4p/types.ts`). T4.2 **consumes** that module — it does not fork it. The same
`deriveSecurityLabels` SSOT already feeds CL-69's at-rest outcome-response labeling
(`useOutcomeEpisodeMutation.ts`); this task mirrors that derive-in-TS / persist-at-rest shape at the
ADT DB chokepoint.

***

## 4. Human-verify-open

End-to-end DS4P propagation against a **live** PF-108 / Contexture HIE feed is **human-verify-open** —
PF-108 is a planning stub, not a live feed. This task proves: (1) the DB label fail-close, (2) the
TS↔DB SSOT agreement (drift trap), and (3) the BLOCKING-before-payer-export relationship. It does
**not** prove a live feed end to end.

***

## 5. Documented follow-up (not this PR)

Surfacing the § 2.32 notice / DS4P label codes **inside** the
`cl_redisclosure_log.records_disclosed` JSONB would require re-opening the audited T4.1 export gate;
it is **deferred** as a follow-up. The federal redisclosure prohibition is already enforced at runtime
by the consent gate and implied by the log's `basis_type = 'consent'` — so the deferral does not leave
the prohibition unenforced.
