AuditEvent Profile¶
| Canonical | ../StructureDefinition/nexus-emr-core-auditevent |
|---|---|
| Status | draft (experimental) · 1.26.0 |
| Base | AuditEvent (constraint) |
| Source | FSH · JSON |
Nexus EMR profile for AuditEvent -- the access and activity log: who acted, on which record, when, through what interaction, and with what outcome.
Overview
Distinct from Provenance, which describes how a record came to be and is written selectively, and
from the AIAST security label, which says only whether a machine produced the content. An audit
entry describes an INTERACTION rather than authorship, is written for reads as well as writes, and
is not part of the record it describes. A report over access to personal health information is this
resource's question.
First pass: constrains the actor, the moment, the interaction, its outcome and the record concerned. The event-type vocabulary, purpose of use and network attribution are not yet constrained. Closed to SMART apps pending an exposure decision.
Overview
The Nexus EMR Core AuditEvent profile is the access and activity log: who acted, on which record, when, through what interaction, and with what outcome. It is what a privacy report over access to personal health information is built from -- who saw whose chart, when, and whether the record left the clinic.
The profile is #draft and experimental = true. It is a first pass: it constrains the elements
the platform's access log guarantees on every entry and states plainly which elements it does not
constrain and what would settle each. It is not reachable by a SMART on FHIR app -- the profile
carries no smart-exposed declaration, so no scope naming this type is granted and it is absent from
the server capability statement. That is a decision, not an oversight; see below.
THE effective date is period.start, falling back to recorded, which R4 makes 1..1 so it always
resolves.
Three questions that look alike, and the one this answers
Reading the wrong mechanism is the most common mistake available here, so the boundary is worth stating before the constraints:
- "Was a machine involved in producing this content?" -- the
AIASTsecurity label on the resource holding the content. One comparison, searchable, no second query. - "How did this record come to be -- from what inputs, under whose supervision?" -- the
Provenance profile, plus the
meta-updated-byextension, which names the user account that wrote a given version so a version list can render without a Provenance per row. - "Who touched this record, and when?" -- this profile.
The distinction that carries the most weight: a Provenance describes authorship and is written selectively, so its absence is inconclusive. An AuditEvent describes an interaction and is written for every one that reaches the FHIR API -- reads included, whether or not anything changed. A disclosure report cannot be answered from Provenance, and an authorship question should not be answered from an audit entry.
Where the estate's own access log lives
The platform records its access log in a dedicated, append-only, tenant-scoped store, fed
asynchronously from the API tier and served through its own administrative search API rather than as
FHIR. That store is not reachable at /AuditEvent. A consumer should not expect a FHIR search to
return the estate's access history today, and should not read an empty result as evidence that no
access occurred.
This profile is therefore doing two jobs:
- It is the shape of an AuditEvent written through the FHIR API, which the store accepts like any other resource type.
- It is the declared mapping from what the access log records onto AuditEvent elements, so that a reporting criterion or an exported disclosure log has a FHIR element to name rather than a private column.
What every entry carries
recorded(1..1) -- the moment the entry was persisted, not the instant of the interaction. Entries reach the log asynchronously and the stamp is applied on arrival. Order interactions by it freely; reconcile it against an external timestamp with care.periodis preferred where present, and is not populated today.agent(1..*), naming the actor. The identity that is always available is not a reference:agent.altIdcarries the identity-provider subject the platform holds, and it is the value to match on when correlating an entry with themeta-updated-byextension on a resource version -- the two carry the same kind of value for the same reason.agent.whois optional and is written only when a producer can resolve the account to a person; not every actor has a Practitioner resource, and resolving the subject needs an identity directory this guide does not describe.agent.nameis a username or email for display only -- never for a matching or authorization decision.entity(0..*) -- the records the interaction concerned. Per the logical-reference modelentity.whatmay be identifier-only, and in a projection of the access log it usually is. Treating a reference with no literalreferenceas absent reads most entries as concerning nothing.
A warning-severity invariant (auditevent-names-an-actor) requires at least one agent carrying
either who or altId. It is a warning rather than an error because an entry recorded for an
unauthenticated or rejected request may legitimately have no identity to name, and a validation error
would make such an entry unstoreable at exactly the moment it is most worth keeping.
action is R4's five codes, and R is broader than it looks
Two things to know before writing a report against action:
- Every interaction that is not a create, an update or a delete is recorded as a read -- searches,
version reads and history reads included.
Rmeans "the record was disclosed to someone", which is what a privacy report wants. A search that returned twelve records yields twelve read entries, one per record returned, not one entry for the search. Count records, not queries. - A transmission is not distinguishable on
action. The platform separates information leaving the estate -- a referral sent, a report exported or downloaded, a packet released -- from an ordinary read, and R4'sC | R | U | D | Ecannot express that. Until it is settled, a disclosure appears asRand the fact that it left is carried ontype: the standard export event type is the recommended value, and the packet disclosure example shows the shape. A report answering "what was disclosed outside the clinic" must filter ontype, not onactionalone.
Read an absent outcome as unknown, never as success
outcome (0..1) is the most consequential misread available on this resource. The platform records
three outcomes -- succeeded, failed, and could not be determined -- against R4's four severity-graded
codes:
- a success is
0; - a failure is
4, because failures are not graded and claiming a serious or major failure would assert something that was not observed; - an outcome that could not be determined carries no code at all.
An access whose outcome is unknown is exactly the access a privacy review wants surfaced, and defaulting it to success buries it.
One further caution: an entry written for an invoked operation is recorded when the invocation is accepted, before the operation resolves, so its success outcome records that the attempt was made rather than that it succeeded. Entries for record interactions are written on the response and their outcome does reflect it.
What is deliberately not constrained
Each of these is open, and the profile says what would settle it rather than leaving a silence:
- Emergency access ("break-the-glass"). Base R4 records an emergency override on
purposeOfEvent, together withagent.purposeOfUseandagent.policy, and that is the shape a reader should expect if such a record appears. Nothing in this estate produces one today, so this guide fixes no code, makes no element mandatory, and asserts no invariant about a justification. Read an absentpurposeOfEventas "not recorded", never as "routine access". What settles it is the override becoming a product behaviour, at which point the open question is whether it is a coded purpose on the access entry or a record of its own that the access entry points at -- the two differ in whether the justification is queryable independently of the access it authorised. - The event-subtype vocabulary. Where an entry describes a named platform activity rather than an
interaction with a chart record, the activity belongs on
subtype. No code system is published for that set: most of the platform's activity names are FHIR resource type names, which already have a published code system, and minting a second one restating them would be a permanent commitment to a duplicate. What settles it is the residue -- the values naming an activity rather than a resource type -- published in its own right. Until then, readsubtypeliberally and do not assume a system URI. agent.network. Not populated. The requesting network address is not captured, so its absence is not evidence about where a request came from.source.siteandsource.observer. The log does not record which service produced an entry; entries arrive from every service on one shared channel carrying no producer identity. R4 requires an observer, so a projection supplies it from the endpoint doing the projecting. Treatsource.observeras naming the reporting endpoint, not the service where the interaction occurred.- Field-level change detail. An entry records the interaction and the record it concerned, not which fields moved. There is no per-field before-and-after value on this resource and none is implied. Reconstructing a field-level history means comparing successive versions of the record itself.
- Retention. How long entries remain queryable is a deployment setting rather than something this guide fixes. Do not assume an audit query reaches arbitrarily far back.
Exposure is a decision, and it was taken
The permission family this profile would name exists on the platform, so declaring smart-exposed
here would resolve and would grant it -- which is precisely why the decision is taken rather
than inherited. An access log is the record of who reached the chart, and granting a third-party
application read over it hands that application the reading history of every user in the tenant: a
wider disclosure than any single clinical resource it might be granted alongside. The permission is
held inside the estate by administrative and auditor roles, not by apps.
What would decide it: a named consumer, the interactions it needs, and a ruling on whether an application's grant over an audit trail is scoped to the patients it may already read or to the whole tenant. Write and delete are a separate and colder question -- an audit trail an application can rewrite or destroy is not an audit trail.
Worked examples
- Chart read -- one record disclosure, the entry a
privacy report is built from. Shows the actor named twice (
altIdplus a resolvedwho),action=R, and the record concerned onentity. - Packet disclosure -- a document packet
released with pages withheld. Shows the export event type marking the disclosure,
entity.detailcarrying the withheld-page count, and an actor identified byaltIdalone.