Skip to content

App Consent Profile

Canonical../StructureDefinition/nexus-emr-core-app-consent
Statusdraft · 1.26.0
BaseConsent (constraint)
SourceFSH · JSON

Simple app consent profile: Patient X consented/refused to share information with Application Y (represented as a Device). This profile models consent for a patient to sign up with an app.

FSH

The authored source of truth: resources/StructureDefinition-nexus-emr-core-app-consent.fsh (edit on GitHub). One FSH file may define several related artifacts.

// NexusEmrCoreAppConsent.fsh
// Defines the Nexus EMR App Consent profile for patient consent to share data with third-party applications
// This profile is specifically designed to model patient consent for signing up with an app
// based on FHIR R4 Consent resource and Nexus EMR standards.

Alias: $NexusEmrCoreAppConsent = ../StructureDefinition/nexus-emr-core-app-consent
Alias: $consent-status = http://hl7.org/fhir/consent-state-codes
Alias: $consent-category = http://terminology.hl7.org/CodeSystem/consentcategorycodes
Alias: $consent-scope = http://terminology.hl7.org/CodeSystem/consentscope
Alias: $consent-policy = http://terminology.hl7.org/CodeSystem/consentpolicycodes
// $v3-ActCode is defined in aliases.fsh.
Alias: $NexusConsentFamilyCS = ../CodeSystem/nexus-consent-family
Alias: $NexusConsentFamiliesVS = ../ValueSet/nexus-consent-families


// https://well-health.atlassian.net/wiki/spaces/POL/pages/6111297818/WIP+Consent+Management+-+Nexus EMR+API+-+Technical+Musings


// Consent identifier: a source/raw identifier SHOULD follow the Nexus EMR pattern (recommended)

// Profile Definition
Profile:        NexusEmrCoreAppConsent
Parent:         Consent
Id:             nexus-emr-core-app-consent
Title:          "App Consent Profile"
Description:    "Simple app consent profile: Patient X consented/refused to share information with Application Y (represented as a Device). This profile models consent for a patient to sign up with an app."
// THE effective date for this profile (see the EffectiveDate / NoEffectiveDate
// RuleSets): one date concept per resource, declared here rather than guessed.
* insert EffectiveDate([[dateTime]])

// SMART on FHIR exposure -- READ-ONLY, and this one is a SECURITY boundary rather than a
// preference: the app-consent profile IS the record of a patient consenting to share data with
// an application. An app able to write it could author or amend its own authorization; able to
// delete it, could destroy the evidence that authorization was ever given. Exposure is per
// resource TYPE, so this necessarily covers item-consent too.
* insert SmartExposedReadOnly(consent)

* ^url = $NexusEmrCoreAppConsent
* ^status = #draft

* . ^short      = "Patient consent for app signup and data sharing Core"
* . ^definition = """
  Records whether a patient consented to or refused to sign up with a specific app and share data.
  This models the consent process when a patient decides to use a third-party application.
  The application is represented as a NexusEmrCoreAppDevice resource.

  **This is one of three Consent families sharing one store, and they all read the same way.** The
  decision is `provision.type` on the root provision -- `permit` grants, `deny` restricts -- and
  `status` says only whether the record is operative. All three carry `scope = patient-privacy`, so
  scope separates nothing; `category` is required and searchable and says WHAT a decision governs.
  See the Consent Family code system.
  """

* meta.source 0..1 MS
* meta.source ^short = "Identifies EMR instance & pipeline version the resource came from"
* meta.source ^definition = "A URI that identifies the EMR pipeline and version from which this resource originated.  This tells you which EMR instance (recommend using the instance identifier), and the version of the pipeline code/transformations."

// Essential elements only - KISS approach
* identifier 0..* MS
* identifier.system 1..1
* identifier.value 1..1
* identifier ^short = "Business identifier(s) for app consent"
* identifier ^definition = """
Unique identifiers for the app consent.

A business identifier is recommended but not required (identifier is 0..*). Where available, carry the source-system identifier using the most appropriate system URI. No specific canonical system is mandated.

A source system's own identifier SHOULD be carried alongside this one, flagged `use = secondary`. See [Carrying the raw code](../guide/patterns/raw-codes.md).
"""


* identifier ^short = "Consent record identifier"

* status 1..1 MS
* status ^short = "active | draft | inactive | entered-in-error"
* status ^definition = "Required field in FHIR R4 Consent. Indicates the current state of this app consent."
* status from http://hl7.org/fhir/ValueSet/consent-state-codes (required)

* scope 1..1 MS
* scope ^short = "patient-privacy - Privacy consent"
* scope ^definition = "Required field in FHIR R4 Consent. For this app consent use case, always set to 'patient-privacy' as we're dealing with patient privacy consent for app data sharing."
* scope from http://hl7.org/fhir/ValueSet/consent-scope (required)
* scope ^fixedCodeableConcept.coding[0].system = "http://terminology.hl7.org/CodeSystem/consentscope"
* scope ^fixedCodeableConcept.coding[0].code = #patient-privacy
* scope ^fixedCodeableConcept.coding[0].display = "Privacy Consent"

// ===========================================================================
// What this consent governs, and which family it belongs to
// ===========================================================================

* category 1..* MS
* category ^short = "INFA (information access), plus the Consent family code"
* category ^definition = """
Required field in FHIR R4 Consent. This profile carries two codings, in two slices discriminated by
code system, and neither substitutes for the other.

**`infa` says WHAT this consent governs**: `v3-ActCode#INFA`, information access — a third-party
application's access to patient information.

**`family` says this Consent is an app consent.** Three Consent families share this store, one
`GET Consent?patient={id}` returns them mixed, and all three carry `scope = patient-privacy` — so
`category` is the only element that separates them, and every family in this guide carries the
family code. Select this family with:

```
GET Consent?patient={id}&category=../CodeSystem/nexus-consent-family|app
```

The slicing is open, so further categories may be carried alongside these two. No other category
identifies the access class or the family, and a consumer MUST NOT infer either from one.
"""
* category ^comment = """
The family coding sits outside HL7's consent-category value set on purpose, as it does on the
sibling Consent profiles: that value set classifies consent *documents* and has no concept for this
guide's own family axis. The required binding to it therefore sits on the `infa` slice, where it
holds, rather than on `category` as a whole, where it would make the family coding non-conformant.
Terminology-aware validators may note that the base extensible binding is unmet on the family
coding; that note is expected, not a defect.
"""

// Slice by code system. THE FAMILY CODE IS WHY THIS ELEMENT IS SLICED AT ALL, and why the INFA pin
// below is expressed per-component rather than as a whole CodeableConcept: `fixedCodeableConcept`
// is deep equality -- R4 defines it as "SHALL be exactly the value ... missing elements/attributes
// must also be missing" -- so a single fixed CC on a 1..1 element forbids the second coding
// outright. The pin is unchanged in what it requires of the INFA coding; it simply no longer
// forbids a sibling.
* category ^slicing.discriminator[0].type = #pattern
* category ^slicing.discriminator[0].path = "coding.system"
* category ^slicing.rules = #open
* category ^slicing.description = "Slice category by code system: the access class, and the family code"

* category contains
    infa 1..1 MS and
    family 1..1 MS

// ⚠️⚠️⚠️ SPEC-BUG FIX (2026-07-06) — DO NOT REVERT TO consentcategorycodes#infa ⚠️⚠️⚠️
// The previous pin was consentcategorycodes#infa, a code that DOES NOT EXIST in that
// CodeSystem. Terminology-aware validators (HL7 validator_cli, others) flag it twice per
// resource: "unknown code in CodeSystem", then the required consent-category binding
// fails because the only coding present is invalid. The correct "information access"
// concept is v3-ActCode#INFA, which IS a member of the required ValueSet. Found by
// cross-validator benchmarking; stardown enforced the old pin faithfully — a bug in a
// pinned value is invisible to any validator that treats the spec as ground truth.
// LOCKSTEP: stardown vendors these rule tables (gen:ig) and the patient-generator
// fixtures emit EXACTLY this pinned CC — re-vendor + regenerate them together with any
// change here. Those fixtures still conform: system and code are pinned as before, and
// `display` is now optional rather than required to match, which is a relaxation.
* category[infa] ^short = "INFA - information access (v3-ActCode)"
* category[infa] ^definition = "What this consent governs: access to patient information by a third-party app. Always `v3-ActCode#INFA`."
* category[infa].coding 1..1
* category[infa].coding.system 1..1
* category[infa].coding.system = $v3-ActCode (exactly)
* category[infa].coding.code 1..1
* category[infa].coding.code = #INFA (exactly)
* category[infa].coding.display 0..1
* category[infa] from http://hl7.org/fhir/ValueSet/consent-category (required)

* category[family] ^short = "Fixed: this Consent is an app consent"
* category[family] ^definition = """
The family this Consent belongs to, fixed to `app`. It tells a consumer which interpretation
contract governs the resource in its hand before the resource is interpreted, which is the point of
carrying it on a searchable element: a mixed result can be partitioned in the search rather than
after it.

It says WHAT this decision governs and nothing about how to read it. The decision is
`provision.type` in every family, so a reader never needs the family code to answer the dangerous
question — it answers the cheap one.
"""
* category[family] ^comment = """
The binding is required, not extensible: the value of this element is that a consumer can enumerate
the families it knows how to interpret and refuse the ones it does not. A family minted under a
deployment's own system would arrive looking like a known one, which is the failure the element
exists to prevent.
"""
* category[family].coding 1..1
* category[family].coding.system 1..1
* category[family].coding.system = $NexusConsentFamilyCS (exactly)
* category[family].coding.code 1..1
* category[family].coding.code = #app (exactly)
* category[family] from $NexusConsentFamiliesVS (required)

* patient 1..1 MS
* patient only Reference(NexusEmrCorePatient)
* patient ^short = "The patient (Patient X)"

* dateTime 1..1 MS
* dateTime ^short = "When consent was given/withdrawn"

* policy 1..* MS
* policy.uri 1..1
* policy ^short = "The policy this consent is given under"
* policy ^definition = """
A URI identifying the policy under which this consent is recorded -- the custodian's published
privacy policy, or the statutory provision that governs the decision.

R4's `ppc-1` invariant requires either `policy` or `policyRule`; this element satisfies it.

**`policyRule` is not used, and the reason is a correction.** Earlier releases fixed `policyRule`
to `consentpolicycodes#cric`. That code is 45 CFR 46 -- the United States Common Rule for
informed consent in human-subjects research. It is not a communication-preference policy, not a
data-sharing policy, and not Canadian. It was never the right value here, and the code system it
comes from contains no Canadian concept at all: its 27 codes are US federal and state instruments
plus three European ones. Rather than substitute another wrong code, this profile names the actual
governing policy by URI, which is both truthful and machine-readable.
"""

// THE DECISION IS `provision.type` ON THE ROOT, matching every other Consent profile in this guide.
//
// This profile used to follow R4's literal element text -- no type on the root, refusal expressed as
// a nested `provision` with `type = deny`, a grant expressed as the ABSENCE of that nested rule. Two
// problems with the absence: it is not a statement, so a truncated or partially-written resource
// reads as a grant, and it disagreed with the sibling profiles, so a consumer needed a different
// rule per family.
//
// R4 says `type` is "not permitted in root rule". This guide does not follow that reading, and the
// divergence is deliberate: IHE Privacy Consent on FHIR and Ontario's Provincial Consent Override
// Interface both constrain the root type to `1..`, because a consent whose base rule states no
// decision states none where a reader looks first. Following the prior art puts the decision where
// a PCF or PCOI reader already looks, and gives this estate one rule for every Consent it holds.
* provision 1..1 MS
* provision ^short = "The decision, and the app it is about"
* provision ^definition = """
Carries whether the patient agreed to share with this application, and names the application.

`provision.type = permit` is agreement; `deny` is refusal. A grant is now a positive statement
rather than the absence of a nested denial.
"""

* provision.type 1..1 MS
* provision.type ^short = "permit (patient agreed) | deny (patient refused)"
* provision.type ^definition = """
The patient's decision about this application.

**Required on the root provision**, which diverges from R4's element text and matches IHE PCF,
Ontario's PCOI, and the other Consent profiles in this guide. A consumer reading a Consent that
carries no root type MUST NOT infer `permit`; treat it as malformed and fail closed.

Refusal was previously a nested `provision` with `type = deny` and a grant was the absence of one.
Both are now stated on this element.
"""
* provision.type from http://hl7.org/fhir/ValueSet/consent-provision-type (required)

* provision.actor 1..1 MS
* provision.actor ^short = "The third-party app (Application Y)"

* provision.actor.role 1..1 MS
* provision.actor.role ^short = "information-recipient"

* provision.actor.reference 1..1 MS
* provision.actor.reference only Reference(NexusEmrCoreAppDevice)
* provision.actor.reference ^short = "Reference to the app Device"

* sourceAttachment 0..1 MS
* sourceAttachment ^short = "Original consent document attachment"
* sourceAttachment ^definition = "The original consent document in the form of an attachment (text/base64/etc). Optional but useful for maintaining a record of the actual signed consent form."