Privacy Directive Profile¶
| Canonical | ../StructureDefinition/nexus-privacy-directive |
|---|---|
| Status | draft (experimental) · 1.26.0 |
| Base | Consent (constraint) |
| Source | FSH · JSON |
A patient's standing restriction on the collection, use or disclosure of their own personal health information -- the instruction PHIPA calls a lockbox.
FSH
The authored source of truth: resources/StructureDefinition-nexus-privacy-directive.fsh (edit on GitHub). One FSH file may define several related artifacts.
// StructureDefinition-nexus-privacy-directive.fsh
// The patient's standing restriction on their own record -- what PHIPA calls a lockbox.
//
// ⚠ FIRST PASS. The shape below is stable enough to build against; the ENFORCEMENT contract it
// implies is not yet implemented anywhere in this estate. Read the handbook's privacy-directives
// page before assuming a directive stored here restricts anything.
//
// THE ONE THING TO KNOW BEFORE READING ANY OTHER LINE. An ACTIVE directive means a restriction is
// IN FORCE -- the patient said no. That is the opposite of what `status = active` means on the
// item-consent profile, where active means the patient said yes. Both are Consent resources, both
// live in the same store, and a single `GET Consent?patient={id}` returns them mixed together. A
// consumer that reads `status` without first selecting on `category` will read a lockbox as
// permission and disclose information the patient restricted.
//
// The rule, stated once and repeated wherever it matters: DISCRIMINATE ON `category`, THEN READ.
// The family code is required, it is searchable, and it is the only safe selector. `scope` is not
// -- all three Consent families in this guide carry `patient-privacy`.
//
// WHERE THE DECISION LIVES. Not in `status`. `status` says whether this directive is operative; the
// permit/deny decision is `provision.type` on the ROOT provision -- `deny` for a lockbox.
//
// THIS FOLLOWS IHE PCF AND ONTARIO'S PCOI, AND DIVERGES FROM R4'S OWN ELEMENT TEXT. R4 defines
// `Consent.provision.type` as "Not permitted in root rule, required in all nested rules". Both the
// international prior art (IHE Privacy Consent on FHIR) and Ontario's Provincial Consent Override
// Interface constrain the root type to `1..` regardless, because a consent whose base rule states
// no decision states none anywhere a reader looks first. This guide follows the prior art: the
// decision goes where a PCF or PCOI reader will look for it.
//
// The divergence is upstream's inconsistency rather than a local preference, and every Consent
// profile in this guide takes the same side of it. App consent and item consent both carried their
// decisions elsewhere until 1.25.1 -- a nested deny and `status` respectively -- and both moved to
// the root type, so one rule now reads every family and `category` answers only WHAT a decision
// governs.
//
// BREAK-GLASS IS AN EXCEPTION INSIDE THIS RESOURCE, NOT A RESOURCE OF ITS OWN. A directive denies
// at the root and carries nested `permit` provisions for what the restriction does not reach --
// including, where policy allows it, a standing exception naming the users who may break the glass:
//
// provision.type = deny
// provision.provision.type = permit
// provision.provision.actor = Group of privileged users
// provision.provision.purpose = BTG
//
// That is PCF's shape, taken deliberately rather than reinvented. The record that a named clinician
// ACTUALLY broke the glass -- when, on what ground, reaching what -- is an AuditEvent, following
// IHE BALP's authorization pattern. Keeping the two apart is what stops a patient's instruction
// being rewritten every time somebody acts on it.
//
// WHAT A DIRECTIVE IS NOT. It is not an access-control list, not a permission grant, and not a
// record of who looked. Access control is the platform's; the record of who looked is AuditEvent.
// A directive is the patient's INSTRUCTION, kept so that a release path can consult it and an
// auditor can see what it said at the moment of a decision.
Alias: $NexusPrivacyDirective = ../StructureDefinition/nexus-privacy-directive
Alias: $NexusConsentFamilyCS = ../CodeSystem/nexus-consent-family
Alias: $NexusConsentFamiliesVS = ../ValueSet/nexus-consent-families
Alias: $NexusPrivacyRestrictionActionsVS = ../ValueSet/nexus-privacy-restriction-actions
Alias: $NexusPrivacyExceptionPurposesVS = ../ValueSet/nexus-privacy-exception-purposes
// An exception that names no purpose is unreadable: nothing can tell a break-glass carve-out from
// an ordinary care-team permission, and the two are reviewed very differently. Warning rather than
// error, because a nested rule scoped by actor and data alone is meaningful even when unlabelled.
Invariant: privacy-directive-exception-states-a-purpose
Description: "A nested exception on a privacy directive SHOULD name the purpose it exists for. Without one, a standing break-glass carve-out cannot be distinguished from an ordinary permission, and the two carry different review obligations"
Severity: #warning
Expression: "provision.provision.all(purpose.exists())"
// ===========================================================================
// Profile Definition
// ===========================================================================
Profile: NexusPrivacyDirective
Parent: Consent
Id: nexus-privacy-directive
Title: "Privacy Directive Profile"
Description: """
A patient's standing restriction on the collection, use or disclosure of their own personal
health information -- the instruction PHIPA calls a lockbox.
The decision is `provision.type` on the ROOT provision -- `deny` for a lockbox -- and `status` says
only whether the record is operative. That is the same reading as every other Consent profile in
this guide, so a consumer does not need to know which family it holds to read a decision.
**Required on the root, which diverges from R4's element text.** R4 says `type` is "not permitted in
root rule". IHE Privacy Consent on FHIR and Ontario's Provincial Consent Override Interface both
require it there anyway, because a consent whose base rule states no decision states none where a
reader looks first. A directive carrying no root type is malformed: do not infer `permit`, fail
closed.
`category` carries a required, searchable family code. It says WHAT a decision governs -- an
application, a catalogue item, or the record itself -- and is no longer needed to read the decision
itself.
⚠ FIRST PASS. This profile defines how a restriction is RECORDED. It does not implement one: no
release path in this estate currently consults a directive before disclosing. What a consumer
must do with a directive is set out in the handbook.
"""
* insert EffectiveDate([[provision.period.start | dateTime]])
* ^url = $NexusPrivacyDirective
* ^status = #draft
* ^experimental = true
* obeys privacy-directive-exception-states-a-purpose
// SMART on FHIR exposure -- READ ONLY, matching the other Consent profiles, and the reason is
// structural rather than a preference. A SMART scope names a RESOURCE TYPE, not a profile, so
// `consent:read` granted for app or item consent already reaches every Consent in the store,
// including this one. There is no way to expose one Consent family to an application and withhold
// another; the choice available is whether Consent is exposed at all.
//
// ONE CONSEQUENCE AN INTEGRATOR SHOULD KNOW. An application holding `consent:read` can see that a
// patient has restricted their record, and can read the standing break-glass exception naming who
// may override it. The first is arguably necessary -- an application that cannot see a restriction
// cannot honour one. Whether an application should be able to read the second is not settled.
//
// WRITE IS NOT GRANTED, AND THAT IS LOAD-BEARING HERE. An application able to write a Consent could
// post a permitting override for itself. Read-only is what stops the authorization-bypass this
// profile warns about from being reachable through an ordinary app grant.
//
// What would change this: a mechanism that scopes a SMART grant to a Consent family. None exists
// today.
* insert SmartExposedReadOnly(consent)
* . ^short = "A patient's standing restriction on their own record (lockbox)"
* . ^definition = """
Records an instruction from the patient, or from a person authorized to instruct on their behalf,
restricting what may be done with their personal health information.
**One directive per instruction, superseded rather than edited.** Unlike item consent, which keeps
one resource per item and updates it in place, a directive is a statement made at a moment. When
the patient changes their instruction, the prior directive moves to `inactive` and a NEW directive
records the new instruction. The reason is evidentiary: a directive is the thing an auditor holds
up to justify or condemn a disclosure decision, and "what did this say on the day we released the
record" must be answerable without reconstructing it from version history.
**Absence of a directive is not permission.** It means no restriction has been recorded. Whether
a disclosure is lawful without one is a question for the applicable policy and the purpose of the
disclosure, not a question this resource answers. A consumer MUST NOT treat "no directive found"
as an authorization; it is the absence of one input to a decision that has other inputs.
**Finding a patient's directives.** Select the family, then filter on status:
```
GET Consent?patient={id}&category=../CodeSystem/nexus-consent-family|privacy-directive&status=active
```
Omitting the category returns app consents and item consents alongside directives, and all three
carry `scope = patient-privacy`, so scope will not separate them.
"""
* 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."
// ===========================================================================
// Identification
// ===========================================================================
* identifier 0..* MS
* identifier.system 1..1
* identifier.value 1..1
* identifier ^short = "Business identifier(s) for the directive"
* identifier ^definition = """
Stable identifier for the instruction, carried so that a directive can be correlated with the
paper or portal request that created it, with a superseding directive, and with an audit entry
that names the directive a decision relied on.
Deliberately NOT a deterministic key derived from patient and type. Item consent uses such a key
because exactly one resource exists per pair and a writer needs to create-or-update in one call.
A directive is the opposite case: several may be active for one patient at once, and a
conditional update keyed on the patient would overwrite an unrelated restriction.
"""
// ===========================================================================
// The lifecycle
// ===========================================================================
* status 1..1 MS
* status ^short = "active (in force) | inactive (superseded or withdrawn) | entered-in-error"
* status ^definition = """
Whether this directive is currently operative. **It is not the decision.** The decision -- what is
restricted, from whom -- is in the provision tree.
- `active` — **in force**. The restriction stated in the provisions applies now, subject to
`provision.period` where one is given.
- `inactive` — **no longer operative**. R4 defines this as terminated or replaced, which is
exactly right here: the patient withdrew the restriction, or a superseding directive replaced
it. Both are recoverable from `identifier` and version history.
- `entered-in-error` — recorded wrongly. Ignore it; it was never an instruction.
`draft` and `proposed` are legal against the required base binding and carry no meaning in this
profile. `rejected` is legal too and MUST NOT be written here: on the item-consent profile it
means the patient declined, and a reader who has seen that family will carry the wrong reading
across.
**Why `inactive` here and `rejected` there.** The two profiles take opposite readings of the same
vocabulary, deliberately. Item consent keeps one resource per item and updates it in place, so it
needs one current-state code meaning "not permitted" whether the patient declined or withdrew;
`rejected` carries that. A directive is superseded rather than updated, so a withdrawn restriction
is a terminated one, which is what `inactive` means. Neither profile is reading the vocabulary
loosely; they are modelling different lifecycles and the codes follow.
"""
* status from http://hl7.org/fhir/ValueSet/consent-state-codes (required)
* scope 1..1 MS
* scope ^short = "patient-privacy - Privacy consent"
* scope ^definition = """
Fixed to `patient-privacy`. Required by R4 and correct here, but it is NOT a discriminator: app
consent and item consent fix the same value, so a consumer separating families on scope will
separate nothing. Use the `category` family slice.
"""
* 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"
// ===========================================================================
// The family discriminator -- the safety mechanism
// ===========================================================================
* category 1..* MS
* category ^short = "MUST carry the privacy-directive family code; this is how a reader knows what `status` means"
* category ^definition = """
Required. Carries the family code that tells a consumer which interpretation contract applies
before it reads any other element.
Slicing is open, so a deployment may carry additional classifications alongside — a jurisdictional
code, a workflow bin — but only the `family` slice identifies the family, and a consumer MUST NOT
infer the family from any other category, from `scope`, or from the shape of the provision tree.
"""
* category ^comment = """
The family coding sits outside HL7's consent-category value set on purpose, for the same reason
the item-consent catalogue does: that value set classifies consent DOCUMENTS (advance directive,
notice of privacy practices, research information access) and holds no concept for "which
interpretation contract governs this resource", which is what this element answers.
Terminology-aware validators may note that the base extensible binding is unmet. That note is
expected.
"""
* category ^slicing.discriminator[0].type = #pattern
* category ^slicing.discriminator[0].path = "coding.system"
* category ^slicing.rules = #open
* category ^slicing.description = "Slice the family-identifying category by code system"
* category contains
family 1..1 MS
* category[family] ^short = "Fixed: this Consent is a privacy directive"
* 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 = #privacy-directive (exactly)
* category[family] from $NexusConsentFamiliesVS (required)
// ===========================================================================
// Whose instruction, and who recorded it
// ===========================================================================
* patient 1..1 MS
* patient only Reference(NexusEmrCorePatient)
* patient ^short = "The patient whose record is restricted"
* patient ^comment = """
Required by this profile rather than inherited. R4's `ppc-2` invariant is meant to require a
patient whenever scope is privacy, but its published expression tests `system='something'` and
therefore never fires. Do not rely on it.
"""
* dateTime 1..1 MS
* dateTime ^short = "When the instruction was given"
* dateTime ^definition = """
When the patient gave this instruction — not when the resource was stored, and not when the
restriction takes effect. Effect is `provision.period.start`, which may be later.
Three different times matter to an auditor reconstructing a decision and they must not be
conflated: when the patient said it (here), when it started applying (`provision.period.start`),
and when the server learned about it (`meta.lastUpdated`). A directive given on Monday, effective
Wednesday, entered Friday is an ordinary and lawful record.
"""
* performer 0..* MS
* performer only Reference(NexusEmrCorePatient or NexusEmrCoreRelatedPerson or NexusEmrCorePractitioner or NexusEmrCorePractitionerRole)
* performer ^short = "Who gave the instruction -- the patient, or a substitute decision-maker"
* performer ^definition = """
Who made this decision. The patient, or the RelatedPerson authorized to decide on their behalf.
**A substitute decision-maker is a RelatedPerson, never the Patient.** The tender requires access
and correction rights to be exercisable by an individual "and their Substitute Decision Makers",
and recording an SDM's instruction as though the patient gave it destroys the fact that authority
was delegated — which is the fact a privacy office checks when the instruction is challenged.
Practitioner and PractitionerRole are permitted for the staff member who RECORDED the instruction.
Both may appear together, and where they do the reading is that the patient decided and the staff
member wrote it down.
A resource naming ONLY staff is not malformed. A restriction is applied by an administrator acting on
what the patient asked for, and a deployment that captures the administrator without separately
identifying the patient as decision-maker is recording what it actually knows. What it costs is the
ability to show, later, that the instruction came from the individual rather than from the clinic --
which is worth capturing where the workflow can.
"""
* organization 0..* MS
* organization only Reference(NexusEmrCoreOrganization)
* organization ^short = "The custodian whose records this directive restricts"
* organization ^definition = """
The health information custodian this instruction is addressed to.
Load-bearing where it is present, because a restriction on one custodian's records says nothing
about another's. A patient who locks their record at this clinic has not thereby locked the
provincial EHR, and a patient who restricts the provincial EHR has not restricted this clinic. A
directive that names no organization is scoped by the deployment's own policy, which must state
what that scope is rather than leaving a reader to assume it means "everywhere".
"""
// ===========================================================================
// The governing policy -- and why policyRule is not used
// ===========================================================================
* policy 1..* MS
* policy.uri 1..1
* policy ^short = "The policy or statutory provision this directive is made under"
* policy ^definition = """
A URI identifying the policy under which this instruction is given and must be honoured — a
PHIPA provision, an Ontario Health EHR policy, or the custodian's own published privacy policy.
Required here, and it is what satisfies R4's `ppc-1` invariant ("Either a Policy or PolicyRule").
**`policyRule` is deliberately NOT used on this profile.** Its binding is to
`consentpolicycodes`, a code system that contains no Canadian policy concept at all — its 27
codes are US federal and state instruments plus Netherlands, Austria and Switzerland. There is no
code in it that means "a restriction under PHIPA", and the nearest-looking one, `cric`, is
45 CFR 46: the US Common Rule for human-subjects research. A privacy directive is not research
consent, and a policy element that pointed at one would be a false statement about the legal basis
of the restriction, made in the resource an auditor reads to check exactly that.
Carrying the actual policy URI says the true thing and stays machine-readable. Where a Canadian
policy code system emerges, `policyRule` becomes available without this element changing.
"""
// ===========================================================================
// What is restricted
// ===========================================================================
* provision 1..1 MS
* provision ^short = "The restriction: `type` states the decision, and nested provisions carve out exceptions"
* provision ^definition = """
The base rule. `type = deny` for a lockbox: the patient is restricting something.
Exceptions are NESTED provisions with `type = permit`, describing what the restriction does not
reach -- a care team that may still see the record, or a standing break-glass carve-out.
"""
* provision.type 1..1 MS
* provision.type ^short = "deny for a lockbox | permit for a base consent"
* provision.type ^definition = """
The decision this directive states. `deny` restricts; `permit` grants.
**Required on the root, which diverges from R4's element text.** R4 says `type` is "Not permitted in
root rule, required in all nested rules". IHE PCF and Ontario's PCOI both require it on the root
anyway, and this guide follows them: a directive whose base rule states no decision states none
where a reader looks first, and every reader of this family will be looking there.
A consumer reading a directive that carries no root type MUST NOT infer `permit`. Treat it as
malformed and fail closed.
"""
* provision.type from http://hl7.org/fhir/ValueSet/consent-provision-type (required)
* provision.period 0..1 MS
* provision.period ^short = "When the restriction is effective, if it is time-bounded"
* provision.period ^definition = """
The window in which this restriction applies. An absent period means the restriction applies for
as long as the directive is `active`, with no end.
An absent `period.end` means the same thing and must be read that way. A consumer that treats a
missing end as "expired" or as "unknown, therefore ignore" will release restricted information.
"""
* provision.action 0..* MS
* provision.action from $NexusPrivacyRestrictionActionsVS (extensible)
* provision.action ^short = "Which acts are restricted: collect | access | use | disclose"
* provision.action ^definition = """
The acts this directive restricts, matching PHIPA's collection / use / disclosure triad plus
`access` for retrieval that permits none of them.
**A deliberate divergence from IHE PCF, which prohibits this element entirely** (`0..0` in all
three of its tiers). PCF targets document-sharing environments, where disclosure is effectively the
only act in play, so the axis buys nothing there. A primary-care EMR is the case it does not cover:
a restriction on internal USE of a record by staff who already hold it is a different instruction
from a restriction on DISCLOSING it outward, and Ontario requires both to be expressible. A
directive exported toward a PCF-conformant system loses this element, and what it meant has to be
carried some other way or declared unsupported at the boundary.
**An absent action restricts EVERY action.** This is stated rather than left to inference because
the opposite reading — an unnamed action is unrestricted — fails open, and a privacy control that
fails open is not a control. A writer who means to restrict only disclosure must say `disclose`.
"""
* provision.actor 0..* MS
* provision.actor ^short = "Whom the restriction is against, where it names anyone"
* provision.actor ^definition = """
The recipient, agent or class of agents the restriction applies to.
`actor.reference` names an individual practitioner, an organization, or a Group standing for a
class of recipients. `actor.role` says how they are involved. The tender requires restrictions
naming "specific HIC's, agents, classes of HICs or agents, or individual providers", and a class
is expressed as a Group rather than as a bare code, because a consumer has to be able to test
membership at decision time rather than interpret a label.
**An absent actor restricts EVERYONE**, on the same fail-closed reasoning as `action`.
**A first-pass limit, stated because the tender asks for the case this does not yet cover.** The
requirement names restrictions against "classes of HICs or agents", and a class is properly a Group
whose membership a consumer can test at decision time. `Reference(Group)` is permitted here, but
this guide does not profile Group and publishes no example of one, so a deployment naming a class
today is pointing at a resource with no agreed shape. Naming an Organization instead works and is
coarser: it covers everyone at that custodian rather than a named subset. What would settle it is a
Group profile and a decision about who maintains membership.
"""
* provision.actor.reference only Reference(NexusEmrCorePractitioner or NexusEmrCorePractitionerRole or NexusEmrCoreOrganization or NexusEmrCoreAppDevice or Group)
* provision.data 0..* MS
* provision.data ^short = "Which records are restricted, where the directive names records"
* provision.data ^definition = """
The specific records this restriction covers. Absent means the restriction covers the patient's
whole record, which is the ordinary lockbox case.
`data.meaning` is required by R4 and decides how far the reference reaches: `instance` is that
resource alone, `related` includes what it points at, `dependents` includes what points at it,
`authoredby` covers everything authored by the referenced actor. Choosing `instance` where the
patient meant "this visit and everything from it" under-restricts, and it under-restricts
silently.
**A first-pass limit worth knowing before designing against it.** These selectors enumerate
records that exist NOW. A directive meant to cover future information — "nothing from this clinic,
ever" — cannot be expressed as a list, and this release does not define a criteria-based selector.
Express a whole-record restriction by naming no data at all; that reading does extend to future
records. A directive naming a data list does NOT.
"""
* provision.provision 0..* MS
* provision.provision ^short = "Exceptions: what the restriction does NOT reach"
* provision.provision ^definition = """
Carve-outs from the base rule, each with an explicit `type` (R4 requires one here and so does this
profile). On a `deny` directive these are the permissions that survive the restriction.
**This is where break-glass lives.** A standing exception naming the users who may reach the record
in an emergency is a nested permit whose `purpose` is `BTG` and whose `actor` names the privileged
group:
```
provision.type = deny
provision.provision.type = permit
provision.provision.actor = Group/privileged-users (role: IRCP)
provision.provision.purpose = BTG
```
That shape is IHE PCF's, adopted rather than reinvented. **It says who MAY break the glass. It never
says that anybody did** -- the record of an actual override, by whom, when, on what lawful ground
and reaching what, is an AuditEvent following IHE BALP's authorization pattern. A deployment that
recorded override events by editing this resource would rewrite the patient's instruction every time
a clinician acted on it, and would make "what did the patient actually ask for" unanswerable.
Nested exceptions do not nest further. R4 permits deeper trees and PCF forbids them; this profile
follows PCF, because a rule whose meaning depends on three levels of precedence is one nobody
implements the same way twice.
"""
* provision.provision.type 1..1 MS
* provision.provision.type ^short = "permit (an exception to a deny directive) | deny"
* provision.provision.purpose 0..* MS
* provision.provision.purpose from $NexusPrivacyExceptionPurposesVS (extensible)
* provision.provision.purpose ^short = "Why this exception exists -- `BTG` marks the break-glass carve-out"
* provision.provision.provision 0..0
* provision.provision.provision ^short = "Not used -- exceptions do not nest further, matching IHE PCF"
* provision.securityLabel 0..* MS
* provision.securityLabel ^short = "Restrict by sensitivity class -- read the high-water-mark warning first"
* provision.securityLabel ^definition = """
Restricts records carrying a given security label rather than records named individually.
**R4 matching here is not equality, and the difference is dangerous.** The specification states:
"If the consent specifies a security label of 'R' then it applies to all resources that are
labeled 'R' or lower. E.g. for Confidentiality, it's a high water mark." So a directive written
to restrict only `restricted` material also restricts everything `normal`, `moderate`, `low` and
`unrestricted` — which, in a chart where most resources carry no elevated label at all, is very
close to restricting the entire record.
A writer meaning "restrict the sensitive material" and reaching for `R` will get a far broader
restriction than intended. Because that error over-restricts rather than over-discloses it will
not cause a privacy breach, but it will cause a clinician to be denied information they are
entitled to, at a moment when they may need it.
Prefer explicit `provision.data` selectors for a first implementation. Use this element only where
the deployment has tested subsumption behaviour against both a higher and a lower classification
than the one it names.
"""
// ===========================================================================
// Evidence
// ===========================================================================
* sourceReference 0..1 MS
* sourceReference only Reference(NexusEmrCoreDocumentReference)
* sourceReference ^short = "The signed instruction, where one was captured"
* sourceReference ^definition = """
The document the patient signed, or the portal submission that recorded the instruction.
Routed through DocumentReference rather than carried inline so that DocumentReference read
permission gates the document itself: a reader entitled to know THAT a restriction exists is not
automatically entitled to read the patient's written explanation of why.
"""
* source[x] only Reference(NexusEmrCoreDocumentReference)