// @generated by openehr-codegen (emit-validate) — DO NOT EDIT.
//! 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 — that
//! message shape is our own convention (it originated in openEHR's reference
//! implementation, which is prior art only, never the ground).
//!
//! # 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`.
//! - `nonempty_list_core` — the whole `x /= Void implies not x.is_empty`
//! family, decidable on the typed model now that an optional container emits
//! as `Option<Vec<T>>`.
//! - `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/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/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/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/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/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/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/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/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/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.Identities_valid` — `app/ferroehr/src/service/demographic/validate.rs`: `not identities.is_empty`: the demographic write boundary rejects a party body whose `identities` is absent or empty (422). (docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.demographic.party.adoc §Invariants).
//! - `PARTY.Is_archetype_root` — `app/ferroehr/src/service/demographic/validate.rs`: unconditional (`is_archetype_root`), so every party body is a root LOCATABLE; the demographic write boundary 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.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.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`.
//!
//! # 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 —
//! `crate::validate::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;
/// The `x /= Void implies not x.is_empty` rules, read from the vendored
/// BMM class invariants: `(class, attribute, invariant)`. Applied to the
/// class and its transitive concrete descendants by
/// [`super::nonempty_list_violations`].
pub(crate) const NONEMPTY_LIST_RULES: &[(&str, &str, &str)] = &[];
/// 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 !crate::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 (archie renders
// them the same way — prior art, not the ground).
#[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; archie `getPrecision() != null &&
// getPrecision() == 0`). A fraction / integer_fraction is therefore invalid
// when its numerator/denominator are non-integral OR its precision is a
// present, non-zero value: CNF `master17.3` (CONT-DV_PROPORTION-validate_open)
// rejects `type 3, 10/500, precision 1` under fraction_validity. (Precision 0
// or absent keeps a whole-numerator/denominator fraction valid.)
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}"
)));
}
}
/// The `x /= Void implies not x.is_empty` invariant family, shared by every RM
/// class that declares one (`LOCATABLE.Links_valid`, `COMPOSITION.Content_valid`,
/// `SECTION.Items_valid`, `ENTRY.Other_participations_valid`,
/// `INSTRUCTION.Activities_valid`, `EVENT_CONTEXT.Participations_validity`,
/// `DV_TEXT.Mappings_valid`, `DV_ORDERED.Other_reference_ranges_validity`,
/// `PARTY_IDENTIFIED.Identifiers_valid`, `ACTOR.Roles_valid`,
/// `ATTESTATION.Items_valid`, `ORIGINAL_VERSION.Attestations_valid` /
/// `Other_input_version_uids_valid`, … — each `docs/specs/openehr/RM/docs/UML/classes/org.openehr.rm.*`
/// §Invariants).
///
/// `present_and_empty` is the whole rule: the attribute is present on the
/// instance AND holds zero members. It is decidable on the typed model because
/// an optional container emits as `Option<Vec<T>>` — `Some(vec![])` is exactly
/// the forbidden state — so callers pass
/// `self.attr.as_ref().is_some_and(Vec::is_empty)`.
pub(crate) fn nonempty_list_core(
ty: &str,
attribute: &str,
invariant: &str,
present_and_empty: bool,
out: &mut Vec<InvariantViolation>,
) {
if present_and_empty {
out.push(InvariantViolation::here(format!(
"{ty}.{attribute} is present but empty — a present list must be \
non-empty ({ty}.{invariant})"
)));
}
}
/// 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",
));
}
}