CONTRIBUTION `UPDATE_AUDIT.change_type` in REST API vs vendor implementations

Dear community,

We are currently working on a TypeScript SDK that should abstract over differences between openEHR CDR vendors. While implementing the Create CONTRIBUTION endpoint, we ran into a mismatch where we are no longer sure what the ground truth should be: the REST specification, the OpenAPI schema, the examples, or the behavior of existing CDRs.

Context

The Create CONTRIBUTION endpoint describes the request body as a relaxed CONTRIBUTION with:

  • versions: array of UPDATE_VERSION
  • audit: UPDATE_AUDIT

Each UPDATE_VERSION also has commit_audit, again typed as UPDATE_AUDIT.

The Platform Service Model defines UPDATE_AUDIT as the client-provided audit object used by the server to create an AUDIT_DETAILS object. In that model, UPDATE_AUDIT.change_type is a Terminology_code.
However, the RM AUDIT_DETAILS class has change_type: DV_CODED_TEXT.

That conceptual distinction makes sense: UPDATE_AUDIT is the input DTO, while AUDIT_DETAILS is the persisted RM object created by the server. The problem is that the JSON shape expected in practice does not seem to follow the UPDATE_AUDIT schema consistently.

What we see in the official OpenAPI schema

In the validation OpenAPI schema, UpdateAudit.change_type references TerminologyCode:

UpdateAudit:
  title: UPDATE_AUDIT
  required:
    - change_type
    - committer
  type: object
  properties:
    _type:
      type: string
      default: UPDATE_AUDIT
    change_type:
      $ref: '#/components/schemas/TerminologyCode'
    description:
      $ref: '#/components/schemas/UDvText'
    committer:
      $ref: '#/components/schemas/UPartyProxy'
  description: The set of attributes required to document the committal of an information item to a repository. Used by the server to create an AUDIT_DETAILS object.
  example:
    change_type:
      value: creation
      defining_code:
        terminology_id: openehr
        code_string: '249'
    description: Description text
    committer:
      _type: PARTY_IDENTIFIED
      name: A user name

So the schema says TerminologyCode, but the example uses a DV_CODED_TEXT-like shape (value + defining_code).

What vendors accept

We tested with:

  • Better EHR Server 4.7.8
  • EHRbase CDR 2.31.0

Both reject a TerminologyCode-shaped change_type and expect a DV_CODED_TEXT-shaped change_type instead.

Better EHR Server 4.7.8

Better accepts change_type as DV_CODED_TEXT, with _type: "UPDATE_AUDIT":

{
  "versions": [
    {
      "data": "<Composition>",
      "lifecycle_state": {
        "value": "complete",
        "defining_code": {
          "terminology_id": { "value": "openehr" },
          "code_string": "532"
        }
      },
      "commit_audit": {
        "_type": "UPDATE_AUDIT",
        "system_id": "integration-test-system",
        "change_type": {
          "value": "creation",
          "defining_code": {
            "terminology_id": { "value": "openehr" },
            "code_string": "249"
          }
        },
        "committer": {
          "_type": "PARTY_IDENTIFIED",
          "name": "integration-test-committer"
        }
      }
    }
  ],
  "audit": {
    "_type": "UPDATE_AUDIT",
    "system_id": "integration-test-system",
    "change_type": {
      "value": "creation",
      "defining_code": {
        "terminology_id": { "value": "openehr" },
        "code_string": "249"
      }
    },
    "committer": {
      "_type": "PARTY_IDENTIFIED",
      "name": "integration-test-committer"
    }
  }
}

EHRbase CDR 2.31.0

EHRbase accepts change_type as DV_CODED_TEXT, but with _type: "AUDIT_DETAILS" rather than _type: "UPDATE_AUDIT":

{
  "versions": [
    {
      "data": "<Composition>",
      "lifecycle_state": {
        "value": "complete",
        "defining_code": {
          "terminology_id": { "value": "openehr" },
          "code_string": "532"
        }
      },
      "commit_audit": {
        "_type": "AUDIT_DETAILS",
        "system_id": "integration-test-system",
        "change_type": {
          "value": "creation",
          "defining_code": {
            "terminology_id": { "value": "openehr" },
            "code_string": "249"
          }
        },
        "committer": {
          "_type": "PARTY_IDENTIFIED",
          "name": "integration-test-committer"
        }
      }
    }
  ],
  "audit": {
    "_type": "AUDIT_DETAILS",
    "system_id": "integration-test-system",
    "change_type": {
      "value": "creation",
      "defining_code": {
        "terminology_id": { "value": "openehr" },
        "code_string": "249"
      }
    },
    "committer": {
      "_type": "PARTY_IDENTIFIED",
      "name": "integration-test-committer"
    }
  }
}

What appears spec-compliant but is rejected

Based on the OpenAPI UpdateAudit.change_type -> TerminologyCode schema, we expected this shape to be valid:

{
  "versions": [
    {
      "data": "<Composition>",
      "lifecycle_state": {
        "value": "complete",
        "defining_code": {
          "terminology_id": { "value": "openehr" },
          "code_string": "532"
        }
      },
      "commit_audit": {
        "_type": "UPDATE_AUDIT",
        "system_id": "integration-test-system",
        "change_type": {
          "terminology_id": "openehr",
          "code_string": "249"
        },
        "committer": {
          "_type": "PARTY_IDENTIFIED",
          "name": "integration-test-committer"
        }
      }
    }
  ],
  "audit": {
    "_type": "UPDATE_AUDIT",
    "system_id": "integration-test-system",
    "change_type": {
      "terminology_id": "openehr",
      "code_string": "249"
    },
    "committer": {
      "_type": "PARTY_IDENTIFIED",
      "name": "integration-test-committer"
    }
  }
}

EHRbase rejects this with validation errors indicating it expected DV_CODED_TEXT fields (value and defining_code) under change_type. Better also rejects it.

Questions

  1. For the REST Create CONTRIBUTION request body, should audit and versions[].commit_audit be serialized as UPDATE_AUDIT or as AUDIT_DETAILS?
  2. Should UPDATE_AUDIT.change_type be serialized as TerminologyCode / TERMINOLOGY_CODE, or as DV_CODED_TEXT?
  3. Is the OpenAPI schema wrong, the example wrong, or are we misunderstanding how Terminology_code in the Service Model should be represented in canonical JSON?
  4. Should implementations accept _type: "UPDATE_AUDIT", _type: "AUDIT_DETAILS", both, or should _type be omitted for this DTO?
  5. For SDK authors, what should be treated as the ground truth: the Platform Service Model, the REST OpenAPI schema, the REST examples, or the common behavior of current CDRs?

Our practical concern is that we need to generate stable TypeScript types and runtime serializers. At the moment, it looks like the most interoperable SDK behavior would be vendor-specific serialization profiles:

  • Better: UPDATE_AUDIT with DV_CODED_TEXT-shaped change_type
  • EHRbase: AUDIT_DETAILS with DV_CODED_TEXT-shaped change_type

But before encoding that into the SDK, we would like to know whether there is a normative answer we are missing.

Thanks in advance for any clarification.

Please never do that :folded_hands:

@HHeiser you should always check against the spec, the schemas can have errors since those are manually edited.

AUDIT_DETAILS.change_type is DV_CODED_TEXT

In fact, the openEHR SDK schema has DV_CODED_TEXT openEHR-SDK/src/main/resources/json_schema/openehr_rm_1.1.0_all.json at master · CaboLabs/openEHR-SDK · GitHub

That schema was copied and evolved in parallel to the spec schema, adding more strict constraints like regexes for formatted data, some of those were contributed back the the spec.

Another thing is that you can do a GitHub Blame on the official schema to check when the change_type started to reference the TerminologyCode. But! also check you are using the latest version.

Thanks for the reply.

The main problem we have is not with AUDIT_DETAILS (which clearly requires AUDIT_DETAILS.change_type as DV_CODED_TEXT), but with UPDATE_AUDIT (which should require UPDATE_AUDIT.change_type as TERMINOLOGY_CODE), since this is the object that should be used in the /contribution endpoint. I could not find UPDATE_AUDIT in your SDK schema.

However, the official OpenAPI specification, which we use for our TypeScript SDK, contradicts itself by using TerminologyCode for the change_type in the properties but the shape of CodedText in the example (See Line 3292-3314):

UpdateAudit:
  title: UPDATE_AUDIT
  required:
	- change_type
	- committer
  type: object
  properties:
	_type:
	  type: string
	  default: UPDATE_AUDIT
	change_type:
	  $ref: '#/components/schemas/TerminologyCode'
	description:
	  $ref: '#/components/schemas/UDvText'
	committer:
	  $ref: '#/components/schemas/UPartyProxy'
  description: The set of attributes required to document the committal of an information item to a repository. Used by the server to create an AUDIT_DETAILS object.
  example:
	change_type:
	  value: creation
	  defining_code:
		terminology_id: openehr
		code_string: '249'
	description: Description text
	committer:
	  _type: PARTY_IDENTIFIED
	  name: A user name

The same problem is found in the website version of the ITS-REST specs:


The attribute audit is described as object(UPDATE_AUDIT), but the example on the right shows change_type as DV_CODED_TEXT (with defining_code), as if it would be part of AUDIT_DETAILS

Does that mean that the API-Specs are wrong, the RM and SM specs are correct and should be considered the sole ground truth? So that in the Contribution Endpoint, audit and commit_audit should be of type UPDATE_AUDIT with change_type of type TERMINOLOGY_CODE.


It may additionally be an implementation problem, as Better and EHRbase do not handle this correctly:
EHRbase incorrectly expects AUDIT_DETAILS with change_type of type DV_CODED_TEXT and yields this error otherwise:
"Error while processing given json input: Could not resolve type id 'UPDATE_AUDIT' as a subtype of com.nedap.archie.base.OpenEHRBase: no such class found\n at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: org.ehrbase.openehr.sdk.response.dto.ContributionCreateDto[\"audit\"])"

Better correctly expects UPDATE_AUDIT (otherwise yields "Could not resolve type id 'AUDIT_DETAILS' as a subtype of com.marand.thinkehr.server.rest.data.openehr.OpenEhrCommitAudit: known type ids = [UPDATE_AUDIT] (for POJO property 'audit')", but ignores the given change_typevalue silently and always persists 249|creation.

@HHeiser where is UPDATE_AUDIT in the RM?

TL;DR: IMO the SM spec and the REST API schemas should be fixed, changing change_type to DV_CODED_TEXT.

Long answer:

I got it now, it’s not an RM class, it’s from the SM (openEHR Platform Service Model). I thought the SM wasn’t even considered by the REST API, though we designed that to be the source for the REST API. There in the SM change_type is actually Terminology_code, which I think is incorrect and should be DV_CODED_TEXT.

UPDATE_AUDIT is used in EHR API openEHR specs

Defined as: “the set of attributes required to document the committal of an information item to a repository. Used by the server to create an AUDIT_DETAILS object.”, which is the definition taken from the SM.

So UPDATE_AUDIT is just AUDIT_DETAILS without the system_id and time_committed, and there is no reason why the change_type is a Terminology_code.

There is a bit too much text posted here and I hardly follow anymore what is what…

But, I am glad that you’ve got Better and EHRbase aligned - if I understand for your message they are.

The SM specs are outdated, in upcoming REST API they are not referred anymore. Terminology_code is a type introduce recently (last few years) but I don’t think is adopted, and I think it got mixed in some specification as dependency (like in SM), but that was not a deliberate action (as it is a breaking change).
The json-schema (ITS-JSON repo) is also potentially outdated, as it was generated from/with Archie and it might validate json payload, but not sure if is validating REST api resources - I would not rely on that too much for now.

For the record, can you tell:

  • which version of REST API specs are you using? (I suppose latest=1.0.3, not development, right?)
  • which OAS yaml variant (there are 3 types) do you use for SDK code-generation?

I would say the order is:

  1. openEHR REST specification on first place
  2. CDR implementation, assuming they are conformant
  3. OAS schema (yaml files) - but I know there were some limitation in what can be specified there, because of polymorphism and the OpenAPI 3.0 specs, and the redocly renderer; perhaps newer OAS specs version are more powerful, but we are not there yet
  4. RM validatiton rules / constraints - where applicable
  5. Examples - but treath them as Examples

So bottom line, what prevails here: ITS-REST specs (check also development branch), and CDR implementations.

Some types had to be introduced by OAS schema, because RM type were not designed to act as REST resources. In this case, the RM mandates some properties to be filled in, but those only happen after server side process the request. Thus the request payload is not a full valid RM instance - hence those REST-only types defined in OAS yaml.

>> Some types had to be introduced by OAS schema

That seems to be the basis for the confusion. The REST specs and examples are aligned to the RM, whereas the OAS introduces a pseudo-class UPDATE_DETAILS which is essentially a constraint on AUDIT_DETAILS with some defaults, but not actually exist as a valid RM object.

Note that the GET Contribution expects an AUDIT_DETAILS to be returned
It feels as if

**change_type of type TERMINOLOGY_CODE
**

is just an error, and that change_type should be a DV_CODED_TEXT

and my interpretation is that the Better CDR is compliant with the OAS but has not understood the requirement to turn UPDATE_DETAILS into plain AUDIT_DETAILS on persisting the data. Possibly built backwards from the OAS like the Cistec Typescript SDK?

Clearly this does need fixed or at least clarified. There may be other similar issues with other OAS wrappers like updateVersion?

This is precisely what the Abstract Platform Services spec was for - to specify these things, including transactions to the API, which is what we are talking about here.

Ah thanks Ian, this gets more clear now.

I see that in 1.0.2 specs (which were not based on OAS) we had examples on POST with:

 "commit_audit": {
                "change_type": {},
                "description": {},
                "committer": {
                    /* optional structure - will use the outer committer if absent */
                }
            }

and response with

"change_type": {
      "value": "creation",
      "defining_code": {
        "terminology_id": {
          "value": "openehr"
        },
        "code_string": "249"
      }
    }

But this was changed in 1.0.3 because of refactor on OAS (which required a local type) and alignment to SM UPDATE_AUDIT. As we decided lately, that wasn’t a good move, and the next release on REST 1.1.0 does not have the tight coupling with SM, and we can emit a correction or clarification there.

Back to these questions:

So, in conclusion, for the specs we should change:

  • both UPDATE_AUDIT and AUDIT_DETAILS should have change_type property as DV_CODED_TEXT; we’ll drop the TERMINOLOGY_CODE typing of that property.
  • examples should be consistent with schema
  • the question is whether to clear out that the UPDATE_AUDIT is just AUDIT_DETAILS without server side values i.e. system_id and time_committed; will then CDRs going to accept it (as is not a type defined in the RM); in any case _type is then necessary.

Would you agree on this? (ping also @matijap @vidi42 and @birger.haarbrandt )

SM specs remains decoupled and should not play a role in this.

I made a ticket SPECITS-95 and we’ll try to fix this in upcoming 1.1.0

Great @sebastian.iancu, thanks for the effort!

Release 1.1.0 has many important and useful improvements. Any rough ideas on when it will be published?

It was finalize, we are now awaiting final acceptance. I can imagine we’ll be able to release it in the upcoming 3-4 weeks, if nothing new more urgent is discovered.

Thanks for your report and analysis - you were very sharp to spot this issue.

A timeline measured in weeks is great!

And I am just the investigator and messenger here, this bug was spotted by Martin, one of our engineers that is working on the TS SDK :slight_smile: I will forward your compliments :+1:

I pushed the change in the specification now - see EHR API openEHR specs - so that we can release it soon, together with the rest of changes for Release 1.1.0

That was the right choice, TERMINOLOGY_CODE didn’t make any sense in REST or SM, just added confusion.