Comparison with upstream EHRbase
FerroEHR is measured against upstream EHRbase (Java) — the project it succeeds — on the same instruments it applies to itself: the CNF 2.0 conformance runner executes the same committed catalogue against both servers’ official deployments, and the benchmark harness drives both with byte-identical clinical workloads on the same host. Both directions are always published; a result that favours upstream is reported exactly like one that favours us.
Each side runs with its own committed party set — an ixit describing the reachable instances (upstream’s Basic auth has no read-only principal, so its ixit declares none) and a statement (the ICS) declaring the capabilities and ambiguity-register options that party actually claims. A capability a party does not claim reads not claimed and never gates its verdicts; a test whose ground cannot exist on a party’s topology or technology profile reads not applicable with a machine citation, never fail. That is how the comparison stays fair without ever weakening a case.
Every number and every curve on this page is generated at build time from
the committed run artifacts (docs/conformance/*/results.json +
verdicts.json + stress.json) — nothing here is hand-typed, and the CI
stale-numbers gate rejects any attempt to hand-type it. To reproduce either
side yourself, see
Conformance and
Benchmarks.
- Conformance
- Systems under test
- Methodology
- Profile verdicts
- In-scope outcomes
- Capability-by-capability
- Failures — both directions
- Performance
- Method, in one paragraph
Conformance
Systems under test
| ferroehr | upstream (Java) | |
|---|---|---|
| Product | ferroehr 3.15.0 | ehrbase-java 2.34.0 |
| Run date | 2026-07-31 | 2026-07-31 |
| Party statement | tools/cnf-runner/party/ferroehr/ | tools/cnf-runner/party/ehrbase-java/ |
| Stack | root compose, built from the current sources | docker/sut-ehrbase-java.yml (official images) |
Methodology
Both systems execute the same committed CNF 2.0 catalogue (863 case-by-format
executions) through the same reference runner (tools/cnf-runner), each on
fresh volumes with its own committed party set: the ixit names the reachable
instances (upstream declares no readonly principal), and the statement (the
ICS) declares the claimed capabilities, spec versions, and ambiguity-register
options — ISO/IEC 9646-style test selection excuses undeclared option
branches, unclaimed capabilities, and release-dated behaviour outside the
declared versions as N/A with a citation, never as silent skips. Verdicts are
pure functions of (statement, results, catalogue, capability matrix).
The declared-version delta matters and is stated, not hidden: ferroehr
declares ITS-REST 1.1.0
while upstream EHRbase declares ITS-REST
1.0.3 —
the catalogue realizes 1.1.0, so every Release-1.1.0-dated behaviour (the
Demographic API, ITEM_TAGs, Simplified Formats on the wire, the admin EHR
delete, the weak-ETag/Location header forms, …) is cited N/A for the
1.0.3 declaration rather than driven against a release upstream never
claimed. The verdict-bearing comparison below is therefore each party’s
in-scope subset, never the raw record.
Profile verdicts
| Profile | ferroehr | upstream (Java) |
|---|---|---|
| CORE | pass | fail |
| STANDARD | pass | fail |
| OPTIONS | pass | not claimed |
| SEC-BASIC | pass | not claimed |
In-scope outcomes
Runs compared: ferroehr (run of 2026-07-31) vs upstream EHRbase 2.34.0 (run of 2026-07-31) — the SAME catalogue through the same runner, each with its own committed party statement. Per the presentation rule, the headline is each party’s VERDICT SCOPE (the cases its own declarations select), never the raw record: a raw count would book release-dated and unclaimed surfaces against a party that never claimed them.
| verdict scope (selected) | driven | in-scope passed | in-scope failed | in-scope inconclusive | |
|---|---|---|---|---|---|
| ferroehr | 863 | 826 | 826 | 0 | 0 |
| upstream (Java) | 499 | 459 | 136 | 132 | 191 |
An inconclusive row’s wire answered outside the operation’s bound outcome
map, or its required ground could not be established (e.g. a refused
provisioning exchange) — never counted as a failure of the behaviour under
test. Every not-run row in the full committed record
(docs/conformance/<sut>/results.json) carries a machine-readable
citation: an undeclared option branch, an unclaimed capability, a
release-dated behaviour outside the declared spec versions, or a ground the
party’s topology cannot establish.
Capability-by-capability
Evidence tokens from each party’s computed verdicts: passed (every gating case green), failed (at least one gating case red), inconclusive (a gating case neither passed nor failed cleanly), not_evidenced (claimed, but no gating case produced a verdict — there is no excused state: a required capability without passing evidence fails its tier, whichever party claims it), or not claimed (absent from that party’s ICS).
| Capability | ferroehr | upstream (Java) |
|---|---|---|
| ActivityReport | passed | not_evidenced |
| Adl14ArchetypeProvisioning | passed | not_evidenced |
| Adl14OptProvisioning | passed | failed |
| Adl2ArchetypeProvisioning | passed | not_evidenced |
| Adl2OptProvisioning | passed | not_evidenced |
| AdminApi | passed | not_evidenced |
| AnonymousEhrs | passed | not_evidenced |
| AqlAdvanced | passed | inconclusive |
| AqlBasic | passed | failed |
| AqlTerminology | passed | not_evidenced |
| ArchetypeValidation | passed | failed |
| AuditAccountability | passed | not_evidenced |
| AuthenticatedAccess | passed | passed |
| AuthorizationSeparation | passed | not_evidenced |
| BulkEhrLoad | passed | not_evidenced |
| ChangeSets | passed | failed |
| CompositionOps | passed | inconclusive |
| DefinitionApi | passed | failed |
| DemographicApi | passed | not_evidenced |
| DemographicArchetypeValidation | passed | not_evidenced |
| DemographicArchive | passed | not_evidenced |
| DirectoryOps | passed | failed |
| EhrApi | passed | failed |
| EhrArchive | passed | not_evidenced |
| EhrDemographicSeparation | passed | passed |
| EhrDumpLoad | passed | not_evidenced |
| EhrExtract | passed | not_evidenced |
| EhrOperations | passed | failed |
| EhrStatus | passed | failed |
| ItemTags | passed | not_evidenced |
| MessageApi | passed | not_evidenced |
| PartyOperations | passed | not_evidenced |
| PartyRelationshipOperations | passed | not_evidenced |
| PhysicalDeletion | passed | not_evidenced |
| QueryApi | passed | failed |
| QueryProvisioning | passed | failed |
| Signing | passed | not_evidenced |
| SimplifiedFormats | passed | not_evidenced |
| SmartAppLaunch | passed | not_evidenced |
| SystemApi | passed | not_evidenced |
| Tds | passed | not_evidenced |
| TemplateExamples | passed | not_evidenced |
| Versioning | passed | failed |
Failures — both directions
ferroehr failures (with the upstream outcome on the identical case)
| Case | Format | Failure | upstream outcome |
|---|---|---|---|
| — | — | none — zero failing cases | — |
Upstream failures by schedule chapter
| Chapter | failed cases |
|---|---|
| CONT | 67 |
| I_EHR_STATUS | 24 |
| I_EHR_DIRECTORY | 12 |
| I_DEFINITION_QUERY | 10 |
| I_DEFINITION_ADL2 | 7 |
| I_EHR_CONTRIBUTION | 7 |
| I_DEFINITION_ADL14 | 6 |
| I_EHR_SERVICE | 5 |
| I_QUERY_SERVICE | 3 |
| I_EHR_COMPOSITION | 1 |
| I_ITS_REST_REVISION_HISTORY | 1 |
| SIG | 1 |
Every upstream-failed case, with the ferroehr outcome on the identical case
| Case | Format | Upstream failure | ferroehr outcome |
|---|---|---|---|
| CONT-COMP-content_card_1plus-context_any | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_1plus-context_mand | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_3plus-context_any | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_3plus-context_mand | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_3to5-context_any | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_3to5-context_mand | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_any-context_any | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_any-context_mand | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_mand-context_any | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_mand-context_mand | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_opt-context_any | — | expected created, observed validation_failed | passed |
| CONT-COMP-content_card_opt-context_mand | — | expected created, observed validation_failed | passed |
| CONT-COMPOSITION-content_cardinality_count6 | — | expected created, observed validation_failed | passed |
| CONT-COMPOSITION-context_existence | — | expected created, observed validation_failed | passed |
| CONT-DV_CODED_TEXT-validate_open | — | expected created, observed validation_failed | passed |
| CONT-DV_DATE-validate_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_DATE-validate_range | — | expected created, observed validation_failed | passed |
| CONT-DV_DATE_TIME-validate_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_DATE_TIME-validate_range | — | expected created, observed validation_failed | passed |
| CONT-DV_DURATION-validate_fields | — | expected created, observed validation_failed | passed |
| CONT-DV_DURATION-validate_fields_range | — | expected created, observed validation_failed | passed |
| CONT-DV_DURATION-validate_range | — | expected created, observed validation_failed | passed |
| CONT-DV_IDENTIFIER-validate_all_list | — | expected created, observed validation_failed | passed |
| CONT-DV_IDENTIFIER-validate_all_pattern | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_DATE-validate_lower_upper_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_DATE-validate_lower_upper_range | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_DATE_TIME-validate_lower_upper_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_DATE_TIME-validate_lower_upper_range | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_DURATION-validate_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_DURATION-validate_range | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_ORDINAL-validate_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_PROPORTION-validate_ratio_range | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_SCALE-validate_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_TIME-validate_lower_upper_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_INTERVAL_DV_TIME-validate_lower_upper_range | — | expected created, observed validation_failed | passed |
| CONT-DV_MULTIMEDIA-validate_media_type | — | expected created, observed validation_failed | passed |
| CONT-DV_PARSABLE-validate_value_formalism | — | expected created, observed validation_failed | passed |
| CONT-DV_TEXT-validate_open | — | expected created, observed validation_failed | passed |
| CONT-DV_TIME-validate_constraint | — | expected created, observed validation_failed | passed |
| CONT-DV_TIME-validate_range | — | expected created, observed validation_failed | passed |
| CONT-EVENT-state_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-EVENT-state_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-EVENT-type_any | — | expected created, observed validation_failed | passed |
| CONT-EVENT-type_interval_event | — | expected created, observed validation_failed | passed |
| CONT-EVENT-type_point_event | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_1plus-summary_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_1plus-summary_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_3plus-summary_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_3plus-summary_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_3to5-summary_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_3to5-summary_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_any-summary_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_any-summary_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_mand-summary_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_mand-summary_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_opt-summary_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-HIST-events_card_opt-summary_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-HISTORY-events_cardinality_count6 | — | expected created, observed validation_failed | passed |
| CONT-ITEM_STR-type_any | — | expected created, observed validation_failed | passed |
| CONT-ITEM_STR-type_item_list | — | expected created, observed validation_failed | passed |
| CONT-ITEM_STR-type_item_single | — | expected created, observed validation_failed | passed |
| CONT-ITEM_STR-type_item_table | — | expected created, observed validation_failed | passed |
| CONT-ITEM_STR-type_item_tree | — | expected created, observed validation_failed | passed |
| CONT-OBS-state_ex_mand-protocol_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-OBS-state_ex_mand-protocol_ex_opt | — | expected created, observed validation_failed | passed |
| CONT-OBS-state_ex_opt-protocol_ex_mand | — | expected created, observed validation_failed | passed |
| CONT-OBS-state_ex_opt-protocol_ex_opt | — | expected created, observed validation_failed | passed |
| I_DEFINITION_ADL14.upload_opt-invalid_opt | — | expected validation_failed, observed not_acceptable | passed |
| I_DEFINITION_ADL14.upload_opt-valid_opt | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL14.upload_opt-valid_opt_twice_conflict | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL14.upload_opt-valid_opt_twice_no_conflict | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL14.validate_opt-invalid_opt | — | expected validation_failed, observed not_acceptable | passed |
| I_DEFINITION_ADL14.validate_opt-valid_opt | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL2.get_artefact-example_unknown | — | expected not_found, observed not_acceptable | passed |
| I_DEFINITION_ADL2.get_artefact-version_prefix | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL2.upload_artefact-duplicate_conflict | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL2.upload_artefact-invalid_artefacts | — | expected validation_failed, observed not_acceptable | passed |
| I_DEFINITION_ADL2.upload_artefact-valid_opt | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_ADL2.valid_artefact-invalid | — | expected validation_failed, observed not_acceptable | passed |
| I_DEFINITION_ADL2.valid_artefact-valid | — | expected created, observed not_acceptable | passed |
| I_DEFINITION_QUERY.list_queries-prefix_all_versions | — | [0]/name: path resolves to nothing | passed |
| I_DEFINITION_QUERY.list_queries-version_get_xml_not_acceptable | — | expected not_acceptable, observed ok | passed |
| I_DEFINITION_QUERY.list_queries-xml_not_acceptable | — | expected not_acceptable, observed ok | passed |
| I_DEFINITION_QUERY.store_query-default_slot_with_higher_version | — | header Location: value “http://localhost:8091/ehrbase/rest/openehr/v1/definition/query/org | passed |
| I_DEFINITION_QUERY.store_query-dotted_name | — | expected stored, observed bad_request | passed |
| I_DEFINITION_QUERY.store_query-unqualified_name | — | expected stored, observed bad_request | passed |
| I_DEFINITION_QUERY.store_query-update_in_place | — | header Location: value “http://localhost:8091/ehrbase/rest/openehr/v1/definition/query/org | passed |
| I_DEFINITION_QUERY.store_query-version_duplicate_case_variant_name | — | expected conflict, observed stored | passed |
| I_DEFINITION_QUERY.store_query-version_prefix_rejected | — | expected bad_request, observed stored | passed |
| I_DEFINITION_QUERY.store_query-version_prerelease_rejected | — | expected bad_request, observed stored | passed |
| I_EHR_COMPOSITION.get_versioned_composition-malformed_uid | — | expected bad_request, observed not_found | passed |
| I_EHR_CONTRIBUTION.commit_contribution-delete_directory | — | expected created, observed not_found | passed |
| I_EHR_CONTRIBUTION.commit_contribution-deleted_member_with_data | — | expected validation_failed, observed created | passed |
| I_EHR_CONTRIBUTION.commit_contribution-ehr_status_incomplete_lifecycle | — | expected validation_failed, observed created | passed |
| I_EHR_CONTRIBUTION.commit_contribution-ehr_status_invalid_change_type | — | expected conflict, observed validation_failed | passed |
| I_EHR_CONTRIBUTION.commit_contribution-ehr_status_invalid_change_type_deleted | — | expected conflict, observed not_found | passed |
| I_EHR_CONTRIBUTION.commit_contribution-fail_modify_non_existing_directory | — | expected validation_failed, observed precondition_failed | passed |
| I_EHR_CONTRIBUTION.commit_contribution-non_exiting_opt | — | expected template_not_found, observed validation_failed | passed |
| I_EHR_DIRECTORY.create_directory-ehr_not_modifiable | — | expected updated, observed precondition_missing | passed |
| I_EHR_DIRECTORY.delete_directory-ehr_with_directory | — | header Last-Modified: expected present, got none | passed |
| I_EHR_DIRECTORY.delete_directory-empty_ehr | — | expected not_found, observed precondition_failed | passed |
| I_EHR_DIRECTORY.delete_directory-etag_names_new_version | — | header Last-Modified: expected present, got none | passed |
| I_EHR_DIRECTORY.get_directory-deleted_head | — | header Last-Modified: expected present, got none | passed |
| I_EHR_DIRECTORY.get_directory-directory_with_structure | — | equivalent: retrieved content differs from committed (modulo the normative ignore-set); $/ | passed |
| I_EHR_DIRECTORY.get_directory_at_time-deleted_at_time | — | header Last-Modified: expected present, got none | passed |
| I_EHR_DIRECTORY.get_directory_at_version-deleted_version | — | header Last-Modified: expected present, got none | passed |
| I_EHR_DIRECTORY.update_directory-empty_ehr | — | expected not_found, observed precondition_failed | passed |
| I_EHR_DIRECTORY.update_directory-invalid_folder | — | expected validation_failed, observed precondition_missing | passed |
| I_EHR_DIRECTORY.update_directory-stale_if_match | — | header ETag: expected the latest version uid, got none | passed |
| I_EHR_DIRECTORY.update_directory-xml | canonical-xml | expected updated, observed precondition_missing | — |
| I_EHR_SERVICE.create_ehr-bulk_load_population | — | expected created, observed validation_failed | passed |
| I_EHR_SERVICE.create_ehr-committal_headers | — | commit_audit/description/value: path resolves to nothing | passed |
| I_EHR_SERVICE.create_ehr-invalid_status | — | expected validation_failed, observed created | passed |
| I_EHR_SERVICE.create_ehr-wrong_method | — | header Allow: expected a value matching “.*(GET.*POST|POST.GET).”, got none | passed |
| I_EHR_SERVICE.get_ehr-malformed_ehr_id | — | expected bad_request, observed not_found | passed |
| I_EHR_STATUS.clear_ehr_modifiable-bad_ehr | — | expected not_found, observed precondition_missing | passed |
| I_EHR_STATUS.clear_ehr_modifiable-existing_ehr | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.clear_ehr_modifiable-stale_if_match | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.clear_ehr_queryable-bad_ehr | — | expected not_found, observed precondition_missing | passed |
| I_EHR_STATUS.clear_ehr_queryable-existing_ehr | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.clear_ehr_queryable-stale_if_match | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.get_ehr_status-at_time_future | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.get_ehr_status-at_time_omitted | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.get_ehr_status_at_version-addressed_version | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.get_versioned_ehr_status-at_time_future | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.get_versioned_ehr_status-at_time_omitted | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.get_versioned_ehr_status-contained_uid_form | — | header Last-Modified: expected present, got none | passed |
| I_EHR_STATUS.get_versioned_ehr_status-container_shape | — | owner_id/type: “ehr” != expected “EHR” | passed |
| I_EHR_STATUS.get_versioned_ehr_status-xml | canonical-xml | header Last-Modified: expected present, got none | passed |
| I_EHR_STATUS.set_ehr_modifiable-bad_ehr | — | expected not_found, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_modifiable-existing_ehr | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_modifiable-missing_if_match | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_modifiable-stale_if_match | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_modifiable-xml_body | canonical-xml | expected updated, observed precondition_missing | — |
| I_EHR_STATUS.set_ehr_queryable-bad_ehr | — | expected not_found, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_queryable-existing_ehr | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_queryable-missing_if_match | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_queryable-stale_if_match | — | expected updated, observed precondition_missing | passed |
| I_EHR_STATUS.set_ehr_queryable-xml_body | canonical-xml | expected updated, observed precondition_missing | — |
| I_ITS_REST_REVISION_HISTORY.versioned_ehr_status_revision_history-two_versions | canonical-json | expected updated, observed precondition_missing | passed |
| I_QUERY_SERVICE.execute_ad_hoc_query-empty_db_bare_ehr | — | row count 100 != expected 1 | passed |
| I_QUERY_SERVICE.execute_ad_hoc_query-unknown_ehr_scope | — | expected not_found, observed ok | passed |
| I_QUERY_SERVICE.execute_stored_query-fetch_with_top | — | expected stored, observed bad_request | passed |
| SIG-VERSION-ehr_status_signature | — | signature: expected present, the ORIGINAL_VERSION envelope carries no signature | passed |
Both servers’ capability conformance, from each party’s committed verdicts (generated, diff-guarded — see Conformance for how to read the grid):
And the per-chapter outcomes side by side. Both charts render the same
chapter-and-band taxonomy, so they read band-for-band: a band upstream did
not exercise shows as an explicit no cases row in the same position.
Compare the printed counts, not the bar lengths — each chart scales its bars
to its own widest band, and the legend states that scale.
Reading the upstream results honestly: the failures concentrate where the catalogue pins strict specification behaviour — archetype-constraint validation depth (the content chapter), exact status codes and version headers, canonical-format details — that upstream implements differently or predates. An inconclusive row means upstream answered with a status the operation’s specification-cited outcome map does not contain, or refused the exchange that would have established the case’s required ground, so the runner refuses to guess a verdict. And where upstream simply does not implement a surface (the ITS-REST simplified-format media types, ADL 2 provisioning, demographics), or where the specification dates a behaviour to a REST-API release newer than the one upstream declares (upstream declares ITS-REST 1.0.3; the catalogue realizes 1.1.0), the result reads not claimed or N/A with a citation — upstream is never counted as failing a surface it never claimed, never against a release it never declared, and never on an ferroehr-only extension.
The principal upstream divergences, stated plainly
Each of these was reproduced live against the composed upstream stack during
triage of the committed record, and each is grounded at or below upstream’s
own declared ITS-REST 1.0.3 unless marked; the full wire evidence lives in
the committed docs/conformance/ehrbase-java/results.json.
- A quoted
If-Matchvalue is rejected (400 "UUID string too large") — including the server’s own echoedETag— while only the non-standard unquoted form is accepted. The quoted form dates to Release 1.0.2. - Semantic model violations answer
400instead of422(a Release 1.0.1 correction), and some model-invalid documents are accepted outright — anEHR_STATUSwithout its mandatory archetype details commits as201. - The
openehr-audit-detailscommittal header is ignored (both the current and the deprecated spelling), and the stored audit description is itself model-invalid (aDV_TEXTwith novalue). - Canonical XML is served with the root element in no namespace, against
the published XSD’s qualified target namespace; the stored-query list even
serves an XML
<List/>document that conforms to no published schema on a JSON-only operation. - Unqualified stored-query names are rejected although the specification
makes the namespace optional and lists
my_compositionsas a valid example; a staleIf-Matchon a directory delete answers404where the specification requires412; and405responses omit the requiredAllowheader. - The stored-query listing does not match by prefix. The specification’s
own worked example lists “all versions of all queries with names starting
with
org.openehr”; upstream returns the versions of an exactly-named query but answers200 []for any shorter prefix, so a client cannot discover what a namespace holds. - A malformed identifier in the path answers
404, not400. Given a path segment that is not a UUID at all, upstream detects the type violation and still reports a miss — literally"EHR not found, in fact, only UUID-type IDs are supported"— for the EHR, versioned-COMPOSITION and CONTRIBUTION reads alike. (It does answer400for a malformed version identifier, so the behaviour is not even internally consistent.) - Directory writes against an EHR that has no directory answer
412. Both the update and the delete reportPrecondition Failedwith the body “does not contain a directory” — a missing resource dressed as a failed precondition. HTTP requires the opposite order: a failure detectable before the precondition is evaluated takes precedence over evaluating it. - A contribution refusal surfaces as a raw
500. Committing a first version whose change type isdeletedreturns500 "An internal error has occurred", where the specification assigns that family a400(“the modification type does not match the operation”). The neighbouring row is worse than an error: a creation whose lifecycle state isdeletedis accepted (204) instead of refused. - (1.1.0-grounded) The template upload refuses
Accept: application/json(406), serving only XML — the released parameter enumeration lists JSON first. This single refusal is what makes most content-chapter rows inconclusive: the runner’s provisioning uploads ask for JSON, upstream refuses, and the case’s ground never exists.
Two red rows are not upstream’s fault, and are called out rather than
counted. Storing a query with no version in the URL has to land at some
version, and no released sentence says which. Our suite pins 1.0.0 because
a suite must pin something; upstream continues the existing series instead
(a stored 2.0.0 makes the next version-less store 3.0.0). Both are
defensible readings of a silence, so the two
store_query-{default_slot_with_higher_version,update_in_place} rows record
a difference of house convention, not a conformance defect — the open
question is with openEHR, not with either implementation.
Performance
Both systems run the same committed step-load stress instrument
(cnf-runner stress) on their own freshly seeded cnf.scale.10k
corpus: the geometric ladder climbs until the system leaves the envelope
(p99 over the budget or errors past tolerance), then bisects to the
maximum sustainable throughput. Every number derives from the two
committed stress.json reports; each load step embeds re-checkable
histograms and, where sampled, per-container resource telemetry.
| max sustainable throughput | worst p99 at the knee | DB peak CPU at the knee | SUT peak RSS at the knee | |
|---|---|---|---|---|
| ferroehr | 512 req/s | 134 ms | 101 % | 247 MB |
| upstream (Java) | 0 req/s | — ms | — % | — MB |
A stress report is exploration evidence: it earns no conformance class (classes are earned exclusively by the hour-long measured class runs) and carries no class vocabulary — the chart shows where each system breaks.
Method, in one paragraph
The conformance instrument derives every expected outcome from the openEHR
specifications — never from either server’s observed behaviour — and runs
against real composed deployments of both systems (scripts/conformance.sh,
CONF_SUT=ehrbase-java for the upstream side). The stress instrument drives
the same hospital-simulation workload (admissions, observations, medication
rounds, lab contributions, chart reviews, corrections, discharges) built
from official CKM templates with seeded determinism, so both servers receive
byte-identical requests; latencies are coordinated-omission-corrected from
each request’s planned arrival instant, and the ladder bisects to the last
rate held inside the envelope. The full method chapters:
Conformance · Benchmarks.