Skip to content

AuditEvent Profile

Canonical../StructureDefinition/nexus-emr-core-auditevent
Statusdraft (experimental) · 1.26.0
BaseAuditEvent (constraint)
SourceFSH · 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 AIAST security 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-by extension, 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. period is preferred where present, and is not populated today.
  • agent (1..*), naming the actor. The identity that is always available is not a reference: agent.altId carries the identity-provider subject the platform holds, and it is the value to match on when correlating an entry with the meta-updated-by extension on a resource version -- the two carry the same kind of value for the same reason. agent.who is 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.name is 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 model entity.what may be identifier-only, and in a projection of the access log it usually is. Treating a reference with no literal reference as 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. R means "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's C | R | U | D | E cannot express that. Until it is settled, a disclosure appears as R and the fact that it left is carried on type: 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 on type, not on action alone.

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 with agent.purposeOfUse and agent.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 absent purposeOfEvent as "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, read subtype liberally 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.site and source.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. Treat source.observer as 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 (altId plus a resolved who), action = R, and the record concerned on entity.
  • Packet disclosure -- a document packet released with pages withheld. Shows the export event type marking the disclosure, entity.detail carrying the withheld-page count, and an actor identified by altId alone.