Conformance statement
FerroFED claims the Federation-Gateway profile of the Federation Tier with AQL specification, at the version and commit the claim table names. This page is that claim. It names the specification text the claim is made against, the node products the tests ran against, every conformance point of §17 and every test track of §16.3 with how each is scored, and every deferral with its reason.
The part between the statement markers is generated by
scripts/conformance/matrix.sh --statement-write from the conformance
matrix, docs/VERSIONS.md, the vendored specification and the
// conformance: markers on the tests. The docs build and the
conformance-matrix guard both fail when it disagrees with them, so the
statement cannot claim more than the matrix holds. The
conformance matrix shows the same points by requirement,
and the obligations checklist covers every normative
statement of the text.
What the claim covers
- Scored by a test means at least one test carries the point’s marker
and CI runs it. The conformance tests page lists
each test by point and track. A test in an
e2emodule runs in the CI e2e job, withFERROFED_E2E=1, against the node products below. - The Federation-Gateway profile only. FerroFED holds no clinical data and is not a CDR, so it makes no Federation-Node claim. A point whose actor is Node or Operator is scored against that actor, and a gateway is not marked down for it (§17, §16.2). FerroFED assists those actors with the node profile checks and the admission check, and the last section of the generated part records what each found.
- CP-20 and CP-39 are tested without a marker. A marker on a Node or
Operator point belongs to a harness check under
tools/, which writes the finding the report reads. The gateway’s part of each is tested in its own crate: the registry load refuses an endpoint with a FHIR connection type (CP-20,app/ferrofed-registry/tests/it/refusal.rs::a_fhir_rest_connection_type_is_refused), and the book answers each §13.4 deployment decision in a section of its own (CP-39,app/ferrofed-server/tests/it/deployment_decisions.rs). - A deferral is the owner’s decision, recorded on the issue its row names, with the reason the specification gives for it.
The claim
| Profile | Federation-Gateway (§16.2) |
| Specification | Federation Tier with AQL 0.9.0, release candidate of 2026-09-13 |
| Source | syntaric/openehr-federation-spec at 7162d0c760d23105d62a743bf0ad1073c45fdb85 |
| Rendered text | https://syntaric.github.io/openehr-federation-spec/federation-aql/0.9/ |
| Gateway points | 34 of 35 scored by a test, 1 deferred, 0 planned |
| Node points | 3, scored against the member CDR |
| Operator points | 3, scored against the federation operator |
| Test tracks | 10 of 11 scored by a test, 1 deferred, 0 planned |
| Requirements | 46: 46 reached by a point, 0 by a track only, 0 by neither |
| Marked tests | 961, carrying 1234 point and track markers |
The nodes
| Product | Pinned | Scores |
|---|---|---|
| FerroEHR 4.3.3 | ghcr.io/ferrohealth/ferroehr:4.3.3@sha256:1a5580b510dca1e49418e4c83431d19b92656d06ea0961d28d7df03d518b941f | the Connectathon tracks, the differential run and the node profile |
| EHRbase 2.36.0 | ehrbase/ehrbase:2.36.0@sha256:c8e642264b73637e0576ec01b5c73f5dc9be6f34eb3644f0ced890c5f916640a | the node profile |
| The reference implementation | syntaric/openehr-federation-ref at 92aff3cb1d8738ea0ce0e013b5a8fc2942438fd5 | the differential run, as evidence and never as an oracle |
The scored matrix
| Point | Actor | Requirements | Tracks | Status | Scored by |
|---|---|---|---|---|---|
| CP-1 | Gateway | N1 | 1 | covered | 9 tests |
| CP-2 | Gateway | N2, N5 | 1, 2 | covered | 4 tests |
| CP-38 | Gateway | N33 | 2, 10 | covered | 5 tests |
| CP-3 | Gateway | N3 | 2 | covered | 13 tests |
| CP-4 | Gateway | N7 | 2 | covered | 3 tests |
| CP-5 | Gateway | N4, N10 | 3, 4 | covered | 49 tests |
| CP-6 | Gateway | N11 | 3 | covered | 20 tests |
| CP-7 | Gateway | N5 | 2 | covered | 2 tests |
| CP-8 | Gateway | N13 | 5 | covered | 55 tests |
| CP-9 | Gateway | N15 | 5 | covered | 46 tests |
| CP-10 | Gateway | N14 | 5 | covered | 45 tests |
| CP-11 | Gateway | N16 | 4 | covered | 4 tests |
| CP-12 | Gateway | N6, N16 | 4 | covered | 15 tests |
| CP-13 | Gateway | N19, N20, N21 | 6 | covered | 19 tests |
| CP-14 | Gateway | N22 | 6 | deferred | deferred |
| CP-15 | Gateway | N23 | 6 | covered | 31 tests |
| CP-16 | Gateway | N24 | 7 | covered | 28 tests |
| CP-17 | Gateway | N25 | 7 | covered | 152 tests |
| CP-18 | Node | N26 | 7 | node-profile | the member CDR, by 4 harness checks |
| CP-19 | Node | N27, N27a | 7 | node-profile | the member CDR, by 2 harness checks |
| CP-20 | Operator | N19 | 6 | operator | the federation operator |
| CP-21 | Gateway | N28 | 9 | covered | 10 tests |
| CP-22 | Gateway | N29 | 9 | covered | 12 tests |
| CP-23 | Gateway | N30 | 9 | covered | 35 tests |
| CP-24 | Gateway | N31 | 9 | covered | 23 tests |
| CP-25 | Gateway | N32 | 9 | covered | 14 tests |
| CP-26 | Gateway | N33 | 10 | covered | 139 tests |
| CP-27 | Node | N34 | 10 | node-profile | the member CDR, by 6 harness checks |
| CP-28 | Gateway | N35 | 3 | covered | 18 tests |
| CP-29 | Gateway | N36 | 5, 6 | covered | 5 tests |
| CP-30 | Gateway | N37 | 4 | covered | 34 tests |
| CP-31 | Gateway | N38, N40 | 4 | covered | 19 tests |
| CP-32 | Gateway | N39, N14, N9 | 5 | covered | 145 tests |
| CP-33 | Gateway | N41, N42 | 6, 11 | covered | 41 tests |
| CP-33a | Operator | N42a | 11 | operator | the federation operator, assisted by 3 harness checks |
| CP-34 | Gateway | N43 | 9 | covered | 30 tests |
| CP-35 | Gateway | N17, N18 | 1 | covered | 13 tests |
| CP-36 | Gateway | N8 | 2, 7 | covered | 41 tests |
| CP-37 | Gateway | N12 | 3 | covered | 19 tests |
| CP-39 | Operator | N25 | 7 | operator | the federation operator |
| CP-40 | Gateway | N44 | 9 | covered | 49 tests |
| Track | Title | Points | Status | Scored by |
|---|---|---|---|---|
| 1 | Transparency | CP-1, CP-2, CP-35 | covered | 3 tests |
| 2 | subject → ehrId resolution | CP-2, CP-38, CP-3, CP-4, CP-7, CP-36 | covered | 7 tests |
| 3 | Directed / endpoint pin | CP-5, CP-6, CP-28, CP-37 | covered | 5 tests |
| 4 | Partial results | CP-5, CP-11, CP-12, CP-30, CP-31 | covered | 7 tests |
| 5 | Dedup + DISTINCT | CP-8, CP-9, CP-10, CP-29, CP-32 | covered | 4 tests |
| 6 | Follow-up read/write routing | CP-13, CP-14, CP-15, CP-20, CP-29, CP-33 | covered | 4 tests |
| 7 | Auth conveyance + consent-deny | CP-16, CP-17, CP-18, CP-19, CP-36, CP-39 | covered | 6 tests |
| 8 | PMIR merge/split (ITI-93/94) (provisional) | - | deferred | deferred |
| 9 | REST surface & self-description | CP-21, CP-22, CP-23, CP-24, CP-25, CP-34, CP-40 | covered | 7 tests |
| 10 | Identifier leakage (adversarial) | CP-38, CP-26, CP-27 | covered | 25 tests |
| 11 | Integrity: ehr_id collision & admission conditions | CP-33, CP-33a | covered | 4 tests |
Deferrals
CP-14
- Actor: Gateway
- Requirements: N22, in track 6
- Decision: section 12.3 and N22 route a version read on creating_system_id, while section 12a.1 route-ehr, section 12.5.1 and N41 route every request under a path ehr_id by that ehr_id; FerroFED follows N41 for every request under a path ehr_id by the owner decision on #64 (2026-10-03), the contradiction is reported on #212, and it is revisited at the 1.0 re-pin (#354)
- Recorded on: #64, #212, #354
Track 8
- Actor: Gateway: no conformance point scores the track (§16.3), and N3 falls in the Federation-Gateway profile (§16.2)
- Requirements: N3
- Decision: provisional in section 16.3, and section 18 lets its subject matter be deferred; the binding lifetime, the invalidation hook and the PMIR ITI-94 subscription that drives it from ITI-93 merges are built (#48, #147), and propagation to a later resolution is not claimed
- Recorded on: #48, #147
Points scored against another actor
- CP-18, Node: a Node obligation, scored against the harness CDR products and never the gateway (section 16.2); the gateway’s node profile checks assist it, which the harness runs against its products and conformance run –node-profile against each member of a deployment: they ask FerroEHR and EHRbase for an EHR they do not hold and for an unparsable query and read the ITS-REST error status, and read whether an EHR the node withholds from one principal is refused on GET /ehr/{ehr_id} and on both ehr_id-scoped query forms while it is served to another: FerroEHR started with Basic authentication and authz.rbac.ehr_access_default restricted withholds every EHR from a clinician, and EHRbase started with Basic authentication withholds the request path of one EHR from its user role through security.additionalAuthorizations, its narrowest access control; FerroEHR from 4.3.3 refuses that EHR on the read and on both query forms (FerroEHR #3562, #566), and EHRbase refuses the read and releases the EHR’s rows on both query forms; the verdicts are the report node class per product, never a gateway pass, and no specification governs the choice of products: our own design (#83, #93, #549, #573).
- CP-19, Node: a Node obligation, scored against the harness CDR products and never the gateway (section 16.2); the node profile consent check reads a consent refusal an operator arranges at the node, on the read and on both ehr_id-scoped query forms; the harness arranges none at FerroEHR or at EHRbase, which records no consent decision, and ITS-REST defines no consent resource, so the point reads node-not-observable for both; the gateway half is CP-36, and no specification governs the choice of products: our own design (#83, #93, #549, #573).
- CP-20, Operator: verified at admission or in the registry, not on a request (section 16.2); the gateway assists: its registry load refuses an endpoint whose connectionType is hl7-fhir-rest or no defined code (#74).
- CP-27, Node: a Node obligation, scored against the harness CDR products and never the gateway (section 16.2); the gateway’s node profile checks assist it, which the harness runs against its products and conformance run –node-profile against each member of a deployment, against FerroEHR and against EHRbase: whether the node serves an EHR, its EHR_STATUS and its compositions by the ehr_id alone, with no patient identifier in any request the capturing proxy journals, whether an EHR created with no EHR_STATUS is read and queried by its ehr_id, and the ehr_id exchange the admission check reports over the harness PIX Manager; the verdicts are the report node class per product, never a gateway pass, and no specification governs the choice of products: our own design (#93, #549, #573).
- CP-33a, Operator: verified at admission or in the registry, not on a request (section 16.2); the gateway assists: ferrofed admission check reports each identifier-integrity condition of section 12b.2 as pass, fail or cannot-check with its evidence, and the registry load refuses a second member with an existing system_id; the harness applies the check as the operator of track 11 to a FerroEHR candidate sharing a member’s system_id (#91); the node profile runs the same check against a FerroEHR member and an EHRbase member, each under a system_id of its own, and records each condition with its evidence (#93, #549) (#79, #91, #93, #549).
- CP-39, Operator: verified at admission or in the registry, not on a request (section 16.2); FerroFED’s own answers to the five section 13.4 obligations, each naming the configuration that changes it, and the operator’s template are the book page website/book/src/operate/deployment-decisions.md, held to one section per obligation by app/ferrofed-server/tests/it/deployment_decisions.rs (#84).
Not observable
Some obligations cannot be observed from outside the node that holds them.
- CP-18 and CP-19 in a deployment run.
ferrofed conformance run --node-profilerecords the CP-18 access check and the CP-19 consent check asnot-observable. The refusal under test is the node’s own access policy or consent decision, which an operator cannot arrange through ITS-REST, and ITS-REST defines no consent resource (Scoring a deployment). - CP-18 in the harness. The harness starts FerroEHR and EHRbase with an
access policy that withholds one EHR from one principal, and its checks
read the node’s answer on the read and on both
ehr_id-scoped query forms. - CP-19 in the harness. Neither product records a consent decision, so
the harness arranges no consent refusal and the point reads
node-not-observablefor both. The gateway’s own part of consent is CP-36 and track 7, both scored by tests (N27, N27a). - Track 10 in a deployment run. The run reports it
not-run, because only node-side wire capture shows what reached a node (§16.3). The harness scores it with a capturing proxy in front of each node.
Where the specification disagrees with itself
CP-14
scores
N22:
a follow-up read of a version is routed on its creating_system_id first.
The EHR-scoped rule of
§12a.1
routes every request under {base}/v1/ehr/{ehr_id}/… on the ehr_id
binding, “not on creating_system_id”, and
N41
orders the steps for every path ehr_id
(§12.5.1).
Every ITS-REST version read sits under a path ehr_id, so the two rules
disagree for every such read. FerroFED routes by N41, scored under CP-13 and
CP-33, and defers CP-14 until the text settles it. The other half of N22,
that a uid is never rewritten, is held by tests the obligations checklist
names.
Every defect, contradiction or silence found in a specification while scoring is reported on the standing upstream-reports issue, #212. That includes the findings of the differential run (#94), which sent the same Connectathon requests to FerroFED and to the reference implementation over the same FerroEHR nodes and judged each difference against the specification text.
Rerunning the suite
The record, offline: the matrix against the vendored specification, every marker, and the generated pages.
bash scripts/checks/conformance-matrix.sh
The tests, and then the per-track and per-point report of a run over the
whole workspace with the e2e harness, which needs Docker. The report lands in
target/conformance/report.md.
cargo nextest run --workspace --locked --all-features
scripts/conformance/report.sh --run
To score your own deployment, run ferrofed conformance run
(Scoring a deployment).
After a change to a status, a marker or a pin, render the pages again:
scripts/conformance/matrix.sh --render-write
scripts/conformance/matrix.sh --statement-write
When the specification is re-pinned
The pinned text is a release candidate. When the 1.0 release is pinned
(#17),
scripts/conformance/matrix.sh --derive refreshes the derived columns. A
point new to the text arrives planned, and the guard refuses it until the
re-pin scores it with a test or records the owner’s deferral. This statement
then names the new version and commit. CP-14 and the other choices the
reports on #212 bear on are revisited then
(#354).