17. Conformance points (consolidated)

A numbered, testable list feeding §16. Each point maps to requirements and a test track.

The Actor column names who is scored against the point. Most are the gateway, but not all: a Node point is an obligation of a member CDR that a gateway cannot discharge, and an Operator point is verified at admission or in the registry, not on any request (§16.2). A gateway is not marked down for a point whose actor is not the gateway.

Conformance points are appended, never renumbered. A CP number is a stable citation, and #cp-14 is expected to keep deep-linking. Where a point belongs topically next to an existing one, the suffix form (CP-33a) is used instead.

Every normative requirement should be named by at least one conformance point or by a test track. tools/traceability.sh computes that closure in CI: a requirement scored by neither must appear in tools/traceability-exceptions.txt with a reason, so "unscored on purpose" is a recorded decision and cannot be mistaken for an oversight.

CP Actor Statement Requirements Track

CP-1

Gateway

The gateway exposes a conformant openEHR Query API and a basic patient query needs no federation-specific syntax.

N1

1

CP-2

Gateway

The gateway accepts a subject predicate as façade input and never sends subject to a node.

N2, N5

1, 2

CP-38

Gateway

Both patient-identifier carriers resolve. The same patient query, expressed once via EHR_STATUS.subject.external_ref and once via an ENTRY-level subject PARTY_IDENTIFIED/DV_IDENTIFIER predicate, resolves at the same gateway and returns the same rows; neither form is rejected, and the accepted set is not a deployment declaration. A predicate over COMPOSITION.composer, EVENT_CONTEXT.health_care_facility, PARTICIPATION.performer or ATTESTATION.committer carrying a clinician or facility identifier is not rejected on path grounds. (Outbound hygiene for both is scored by CP-26.)

N33

2, 10

CP-3

Gateway

subject is resolved to a per-node ehr_id via the cross-reference service (proposed: PIXm $ihe-pix), outside AQL.

N3

2

CP-4

Gateway

Nodes are queried with standard AQL keyed on ehr_id only.

N7

2

CP-5

Gateway

Undirected queries derive their node set from localization, and ask-all is an explicit fallback where no localizer is configured. Localization supplies candidates, not consent. With a localizer configured but unreachable, the gateway behaves as it declares in OPTIONS: fail-closed by default, reporting every member not-localized with the error and dispatching to none, never silently widening to ask-all.

N4, N10

3, 4

CP-6

Gateway

Directed FROM ENDPOINT / ORGANISATION selects the node set independently of patient resolution; not-resolved members are reported, not errored.

N11

3

CP-7

Gateway

A subject column in the result is the re-injected input, never read from a CDR.

N5

2

CP-8

Gateway

DISTINCT and ORDER BY are honoured at the Tier.

N13

5

CP-9

Gateway

Default is pass-through (duplicates kept); opt-in object_id dedup keeps the originating copy for imported compositions.

N15

5

CP-10

Gateway

Undirected aggregates are blocked unless cross-node-correct; a directed single-node aggregate is allowed.

N14

5

CP-11

Gateway

Every in-scope node appears in meta.federation.endpoints[] with a status from the set of §11.1; unresponsive nodes contribute no rows. A node that answered with an HTTP error appears as node-error carrying that error, never as offline or active. Registry members localization did not name appear as not-localized, and members a directive did not name as excluded - reported, but not in scope.

N16

4

CP-12

Gateway

Incomplete coverage also surfaces as OperationOutcome warning/incomplete for FHIR consumers, and status codes follow §11.2. In particular a node timeout under the default strategy is a 504, not a 200, and is a 200 only where openEHR-federation-completeness: partial was requested and offered. "Found nowhere" → 200 + empty rows, never a 424 (§11.3).

N6, N16

4

CP-13

Gateway

The registry maps Organizations, Endpoints and every observed system_id/creating_system_id → CDR base URL.

N19, N20, N21

6

CP-14

Gateway

Follow-up reads route to the owning CDR by creating_system_id (then endpoint_id, then ask-all); uids are never rewritten.

N22

6

CP-15

Gateway

Versioned writes route to the single controlling CDR; unroutable writes are rejected; new objects target one chosen node.

N23

6

CP-16

Gateway

Client identity is conveyed to the source CDR on every routed follow-up.

N24

7

CP-17

Gateway

The client authenticates to the gateway; the gateway authenticates onward and propagates identity (OAuth 2.0 client-credentials with an RFC 7523 signed JWT client assertion, or an equivalent regional stack).

N25

7

CP-18

Node

Access decisions needing gateway-invisible information are delegated to the source CDR.

N26

7

CP-19

Node

Every node checks consent before releasing data, regardless of any upstream filtering; Step-1 consent pre-filtering is optional and never the sole gate. The gateway’s half - that it does not treat its own pre-filter as the gate - is scored by CP-36.

N27, N27a

7

CP-20

Operator

An openEHR Query API endpoint uses a defined connectionType (e.g. openehr-rest-query), not hl7-fhir-rest and not an informal string.

N19

6

CP-21

Gateway

The gateway serves ITS-REST at its declared base URL with no mandated prefix; a client configured from the registry works without /rest/openehr or any other hard-coded prefix.

N28

9

CP-22

Gateway

Both EHR addressing forms work: the canonical path {base}/v1/ehr/{ehr_id} and the AQL predicate WHERE e/ehr_id/value = … (and, where supported, FROM EHR e[ehr_id/value=…]).

N29

9

CP-23

Gateway

OPTIONS {base}/ returns the supported/unsupported ITS-REST areas, dedup mode, timeout policy, and the member endpoints behind the gateway - without requiring a patient identifier. It does not declare the accepted targeting mechanisms: both are mandatory (N35), so there is nothing to declare.

N30

9

CP-24

Gateway

A routed write returns openEHR-federation-endpoint (and SHOULD, openEHR-federation-system-id) with Location/ETag passed through unmodified.

N31

9

CP-25

Gateway

The DEMOGRAPHIC API is not federated: it is either 501 or single-node-routed, and the behaviour matches what OPTIONS declares.

N32

9

CP-26

Gateway

No identifier leaks into the composed query. With a directly identifying identifier supplied via external_ref, via PARTY_IDENTIFIED/DV_IDENTIFIER, in a projection and in the query string or headers, node-side capture shows the ehr_id and no identifier value in the dispatched AQL, path, query string or headers. A query that cannot be brought into that state is rejected 400, not forwarded. A write body, by contrast, is delivered unmodified.

N33

10

CP-27

Node

A node is invocable on ehr_id alone, and its environment answers the patient-identifier → ehr_id cross-reference; no requirement is placed on what the node stores internally.

N34

10

CP-28

Gateway

Endpoint targeting works both in AQL and via the openEHR-federation-endpoint request header, on the same stored query; conflicting node sets in one request are rejected 400. A node set offered as a query parameter (?endpoint=) is not a supported targeting mechanism.

N35

3

CP-29

Gateway

A write derived from a de-duplicated row reaches the originating CDR; a write whose only reachable holder is a copy is rejected 409 and the object is never forked, and suppressed endpoints stay visible in meta.federation.dedup.

N36

5, 6

CP-30

Gateway

All-or-nothing by default: with one node down and nothing requested, the query fails, with 504 for a timeout or unreachable node and 424 for a node that returned an error (504 where both occur), and the failing response still carries meta.federation.endpoints[] naming the failed node with its status (time-out, offline or node-error) and error, plus meta.federation.complete = false. With openEHR-federation-completeness: partial requested against a gateway that offers it, the same scenario returns 200 with the reachable nodes' rows and complete = false. Against a gateway that does not offer it, the request is rejected, not silently served all-or-nothing. The carve-outs hold: a query where every in-scope node is not-resolved returns 200 with empty rows, and one where a node is consent-denied returns 200 with the rest, both with complete = false and neither a 424. An undirected query whose localizer named a subset still returns meta.federation.complete = true when every node it named answered, because not-localized members do not clear the flag.

N37

4

CP-31

Gateway

Timeouts are bounded and visible: a slow node is abandoned at the declared per-node timeout and marked time-out, never reported as empty; the overall budget is honoured; latency_ms is reported per endpoint; and a client Prefer: wait= can shorten but not extend the budget.

N38, N40

4

CP-32

Gateway

Cross-node result shaping is correct: ORDER BY + LIMIT n returns the global top n deterministically; OFFSET > 0 is rejected or computed correctly (never silently pushed down); an undirected aggregate is rejected 400 with a reason and never answered with per-node aggregate rows. Per-node execution alone is asserted not to be accepted as the federated answer.

N39, N14, N9

5

CP-33

Gateway

ehr_id routing and integrity: a path ehr_id routes by explicit target / binding / index, a write is rejected 400 and never ask-all-probed, and an ehr_id claimed by two nodes yields 409 plus an integrity incident - never a guess.

N41, N42

6, 11

CP-33a

Operator

Admission conditions exist and are applied (§12b.2): a candidate node demonstrates UUID-v4 (or equivalent) ehr_id generation, no ehr_id reuse across restores/migrations, no adoption of foreign ehr_id s on import, a system_id unique in the federation, and a working patient-identifier → ehr_id exchange. Verified at admission, not per request.

N42a

11

CP-34

Gateway

Definition handling: {base}/v1/definition/… routes to one explicitly chosen node and no merged catalogue is presented. Where fan-out template upload is offered it is explicit, per-node reported, and a partial failure is reported as partial, never as success. A gateway offering the stored-query registry of §12.7 is scored on templates here and on stored queries by CP-40. The single-node rule remains the assertion for a gateway that declares no registry.

N43

9

CP-35

Gateway

The result envelope validates as an ITS-REST Release-1.1.0 RESULT_SET, with rows entries as ordered arrays positionally matching columns[], not objects, carrying q, columns, rows and meta, and with the federation’s meta additions unprefixed (no _complete, no _endpoints, no _federation) and nested under a single meta.federation object (no meta.complete, no meta.endpoints; the flat form of releases up to 0.9.0 is a failure). Rows carry no endpoint columns unless ENDPOINT attributes were selected (single-CDR compatibility). An ENDPOINT alias resolves a collision with an EHR-derived column; no column is shadowed. And columns[] is the gateway’s rendering of the client’s submitted AQL, identical for the same query regardless of which node answered or in what order. Mechanically checkable against federated-result-set.schema.json.

N17, N18

1

CP-36

Gateway

The gateway dispatches only to nodes where the patient resolved, minus any removed by an optional Step-1 consent pre-filter - and dispatching is never treated as evidence that consent permits release.

N8

2, 7

CP-37

Gateway

Selecting ENDPOINT or Organization attributes adds them to the rows; not selecting them leaves the row shape untouched.

N12

3

CP-39

Operator

The deployment’s authn/authz decisions are published. Documentation exists answering each obligation of §13.4: which identity is verified across the trust boundary and against which authentic source of organization identity; who authenticates the end user and where that trust stops; how purpose of use or legal basis is conveyed, and that a node is not left to infer it; what the access token is bound to, and whether transport identity is treated as organization identity; and which risks the deployment addresses technically and which by agreement. Verified as documentation at admission, not per request. The actor is the operator because there is no wire artefact to assert.

N25

7

CP-40

Gateway

Stored-query registry (where offered). A definition stored at the gateway with PUT {base}/v1/definition/query/{name}/{version} is invocable by name with POST {base}/v1/query/{name} and fans out, so the result carries rows from more than one member with a meta.federation.endpoints[] covering them, and the response carries the ITS-REST name member naming the gateway’s query. A second PUT to the same {name}/{version} is refused, not silently applied. Where definition fan-out to nodes is also offered, a run in which one node rejects the definition is reported as a partial success per node, never as overall success. A definition whose AQL carries FROM ENDPOINT targeting is refused for fan-out with a reason, while remaining storable and federated-executable. A gateway declaring no registry is scored against CP-34 instead, and is not marked down here.

N44

9