openehr 0.7.4

openEHR® Reference Model types, validation, paths, AQL parsing, and change-control security primitives. openEHR® is the registered trademark of the openEHR Foundation and is used with the permission of openEHR International. Use of the trademark does not constitute endorsement of this product by openEHR International or openEHR Foundation. This project is an independent work.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
# Conformance matrix

**Non-normative.** This records what is true today, not what is intended. It is
the file to distrust last: if it disagrees with the code, the code wins and this
file has a defect.

**Assessed:** 2026-08-02, against `rustc 1.97.1`, `openehr` 0.2.0. Rows touched
by **A-36** were re-derived 2026-08-20 against `openehr` 0.3.0 and `rustc 1.98.0`;
the rest carry the earlier date and have not been re-checked since, which is
stated rather than papered over (`W0.10`). The `D3.18b`/`D3.18c` rows were
derived 2026-08-21 with **A-35**.
**Method:** each requirement read against the code that implements it and the
test that exercises it; test names below are real and runnable with
`cargo test <name>`.

## Key

| | Meaning |
| --- | --- |
| **•** | Implemented **and** verified by a named test (`C0.7`) |
| **?** | Implemented, no test exercises it directly (`C0.8`) |
| **doc** | A documentation requirement; satisfied by prose, not by code |
| **type** | Enforced by the type system. A runtime test for it could not fail (`T13.2`), so writing one would be theatre. |
| **—** | Deliberately not implemented; see [`01-scope.md`](01-scope.md) |
| **spec** | Specified and **not implemented**: the requirement is in force, no code claims it, and the gap is tracked in [`audit.md`](audit.md). Distinct from **—**, which is a decision not to implement, and from **open**, which is a defect against something the crate purports to do |
| **withdrawn** | The requirement no longer stands. Its identifier is permanent (`C0.5`) and its text is kept where it was, marked, so a citation to it still resolves (`C0.18`, `C0.19`) |
| **open** | Not satisfied; tracked in [`audit.md`](audit.md) |

Doctests count as tests: they compile and run in CI (`T13.8`).

## Totals

Counted mechanically from the tables below, with every requirement id in
`spec/*.md` checked to appear exactly once — **344 ids, 344 covered, none
missing**.

A hand-written total in a file like this is a number nobody rechecks, and this
one proved the point: it said 291 of 291 while six requirements added after the
assessment had no row at all — `S1.18`, `S1.19`, `S1.20`, `L10.9`, `L10.10`,
`L10.11`. "Derived from the rows" was true once and then was not.

**CI now re-derives it** on every push, expanding the ranges in the `Id` column
and comparing against the requirements the specification defines. A new
requirement with no row fails the build; so does a row for a requirement that
does not exist, and so does an id covered twice.

**Re-derived 2026-08-26**, mechanically, from the rows below — and the previous
numbers were wrong, which is the second time this table has drifted from its own
tables. It said 291 total against 300 in the sentence above while the rows held
311, because CI re-derives *coverage* and nothing re-derived the *tally*.
**A-41** is the finding; the counts below were produced by expanding every `Id`
cell and counting statuses, and re-running that is how they should be checked
again.

| Status | 2026-08-26 | 2026-07-31 |
| --- | --- | --- |
| • verified | 258 | 175 |
| doc | 33 | 30 |
| **spec** — in force, unimplemented | 28 | — |
| type | 13 | — |
| — out of scope | 8 | 6 |
| ? implemented, untested | 3 | 54 |
| withdrawn | 1 | — |
| open | 0 | 4 |
| **total requirements** | **344** | 269 |

The **spec** rows are §15 plus `S1.21`, added on 2026-08-26 when `S1.4` was
withdrawn and the Archetype Model brought into scope. There were 32 of them that
day; `K15.1`–`K15.4` — the AOM2 object model — were implemented and tested the
same day, leaving **28**. They remain the largest block of unsatisfied
requirements this crate has ever carried, and they are counted here rather than
described elsewhere so that the size of the gap is a number a reader can see.

Three things moved these numbers, and they are not the same thing.

**Requirements gained tests.** `tests/invariants.rs` closed **A-06**: 32 tests,
five of them mutation-verified by disabling the check and watching the test
fail.

**Thirteen were reclassified `type`** — enforced by the compiler, where a
runtime test could not fail and writing one would be theatre (`T13.2`). Calling
them `type` rather than `•` keeps the verified count meaning what it says.

**Twenty-two requirements were added**, and this is the part worth reading. The
rendered openEHR pages omit every class-definition table — the sources
`include::` them from a UML export generated at build time — so anything
implemented from the prose alone is a guess. All four Reference Model packages
have now been read from the published PDFs, and **all four times the primary
source contradicted what had been implemented**: three rules backwards, sixteen
invariants missing, five reported under names openEHR does not use, and one
place where **openEHR contradicts itself** (`V8.7a`). A rising total here is the
specification catching up with the one it implements.

The three remaining `?` are named individually below rather than lumped: a
constant-time claim nobody has timed, an error path that cannot be provoked, and
mutation verification being five checks rather than a policy.

## §0 Conformance — `C0`

Process requirements; they govern this specification rather than the code.

| Id | Status | Evidence |
| --- | --- | --- |
| C0.1–C0.3 | doc | this directory's prose |
| C0.4–C0.6 | • | every requirement in this directory carries an id; no gaps or reuse |
| C0.7–C0.11 | doc | this file, and [`audit.md`](audit.md) |
| C0.12–C0.14 | • | `I2.15` and `R4.9` are the two declared departures |
| C0.15–C0.18 | doc | commit conventions |
| C0.19 | doc | `S1.4` is the first exclusion reversed under it — withdrawn in place, reason kept, §15 named |

## §1 Scope — `S1`

| Id | Status | Evidence |
| --- | --- | --- |
| S1.1–S1.3 | • | `canonical_json::a_composition_covering_every_modelled_class_round_trips` |
| S1.4 | withdrawn | reversed 2026-08-26; the Archetype Model is in scope under `S1.21` and §15. Text kept in place, marked (`C0.19`) |
| S1.5–S1.7 | — | not implemented, by decision |
| S1.8 | • | `time_specification::tests::unimplemented_accessors_refuse_rather_than_guess` |
| S1.9 | • | `quantity::tests::different_units_are_not_comparable_in_either_direction` |
| S1.10 | • | `text::tests::rubric_checking_reports_unchecked_separately_from_valid` |
| S1.11 | doc | `security` module header; `V8.18` |
| S1.12 | • | `guarantees::unimplemented_operations_refuse_and_cite_the_spec` |
| S1.13 | • | `canonical_json` round trip covers `DV_PARSABLE` and time specifications |
| S1.14 | doc | `security` module header |
| S1.15 | doc | `J9.15`; bounded by `canonical_json::reading_a_composition_stays_within_a_small_stack` |
| S1.21 | spec | §15 in full; nothing implemented — **A-40** |
| S1.16–S1.17 | • | `terminology::tests::the_codes_that_disagree_between_terminology_repositories_are_the_current_ones` |
| S1.18 | — | declared departure: ISO 3166 and ISO 639 are not carried, so `COMPOSITION.Territory_valid` and `Language_valid` are not checked. Its closing requirement — that a caller can do the check instead — **is** verified: `guarantees::a_caller_can_read_every_code_the_crate_declines_to_check` (`A-19`) |
| S1.19 | — | declared exclusion: no demographic repository, so the four `PARTY` graph invariants cannot be checked from a value in hand |
| S1.20 | — | declared departure: an `EHR_ACCESS` may record no policy, so `Scheme_valid` is not checked. `X11.24`'s fail-closed default is why that is safe |

## §2 Identifiers — `I2`

| Id | Status | Evidence |
| --- | --- | --- |
| I2.1 | • | `object_id::tests::version_id_round_trips_exactly`, `…::archetype_id_round_trips_and_splits` — the parts are asserted, not just the text |
| I2.2–I2.3 | • | `object_id::tests::version_id_round_trips_exactly`, `…::archetype_id_round_trips_and_splits` |
| I2.4–I2.5 | • | `uid::tests::oid_wins_over_internet_id_for_all_digit_text` |
| I2.6 | • | `uid::tests::rejects_near_misses` |
| I2.7 | • | `uid::tests::uuid_case_is_preserved_but_not_significant`, `…::uuid_hash_agrees_with_eq` |
| I2.8–I2.9 | • | doctests on `IsoOid`, `InternetId` |
| I2.10 | • | `object_id::tests::hier_object_id_rejects_a_double_colon_extension` |
| I2.11 | • | `object_id::tests::version_id_round_trips_exactly` |
| I2.12–I2.13 | • | doctest on `VersionTreeId` |
| I2.14–I2.15 | • | `object_id::tests::archetype_id_rejects_malformed_forms` |
| I2.16 | • | doctest on `TemplateId` |
| I2.17 | • | doctest on `TerminologyId` |
| I2.18 | • | doctest on `GenericId` |
| I2.19 | • | `invariants::a_uid_field_admits_only_the_two_uid_based_classes` |
| I2.20 | • | `object_id::tests::uid_based_id_infers_by_separator_count` |
| I2.21–I2.22 | • | `object_ref::tests::namespace_grammar_is_enforced` |
| I2.23 | • | `object_ref::tests::party_ref_refuses_a_non_demographic_class` |
| I2.24 | • | `invariants::a_locatable_ref_refuses_an_empty_path` |
| I2.25 | • | `object_ref::tests::locatable_ref_uri_does_not_double_the_slash` |

## §3 Data types — `D3`

| Id | Status | Evidence |
| --- | --- | --- |
| D3.1 | • | `data_types::tests::every_variant_round_trips_through_its_type_tag` |
| D3.2 | • | `invariants::the_deprecated_paragraph_type_still_round_trips` |
| D3.3 | • | `text::tests::errors_from_text_do_not_echo_the_text` |
| D3.4 | • | `text::tests::plain_no_newlines_is_checked_against_the_value` |
| D3.5 | • | `invariants::a_code_phrase_needs_a_terminology_and_a_code` |
| D3.6 | • | `invariants::a_term_mapping_match_is_one_of_four_characters` |
| D3.7–D3.8 | • | `text::tests::rubric_checking_reports_unchecked_separately_from_valid` |
| D3.9–D3.10 | • | `iso8601::tests::partial_dates_keep_their_precision_and_text`; `canonical_json::partial_dates_and_negative_durations_survive_verbatim` |
| D3.11 | • | `iso8601::tests::leap_day_validity_follows_the_gregorian_rule` |
| D3.12 | • | `invariants::a_leap_second_is_accepted_and_a_sixty_first_is_not` |
| D3.13 | • | `iso8601::tests::durations_round_trip_and_reject_disorder` |
| D3.13a | doc | recorded as a limitation, **A-02** |
| D3.14 | • | `guarantees::every_undecidable_comparison_answers_none`; `rm::data_types::tests::every_comparable_variant_of_a_data_value_compares`, which covers the **decidable** half — six arms of `semantic_cmp` were deletable in silence until **A-39** |
| D3.14a | • | `guarantees::a_reference_range_is_unmoved_by_how_an_instant_is_spelled`, which asserts an incomparable value is excluded rather than admitted (**A-39**) |
| D3.15 | • | `quantity::tests::different_units_are_not_comparable_in_either_direction` |
| D3.16 | • | `quantity::tests::ordinals_from_different_terminologies_do_not_compare` |
| D3.17 | • | `invariants::proportions_of_different_kinds_do_not_compare` |
| D3.18 | • | `iso8601::tests::offsets_normalise_before_comparison` |
| D3.18a | • | resolved: `PartialOrd`/`Ord` removed from the base ISO 8601 types, semantic order is the plain method `semantic_cmp` — `iso8601::tests::eq_is_lexical_and_semantic_cmp_is_not_the_same_question` (`A-32`, fixed) |
| D3.18b | • | the same resolution over all ten `DV_ORDERED` types and `DATA_VALUE` — `guarantees::equality_and_order_disagree_by_design_and_neither_is_partial_ord` (`A-35`, fixed) |
| D3.18c | • | `INTERVAL<T>` is bounded on `SemanticOrd`; `guarantees::a_reference_range_is_unmoved_by_how_an_instant_is_spelled` covers the `contains` rewrite |
| D3.18d | • | `base::real::tests::a_measured_precision_survives_a_round_trip`; `every_digit_survives_and_only_the_exponent_form_is_normalised`, which asserts the one departure |
| D3.18e | • | `base::real::tests::equality_and_order_answer_different_questions` — lexical equality, numeric order, and no `PartialOrd` |
| D3.18f | • | `base::real::tests::a_real_that_never_saw_a_constructor_is_still_readable` |
| D3.19 | • | `quantity::tests::non_finite_magnitudes_are_refused` |
| D3.19a | doc | declared narrowing; recorded here and in §3 |
| D3.20, D3.20a | • | `quantity::tests::precision_accepts_the_unlimited_sentinel`; `validation::tests::the_unlimited_precision_sentinel_validates` |
| D3.21 | • | `quantity::tests::accuracy_zero_may_not_be_a_percentage`, `…::a_percentage_accuracy_stays_within_a_hundred` |
| D3.21a | • | `quantity::tests::unknown_accuracy_is_a_value_and_not_an_absence` |
| D3.22 | • | verified verbatim against the 1.0.2 class table; `MagnitudeStatus::parse` is the closed set |
| D3.23 | • | `quantity::tests::proportion_kind_rules_are_enforced` |
| D3.23a | • | `quantity::tests::proportion_precision_asserts_integrality_rather_than_forbidding_it` |
| D3.24 | • | `quantity::tests::is_abnormal_distinguishes_unknown_from_normal` |
| D3.24a | • | `validation::tests::a_normal_status_outside_the_code_set_is_reported` |
| D3.24b | • | `validation::tests::a_normal_flag_beside_an_abnormal_number_is_reported` |
| D3.25 | • | `encapsulated::tests::debug_does_not_print_the_bytes`; `guarantees::display_never_reveals_an_identifier_or_a_media_blob` |
| D3.26 | • | `encapsulated::tests::integrity_outcomes_are_distinguished` |
| D3.27 | • | `encapsulated::tests::sha1_is_readable_but_not_writable` |
| D3.28 | • | `invariants::parsable_content_needs_a_formalism` |
| D3.29 | • | `encapsulated::tests::base64_round_trips_including_padding_lengths` |
| D3.30 | • | `uri::tests::schemes_are_checked_not_assumed`; `guarantees::a_uri_that_never_saw_a_constructor_is_reported_rather_than_panicking`; `guarantees::an_empty_uri_is_reported_under_openehrs_own_invariant_name` |
| D3.30a | • | `guarantees::a_uri_that_never_saw_a_constructor_is_reported_rather_than_panicking` |
| D3.31 | • | `uri::tests::ehr_uri_refuses_an_external_target`; `guarantees::an_ehr_uri_deserialized_with_a_foreign_scheme_is_reported`; `guarantees::a_link_target_is_validated_on_every_locatable_that_carries_it` |
| D3.32 | doc | `uri` module header |
| D3.33 | • | `basic::tests::an_empty_identifier_is_refused` |
| D3.34 | • | `basic::tests::display_never_reveals_the_identifier` |

## §4 Data structures — `R4`

| Id | Status | Evidence |
| --- | --- | --- |
| R4.1 | • | `canonical_json::every_polymorphic_attribute_carries_its_type_tag` |
| R4.2 | type | every structure embeds `LocatableAttrs` |
| R4.3–R4.4 | • | `data_structures::tests::an_element_is_valued_or_null_and_never_both`; `validation::tests::deserialization_bypasses_constructors_and_validation_catches_it` |
| R4.5–R4.7 | • | `data_structures::tests::the_four_null_flavours_stay_four`; `guarantees::the_four_null_flavours_remain_four` |
| R4.8 | • | `invariants::a_cluster_refuses_to_be_empty` |
| R4.9 | • | `invariants::a_ragged_table_is_reported_rather_than_assumed_regular`; doctest on `ItemTable` covers indexing |
| R4.10 | • | `data_structures::tests::element_traversal_reaches_every_leaf_of_every_structure` |
| R4.11 | • | `data_structures::tests::a_history_needs_events_or_a_summary` |
| R4.12 | • | `invariants::a_periodic_history_needs_a_positive_period` |
| R4.13 | • | `validation::tests::an_event_before_its_history_origin_is_reported` |
| R4.14 | doc | `data_structures` module header and `IntervalEvent::time` |
| R4.12a–R4.12c | • | `invariants::a_periodic_history_checks_that_its_events_fall_on_the_period`; `iso8601::tests::differencing_two_instants_crosses_months_and_offsets` |
| R4.15, R4.15a | • | `invariants::an_interval_event_checks_its_width_and_math_function` |
| R4.16 | • | `data_structures::tests::interval_start_time_is_derived_where_it_can_be_and_refused_where_it_cannot`, `…::interval_start_time_crosses_a_day_boundary_correctly` |
| R4.17 | type | `state` and `data` are separate attributes; exercised by the round-trip fixture |

## §5 Common — `M5`

| Id | Status | Evidence |
| --- | --- | --- |
| M5.1 | type | every clinical class embeds `LocatableAttrs` |
| M5.2 | • | `invariants::every_validation_check_fires_on_a_document_that_breaks_it` (`Archetype_node_id_valid`) |
| M5.3–M5.4 | • | `text::tests::a_coded_payload_without_a_type_does_not_lose_its_code` |
| M5.5 | • | `invariants::only_an_archetype_root_has_a_concept` |
| M5.6 | • | doctest on `Archetyped` |
| M5.7 | • | `validation::tests::an_archetype_id_on_the_wrong_class_is_reported` |
| M5.8 | • | `canonical_json::a_composition_covering_every_modelled_class_round_trips` carries `rm_version` unchanged |
| M5.9 | • | `uri::tests::ehr_uri_refuses_an_external_target` |
| M5.10 | • | `invariants::feeder_audit_original_content_must_be_encapsulated` |
| M5.11 | • | `invariants::feeder_audit_details_must_name_a_system` |
| M5.12–M5.13 | • | `common::tests::party_proxy_infers_its_class_without_a_type_tag` |
| M5.13a | • | `invariants::conditionally_coded_attributes_are_checked_only_when_openehr_coded` |
| M5.14 | • | doctest on `PartyIdentified` |
| M5.15 | • | `common::tests::a_self_related_party_counts_as_the_subject` |
| M5.16 | • | `invariants::an_anonymous_subject_round_trips_as_itself` |
| M5.17–M5.18 | type | `PARTICIPATION`'s attributes and the `DV_TEXT` typing of `function` are structural |
| M5.18a | • | `invariants::conditionally_coded_attributes_are_checked_only_when_openehr_coded` |

## §6 EHR — `E6`

| Id | Status | Evidence |
| --- | --- | --- |
| E6.1 | type | `Ehr` holds `ObjectRef`s throughout |
| E6.2 | • | `invariants::the_directory_is_the_first_folder_or_there_is_neither` |
| E6.3–E6.4 | • | `ehr::tests::deactivating_a_record_does_not_make_it_unreadable` |
| E6.3a | • | `invariants::a_composition_and_an_ehr_status_must_be_archetype_roots` |
| E6.5 | type | typed `PartySelf` |
| E6.6 | • | `invariants::coded_attributes_are_checked_against_their_openehr_group` |
| E6.6a | • | `invariants::a_composition_and_an_ehr_status_must_be_archetype_roots` |
| E6.6b | • | `invariants::a_persistent_composition_may_not_carry_an_event_context` — constructor and validation |
| E6.7 | type | required by the constructor's signature |
| E6.7a | doc | declared not-enforced; out of scope by `S1.10` |
| E6.8 | • | `canonical_json::a_composition_covering_every_modelled_class_round_trips` |
| E6.9 | • | `ehr::tests::entries_are_found_through_nested_sections` |
| E6.10 | doc | `Section` documentation |
| E6.11 | • | `invariants::coded_attributes_are_checked_against_their_openehr_group` |
| E6.12 | • | `ehr::tests::an_event_context_cannot_end_before_it_starts` |
| E6.12a | • | `invariants::an_event_context_location_is_absent_or_non_empty` |
| E6.13 | • | `canonical_json::every_polymorphic_attribute_carries_its_type_tag` |
| E6.14 | • | `ehr::tests::an_entry_about_someone_else_says_so` |
| E6.15 | • | `invariants::administrative_entries_are_distinguishable_from_clinical_ones` |
| E6.16–E6.17 | type | enforced by the types |
| E6.18 | doc | `Instruction::new` documentation |
| E6.19 | • | `ehr::tests::an_instruction_needs_at_least_one_activity` |
| E6.20 | doc | `Activity::new` documentation |
| E6.21 | • | `invariants::coded_attributes_are_checked_against_their_openehr_group` |
| E6.22 | • | `ehr::tests::terminal_ism_states_are_recognised` |
| E6.23 | • | `invariants::an_action_must_say_which_activity_it_fulfilled` |
| E6.24 | type | `Folder` holds `ObjectRef`s |

## §7 Demographics — `G7`

| Id | Status | Evidence |
| --- | --- | --- |
| G7.1–G7.2 | doc | `demographic` module header |
| G7.3 | • | `demographic::tests::a_party_needs_at_least_one_identity` |
| G7.4 | • | `demographic::tests::a_party_without_a_uid_is_refused` |
| G7.5 | • | `demographic::tests::a_role_is_a_party_but_not_an_actor` |
| G7.6–G7.7 | doc | type documentation |
| G7.8 | doc | `PartyIdentity` documentation |
| G7.9 | • | `invariants::a_contact_needs_an_address_and_may_carry_a_validity_period` |
| G7.10 | • | `demographic::tests::an_unrecorded_validity_period_is_not_a_yes` |
| G7.11 | • | `invariants::a_contact_needs_an_address_and_may_carry_a_validity_period` |
| G7.12 | type | `PARTY_RELATIONSHIP` references both parties by `PartyRef` |

## §8 Change control — `V8`

| Id | Status | Evidence |
| --- | --- | --- |
| V8.1 | • | `common::tests::a_version_of_another_object_is_refused` |
| V8.2 | • | `common::tests::a_duplicate_version_id_is_refused` |
| V8.3 | • | doctest on `VersionedObject` |
| V8.4–V8.5 | • | `common::tests::concurrent_writes_are_refused_rather_than_silently_ordered` |
| V8.6 | • | `common::tests::version_at_time_skips_incomparable_commit_times` |
| V8.7, V8.7a, V8.7b | • | `invariants::an_empty_history_has_no_revision_history_at_all` — order, most-recent, and commit time |
| V8.8–V8.9 | • | `common::tests::a_deleted_version_may_have_no_data_and_others_may_not` |
| V8.10 | • | `canonical_json::a_versioned_composition_round_trips_with_its_audit_trail` |
| V8.11–V8.12 | • | `invariants::an_imported_version_keeps_the_identity_it_arrived_with` |
| V8.13–V8.14 | • | doctest on `AuditDetails` |
| V8.15–V8.16 | • | `common::tests::contribution_change_types_are_restricted` |
| V8.17 | • | `invariants::an_attestation_carries_what_was_signed_and_whether_it_is_outstanding` |
| V8.17a | • | `invariants::conditionally_coded_attributes_are_checked_only_when_openehr_coded` |
| V8.18 | doc | `Attestation` documentation |
| V8.19 | • | `invariants::an_attestation_carries_what_was_signed_and_whether_it_is_outstanding` |

## §9 Serialization — `J9`

| Id | Status | Evidence |
| --- | --- | --- |
| J9.1 | • | `canonical_json::a_composition_covering_every_modelled_class_round_trips` |
| J9.2 | • | `canonical_json::round_tripping_twice_is_byte_identical` |
| J9.3 | • | `canonical_json::partial_dates_and_negative_durations_survive_verbatim` |
| J9.4 | • | `canonical_json::every_polymorphic_attribute_carries_its_type_tag` |
| J9.5 | • | `data_types::tests::a_value_without_a_type_tag_is_refused` |
| J9.6 | • | `text::tests::a_coded_payload_without_a_type_does_not_lose_its_code`; `common::tests::party_proxy_infers_its_class_without_a_type_tag` |
| J9.7 | • | `invariants::a_type_tag_naming_the_wrong_class_is_refused` |
| J9.8–J9.10 | • | `canonical_json::a_payload_written_by_another_implementation_reads` |
| J9.11 | • | `invariants::absent_and_empty_attributes_are_omitted_not_nulled` |
| J9.12 | • | `canonical::tests::nested_objects_are_sorted_at_every_level` |
| J9.13 | • | `canonical::tests::measured_precision_is_not_normalised_away` |
| J9.14 | • | `canonical::tests::keys_sort_by_scalar_value_not_by_locale` |
| J9.15 | • | `canonical_json::reading_a_composition_stays_within_a_small_stack`; **A-03** |

## §10 Validation — `L10`

| Id | Status | Evidence |
| --- | --- | --- |
| L10.1, L10.1a, L10.1b | • | `validation::tests::deserialization_bypasses_constructors_and_validation_catches_it` |
| L10.2 | doc | `validation` module header |
| L10.3 | • | `validation::tests::every_violation_is_reported_not_just_the_first` |
| L10.4 | • | `validation::tests::a_coded_text_that_contradicts_its_own_code_is_reported` |
| L10.5 | • | `guarantees::a_validation_report_names_paths_and_never_values` |
| L10.5a | • | `invariants::every_validation_check_fires_on_a_document_that_breaks_it` attributes an empty name to `DV_TEXT.Valid_value` |
| L10.6 | • | `invariants::every_validation_check_fires_on_a_document_that_breaks_it` drives twelve of them from JSON; the `DV_ORDERED` pair have their own tests |
| L10.7 | • | `invariants::violations_are_reported_in_document_order_and_that_order_is_stable` |
| L10.8 | • | `text::tests::rubric_checking_reports_unchecked_separately_from_valid` |
| L10.9–L10.10 | • | the crate-added register in [`10-validation.md`](10-validation.md); `openehr-assets` fails the build when it and the generated report disagree |
| L10.11 | • | the unenforced register, same file, same check — in both directions (`D-09` is the same defect one tree over) |

## §11 Security — `X11`

| Id | Status | Evidence |
| --- | --- | --- |
| X11.1 | doc | `security` module header |
| X11.2 | type | `AuditDetails` is required by `OriginalVersion::new`'s signature |
| X11.3 | • | `access::tests::an_unimplemented_scheme_round_trips_unchanged` |
| X11.3a | • | `access::tests::an_unimplemented_scheme_denies_and_names_itself` |
| X11.4 | • | `access::tests::nothing_recorded_denies_rather_than_permits` |
| X11.5 | • | `access::tests::an_unimplemented_scheme_denies_and_names_itself` |
| X11.5a | • | `access::tests::operations_are_separately_permitted` |
| X11.6 | • | `guarantees::display_never_reveals_an_identifier_or_a_media_blob` |
| X11.7, X11.7a | • | `guarantees::no_construction_error_echoes_a_submitted_value` |
| X11.8 | • | `redact::tests::sensitive_hides_from_display_and_debug_but_not_from_serde` |
| X11.9 | • | `audit_chain::tests::removing_an_entry_from_the_middle_breaks_the_link` |
| X11.10 | doc | `audit_chain` module header |
| X11.11 | • | `audit_chain::tests::a_clean_chain_verifies_and_an_edited_one_does_not` |
| X11.12 | ? | `Mac` implements neither `PartialEq` nor `Eq`, so `==` beside the one comparison does not compile; `guarantees::a_forged_tag_is_refused` pins the behaviour. Timing is still not measured, and the absence of the derive is not itself tested — see `Mac`'s documentation for why a `compile_fail` for it passed for the wrong reason |
| X11.13 | • | `audit_chain::tests::an_unheld_key_is_reported_as_such_and_not_as_forgery`, `…::a_forged_tag_under_a_held_key_is_a_finding`, `…::an_unkeyed_chain_does_not_claim_full_verification` |
| X11.14 | • | `audit_chain::tests::key_rotation_is_additive` |
| X11.15 | • | `invariants::a_chain_begins_where_it_begins_and_says_when_it_began_late` |
| X11.16 | • | `audit_chain::tests::short_keys_and_empty_ids_are_refused` |
| X11.17 | • | `audit_chain::tests::key_debug_does_not_print_the_material` |
| X11.18 | • | `encapsulated::tests::sha1_is_readable_but_not_writable` |
| X11.19 | • | `guarantees::a_chain_checkpoint_carries_no_patient_data` |
| X11.20 | • | `redact::tests::a_masked_element_says_the_value_exists` |
| X11.21 | • | `redact::tests::a_redacted_composition_is_still_valid` |
| X11.22 | • | `guarantees::redaction_masks_and_reports_a_count_not_a_category` |
| X11.23 | • | `redact::tests::a_reason_appears_and_does_not_disclose_the_category` |
| X11.24 | ? | `redact` returns `Result` and yields nothing on error, and the error path cannot be provoked. The premise is now tested — `guarantees::no_document_this_crate_can_build_carries_a_non_finite_float` — because `serde_json` writes `null` for a non-finite float rather than failing, so the constructors are the only barrier (**A-10**) |
| X11.25 | • | `guarantees::redaction_masks_and_reports_a_count_not_a_category` |

## §12 Paths and query — `Q12`

| Id | Status | Evidence |
| --- | --- | --- |
| Q12.1 | • | `path::tests::a_full_path_reaches_a_magnitude`, `…::name_predicates_select_the_right_repeat` |
| Q12.2 | • | `path::tests::malformed_paths_report_where_they_broke` |
| Q12.3–Q12.4 | • | `path::tests::index_predicates_are_zero_based_and_quoted_digits_are_names` |
| Q12.5 | • | `guarantees::an_ambiguous_path_refuses_instead_of_choosing` |
| Q12.6 | • | `path::tests::a_wrong_attribute_is_no_match_not_an_error` |
| Q12.7 | • | `path::tests::a_full_path_reaches_a_magnitude` |
| Q12.7a, Q12.7b | • | `path::tests::reference_ranges_are_navigable`, `…::an_interval_valued_element_is_navigable` |
| Q12.8 | • | `path::tests::predicates_round_trip_through_display_in_long_form` |
| Q12.9 | • | `aql::tests::the_canonical_blood_pressure_query_parses`, `…::aggregates_and_distinct_parse`, `…::not_contains_parses_and_keeps_its_negation`, `…::like_and_offset_parse` |
| Q12.9a | • | `guarantees::aql_refuses_what_it_does_not_model_and_says_where_that_is_recorded` |
| Q12.9b | • | signed numeric literals parse, resolved at operand position — `aql::a_sign_is_a_number_where_a_value_belongs_and_nowhere_else` (`A-27`, fixed) |
| Q12.9d | • | `LIMIT -5` and `OFFSET -1` refused with a message naming the reason — `aql::a_negative_limit_or_offset_is_refused_rather_than_clamped` |
| Q12.9e | • | a real renders with a decimal point so it lexes back a real — `guarantees::aql_rendering_round_trips_through_the_parser`, which compares trees |
| Q12.9c | • | declared limitation: no node-id predicate shorthand — `aql::only_a_dashed_and_dotted_word_standing_alone_is_an_archetype_id` (`A-30`) |
| Q12.10 | — | no execution API exists |
| Q12.11 | • | `aql::tests::keywords_are_case_insensitive` |
| Q12.12 | • | `aql::tests::malformed_queries_report_an_offset` |
| Q12.13 | • | `aql::tests::parameters_are_collected_from_every_clause_and_deduplicated` |
| Q12.14 | • | `guarantees::aql_catches_a_path_rooted_at_an_unbound_alias` |
| Q12.15 | • | `aql::tests::a_parsed_query_reparses_from_its_own_rendering`; `guarantees::aql_rendering_round_trips_through_the_parser`, which compares the **tree** and not only the text; cosmetic spacing difference noted in **A-05** |
| Q12.15a | • | `guarantees::aql_rendering_round_trips_through_the_parser` over the `CONTAINS`/`OR` shapes that broke it (**A-37**) |
| Q12.15b | • | `guarantees::an_aql_string_literal_is_not_mangled_by_the_lexer` (**A-37**) |

## §13 Conformance testing — `T13`

| Id | Status | Evidence |
| --- | --- | --- |
| T13.1 | • | 222 of 276 requirements cite a test; 3 remain `?` and are named above |
| T13.2 | ? | `cargo-mutants` over every module of meaningful size in `openehr` (all now 0 or equivalent survivors — see the `lib:A-09` table in `spec/audit.md` for the full per-module count) plus `openehr-sqlite/{store,dialect}.rs`, `openehr-store/{integrity,record,dialect}.rs`, `openehr-loco`'s auth/controllers/access/app/tasks/views, and the five schema-level engine crates' `Dialect` impls. `openehr-store/conformance.rs` — the logic those numbers actually exercise — is measured from `openehr-sqlite`, not from `openehr-store` itself, because nothing in `openehr-store`'s own test target calls it. Not in CI as a standing gate (the `mutants` job covers only a PR's diff); `openehr-postgresql`/`mysql`/`mariadb`/`mssql`/`oracle`'s `store.rs`-equivalent logic doesn't exist (Schema level, no `Store` impl) and `App::before_run` has one structural residual — see `spec/audit.md` — and the six `-fuzz` crates are untouched — **A-09** |
| T13.3 | • | every test in `tests/guarantees.rs` states its failure mode |
| T13.4 | • | `canonical_json` |
| T13.5–T13.6 | • | `tests/guarantees.rs` |
| T13.7 | • | `canonical_json::a_payload_written_by_another_implementation_reads` |
| T13.8 | • | 79 doctests run in `cargo test` |
| T13.9 | — | no test in this crate self-skips |
| T13.10 | • | this file |
| T13.11 | • | `reading_a_composition_stays_within_a_small_stack` names its toolchain, method, and figures |
| T13.12–T13.13 | • | `cargo clippy --all-targets` is clean with the lint table in `Cargo.toml`, and `#![forbid(unsafe_code)]` at the crate root states it in the source as well — belt and braces, because a manifest edit removes the one and not the other |
| T13.14 | • | every `#[allow]` in the crate carries a reason |

## §15 Archetypes and templates — `K15`

The section was added on 2026-08-26 when `S1.4` was withdrawn. **Four rows are
satisfied and twenty-eight are not**: `openehr::am` is the AOM2 object model,
and no code in this crate parses ADL, flattens an archetype, expands a template,
reads an operational template, retrieves an artefact, or validates data against
one. **A-40** tracks the rest.

This table exists so that the gap is counted rather than described. A row moves
off **spec** when the code implements it *and* a named test exercises it
(`C0.7`) — not when a parser lands, and not when a README says so (`K15.30`,
`K15.31`).

| Id | Status | Evidence |
| --- | --- | --- |
| K15.1 | • | `am::archetype::tests::a_definition_constraining_the_wrong_rm_class_is_refused`, `…a_node_the_terminology_does_not_define_is_refused`, `…a_code_specialised_deeper_than_its_archetype_is_refused`, `…a_terminology_constraint_naming_no_value_set_is_refused`; `am::constraint::tests::two_constraints_on_one_attribute_are_refused` |
| K15.2 | • | `archetype_model::the_targeted_archetype_model_release_is_named`; `am::AM_RELEASE` is 2.3.0 and an artefact's own declared versions round-trip unenforced |
| K15.3 | • | `archetype_model::an_archetype_round_trips_through_json_unchanged`, `…a_constraint_this_crate_cannot_model_survives_rather_than_disappearing`. **Scope:** the only serialisation this crate accepts today is its own JSON. ADL and the AM ITS forms are `K15.5`, `K15.8`, and `K15.16`, all below |
| K15.4 | • | `archetype_model::an_archetype_is_constructible_without_a_parser` |
| K15.5–K15.7 | spec | ADL 2 parsing, and the refusal discipline that replaces recovery |
| K15.8–K15.10 | spec | ADL 1.4 ingestion, provenance, and the assertion subset |
| K15.11–K15.13 | spec | specialisation, flattening, and the narrowing check |
| K15.14–K15.17 | spec | template expansion and operational templates, both directions |
| K15.18–K15.23 | spec | validation against an operational template, and its separateness from `L10.x` |
| K15.24–K15.27 | spec | the repository abstraction, provenance, and the refusal on retrieval failure |
| K15.28–K15.29 | spec | the boundaries this section does **not** move: authoring, publishing, AQL execution |
| K15.30–K15.31 | spec | the honesty gate while the rest is unbuilt — refuse, and do not describe a parser as archetype support |