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

Conformance matrix

Every conformance point of the Federation Tier with AQL specification (section 17), the test tracks of section 16.3 and every requirement, with the status FerroFED holds for each. The actor, requirement and track columns are derived from the vendored specification; the status, issue and reason columns are kept by FerroFED. A point is covered only when a test carries its marker and CI runs it.

Gateway points: 35 of 35 covered, 0 planned, 0 deferred.

The other 6 points belong to a member node or to the federation operator, and a gateway is never marked down for them (section 17).

Conformance points

PointActorRequirementsTracksStatusIssuesReason
CP-1GatewayN11covered#38, #287scored over two mock nodes: POST {base}/v1/query/aql and its ITS-REST GET form, and the GET forms of stored query execution with and without a version, send each node the same request and answer the same RESULT_SET; the query string is decoded by the generated openehr-its parameters, and one it refuses is a 400 that asks nobody
CP-2GatewayN2, N51, 2covered#35, #38-
CP-38GatewayN332, 10covered#44-
CP-3GatewayN32covered#42, #43-
CP-4GatewayN72covered#35, #38-
CP-5GatewayN4, N103, 4covered#46, #85scored over three mock nodes with a localizer stub and the test kit’s fixed cross-reference: a member the localizer does not name is not-localized and never asked, a localizer that is down or silent past its budget fails closed with its error on every member and in meta.federation, and ask-all on failure holds only where declared and is declared in OPTIONS
CP-6GatewayN113covered#70-
CP-7GatewayN52covered#35, #38-
CP-8GatewayN135covered#52, #55-
CP-9GatewayN155covered#56-
CP-10GatewayN145covered#54, #187-
CP-11GatewayN164covered#49, #85-
CP-12GatewayN6, N164covered#43, #50, #58the status codes are scored (section 11.2, section 11.3); the OperationOutcome clause has no FHIR-facing consumer to apply to on an ITS-REST gateway, whose answer is a RESULT_SET with its additions under meta.federation only (N17, section 9.1, section 11.4)
CP-13GatewayN19, N20, N216covered#36, #64, #67, #74-
CP-14GatewayN226covered#64, #1478scored over two mock nodes on the 1.0.1 order of section 12.5.1: a version read whose path ehr_id the explicit target or the index places at the holder of an imported copy is answered by the holder, with openEHR-federation-system-id naming it, and the creator is never sent that ehr_id; with no target, binding or index the read reaches the member whose system_id is the uid creating_system_id (step 4) and nothing is probed; a creating_system_id the registry does not route falls to the ask-all probe; no uid is rewritten
CP-15GatewayN236covered#65, #286, #289scored over two mock nodes: a versioned write naming its preceding version in If-Match, or in the path of a composition DELETE, reaches only the node its path ehr_id routes to, and only when the registry routes that version’s creating_system_id there as the member’s own system_id or a registered mapping; otherwise it is refused 409 and no node is sent it, a holder the map learned included; a versioned write naming no single version is refused 400; a new EHR with no target, or naming two endpoints, is refused 400 and never probed; a CONTRIBUTION is held to the same rule for every preceding_version_uid its body names, read with the ITS-REST NewContribution type and forwarded byte-identical, so one version another member controls refuses it 409 and no node is sent it, a CONTRIBUTION of creations alone routes by its path ehr_id, and a body that is no canonical-JSON CONTRIBUTION is refused 400; a PUT of an ehr_id a resolution binding or the ehr_id index places at another member than the targeted one is refused 409 ehr-id-held and no node is sent it, one at the member that holds it is forwarded for that node’s own 409, and an ehr_id nothing places is forwarded and then indexed at the targeted member
CP-16GatewayN247covered#82scored over mock nodes through the real configuration path: a federated query, a routed read and write, a definition request, the ask-all probe, a stored-query definition and the admission check’s EHR calls each reach their node with one openEHR-federation-client token, an ES384 JWS signed with the key the gateway publishes as federation.auth.jwks_uri, its aud the node’s endpoint_id, exp at most 60 s after iat, naming the caller the guard verified (sub, iss_upstream, verified_by, the organisation, the purpose of use and the scopes as granted) and never the caller’s own token; a caller claim carrying the patient identifier stops the request unsent, and a request with no verified caller reaches no node
CP-17GatewayN257covered#80, #81, #439the onward half is scored against the harness token endpoint and mock nodes: the gateway obtains a token with the client-credentials grant and an ES384 RFC 7523 client assertion the endpoint verifies against the JWK Set the gateway publishes and declares as federation.auth.jwks_uri, the node receives that token and never the caller token, a rotation publishes both keys for its overlap window, a token is cached until 30 s before it expires and dropped on a 401, and a token that cannot be obtained fails that node as node-error with nothing sent; the inbound half is scored over mock nodes: every caller presents an RFC 9068 token verified by key set or introspection, each failure (signature, issuer, audience, expiry, clock skew, algorithm, type, key, scope, purpose of use, an unreachable issuer) is refused before any node is asked, and the caller token never reaches a node; under token exchange the node receives a token exchanged for the verified caller, the gateway an RFC 7523 actor, the scope the caller scopes that cover the operation and the node the resource, an edge-asserted caller fails that node with nothing sent, and a DPoP-bound grant proves every request to its node and token endpoint and answers a demanded nonce once
CP-18NodeN267node-profile#83, #93, #549, #573a 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
CP-19NodeN27, N27a7node-profile#83, #93, #549, #573, #1389a 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; at FerroEHR the harness arranges a restriction of processing (GDPR Art. 18) through the node’s ADMIN API, outside ITS-REST, and the restriction check reads the restricted composition withheld on the read and the composition query, and the federated query through the gateway leaves it out (#1389); EHRbase records no consent decision, and ITS-REST defines no consent resource, so the point reads node-not-observable there; the gateway half is CP-36, and no specification governs the choice of products: our own design
CP-20OperatorN196operator#74verified at admission or in the registry, not on a request (section 16.2); the gateway assists: its registry load refuses a Query API endpoint whose connectionType is hl7-fhir-rest or no defined code
CP-21GatewayN289covered#69-
CP-22GatewayN299covered#69-
CP-23GatewayN309covered#73, #290, #80the body, the endpoint list without a patient identifier, the sub-path Allow, and each its_rest area declaration held to the behaviour of that area in every mode are scored, and so is the clause that the endpoint list is subject to the gateway normal authentication (section 7a.2, section 13): OPTIONS {base}/ answers 401 without a token
CP-24GatewayN319covered#61, #319, #288scored over mock nodes on the routed path and on the federated AQL answer: a request routed or dispatched to one endpoint, by a directive, a header or a patient resolving at one member alone, names it and its system_id whatever it answered; a fan-out lists the endpoints that contributed rows and their system_ids in registry order, comma-separated as the section 8.4 request header, and a failing answer or a node with no rows is not listed
CP-25GatewayN329covered#68, #311scored over two mock nodes in both modes: by default a read and a create answer 501 and no node is asked; with federation.demographic_endpoint set a request naming that endpoint, or naming none, reaches it alone, byte-identical and with the N31 provenance headers, while one naming another endpoint, several endpoints or * is refused 400, and no node is asked; an endpoint the registry does not hold refuses the configuration; OPTIONS its_rest.demographic declares each mode and the behaviour matches it
CP-26GatewayN3310covered#45, #90, #217-
CP-27NodeN3410node-profile#93, #549, #573a 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 a PIXm Manager stub; the verdicts are the report node class per product, never a gateway pass, and no specification governs the choice of products: our own design
CP-28GatewayN353covered#70, #71, #77the stored-query clause is scored on a query stored at the gateway and invoked by name, targeted by the header and by the directive with the same answer, and refused 400 when the two select different node sets (section 8.4, section 12.7)
CP-29GatewayN365, 6covered#56, #66, #1478scored over two mock nodes with node A behind the capturing proxy: a version node A created and node B holds as an import is one row under version-identity, read from node A with node B suppressed in meta.federation.dedup; a write against it through node B’s ehr_id, routed by the ehr_id index or the targeting header, is refused 409 naming the creating_system_id and node A’s endpoint whether node A refuses connections or answers, node B is sent nothing and node A is never tried, so the write is never redirected to the owner under the holder ehr_id; a write through node A’s own ehr_id reaches node A alone and its answer names node A and no copy
CP-30GatewayN374covered#37, #50, #343-
CP-31GatewayN38, N404covered#49, #51-
CP-32GatewayN39, N14, N95covered#52, #53, #54, #55, #157, #187-
CP-33GatewayN41, N426, 11covered#62, #63, #91, #289, #1478the routing clause is scored in the 1.0.1 order (explicit target, binding, index, the member a path OBJECT_VERSION_ID creating_system_id names, then a read-only ask-all, and a write no earlier step locates refused 400 and never probed), and so is the integrity clause over two mock nodes: an ehr_id a binding, the index or the probe finds at two members is a 409 listing them on a read as on a write, neither is sent the request, one integrity incident is logged per refusal, and the index-insert alarm of section 12b.2 fires once; a binding collision is scored on the routing order alone until client sessions exist (#80), and track 11 repeats the integrity clause over the two FerroEHR nodes of the harness (#91); two creates racing for one ehr_id at two members raise the index-insert alarm once
CP-33aOperatorN42a11operator#79, #91, #93, #549verified 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)
CP-34GatewayN439covered#75, #76, #298, #343scored over two mock nodes: every template and stored-query definition request reaches the one endpoint the targeting headers name, byte-identical, and is answered as that node answered, its 4xx included; without a target, with *, an unknown id or several ids it is refused 400 and no node is asked; a template list is one node’s answer, never a union; with the registry offered, stored queries stay at the gateway while templates route; without the registry both stored-query PUTs route, the versioned one included, and every forwarded body carries a Content-Type its operation declares (#298); the fan-out upload clauses are scored over three mock nodes with federation.fan_out_template_upload on (off by default, declared in OPTIONS): a template upload naming * or several endpoints reaches each member byte-identical and is answered per node in the meta.federation shape, 207 with complete false when one member rejects or times out, the accepting members keep the template and are never sent a rollback, 200 only when every named member accepted, 424 when none did, a failing member reported with its HTTP status and an excerpt of its message, no other node body copied; a plain upload still names its one node, and * stays a 400 on every other definition request and whenever the setting is off (#76)
CP-35GatewayN17, N181covered#33, #38, #72-
CP-36GatewayN82, 7covered#43, #83-
CP-37GatewayN123covered#72-
CP-39OperatorN257operator#84verified 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
CP-40GatewayN449covered#77, #78, #268, #342, #343the registry clauses are scored: invocation by name over more than one member with name and endpoints[], the refused second PUT, also between two gateways racing one new version over one shared PostgreSQL store with exactly one stored (e2e, #268), a read-only registry answering PUT 405, and a directed definition stored and executed federated; the definition fan-out clauses are scored over three mock nodes with federation.fan_out_stored_queries on (off by default, refused by config check without the registry, declared in OPTIONS only beside it; with it off, a registry PUT or version GET carrying a targeting header is a 400 storing and reading nothing): a PUT naming * is stored at the registry first and reaches each member as the registry’s canonical AQL, 207 with complete false when one member fails and 424 when all do, nothing rolled back and the registry’s version standing, a failing member reported with its HTTP status and an excerpt of its message, no other node body copied; a FROM ENDPOINT or ORGANISATION definition is a 400 before anything is stored and stays storable and executable federated; an invocation dispatches the registry’s AQL inline even where a member holds a differing copy; a GET of a version naming * reports per member a matching copy as active and a differing or missing one as node-error with a code (#78); every second PUT of a held version is 409 and reaches no node, the same query naming members included, and drift is repaired by the operator on the admin listener (present only with metrics.listen, loopback unless allow_remote), whose POST sends the held copy to the named members on the same statuses with meta.registry held and leaves the registry’s copy unchanged, reaching a member that missed the first distribution or was admitted after it (#342)

Test tracks

TrackTitleRequirementsPointsStatusIssuesReason
1TransparencyN1, N2, N17, N18CP-1, CP-2, CP-35covered#38, #72, #92scenario over the two FerroEHR nodes of the harness: a plain patient AQL by POST and by GET answers one RESULT_SET validated against the vendored schema, with the client columns only, every row an ordered array, the additions nested under meta.federation, and columns[] the same whichever node answers first
2subject → ehrId resolutionN3, N5, N7, N33CP-2, CP-38, CP-3, CP-4, CP-7, CP-36covered#42, #44, #45, #92scenario over the two FerroEHR nodes with FerroPIX as resolver: both carriers resolve to the same rows, each node is asked by its own ehr_id and its AQL carries no subject, a selected subject column is the re-injected input, and a member where the patient does not resolve is never asked
3Directed / endpoint pinN10, N11CP-5, CP-6, CP-28, CP-37covered#70, #71, #72, #92scenario over the two FerroEHR nodes: ENDPOINT and ORGANISATION directives select the node set and report the rest excluded, a named member where the patient does not resolve is not-resolved, the endpoint header selects what the directive selects and a conflict is a 400, ?endpoint= targets nothing, and the PIXm localizer names the members asked and fails closed when it cannot answer
4Partial resultsN6, N16CP-5, CP-11, CP-12, CP-30, CP-31covered#49, #50, #51, #92scenario over the two FerroEHR nodes with faults at the capturing proxy: an error is 424 node-error, an unreachable node 504 offline, a slow node 504 time-out at the per-node timeout with latency_ms reported, a client wait shortens and never extends the budget, partial answers 200 only where offered, and found-nowhere and consent-denied are 200
5Dedup + DISTINCTN13, N15CP-8, CP-9, CP-10, CP-29, CP-32covered#52, #53, #54, #55, #56, #92, #187scenario over the two FerroEHR nodes holding the same composition: duplicates pass through by default, DISTINCT, ORDER BY and LIMIT apply over the union with OFFSET never pushed down, and an undirected COUNT is recombined or refused 400 while a directed one is answered; the opt-in object_id dedup of an imported copy is scored over mock nodes
6Follow-up read/write routingN21, N22, N23, N36, N41CP-13, CP-14, CP-15, CP-20, CP-29, CP-33covered#64, #65, #66, #67, #92scenario over the two FerroEHR nodes: a follow-up read of a row reaches the CDR that holds it with its uid unchanged, an unseen ehr_id is probed read-only while a write to it is refused 400 and never probed, a new EHR is created at the one node named, and a versioned write reaches only its controlling node; CP-14 is deferred on the contradiction recorded on #212 (#354), CP-20 is an operator point, and CP-29 is scored over mock nodes
7Auth conveyance + consent-denyN24, N25, N26, N27, N27aCP-16, CP-17, CP-18, CP-19, CP-36, CP-39covered#80, #81, #82, #83, #92scenario over the two FerroEHR nodes: the caller authenticates, each node receives the token its token endpoint issued for a client assertion verified against the published JWK Set and a signed token naming the caller, on the query and the follow-up, and consent is exercised with no consent service, with the development pre-filter denying and admitting, and on a directed query, with the refusal answered at node B proxy; CP-18 and CP-19 are node points and CP-39 an operator point
8PMIR merge/split (ITI-93/94) (provisional)N3-deferred#48, #147provisional 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
9REST surface & self-descriptionN28, N43, N44CP-21, CP-22, CP-23, CP-24, CP-25, CP-34, CP-40covered#61, #68, #73, #75, #77, #92scenario over the two FerroEHR nodes: a plain client given only a prefixed base reads and writes, both addressing forms answer the same rows, OPTIONS lists the members, DEMOGRAPHIC answers 501 as declared, a template list routes to the one named node, and a stored query is held at the gateway, runs by name over both members, refuses a second PUT, and a definition fan-out reports a one-node failure as 207
10Identifier leakage (adversarial)N33, N34, N5CP-38, CP-26, CP-27covered#45, #61, #90the gateway half is scored: the four positions on the fan-out, a directed query and the single-node route, and the converse write, judged on node-side capture over two mock nodes and over the two FerroEHR nodes of the harness; CP-27, the N34 obligation of the node itself, is scored by the node profile (#93)
11Integrity: ehr_id collision & admission conditionsN41, N42, N42aCP-33, CP-33acovered#63, #79, #91the gateway half is scored over the two FerroEHR nodes of the harness: one ehr_id seeded at both for two patients is a 409 listing both claimants with one integrity incident, on a read and a scoped query the ask-all probe finds, and on a read and two writes the ehr_id index finds after its insert alarm, with no row served and no write applied; a versioned write no earlier step routes is a 400 and probes nobody; the admission half (CP-33a) is applied by the harness as the federation operator: a FerroEHR candidate sharing a member’s system_id is refused by the registry load as a duplicate, and failed by the admission check under a system_id of its own

Requirements

46 requirements: 46 reached by a conformance point, 0 by a test track only, 0 by neither.

RequirementPointsTracksReachability
N1CP-11direct
N2CP-21direct
N3CP-32, 8direct
N4CP-5-direct
N5CP-2, CP-72, 10direct
N6CP-124direct
N7CP-42direct
N8CP-36-direct
N9CP-32-direct
N10CP-53direct
N11CP-63direct
N12CP-37-direct
N13CP-85direct
N14CP-10, CP-32-direct
N15CP-95direct
N16CP-11, CP-124direct
N17CP-351direct
N18CP-351direct
N19CP-13, CP-20-direct
N20CP-13-direct
N21CP-136direct
N22CP-146direct
N23CP-156direct
N24CP-167direct
N25CP-17, CP-397direct
N26CP-187direct
N27CP-197direct
N27aCP-197direct
N28CP-219direct
N29CP-22-direct
N30CP-23-direct
N31CP-24-direct
N32CP-25-direct
N33CP-38, CP-262, 10direct
N34CP-2710direct
N35CP-28-direct
N36CP-296direct
N37CP-30-direct
N38CP-31-direct
N39CP-32-direct
N40CP-31-direct
N41CP-336, 11direct
N42CP-3311direct
N42aCP-33a11direct
N43CP-349direct
N44CP-409direct