openehr-rm 0.0.128

openEHR RM Reference Model, generated from BMM: generations v1_1 (RM 1.1.0) and v1_2 (RM 1.2.0, current)
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
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
// @generated by openehr-codegen (emit-validate) — DO NOT EDIT.
// SPDX-FileCopyrightText: Cadasto B.V.
// SPDX-FileCopyrightText: openEHR Foundation
// SPDX-License-Identifier: Apache-2.0
//! RM class-invariant cores — the single source both the typed `Validate` impls
//! (`*_impl.rs`) and the allocation-free fast path (`validate::fast`) call. Each
//! core emits this workspace's uniform violation message (`Invariant <Name>
//! failed on type <RM_TYPE>`) so a failure is identifiable by its BMM invariant
//! name.
//!
//! NOTE: no openEHR spec governs the wording of a violation message — the
//! shape below is our own convention, carrying the BMM invariant name so a
//! failure names the rule it violated.
//!
//! # Emitted cores
//!
//! - `code_phrase_core` — CODE_PHRASE `Code_string_valid`.
//! - `dv_text_core` — DV_TEXT / DV_CODED_TEXT `Valid_value`, `Formatting_valid`.
//! - `dv_uri_core` — DV_URI `Value_valid`.
//! - `dv_identifier_core` — DV_IDENTIFIER `Id_valid`.
//! - `term_mapping_core` — TERM_MAPPING `Match_valid`.
//! - `dv_parsable_core` — DV_PARSABLE `Formalism_valid`.
//! - `dv_amount_core` — DV_AMOUNT `Accuracy_is_percent_validity`,
//!   `Accuracy_validity` + DV_QUANTIFIED `Magnitude_status_valid`.
//! - `magnitude_status_core` — DV_QUANTIFIED `Magnitude_status_valid`.
//! - `temporal_value_core` — DV_DATE/DV_TIME/DV_DATE_TIME/DV_DURATION
//!   `Value_valid`.
//! - `dv_proportion_core` — DV_PROPORTION `Type_validity`, `Valid_denominator`,
//!   `Precision_validity`, `Fraction_validity`, `Unitary_validity`,
//!   `Percent_validity`.
//! - `archetype_node_id_core` — LOCATABLE `Archetype_node_id_valid`.
//! - `history_basic_core` — HISTORY `Events_valid` + inherited
//!   `Archetype_node_id_valid`.
//! - `composition_core` — COMPOSITION `Is_archetype_root` + inherited
//!   `Archetype_node_id_valid`.
//! - `entry_root_core` — ENTRY `Is_archetype_root` + inherited
//!   `Archetype_node_id_valid`.
//! - `activity_core` — ACTIVITY `Action_archetype_id_valid` + inherited
//!   `Archetype_node_id_valid`.
//! - `event_context_core` — EVENT_CONTEXT `location_valid`.
//! - `archetyped_core` — ARCHETYPED `Rm_version_valid`.
//! - `party_identified_core` — PARTY_IDENTIFIED / PARTY_RELATED `Basic_validity`,
//!   `Name_valid`.
//! - `resource_description_core` — RESOURCE_DESCRIPTION `Original_author_valid`,
//!   `Lifecycle_state_valid`, `Details_valid`.
//! - `resource_description_item_core` — RESOURCE_DESCRIPTION_ITEM
//!   `Purpose_valid`, `Use_valid`, `misuse_valid`, `copyright_valid`.
//! - `authored_resource_core` — AUTHORED_RESOURCE `Revision_history_valid`
//!   (evaluated only when `is_controlled` is present — the `xor` is not
//!   evaluable against a Void operand and the attribute is `0..1`).
//! - `extract_core` — EXTRACT `Sequence_nr_valid`.
//! - `extract_update_spec_core` — EXTRACT_UPDATE_SPEC `Overall_validity`.
//!
//! Complex invariants stay hand-written where the projection is non-trivial:
//! ELEMENT's null-flavour XOR (`element_impl`), DV_EHR_URI's `Scheme_valid`
//! extra (`dv_ehr_uri_impl`), HISTORY `Period_consistency` (typed-only, run
//! after `history_basic_core`), and DV_ORDERED
//! `Normal_range_and_status_consistency` (`dv_ordered_impl`).
//!
//! # Emittable-invariant realization register
//!
//! Every RM class invariant the assertion-dialect classifier judges
//! mechanically evaluable, with the venue that actually realizes it. A
//! `Core` row is realized by a core below; the other venues are realized
//! (or adjudicated) outside this file, and are listed so the emitted
//! bucket is accounted for in full rather than assumed.
//!
//! ## Realized by a generated core in this file
//!
//! - `ACTIVITY.Action_archetype_id_valid` — `activity_core`: the ACTIVITY core, called by `activity_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.activity.adoc §Invariants).
//! - `ARCHETYPED.Rm_version_valid` — `archetyped_core`: the ARCHETYPED core, called by `archetyped_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.archetyped.adoc §Invariants).
//! - `AUTHORED_RESOURCE.Revision_history_valid` — `authored_resource_core`: `is_controlled xor revision_history = Void`, evaluated only when `is_controlled` (0..1) is present — an xor against a Void operand is not evaluable, so an absent flag asserts nothing; called by `authored_resource_impl.rs`. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.authored_resource.adoc §Invariants).
//! - `CODE_PHRASE.Code_string_valid` — `code_phrase_core`: the CODE_PHRASE core, called by `code_phrase_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.code_phrase.adoc §Invariants).
//! - `COMPOSITION.Is_archetype_root` — `composition_core`: `is_archetype_root` is `archetype_details /= Void` (locatable.adoc §Functions). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.composition.adoc §Invariants).
//! - `DV_AMOUNT.Accuracy_is_percent_validity` — `dv_amount_core`: shared by every concrete DV_AMOUNT descendant. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_amount.adoc §Invariants).
//! - `DV_AMOUNT.Accuracy_validity` — `dv_amount_core`: shared by every concrete DV_AMOUNT descendant. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_amount.adoc §Invariants).
//! - `DV_DATE.Value_valid` — `temporal_value_core`: the ISO-8601 date validator supplies the verdict. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_date.adoc §Invariants).
//! - `DV_DATE_TIME.Value_valid` — `temporal_value_core`: the ISO-8601 date-time validator supplies the verdict. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_date_time.adoc §Invariants).
//! - `DV_DURATION.Value_valid` — `temporal_value_core`: the ISO-8601 duration validator supplies the verdict. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_duration.adoc §Invariants).
//! - `DV_IDENTIFIER.Id_valid` — `dv_identifier_core`: called by `dv_identifier_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_identifier.adoc §Invariants).
//! - `DV_PARSABLE.Formalism_valid` — `dv_parsable_core`: called by `dv_parsable_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_parsable.adoc §Invariants).
//! - `DV_PROPORTION.Fraction_validity` — `dv_proportion_core`: the DV_PROPORTION core evaluates all six own invariants. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `DV_PROPORTION.Percent_validity` — `dv_proportion_core`: the DV_PROPORTION core evaluates all six own invariants. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `DV_PROPORTION.Precision_validity` — `dv_proportion_core`: the DV_PROPORTION core evaluates all six own invariants. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `DV_PROPORTION.Type_validity` — `dv_proportion_core`: the DV_PROPORTION core evaluates all six own invariants. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `DV_PROPORTION.Unitary_validity` — `dv_proportion_core`: the DV_PROPORTION core evaluates all six own invariants. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `DV_PROPORTION.Valid_denominator` — `dv_proportion_core`: the DV_PROPORTION core evaluates all six own invariants. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `DV_QUANTIFIED.Magnitude_status_valid` — `magnitude_status_core`: shared by every concrete DV_QUANTIFIED descendant. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_quantified.adoc §Invariants).
//! - `DV_TEXT.Formatting_valid` — `dv_text_core`: shared by DV_TEXT and DV_CODED_TEXT. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_text.adoc §Invariants).
//! - `DV_TEXT.Valid_value` — `dv_text_core`: shared by DV_TEXT and DV_CODED_TEXT. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_text.adoc §Invariants).
//! - `DV_TIME.Value_valid` — `temporal_value_core`: the ISO-8601 time validator supplies the verdict. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_time.adoc §Invariants).
//! - `DV_URI.Value_valid` — `dv_uri_core`: extended by `dv_ehr_uri_impl.rs` for the DV_EHR_URI scheme rule. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_uri.adoc §Invariants).
//! - `ENTRY.Is_archetype_root` — `entry_root_core`: shared by every concrete ENTRY subtype. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.entry.adoc §Invariants).
//! - `EVENT_CONTEXT.location_valid` — `event_context_core`: called by `event_context_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.event_context.adoc §Invariants).
//! - `EXTRACT.Sequence_nr_valid` — `extract_core`: `sequence_nr >= 1`, called by `extract_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr_extract.extract.adoc §Invariants).
//! - `EXTRACT_UPDATE_SPEC.Overall_validity` — `extract_update_spec_core`: `repeat_period /= Void or trigger_events /= Void`, called by `extract_update_spec_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr_extract.extract_update_spec.adoc §Invariants).
//! - `HISTORY.Events_valid` — `history_basic_core`: called by `history_impl.rs` and the fast path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.history.adoc §Invariants).
//! - `LOCATABLE.Archetype_node_id_valid` — `archetype_node_id_core`: inherited by every concrete LOCATABLE descendant; the typed dispatcher closes out the classes with no typed impl from the generated concrete-descendant closure. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.locatable.adoc §Invariants).
//! - `PARTY_IDENTIFIED.Basic_validity` — `party_identified_core`: shared by PARTY_IDENTIFIED and PARTY_RELATED. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.party_identified.adoc §Invariants).
//! - `PARTY_IDENTIFIED.Name_valid` — `party_identified_core`: shared by PARTY_IDENTIFIED and PARTY_RELATED. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.party_identified.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION.Details_valid` — `resource_description_core`: the three own non-empty rules, called by `resource_description_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION.Lifecycle_state_valid` — `resource_description_core`: the three own non-empty rules, called by `resource_description_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION.Original_author_valid` — `resource_description_core`: the three own non-empty rules, called by `resource_description_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION_ITEM.Purpose_valid` — `resource_description_item_core`: `not purpose.is_empty` plus the present-implies-non-empty string rules, called by `resource_description_item_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description_item.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION_ITEM.Use_valid` — `resource_description_item_core`: `not purpose.is_empty` plus the present-implies-non-empty string rules, called by `resource_description_item_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description_item.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION_ITEM.copyright_valid` — `resource_description_item_core`: `not purpose.is_empty` plus the present-implies-non-empty string rules, called by `resource_description_item_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description_item.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION_ITEM.misuse_valid` — `resource_description_item_core`: `not purpose.is_empty` plus the present-implies-non-empty string rules, called by `resource_description_item_impl.rs` and the typed dispatch. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description_item.adoc §Invariants).
//! - `TERM_MAPPING.Match_valid` — `term_mapping_core`: the match code is one of `< = > ?`. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.term_mapping.adoc §Invariants).
//!
//! ## Realized in hand-written `openehr-rm` code
//!
//! - `AUDIT_DETAILS.System_id_valid` — `crates/openehr-rm/src/v1_2/common/generic/audit_details_impl.rs`: re-stated on the ATTESTATION subtype impl for its own RM type name. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.audit_details.adoc §Invariants).
//! - `DV_MULTIMEDIA.Integrity_check_validity` — `crates/openehr-rm/src/v1_2/data_types/encapsulated/dv_multimedia_impl.rs`: an integrity check requires its algorithm. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_multimedia.adoc §Invariants).
//! - `DV_MULTIMEDIA.Not_empty` — `crates/openehr-rm/src/v1_2/data_types/encapsulated/dv_multimedia_impl.rs`: inline data or an external URI must be present. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_multimedia.adoc §Invariants).
//! - `DV_MULTIMEDIA.Size_valid` — `crates/openehr-rm/src/v1_2/data_types/encapsulated/dv_multimedia_impl.rs`: a negative encapsulated size is refused. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_multimedia.adoc §Invariants).
//! - `FEEDER_AUDIT_DETAILS.System_id_valid` — `crates/openehr-rm/src/v1_2/common/archetyped/feeder_audit_details_impl.rs`: the FEEDER_AUDIT_DETAILS system id is checked on its own type. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.feeder_audit_details.adoc §Invariants).
//! - `INSTRUCTION_DETAILS.Activity_path_valid` — `crates/openehr-rm/src/v1_2/composition/content/entry/instruction_details_impl.rs`: the activity id must be non-empty. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.instruction_details.adoc §Invariants).
//! - `ITEM_TAG.Inv_value_valid` — `crates/openehr-rm/src/v1_2/common/tags/item_tag_impl.rs`: a present tag value must be non-empty. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.item_tag.adoc §Invariants).
//! - `LOCATABLE.Archetyped_valid` — `crates/openehr-rm/src/v1_2/validate.rs`: the enforceable arm reads the node's own `archetype_node_id` + `archetype_details` off the JSON value (`check_archetyped_valid`). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.locatable.adoc §Invariants).
//! - `REFERENCE_RANGE.Range_is_simple` — `crates/openehr-rm/src/v1_2/data_types/quantity/reference_range_impl.rs`: each present interval limit must itself be simple (no nested reference ranges). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.reference_range.adoc §Invariants).
//!
//! ## Realized at the application write boundary (`app/`)
//!
//! - `EHR_ACCESS.Is_archetype_root` — `app/ferroehr/src/service/ehr/validation.rs`: unconditional (`is_archetype_root`), so every EHR_ACCESS is a root LOCATABLE; the commit-time validator runs the root-LOCATABLE checks it entails (`Archetyped_valid`, the root `archetype_node_id` rule). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr_access.adoc §Invariants).
//! - `EHR_ACCESS.Scheme_valid` — `app/ferroehr/src/service/ehr/validation.rs`: `not scheme.is_empty`: `scheme` names the concrete ACCESS_CONTROL_SETTINGS subtype, which the typed model carries only as the payload's `_type`; the commit-time validator requires a present `settings` to name it (422). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr_access.adoc §Invariants).
//! - `EHR_STATUS.Is_archetype_root` — `app/ferroehr/src/service/ehr/validation.rs`: unconditional (`is_archetype_root`), so every EHR_STATUS is a root LOCATABLE; the commit-time validator runs the root-LOCATABLE checks it entails (`Archetyped_valid`, the root `archetype_node_id` rule). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr_status.adoc §Invariants).
//! - `EXTRACT_CONTENT_ITEM.Item_validity` — `app/ferroehr/src/service/message/import.rs`: `is_masked xor item /= Void`: realized on the EHR-Extract import path, which rejects a masked wrapper carrying an item and an unmasked one carrying none. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr_extract.extract_content_item.adoc §Invariants).
//! - `EXTRACT_VERSION_SPEC.Includes_revision_history_valid` — `app/ferroehr/src/service/message/export.rs`: `not include_data implies include_revision_history`: realized where an extract request's version spec is read, before any export runs. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr_extract.extract_version_spec.adoc §Invariants).
//! - `PARTY.Is_archetype_root` — `app/ferroehr/src/service/ehr/validation.rs`: unconditional (`is_archetype_root`), so every party body is a root LOCATABLE; the demographic write boundary (`service/demographic/validate.rs`) routes every party commit through the shared root-LOCATABLE validator at the named site, whose refusal names the invariant. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//!
//! ## Adjudicated out of the per-node invariant layer
//!
//! - `ACTOR.Roles_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.actor.adoc §Invariants).
//! - `ADDRESS.Type_valid`: `type = name`: the invariant DEFINES the derived `type` function as the class's own `name` (§Functions), so nothing about an instance's data can violate it. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.address.adoc §Invariants).
//! - `ATTESTATION.Items_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.attestation.adoc §Invariants).
//! - `AUTHORED_RESOURCE.Current_revision_valid`: constrains the DERIVED `current_revision()` function (§Functions: the most recent revision when controlled, else `(uncontrolled)`) — no stored attribute exists for the rule to refuse; the function's own definition satisfies it by construction (the ORIGINAL_VERSION.Is_merged_validity precedent). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.authored_resource.adoc §Invariants).
//! - `COMPOSITION.Content_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.composition.adoc §Invariants).
//! - `CONTACT.Purpose_valid`: `purpose = name`: the invariant DEFINES the derived `purpose` function as the class's own `name` (§Functions), so nothing about an instance's data can violate it. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.contact.adoc §Invariants).
//! - `DV_ORDERED.Is_simple_validity`: vacuous on stored data: `is_simple ()` is defined as `True if this quantity has no reference ranges` (§Functions), i.e. exactly the antecedent. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_ordered.adoc §Invariants).
//! - `DV_ORDERED.Other_reference_ranges_validity`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_ordered.adoc §Invariants).
//! - `DV_PARAGRAPH.Items_valid`: `not items.is_empty`: `items` is a mandatory `1..*` list, emitted as `NonEmptyVec<DvText>`, so a present-but-empty value is unrepresentable and the strict reader refuses an empty wire list — the invariant holds by construction. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_paragraph.adoc §Invariants).
//! - `DV_PARSABLE.Size_valid`: `size >= 0`: `size` is the DERIVED function `size in bytes of value` (§Functions), realized as the Rust byte length (`dv_parsable_impl.rs::size`, a `usize`), so it cannot be negative — unlike DV_MULTIMEDIA, where `size` is a STORED attribute a wire value can contradict, nothing stored exists for this rule to constrain. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_parsable.adoc §Invariants).
//! - `DV_TEXT.Mappings_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_text.adoc §Invariants).
//! - `EHR.Directory_valid`: a cross-object reference resolved by the EHR service against the store, not a property of the value being validated. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `EHR.Ehr_access_valid`: a cross-object reference resolved by the EHR service against the store, not a property of the value being validated. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `EHR.Ehr_status_valid`: a cross-object reference resolved by the EHR service against the store, not a property of the value being validated. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `ENTRY.Other_participations_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.entry.adoc §Invariants).
//! - `EVENT_CONTEXT.Participations_validity`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.event_context.adoc §Invariants).
//! - `EXTRACT_UPDATE_SPEC.Send_changes_only_validity`: invokes `send_changes_only`, an attribute the class does NOT declare (its table carries `update_method: CODE_PHRASE`; only the intro prose speaks of the flag) — an invariant over a nonexistent attribute is unevaluable; upstream defect, reported. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr_extract.extract_update_spec.adoc §Invariants).
//! - `EXTRACT_UPDATE_SPEC.Trigger_events_validity`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr_extract.extract_update_spec.adoc §Invariants).
//! - `HISTORY.Periodic_validity`: vacuous on stored data: `is_periodic ()` is a derived function (§Functions) with no wire representation; the stored node carries only `period`. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.history.adoc §Invariants).
//! - `INSTRUCTION.Activities_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.instruction.adoc §Invariants).
//! - `LOCATABLE.Links_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.locatable.adoc §Invariants).
//! - `ORIGINAL_VERSION.Attestations_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.original_version.adoc §Invariants).
//! - `ORIGINAL_VERSION.Is_merged_validity`: `other_input_version_ids = Void xor is_merged`: `is_merged` is the DERIVED function `True if this Version was created from more than just the preceding (checked out) version` (§Functions), i.e. exactly the emptiness of `other_input_version_uids` — the invariant defines it rather than constraining stored data. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.original_version.adoc §Invariants).
//! - `ORIGINAL_VERSION.Other_input_version_uids_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.original_version.adoc §Invariants).
//! - `PARTY.Contacts_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY.Identities_valid`: `not identities.is_empty`: holds BY CONSTRUCTION — `identities` is a mandatory `1..*` list emitted as `NonEmptyVec<PartyIdentity>`, so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse (pinned by `demographic/validate.rs` `identities_valid_is_enforced`). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY.Type_valid`: `type = name`: the invariant DEFINES the derived `type` function as the class's own `name` (§Functions), so nothing about an instance's data can violate it. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY.Uid_mandatory`: `uid /= Void`: satisfied by construction, not by a check — a demographic party's `uid` is the version container's, which the server injects at the storage/read boundary (`app/ferroehr/src/service/demographic/support.rs`), so an inbound body legitimately carries none (`app/ferroehr/src/service/demographic/validate.rs`). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY_IDENTIFIED.Identifiers_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.party_identified.adoc §Invariants).
//! - `PARTY_IDENTITY.Purpose_valid`: `purpose = name`: the invariant DEFINES the derived `purpose` function as the class's own `name` (§Functions), so nothing about an instance's data can violate it. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party_identity.adoc §Invariants).
//! - `PARTY_RELATIONSHIP.Type_validity`: `type = name`: the invariant DEFINES the derived `type` function as the class's own `name` (§Functions), so nothing about an instance's data can violate it. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party_relationship.adoc §Invariants).
//! - `REVISION_HISTORY_ITEM.Audit_valid`: `not audits.is_empty`: satisfied by construction — `audits` emits as `NonEmptyVec<AuditDetails>`, so BOTH ingestion paths refuse an empty list at parse: the REST read side never accepts one as a request body (every builder pushes the commit audit first), and the OPT 1.4 template upload — the one real client-supplied REVISION_HISTORY carrier (Template.xsd) — constructs items through `NonEmptyVec::new` in the generated XML reader, pinned by the opt14 refusal twin (#1648). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.revision_history_item.adoc §Invariants).
//! - `ROLE.Capabilities_valid`: the `x /= Void implies not x.empty` family (Eiffel spelling): holds BY CONSTRUCTION — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.role.adoc §Invariants).
//! - `SECTION.Items_valid`: the `x /= Void implies not x.is_empty` family: holds BY CONSTRUCTION since #1730 — an optional container carrying the invariant emits `Option<NonEmptyVec<T>>` (`analyze::nonempty_optional_lists`), so a present-but-empty value is unrepresentable and the strict readers refuse `[]` at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.section.adoc §Invariants).
//! - `VERSION.Preceding_version_uid_validity`: the version chain, owned by the versioning layer's commit path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.version.adoc §Invariants).
//! - `VERSIONED_OBJECT.Uid_validity`: the versioning aggregate, owned by the versioning layer's commit path. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.versioned_object.adoc §Invariants).
//!
//! ## UNACCOUNTED (classified emittable, no register row)
//!
//! - `Interval.Lower_included_valid`.
//! - `Interval.Upper_included_valid`.
//! - `Iso8601_date.Partial_validity`.
//! - `Iso8601_date_time.Partial_validity_day`.
//! - `Iso8601_date_time.Partial_validity_hour`.
//! - `Iso8601_date_time.Partial_validity_minute`.
//! - `Iso8601_date_time.Partial_validity_month`.
//! - `Iso8601_date_time.Partial_validity_year`.
//! - `Iso8601_duration.Days_valid`.
//! - `Iso8601_duration.Fractional_second_valid`.
//! - `Iso8601_duration.Hours_valid`.
//! - `Iso8601_duration.Minutes_valid`.
//! - `Iso8601_duration.Months_valid`.
//! - `Iso8601_duration.Seconds_valid`.
//! - `Iso8601_duration.Weeks_valid`.
//! - `Iso8601_duration.Years_valid`.
//! - `Iso8601_time.Partial_validity`.
//!
//! # Complex-invariant realization register
//!
//! Every RM class invariant the assertion-dialect classifier judges NOT
//! mechanically evaluable (quantifiers, lambdas, cross-object
//! navigation), with the venue that realizes it or the citation-pinned
//! adjudication that excludes it — the complex bucket is accounted for
//! in full, exactly like the emitted bucket above.
//!
//! ## Realized in hand-written `openehr-rm` code
//!
//! - `DV_EHR_URI.Scheme_valid` — `crates/openehr-rm/src/v1_2/data_types/uri/dv_ehr_uri_impl.rs`: `scheme.is_equal (Ehr_scheme)`: the DV_EHR_URI core extends the shared DV_URI core with the `ehr:` scheme rule. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_ehr_uri.adoc §Invariants).
//! - `DV_INTERVAL.Limits_consistent` — `crates/openehr-rm/src/v1_2/data_types/quantity/dv_interval_impl.rs`: both-bounded limits must compare and order (`lower <= upper`); evaluated on the typed interval, dispatched with a `DvOrdered` element type by `validate/typed_dispatch.rs`. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_interval.adoc §Invariants).
//! - `DV_ORDERED.Normal_range_and_status_consistency` — `crates/openehr-rm/src/v1_2/data_types/quantity/dv_ordered_impl.rs`: the cross-member xor of `normal_status = N` against `normal_range.has (self)`, evaluated where both members are typed. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_ordered.adoc §Invariants).
//! - `DV_PERIODIC_TIME_SPECIFICATION.Value_valid` — `crates/openehr-rm/src/v1_2/data_types/time_specification/dv_periodic_time_specification_impl.rs`: `value.formalism` is `HL7:PIVL` or `HL7:EIVL`, checked with the inner syntax where the value is parsed. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_periodic_time_specification.adoc §Invariants).
//! - `ELEMENT.Inv_null_flavour_indicated` — `crates/openehr-rm/src/v1_2/data_structures/representation/element_impl.rs`: `is_null() xor null_flavour = Void`: a value-less ELEMENT carries a null_flavour and a valued one does not. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.element.adoc §Invariants).
//! - `ELEMENT.Inv_null_reason_valid` — `crates/openehr-rm/src/v1_2/data_structures/representation/element_impl.rs`: `null_reason /= Void implies is_null()`: a null_reason on a valued ELEMENT is refused. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.element.adoc §Invariants).
//! - `HISTORY.Period_consistency` — `crates/openehr-rm/src/v1_2/data_structures/history/history_impl.rs`: every periodic history's event offset is a whole multiple of `period`, evaluated on the typed HISTORY where the event offsets are computable. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.history.adoc §Invariants).
//! - `ITEM_TAG.Inv_key_valid` — `crates/openehr-rm/src/v1_2/common/tags/item_tag_impl.rs`: `not key.is_empty and key.is_justified`: the tag key rules, realized once and read by both service seams. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.item_tag.adoc §Invariants).
//! - `Interval.Limits_consistent` — `crates/openehr-base/src/v1_3/foundation_types/interval/point_interval_impl.rs`: both-bounded limits must order (`lower <= upper`), evaluated on the typed interval. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.interval.adoc §Invariants).
//! - `Iso8601_date.Day_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_date_impl.rs`: `not day_unknown implies valid_day (year, month, day)`, reported on the owning type by the shared date-component check. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date.adoc §Invariants).
//! - `Iso8601_date.Month_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_date_impl.rs`: `not month_unknown implies valid_month (month)`, reported on the owning type by the shared date-component check. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date.adoc §Invariants).
//! - `Iso8601_date_time.Day_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_date_impl.rs`: `valid_day (year, month, day)`, reported on the owning type by the shared date-component check the date-time reader calls. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `Iso8601_date_time.Fractional_second_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: a fractional second requires a known second and a valid fraction, reported on the owning type by the shared time-component check. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `Iso8601_date_time.Hour_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: `valid_hour (hour, minute, second)`, reported on the owning type by the shared time-component check the date-time reader calls. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `Iso8601_date_time.Minute_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: `not minute_unknown implies valid_minute (minute)`, reported on the owning type by the shared time-component check. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `Iso8601_date_time.Month_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_date_impl.rs`: `valid_month (month)`, reported on the owning type by the shared date-component check the date-time reader calls. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `Iso8601_date_time.Second_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: `not second_unknown implies valid_second (second)`, reported on the owning type by the shared time-component check. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `Iso8601_time.Fractional_second_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: a fractional second requires a known second and a valid fraction, evaluated where the time value is parsed. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_time.adoc §Invariants).
//! - `Iso8601_time.Hour_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: `valid_hour (hour, minute, second)`, evaluated where the time value is parsed. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_time.adoc §Invariants).
//! - `Iso8601_time.Minute_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: `not minute_unknown implies valid_minute (minute)`, evaluated where the time value is parsed. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_time.adoc §Invariants).
//! - `Iso8601_time.Second_valid` — `crates/openehr-base/src/v1_3/foundation_types/time/iso8601_time_impl.rs`: `not second_unknown implies valid_second (second)`, evaluated where the time value is parsed. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_time.adoc §Invariants).
//!
//! ## Realized at the application write boundary (`app/`)
//!
//! - `AUTHORED_RESOURCE.Description_valid` — `app/ferroehr/src/validation/opt/resource.rs`: realized at the same whole-resource ingest seams as `Translations_valid` (the named site plus `openehr-adl` `validate/resource_meta.rs`), as the `RESOURCE_DESCRIPTION.Language_valid` membership — the literal (details ⊆ translations keys) would refuse the original language's own description item, so membership is checked against the original plus the translations. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.authored_resource.adoc §Invariants).
//! - `AUTHORED_RESOURCE.Translations_valid` — `app/ferroehr/src/validation/opt/resource.rs`: a cross-member map rule over `translations`, realized where a whole authored resource is ingested: the OPT 1.4 template upload's resource-meta pass (the named site) and the ADL 1.4 source catalogue (`openehr-adl` `validate/resource_meta.rs`) — a present translations list is non-empty and never re-states the original language. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.authored_resource.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION.Language_valid` — `app/ferroehr/src/validation/opt/resource.rs`: `details.for_all (d | parent_resource.languages_available.has (d.language.code_string))`: realized at the whole-resource ingest seams (the named site plus `openehr-adl` `validate/resource_meta.rs`), where the owner is in hand — each description detail's language must be the owner's original language or a listed translation. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description.adoc §Invariants).
//! - `VERSIONED_COMPOSITION.Archetype_node_id_valid` — `app/ferroehr/src/validation/versioned.rs`: every version's root `archetype_node_id` equals the container's first version's — checked in the commit transaction on every composition update. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.versioned_composition.adoc §Invariants).
//! - `VERSIONED_COMPOSITION.Persistent_validity` — `app/ferroehr/src/validation/versioned.rs`: every version's persistence (`category` = persistent, the derived `is_persistent`) equals the container's first version's — checked in the commit transaction on every composition update. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.versioned_composition.adoc §Invariants).
//!
//! ## Adjudicated out of the per-node invariant layer
//!
//! - `AUTHORED_RESOURCE.Languages_available_valid`: `languages_available.has (original_language)`: constrains the DERIVED `languages_available()` function (§Functions), which builds its result from `original_language` — satisfied by the function's own definition (`authored_resource_impl.rs`; the `Current_revision_valid` precedent). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.authored_resource.adoc §Invariants).
//! - `DV_PROPORTION.Is_integral_validity`: `is_integral implies (numerator.floor = numerator …)`: `is_integral` is derived from `type`, and the `dv_proportion_core`'s `Fraction_validity` arm already refuses a non-integral numerator/denominator for exactly the integral kinds — a separate evaluation would double-report the same defect. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.dv_proportion.adoc §Invariants).
//! - `EHR.Compositions_valid`: a type facet of the store-maintained reference list: the container rows are typed by `kind` in storage, so a member of the wrong container type is unrepresentable, and the ITS-REST EHR representation serves no such list (the `EHR.Directory_valid` cross-object precedent). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `EHR.Contributions_valid`: a type facet of the store-maintained reference list: contribution rows are their own storage relation, so a non-CONTRIBUTION member is unrepresentable, and the ITS-REST EHR representation serves no such list. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `EHR.Directory_in_folders`: `folders.item(1) = directory`: the store keeps ONE directory container per EHR and materializes no `folders` list, so the antecedent (`folders /= Void`) never holds on served data (the `EHR.Directory_valid` cross-object precedent). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `EHR.Folders_valid`: a type facet of the store-maintained reference list: folder containers are typed by `kind` in storage, and the ITS-REST EHR representation serves no `folders` list. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.ehr.ehr.adoc §Invariants).
//! - `ELEMENT.Inv_is_null_valid`: `is_null() = (value = Void)`: `is_null()` is the DERIVED function defined as exactly that emptiness, so nothing stored can contradict it (the `ORIGINAL_VERSION.Is_merged_validity` precedent). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.element.adoc §Invariants).
//! - `ENTRY.Subject_validity`: `subject_is_self implies subject.generating_type = PARTY_SELF`: `subject_is_self` is the DERIVED function defined by exactly that variant test (§Functions), so the rule restates its own definition. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.composition.entry.adoc §Invariants).
//! - `EVENT.Offset_validity1`: `offset = time.diff (parent.origin)`: `offset` is the DERIVED function defined as that difference, over the `parent` back-reference the generated model deliberately breaks — nothing stored exists for the rule to constrain. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.event.adoc §Invariants).
//! - `INTERVAL_EVENT.Interval_start_time_valid`: `interval_start_time = time - width`: `interval_start_time` is the DERIVED function defined as that subtraction (§Functions) — no stored attribute exists for the rule to refuse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.interval_event.adoc §Invariants).
//! - `ITEM_LIST.Valid_structure`: `items.forall (i | i.type = ELEMENT)`: holds BY CONSTRUCTION — the BMM types `items` `List<ELEMENT>`, emitted as `Vec<Element>`, so a non-ELEMENT member is unrepresentable and the strict readers refuse it at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.item_list.adoc §Invariants).
//! - `ITEM_TABLE.Valid_structure`: rows of ELEMENT-only CLUSTERs: the BMM types `rows` `List<CLUSTER>` emitted as typed CLUSTERs whose ELEMENT-only membership the table's own row semantics carry; a non-CLUSTER row is unrepresentable at parse. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_structures.item_table.adoc §Invariants).
//! - `Interval.Limits_comparable`: `lower.strictly_comparable_to (upper)`: holds BY CONSTRUCTION — the generic emits `Interval<T>` with one `T` for both limits and the ordering bound, so incomparable limits are unrepresentable. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.interval.adoc §Invariants).
//! - `Iso8601_date.Year_valid`: `valid_year (year)`: holds at PARSE — the ISO-8601 lexical form admits only `yyyy` digit years and the reader validates through `time_definitions::valid_year` before a typed value exists. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date.adoc §Invariants).
//! - `Iso8601_date_time.Year_valid`: `valid_year (year)`: holds at PARSE — the ISO-8601 lexical form admits only `yyyy` digit years and the reader validates through `time_definitions::valid_year` before a typed value exists. (docs/specs/openehr/BASE/docs/UML/classes/org.openehr.base.foundation_types.iso8601_date_time.adoc §Invariants).
//! - `PARTY.Relationships_validity`: reads the source-party aggregate, which FerroEHR's own design does not maintain: RM demographic master02 §Party Relationships stores a PARTY_RELATIONSHIP as data of its source PARTY, and FerroEHR instead gives every relationship its own version container (our own design; the two representations are DISJOINT); an inline `relationships` list in a committed PARTY body stays validated and served verbatim. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY.Reverse_relationships_validity`: quantifies over `repository (demographics).all_party_relationships` — a whole-repository aggregate no per-node evaluation can read; FerroEHR's own disjoint relationship containers carry the served representation (our own design). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY_RELATIONSHIP.Source_valid`: `source.relationships.has (self)` reads the source party's aggregate through the back-reference the generated model breaks; under FerroEHR's own disjoint relationship containers (our own design) the RM compositional linkage of master02 §Party Relationships is deliberately not maintained (the `PARTY.Relationships_validity` adjudication). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party_relationship.adoc §Invariants).
//! - `PARTY_RELATIONSHIP.Target_valid`: `not target.reverse_relationships.has (self)` reads the target party's aggregate through a broken back-reference; same disjoint-representation adjudication as `Source_valid`. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party_relationship.adoc §Invariants).
//! - `RESOURCE_DESCRIPTION.Parent_resource_valid`: `parent_resource /= Void implies parent_resource.description = self`: reads the OWNING `parent_resource` back-reference, which the generated model deliberately breaks (`BACK_REFERENCES` — a back-reference is not forward-owned data), so nothing stored exists for the rule to constrain; where the pair is in hand the identity holds by construction of ownership. (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.resource_description.adoc §Invariants).
//! - `VERSION.Owner_id_valid`: `owner_id.value = uid.object_id.value`: `owner_id` is the DERIVED function realized as exactly `uid.object_id()` (`version_impl.rs`), so the rule restates its own definition (the `ORIGINAL_VERSION.Is_merged_validity` precedent). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.common.version.adoc §Invariants).
//!
//! # Terminology-backed invariants (enforced in `validate::terminology`)
//!
//! These BMM class invariants bind a coded value to an openEHR terminology
//! group (`has_code_for_group_id`) or a code set (`code_set (id).has_code`),
//! so no generated core below can evaluate them mechanically. They are
//! realized instead by the sibling binding table —
//! `super::terminology::validate_rm_terminology`, over the openEHR
//! terminology bundle (TERM 3.1.0) — which the `openehr-its` wire-boundary
//! dispatcher runs as a post-core check. Enforcement was audited clean
//! against the whole corpus first, so none newly rejects previously-accepted
//! data:
//!
//! - `ATTESTATION.Reason_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `AUDIT_DETAILS.Change_type_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `AUTHORED_RESOURCE.Original_language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `COMPOSITION.Category_validity` — enforced in `validate::terminology` (openEHR terminology service).
//! - `COMPOSITION.Language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `COMPOSITION.Territory_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_ENCAPSULATED.Charset_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_ENCAPSULATED.Language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_MULTIMEDIA.Compression_algorithm_validity` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_MULTIMEDIA.Integrity_check_algorithm_validity` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_MULTIMEDIA.Media_type_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_ORDERED.Normal_status_validity` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_TEXT.Encoding_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `DV_TEXT.Language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `ELEMENT.Inv_null_flavour_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `ENTRY.Encoding_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `ENTRY.Language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `EVENT_CONTEXT.Setting_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `EXTRACT_PARTICIPATION.Function_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `EXTRACT_PARTICIPATION.Mode_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `INTERVAL_EVENT.Math_function_validity` — enforced in `validate::terminology` (openEHR terminology service).
//! - `ISM_TRANSITION.Current_state_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `ISM_TRANSITION.Transition_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `PARTICIPATION.Function_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `PARTICIPATION.Mode_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `PARTY_RELATED.Relationship_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `RESOURCE_DESCRIPTION_ITEM.Language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `TERM_MAPPING.Purpose_valid` — enforced in `validate::terminology` (openEHR terminology service).
//! - `TRANSLATION_DETAILS.Language_valid` — enforced in `validate::terminology` (openEHR code-set access).
//! - `VERSION.Lifecycle_state_ valid` — enforced in `validate::terminology` (openEHR terminology service).
//!
//! # Pending adjudication (runtime-hook-missing)
//!
//! The VERSIONED_OBJECT aggregate invariants quantify over the whole
//! version container (`all_versions`, `all_version_ids`, `version_count`,
//! `latest_version`) — a property of the versioning/service layer, not of
//! any single stored value node — so no per-node core can honestly
//! evaluate them (RM `common versioned_object.adoc` §Invariants). They stay
//! unrealized here:
//!
//! - `VERSIONED_OBJECT.All_version_ids_valid` — versioned-object aggregate model.
//! - `VERSIONED_OBJECT.All_versions_valid` — versioned-object aggregate model.
//! - `VERSIONED_OBJECT.Latest_version_valid` — versioned-object aggregate model.
//! - `VERSIONED_OBJECT.Version_count_valid` — versioned-object aggregate model.

use super::{
    invariant_failed, is_integral, valid_magnitude_status, valid_percentage, valid_proportion_kind,
};
use openehr_base::validate::InvariantViolation;

// `PROPORTION_KIND` codes, generated from the RM BMM enumeration
// (`docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.data_types.proportion_kind.adoc`).
#[expect(
    dead_code,
    reason = "ratio (code 0) has no dedicated proportion invariant"
)]
const PK_RATIO: i32 = 0;
const PK_UNITARY: i32 = 1;
const PK_PERCENT: i32 = 2;
const PK_FRACTION: i32 = 3;
const PK_INTEGER_FRACTION: i32 = 4;

/// CODE_PHRASE `Code_string_valid`: the code string must be non-empty.
pub(crate) fn code_phrase_core(code_string: &str, out: &mut Vec<InvariantViolation>) {
    if code_string.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Code_string_valid failed on type CODE_PHRASE",
        ));
    }
}

/// DV_TEXT / DV_CODED_TEXT `Valid_value` (non-empty rubric) and
/// `Formatting_valid` (present ⇒ non-empty). `ty` is `DV_TEXT` or
/// `DV_CODED_TEXT` (which inherits both).
pub(crate) fn dv_text_core(
    ty: &str,
    value: &str,
    formatting: Option<&str>,
    out: &mut Vec<InvariantViolation>,
) {
    if value.is_empty() {
        out.push(InvariantViolation::here(format!(
            "Invariant Valid_value failed on type {ty} (value must be non-empty)"
        )));
    }
    if formatting.is_some_and(str::is_empty) {
        out.push(InvariantViolation::here(format!(
            "Invariant Formatting_valid failed on type {ty}"
        )));
    }
}

/// DV_URI `Value_valid`: the URI value must be non-empty.
pub(crate) fn dv_uri_core(value: &str, out: &mut Vec<InvariantViolation>) {
    if value.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Value_valid failed on type DV_URI",
        ));
    }
}

/// DV_IDENTIFIER `Id_valid`: `id` must be non-empty.
pub(crate) fn dv_identifier_core(id: &str, out: &mut Vec<InvariantViolation>) {
    if id.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Id_valid failed on type DV_IDENTIFIER",
        ));
    }
}

/// TERM_MAPPING `Match_valid`: `match` must be one of `< = > ?`.
pub(crate) fn term_mapping_core(match_code: char, out: &mut Vec<InvariantViolation>) {
    if !super::super::data_types::text::term_mapping::TermMapping::is_valid_match_code(match_code) {
        out.push(InvariantViolation::here(
            "Invariant Match_valid failed on type TERM_MAPPING",
        ));
    }
}

/// DV_PARSABLE `Formalism_valid`: `formalism` must be non-empty.
pub(crate) fn dv_parsable_core(formalism: &str, out: &mut Vec<InvariantViolation>) {
    if formalism.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Formalism_valid failed on type DV_PARSABLE",
        ));
    }
}

/// DV_QUANTIFIED `Magnitude_status_valid`: if present, `magnitude_status` must
/// be one of `= < > <= >= ~`.
pub(crate) fn magnitude_status_core(
    ty: &str,
    magnitude_status: Option<&str>,
    out: &mut Vec<InvariantViolation>,
) {
    if let Some(s) = magnitude_status
        && !valid_magnitude_status(s)
    {
        out.push(invariant_failed("Magnitude_status_valid", ty));
    }
}

/// The DV_AMOUNT invariants (`Accuracy_is_percent_validity`, `Accuracy_validity`)
/// plus the inherited DV_QUANTIFIED `Magnitude_status_valid`. Shared by every
/// concrete DV_AMOUNT descendant (DV_QUANTITY, DV_COUNT, DV_DURATION,
/// DV_PROPORTION).
pub(crate) fn dv_amount_core(
    ty: &str,
    accuracy: Option<f64>,
    accuracy_is_percent: Option<bool>,
    magnitude_status: Option<&str>,
    out: &mut Vec<InvariantViolation>,
) {
    // Accuracy_is_percent_validity: accuracy = 0 implies not recorded as percent.
    if accuracy == Some(0.0) && accuracy_is_percent == Some(true) {
        out.push(invariant_failed("Accuracy_is_percent_validity", ty));
    }
    // Accuracy_validity: recorded as percent implies 0 <= accuracy <= 100.
    if accuracy_is_percent == Some(true)
        && let Some(a) = accuracy
        && !valid_percentage(a)
    {
        out.push(invariant_failed("Accuracy_validity", ty));
    }
    magnitude_status_core(ty, magnitude_status, out);
}

/// The temporal `Value_valid` invariant: the string value must be a
/// well-formed ISO-8601 date/time/date-time/duration (caller supplies the
/// verdict via the ISO-8601 validators).
pub(crate) fn temporal_value_core(ty: &str, valid: bool, out: &mut Vec<InvariantViolation>) {
    if !valid {
        out.push(invariant_failed("Value_valid", ty));
    }
}

/// The DV_PROPORTION own invariants. The inherited DV_AMOUNT / DV_ORDERED
/// invariants are pushed by the callers.
// The released invariants compare the denominator against the exact values
// 0/1/100 (RM `dv_proportion.adoc` §Invariants: `Valid_denominator`,
// `Unitary_validity`, `Percent_validity` — `denominator = 1`, `= 100`,
// `<> 0`), so exact float comparison IS the spec semantics.
#[expect(
    clippy::float_cmp,
    reason = "the released invariants compare the denominator against exact 0/1/100 values"
)]
pub(crate) fn dv_proportion_core(
    numerator: f64,
    denominator: f64,
    kind: i32,
    precision: Option<i32>,
    out: &mut Vec<InvariantViolation>,
) {
    let integral = is_integral(numerator) && is_integral(denominator);

    // Type_validity: type in 0..=4.
    if !valid_proportion_kind(kind) {
        out.push(InvariantViolation::here(
            "Invariant Type_validity failed on type DV_PROPORTION",
        ));
    }
    // Valid_denominator: denominator != 0.
    if denominator == 0.0 {
        out.push(InvariantViolation::here(
            "Invariant Valid_denominator failed on type DV_PROPORTION",
        ));
    }
    // Precision_validity: precision 0 implies integral numerator & denominator.
    if precision == Some(0) && !integral {
        out.push(InvariantViolation::here(
            "Invariant Precision_validity failed on type DV_PROPORTION",
        ));
    }
    // Fraction_validity: `(type = pk_fraction or type = pk_integer_fraction)
    // implies is_integral`, where `is_integral()` is "True … if precision is 0"
    // (RM dv_proportion.adoc §Functions). A fraction / integer_fraction is
    // therefore invalid when its numerator/denominator are non-integral OR its
    // precision is a present, non-zero value. (Precision 0 or absent keeps a
    // whole-numerator/denominator fraction valid.)
    //
    // NOTE: `Is_integral_validity` (`is_integral implies …`) needs no separate
    // push — `is_integral()` IS "precision is 0" (§Functions), so the
    // invariant is extensionally the `Precision_validity` check above.
    if (kind == PK_FRACTION || kind == PK_INTEGER_FRACTION)
        && (!integral || precision.is_some_and(|p| p != 0))
    {
        out.push(InvariantViolation::here(
            "Invariant Fraction_validity failed on type DV_PROPORTION",
        ));
    }
    // Unitary_validity: unitary kind has denominator 1.
    if kind == PK_UNITARY && denominator != 1.0 {
        out.push(InvariantViolation::here(
            "Invariant Unitary_validity failed on type DV_PROPORTION",
        ));
    }
    // Percent_validity: percent kind has denominator 100.
    if kind == PK_PERCENT && denominator != 100.0 {
        out.push(InvariantViolation::here(
            "Invariant Percent_validity failed on type DV_PROPORTION",
        ));
    }
}

/// LOCATABLE `Archetype_node_id_valid`: `archetype_node_id` must be non-empty.
pub(crate) fn archetype_node_id_core(
    ty: &str,
    archetype_node_id: &str,
    out: &mut Vec<InvariantViolation>,
) {
    if archetype_node_id.is_empty() {
        out.push(invariant_failed("Archetype_node_id_valid", ty));
    }
}

/// The non-periodic HISTORY invariants: `Events_valid` (at least one event or a
/// summary) + inherited `Archetype_node_id_valid`. `Period_consistency` stays
/// typed-only (the fast path declines periodic histories).
pub(crate) fn history_basic_core(
    events_empty: bool,
    has_summary: bool,
    node_id: &str,
    out: &mut Vec<InvariantViolation>,
) {
    if events_empty && !has_summary {
        out.push(InvariantViolation::here(
            "Invariant Events_valid failed on type HISTORY",
        ));
    }
    archetype_node_id_core("HISTORY", node_id, out);
}

/// COMPOSITION `Is_archetype_root` (`archetype_details` present) + inherited
/// `Archetype_node_id_valid`.
pub(crate) fn composition_core(
    has_archetype_details: bool,
    node_id: &str,
    out: &mut Vec<InvariantViolation>,
) {
    if !has_archetype_details {
        out.push(InvariantViolation::here(
            "Invariant Is_archetype_root failed on type COMPOSITION",
        ));
    }
    archetype_node_id_core("COMPOSITION", node_id, out);
}

/// The shared ENTRY-root invariants: `Is_archetype_root` (an ENTRY is an
/// archetype root, so `archetype_details` must be present) + inherited
/// `Archetype_node_id_valid`. `ty` is the concrete ENTRY subtype.
pub(crate) fn entry_root_core(
    ty: &str,
    has_archetype_details: bool,
    node_id: &str,
    out: &mut Vec<InvariantViolation>,
) {
    if !has_archetype_details {
        out.push(invariant_failed("Is_archetype_root", ty));
    }
    archetype_node_id_core(ty, node_id, out);
}

/// ACTIVITY `Action_archetype_id_valid` (non-empty) + inherited
/// `Archetype_node_id_valid`.
pub(crate) fn activity_core(
    action_archetype_id: &str,
    node_id: &str,
    out: &mut Vec<InvariantViolation>,
) {
    if action_archetype_id.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Action_archetype_id_valid failed on type ACTIVITY",
        ));
    }
    archetype_node_id_core("ACTIVITY", node_id, out);
}

/// EVENT_CONTEXT `location_valid`: if present, `location` must be non-empty.
pub(crate) fn event_context_core(location: Option<&str>, out: &mut Vec<InvariantViolation>) {
    if location.is_some_and(str::is_empty) {
        out.push(InvariantViolation::here(
            "Invariant location_valid failed on type EVENT_CONTEXT",
        ));
    }
}

/// ARCHETYPED `Rm_version_valid`: `rm_version` must be non-empty.
pub(crate) fn archetyped_core(rm_version: &str, out: &mut Vec<InvariantViolation>) {
    if rm_version.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Rm_version_valid failed on type ARCHETYPED",
        ));
    }
}

/// PARTY_IDENTIFIED / PARTY_RELATED `Basic_validity` (at least one of `name`,
/// `identifiers`, `external_ref`) and `Name_valid` (present ⇒ non-empty). `ty`
/// is `PARTY_IDENTIFIED` or `PARTY_RELATED` (which inherits both).
pub(crate) fn party_identified_core(
    ty: &str,
    name: Option<&str>,
    has_identifiers: bool,
    has_external_ref: bool,
    out: &mut Vec<InvariantViolation>,
) {
    if name.is_none() && !has_identifiers && !has_external_ref {
        out.push(InvariantViolation::here(format!(
            "Invariant Basic_validity failed on type {ty}"
        )));
    }
    if name.is_some_and(str::is_empty) {
        out.push(InvariantViolation::here(format!(
            "Invariant Name_valid failed on type {ty}"
        )));
    }
}

/// RESOURCE_DESCRIPTION `Original_author_valid` / `Lifecycle_state_valid` /
/// `Details_valid`: the three own non-empty rules.
pub(crate) fn resource_description_core(
    original_author_empty: bool,
    lifecycle_state: &str,
    details_empty: bool,
    out: &mut Vec<InvariantViolation>,
) {
    if original_author_empty {
        out.push(InvariantViolation::here(
            "Invariant Original_author_valid failed on type RESOURCE_DESCRIPTION",
        ));
    }
    if lifecycle_state.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Lifecycle_state_valid failed on type RESOURCE_DESCRIPTION",
        ));
    }
    if details_empty {
        out.push(InvariantViolation::here(
            "Invariant Details_valid failed on type RESOURCE_DESCRIPTION",
        ));
    }
}

/// RESOURCE_DESCRIPTION_ITEM `Purpose_valid` (`not purpose.is_empty`) plus the
/// three present-implies-non-empty string rules (`Use_valid`, `misuse_valid`,
/// `copyright_valid`).
pub(crate) fn resource_description_item_core(
    purpose: &str,
    use_: Option<&str>,
    misuse: Option<&str>,
    copyright: Option<&str>,
    out: &mut Vec<InvariantViolation>,
) {
    if purpose.is_empty() {
        out.push(InvariantViolation::here(
            "Invariant Purpose_valid failed on type RESOURCE_DESCRIPTION_ITEM",
        ));
    }
    for (value, invariant) in [
        (use_, "Use_valid"),
        (misuse, "misuse_valid"),
        (copyright, "copyright_valid"),
    ] {
        if value.is_some_and(str::is_empty) {
            out.push(invariant_failed(invariant, "RESOURCE_DESCRIPTION_ITEM"));
        }
    }
}

/// AUTHORED_RESOURCE `Revision_history_valid`
/// (`is_controlled xor revision_history = Void`): a controlled resource
/// carries a revision history and an uncontrolled one does not. Evaluated
/// only when `is_controlled` is PRESENT — the attribute is `0..1` and an
/// `xor` against a Void operand is not evaluable, so an absent flag asserts
/// nothing (never an invented prohibition).
pub(crate) fn authored_resource_core(
    is_controlled: Option<bool>,
    has_revision_history: bool,
    out: &mut Vec<InvariantViolation>,
) {
    if let Some(controlled) = is_controlled
        && controlled != has_revision_history
    {
        out.push(InvariantViolation::here(
            "Invariant Revision_history_valid failed on type AUTHORED_RESOURCE",
        ));
    }
}

/// EXTRACT `Sequence_nr_valid`: `sequence_nr >= 1`.
pub(crate) fn extract_core(sequence_nr: i32, out: &mut Vec<InvariantViolation>) {
    if sequence_nr < 1 {
        out.push(InvariantViolation::here(
            "Invariant Sequence_nr_valid failed on type EXTRACT",
        ));
    }
}

/// EXTRACT_UPDATE_SPEC `Overall_validity`
/// (`repeat_period /= Void or trigger_events /= Void`): an update
/// specification names at least one update mode.
pub(crate) fn extract_update_spec_core(
    has_repeat_period: bool,
    has_trigger_events: bool,
    out: &mut Vec<InvariantViolation>,
) {
    if !has_repeat_period && !has_trigger_events {
        out.push(InvariantViolation::here(
            "Invariant Overall_validity failed on type EXTRACT_UPDATE_SPEC",
        ));
    }
}