Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Exporting the identity state

When you replace FerroFED with another product, the routing state the gateway learned while it served goes with you. Regulation (EU) 2025/327 Annex II 2.6 says the harmonised components of an EHR system “shall not include features that prohibit, restrict or place an undue burden on authorised exporting of personal electronic health data for the reasons of replacing the EHR system by another product”. The access records are exported from the Audit Record Repository (The audit trail). This page covers the rest: the state the gateway learned and holds in memory.

What is exported and what is not

The export holds the creating_system_id mappings the gateway learned from answers. A member’s CDR can hold versions another system created, and the gateway learns which member to send a follow-up read of such a version to (§12.2, N21). The mapping is lost when the process stops, and a replacement that has it routes those follow-ups from its first request.

The export leaves out:

StateWhy it is left out
The resolution bindings of each callerThey are scoped to one client session and expire after their time to live. A replacement asks the identifier cross-reference (the PIX Manager or the NVI) itself, which is the source of truth. A binding is pseudonymous personal data, and the export carries none.
The ehr_id indexIt holds one entry per patient’s EHR and is a hint: the next read that names no node asks every member and finds the owner again (§12.5.1).
The development cross-referenceIt is [[dev.crossref]] configuration, used only under profile = "development".
The membershipThe registry document is the record, and you already hold it.
The stored queriesTheir backend holds them, and GET {base}/operator/stored-queries reads them (Client authentication).

The export therefore carries system_ids, endpoint_ids and node_ids only, and no value a patient is known by: no patient identifier, no ehr_id, no demographic (§5.4.1, N33).

Running the export

Send the request to the admin listener with an access token that carries auth.product_scope:

POST /admin/identity-state/export
Authorization: Bearer <an access token with the operator scope>
AnswerWhen
200The export, as JSON, with Cache-Control: no-store.
400 (body-invalid)The request carries a body. The export takes none.
401, 403The token is missing, does not verify, or lacks the operator scope.
501The gateway runs without a registry document, so it learned nothing.

The gateway records the export like every admin action: under ferrofed::security, once when it begins and once when it ends, with your issuer and subject, the route and the status (Metrics). It also logs how many mappings the export held, never a value.

Each replica learns its own mappings and shares them with no other replica (Running several replicas). Export every replica, one request to the admin listener of each, and keep every file. The files of two replicas can disagree, because each saw different answers. Each file names its replica in replica, so give each replica a name you can tell apart ([server] replica, or the pod name Kubernetes gives it).

The format

{
  "format_version": 1,
  "federation_id": "example-federation",
  "exported_at": "2026-10-15T09:30:00Z",
  "replica": "ferrofed-7d9c4b5f6-x2k8q",
  "learned_creating_systems": [
    {
      "creating_system_id": "legacy-x.example.org",
      "status": "learned",
      "node": "node-a",
      "endpoint": "node-a-pub"
    },
    {
      "creating_system_id": "legacy-y.example.org",
      "status": "withdrawn",
      "node": null,
      "endpoint": null
    }
  ],
  "not_exported": [
    "resolution-bindings",
    "ehr-id-index",
    "development-cross-reference",
    "membership",
    "stored-queries"
  ]
}
FieldMeaning
format_versionThe version of this format, 1. A change that removes a field or changes what a field means raises it. A reader refuses a version it does not know.
federation_idThe [federation] id the gateway declares in OPTIONS {base}/ (§7a.2, N30).
exported_atWhen the process took the export, as an RFC 3339 timestamp in UTC.
replicaThe name of the process whose state the export holds: [server] replica, or the host name the process runs with, which Kubernetes sets to the pod’s name, or unnamed without either (Configuration). Each file names the replica it came from.
learned_creating_systemsEvery mapping the process learned, ordered by creating_system_id. It is empty when the process learned none.
learned_creating_systems[].creating_system_idThe creating_system_id of the versions, as their version uid spells it.
learned_creating_systems[].statuslearned: the process saw versions of this creating_system_id at one member only and routes follow-up reads there. withdrawn: an integrity incident withdrew the mapping, because the versions were seen at two members or the registry document names another member, and the process routes nothing by it (Integrity incidents).
learned_creating_systems[].nodeThe node_id of the member, from the registry document the process runs on. null for a withdrawn mapping.
learned_creating_systems[].endpointThe endpoint_id the versions were seen at. null for a withdrawn mapping.
not_exportedThe state the process holds that the export leaves out, as the table above lists it, so a reader of the file alone knows it is not there.

The node_id and endpoint_id values are those of your registry document, so keep the document of the same date with the export. A member’s own system_id and the document’s [[creating_system]] mappings are routes too; they are in the document, and the export does not repeat them.

A mapping learned from answers shows where versions are held, never which CDR controls them: the gateway routes reads by it and never a write (§10.3, §12a.1, N23). A replacement that imports the file should read it the same way.