davinci_crd_test_kit 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/config/presets/{inferno_crd_server_suite.json.erb → client_against_inferno_crd_server_suite.json.erb} +0 -33
  3. data/config/presets/{ri_crd_request_generator.json.erb → client_against_ri_crd_request_generator.json.erb} +1 -14
  4. data/config/presets/client_v221_against_inferno_crd_server_suite.json.erb +84 -0
  5. data/config/presets/client_v221_against_ri_br_provider.json.erb +31 -0
  6. data/config/presets/{ri_crd_request_generator.json_v221.json.erb → client_v221_against_ri_crd_request_generator.json.erb} +1 -1
  7. data/config/presets/{inferno_crd_client_suite.json.erb → server_against_inferno_crd_client_suite.json.erb} +0 -43
  8. data/config/presets/{ri_crd_server.json.erb → server_against_ri_crd_server.json.erb} +1 -44
  9. data/config/presets/{inferno_crd_client_suite_v221.json.erb → server_v221_against_inferno_crd_client_suite.json.erb} +48 -45
  10. data/config/presets/{inferno_crd_client_suite_prefetch_subset_v221.json.erb → server_v221_against_inferno_crd_client_suite_prefetch_subset.json.erb} +0 -46
  11. data/config/presets/server_v221_against_inferno_g31_suite.json.erb +49 -0
  12. data/lib/davinci_crd_test_kit/client/cross_hook_helper.rb +26 -0
  13. data/lib/davinci_crd_test_kit/client/endpoints/custom_service_response.rb +7 -7
  14. data/lib/davinci_crd_test_kit/client/endpoints/gather_response_generation_data.rb +4 -1
  15. data/lib/davinci_crd_test_kit/client/endpoints/hook_request_endpoint.rb +70 -39
  16. data/lib/davinci_crd_test_kit/client/endpoints/mock_service_response.rb +26 -7
  17. data/lib/davinci_crd_test_kit/client/endpoints/mocked_card_responses/external_reference.json +1 -1
  18. data/lib/davinci_crd_test_kit/client/endpoints/mocked_card_responses/launch_smart_app.json +0 -7
  19. data/lib/davinci_crd_test_kit/client/endpoints/scenario_service_response.rb +54 -0
  20. data/lib/davinci_crd_test_kit/client/tagged_request_load_helper.rb +8 -27
  21. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/retrieve_jwks_test.rb +35 -23
  22. data/lib/davinci_crd_test_kit/client/v2.0.1/auth/token_payload_test.rb +34 -21
  23. data/lib/davinci_crd_test_kit/client/v2.0.1/cds-services-v201.json +20 -20
  24. data/lib/davinci_crd_test_kit/client/v2.0.1/client_appointment_book_group.rb +12 -10
  25. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_discharge_group.rb +12 -10
  26. data/lib/davinci_crd_test_kit/client/v2.0.1/client_encounter_start_group.rb +12 -10
  27. data/lib/davinci_crd_test_kit/client/v2.0.1/client_hooks_group.rb +5 -5
  28. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_dispatch_group.rb +12 -10
  29. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_select_group.rb +12 -10
  30. data/lib/davinci_crd_test_kit/client/v2.0.1/client_order_sign_group.rb +12 -10
  31. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_coverage_information.rb +3 -36
  32. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_external_reference.rb +3 -36
  33. data/lib/davinci_crd_test_kit/client/v2.0.1/must_support/client_card_must_support_instructions.rb +3 -36
  34. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_fetched_data_test.rb +4 -13
  35. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_request/hook_request_prefetch_equals_queried_test.rb +3 -13
  36. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/client_display_cards_attest.rb +3 -13
  37. data/lib/davinci_crd_test_kit/client/v2.0.1/verify_response/inferno_response_validation.rb +3 -13
  38. data/lib/davinci_crd_test_kit/client/v2.2.1/api/client_coverage_info_update_test.rb +1 -1
  39. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/attestation_instructions.rb +10 -0
  40. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_authorized_scope_attestation_test.rb +45 -0
  41. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_coverage_based_invocation_attestation_test.rb +44 -0
  42. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_data_element_expectations_attestation_test.rb +44 -0
  43. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_hook_invocation_logging_attestation_test.rb +43 -0
  44. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_exposure_attestation_test.rb +43 -0
  45. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_must_support_use_attestation_test.rb +41 -0
  46. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_order_sign_support_attestation_test.rb +42 -0
  47. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_prefetch_key_omission_attestation_test.rb +42 -0
  48. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_resource_identifiers_attestation_test.rb +43 -0
  49. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_response_case_distinction_attestation_test.rb +43 -0
  50. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_security_and_privacy_attestation_test.rb +43 -0
  51. data/lib/davinci_crd_test_kit/client/v2.2.1/attestations/client_workflow_integration_attestation_test.rb +44 -0
  52. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/decode_auth_token_test.rb +1 -1
  53. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/retrieve_jwks_test.rb +29 -18
  54. data/lib/davinci_crd_test_kit/client/v2.2.1/auth/token_payload_test.rb +35 -22
  55. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-prefetch-subset-v221.json +47 -47
  56. data/lib/davinci_crd_test_kit/client/v2.2.1/cds-services-v221.json +50 -50
  57. data/lib/davinci_crd_test_kit/client/v2.2.1/client_appointment_book_group.rb +24 -15
  58. data/lib/davinci_crd_test_kit/client/v2.2.1/client_attestations_group.rb +49 -0
  59. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_group.rb +22 -6
  60. data/lib/davinci_crd_test_kit/client/v2.2.1/client_cross_hook_interaction_group.rb +98 -0
  61. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_discharge_group.rb +19 -11
  62. data/lib/davinci_crd_test_kit/client/v2.2.1/client_encounter_start_group.rb +19 -11
  63. data/lib/davinci_crd_test_kit/client/v2.2.1/client_fhir_api_group.rb +14 -12
  64. data/lib/davinci_crd_test_kit/client/v2.2.1/client_hooks_group.rb +25 -5
  65. data/lib/davinci_crd_test_kit/client/v2.2.1/client_long_running_hook_group.rb +3 -2
  66. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_dispatch_group.rb +23 -15
  67. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_select_group.rb +19 -11
  68. data/lib/davinci_crd_test_kit/client/v2.2.1/client_order_sign_group.rb +23 -15
  69. data/lib/davinci_crd_test_kit/client/v2.2.1/client_registration_group.rb +6 -0
  70. data/lib/davinci_crd_test_kit/client/v2.2.1/client_scenarios_group.rb +26 -0
  71. data/lib/davinci_crd_test_kit/client/v2.2.1/client_self_pay_hook_group.rb +36 -0
  72. data/lib/davinci_crd_test_kit/client/v2.2.1/client_unknown_content_hook_group.rb +34 -0
  73. data/lib/davinci_crd_test_kit/client/v2.2.1/crd_client_suite.rb +24 -3
  74. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_card_must_support_coverage_information_test.rb +1 -1
  75. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_fhirpath_collection_as_comma_delimited_string_test.rb +4 -11
  76. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_hook_instances_unique_test.rb +1 -9
  77. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_location_address_propagation_test.rb +6 -2
  78. data/lib/davinci_crd_test_kit/client/v2.2.1/cross_hook/client_prefetch_complete_and_subset_test.rb +3 -12
  79. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/appointment_book_receive_request_test.rb +8 -89
  80. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/base_hook_invocation_receive_request_test.rb +163 -0
  81. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/cross_hooks_receive_request_test.rb +97 -0
  82. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_discharge_receive_request_test.rb +8 -90
  83. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/encounter_start_receive_request_test.rb +8 -90
  84. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_dispatch_receive_request_test.rb +8 -98
  85. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_select_receive_request_test.rb +8 -98
  86. data/lib/davinci_crd_test_kit/client/v2.2.1/invocation/order_sign_receive_request_test.rb +8 -98
  87. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_long_running_receive_request_test.rb +10 -2
  88. data/lib/davinci_crd_test_kit/client/v2.2.1/long_running/client_skip_long_running_attestation_test.rb +11 -6
  89. data/lib/davinci_crd_test_kit/client/v2.2.1/registration/client_service_registration_attestation_test.rb +2 -1
  90. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_no_request_test.rb +30 -0
  91. data/lib/davinci_crd_test_kit/client/v2.2.1/self_pay/client_self_pay_workflow_test.rb +55 -0
  92. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_attestation_test.rb +99 -0
  93. data/lib/davinci_crd_test_kit/client/v2.2.1/unknown_content/client_unknown_content_receive_request_test.rb +62 -0
  94. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_conformance_test.rb +1 -1
  95. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_coverage_verification_test.rb +10 -2
  96. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_data_fetch_verification_test.rb +1 -1
  97. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_granted_scopes_test.rb +1 -1
  98. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_complete_test.rb +7 -1
  99. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_prefetch_profiles_test.rb +1 -1
  100. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_requested_version_test.rb +1 -1
  101. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_request/hook_request_secured_transport_test.rb +1 -1
  102. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/client_display_cards_attest.rb +54 -21
  103. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/hook_response_support_coverage_information_test.rb +1 -1
  104. data/lib/davinci_crd_test_kit/client/v2.2.1/verify_response/inferno_response_validation.rb +6 -3
  105. data/lib/davinci_crd_test_kit/cross_suite/cards_identification.rb +18 -0
  106. data/lib/davinci_crd_test_kit/cross_suite/logical_models_override_helper.rb +30 -13
  107. data/lib/davinci_crd_test_kit/cross_suite/prefetch_completeness_checker.rb +25 -11
  108. data/lib/davinci_crd_test_kit/cross_suite/prefetch_profile_validation.rb +2 -0
  109. data/lib/davinci_crd_test_kit/cross_suite/requests_logical_model_validation.rb +28 -28
  110. data/lib/davinci_crd_test_kit/cross_suite/response_logical_model_validation.rb +211 -43
  111. data/lib/davinci_crd_test_kit/cross_suite/tags.rb +7 -0
  112. data/lib/davinci_crd_test_kit/requirements/cds-hooks_3.0.0-ballot_requirements.xlsx +0 -0
  113. data/lib/davinci_crd_test_kit/requirements/davinci_crd_test_kit_requirements.csv +6 -2
  114. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_requirements_coverage.csv +107 -107
  115. data/lib/davinci_crd_test_kit/requirements/generated/crd_client_v221_requirements_coverage.csv +101 -97
  116. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_requirements_coverage.csv +18 -18
  117. data/lib/davinci_crd_test_kit/requirements/generated/crd_server_v221_requirements_coverage.csv +43 -39
  118. data/lib/davinci_crd_test_kit/requirements/hl7.fhir.us.davinci-crd_2.2.1_requirements.xlsx +0 -0
  119. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook.rb +3 -3
  120. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook_no_special_requests.rb +17 -0
  121. data/lib/davinci_crd_test_kit/server/jobs/invoke_hook_with_bad_auth.rb +14 -0
  122. data/lib/davinci_crd_test_kit/server/server_abstract_invoke_hook_test.rb +24 -6
  123. data/lib/davinci_crd_test_kit/server/server_hook_helper.rb +2 -2
  124. data/lib/davinci_crd_test_kit/server/v2.0.1/crd_server_suite.rb +1 -1
  125. data/lib/davinci_crd_test_kit/server/v2.0.1/server_hooks_group.rb +5 -5
  126. data/lib/davinci_crd_test_kit/server/v2.2.1/crd_server_suite.rb +3 -0
  127. data/lib/davinci_crd_test_kit/server/v2.2.1/discovery/discovery_standard_prefetch_expressions_test.rb +165 -0
  128. data/lib/davinci_crd_test_kit/server/v2.2.1/interaction/server_invoke_hook_single_request_test.rb +22 -0
  129. data/lib/davinci_crd_test_kit/server/v2.2.1/server_appointment_book_group.rb +10 -102
  130. data/lib/davinci_crd_test_kit/server/v2.2.1/server_demonstrate_hook_response_group.rb +3 -2
  131. data/lib/davinci_crd_test_kit/server/v2.2.1/server_discovery_group.rb +2 -0
  132. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_discharge_group.rb +10 -97
  133. data/lib/davinci_crd_test_kit/server/v2.2.1/server_encounter_start_group.rb +10 -97
  134. data/lib/davinci_crd_test_kit/server/v2.2.1/server_hooks_group.rb +5 -5
  135. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_dispatch_group.rb +10 -103
  136. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_select_group.rb +12 -116
  137. data/lib/davinci_crd_test_kit/server/v2.2.1/server_order_sign_group.rb +12 -121
  138. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_response_group.rb +23 -0
  139. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/coverage_info_reason_test.rb +108 -0
  140. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/coverage_not_found_group.rb +116 -0
  141. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/no_active_coverage_group.rb +115 -0
  142. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/no_member_found_group.rb +117 -0
  143. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/technical_issues_group.rb +121 -0
  144. data/lib/davinci_crd_test_kit/server/v2.2.1/specific_coverage_responses/technical_issues_invoke_test.rb +22 -0
  145. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_context_validation_test.rb +1 -0
  146. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_optional_fields_validation_test.rb +1 -0
  147. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_request/service_request_required_fields_validation_test.rb +1 -0
  148. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/additional_orders_validation_test.rb +78 -35
  149. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_card_absence_test.rb +76 -0
  150. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_validation_test.rb +70 -124
  151. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/create_or_update_coverage_info_response_validation_test.rb +83 -39
  152. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/external_reference_card_validation_test.rb +59 -17
  153. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/form_completion_response_validation_test.rb +80 -45
  154. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/hook_request_resource_resolution.rb +37 -0
  155. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/instructions_card_received_test.rb +57 -6
  156. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/launch_smart_app_card_validation_test.rb +56 -17
  157. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/propose_alternate_request_card_validation_test.rb +52 -25
  158. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/service_response_validation_test.rb +17 -51
  159. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_cds_hooks_elements_test.rb +1 -1
  160. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_configuration_test.rb +1 -1
  161. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/unknown_context_test.rb +1 -1
  162. data/lib/davinci_crd_test_kit/version.rb +2 -2
  163. metadata +54 -16
  164. data/config/presets/inferno_crd_server_suite_v221.json.erb +0 -94
  165. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/card_optional_fields_validation_test.rb +0 -57
  166. data/lib/davinci_crd_test_kit/server/v2.2.1/verify_response/coverage_information_system_action_received_test.rb +0 -65
@@ -29,6 +29,12 @@ module DaVinciCRDTestKit
29
29
  During this test, Inferno will verify that each hook request body includes a `prefetch` field populated with
30
30
  valid JSON that contains exactly the requested prefetch keys and data sets described in the service description
31
31
  for the invoked service as calculated by Inferno based on the resources provided in the request.
32
+
33
+ Note that Inferno requires **all** requested prefetch keys to be present in the request even if there is no
34
+ data for that key. This because the [CDS Hook specification requires](https://cds-hooks.hl7.org/2026Jan/en/#example-of-pagination-in-prefetch)
35
+ that if "the CDS Client has no data to populate a template prefetch key, the prefetch template key MUST have a
36
+ value of null." For templates that request the results of a FHIR search interaction, an empty Bundle is also
37
+ acceptable.
32
38
  )
33
39
  verifies_requirements 'cds-hooks_3.0.0-ballot@30', 'cds-hooks_3.0.0-ballot@231', 'cds-hooks_3.0.0-ballot@45',
34
40
  'cds-hooks_3.0.0-ballot@46', 'cds-hooks_3.0.0-ballot@47', 'cds-hooks_3.0.0-ballot@232',
@@ -83,7 +89,7 @@ module DaVinciCRDTestKit
83
89
  end
84
90
 
85
91
  run do
86
- hook_requests = load_hook_requests
92
+ hook_requests = load_interaction_group_requests
87
93
 
88
94
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
89
95
 
@@ -34,7 +34,7 @@ module DaVinciCRDTestKit
34
34
  'hl7.fhir.us.davinci-crd_2.2.1@prof-10', 'hl7.fhir.us.davinci-crd_2.2.1@prof-11',
35
35
  'hl7.fhir.us.davinci-crd_2.2.1@prof-12'
36
36
  run do
37
- hook_requests = load_hook_requests
37
+ hook_requests = load_interaction_group_requests
38
38
 
39
39
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
40
40
 
@@ -21,7 +21,7 @@ module DaVinciCRDTestKit
21
21
  'hl7.fhir.us.davinci-crd_2.2.1@dev-12'
22
22
 
23
23
  run do
24
- hook_requests = load_hook_requests
24
+ hook_requests = load_interaction_group_requests
25
25
 
26
26
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
27
27
 
@@ -18,7 +18,7 @@ module DaVinciCRDTestKit
18
18
  'hl7.fhir.us.davinci-crd_2.2.1@sec-2'
19
19
 
20
20
  run do
21
- hook_requests = load_hook_requests
21
+ hook_requests = load_interaction_group_requests
22
22
 
23
23
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
24
24
 
@@ -10,40 +10,73 @@ module DaVinciCRDTestKit
10
10
  include DaVinciCRDTestKit::TaggedRequestLoadHelper
11
11
 
12
12
  id :crd_v221_card_display_attest_test
13
- title 'Client displays returned decision support details to the user (Attestation)'
13
+ title 'Client displays returned decision support details to the user'
14
14
  description %(
15
15
  During this test, the tester will confirm that the received cards and actions in the
16
16
  hook responses have been displayed or otherwise made available to users of the client system
17
17
  in an appopriate way that allows for consideration and action if warranted.
18
18
  )
19
+ attestation
20
+
21
+ verifies_requirements 'hl7.fhir.us.davinci-crd_2.2.1@resp-49'
19
22
 
20
23
  def responded_card_types
21
- list_card_types_in_requests(requests)
24
+ @responded_card_types ||= list_card_types_in_requests(requests)
22
25
  end
23
26
 
24
- def format_responded_response_types
25
- responded_card_types
27
+ def format_responded_response_types(response_types = responded_card_types)
28
+ response_types
26
29
  .map do |response_type|
27
- response_type_string =
28
- response_type.split('_')
29
- .map(&:capitalize)
30
- .join(' ')
30
+ DaVinciCRDTestKit::CardsIdentification.humanize_response_type(response_type)
31
31
  .prepend('- ')
32
- .sub('Smart', 'SMART')
33
- .sub('Create Update', 'Create/Update')
34
- .sub('Companions Prerequisites', 'Companions/Prerequisites')
35
- .sub('Card', '(card)')
36
- .sub('Action', '(systemAction)')
37
- response_type_string
32
+ .sub(' Card', '')
33
+ .sub(' Action', '')
34
+ end.join("\n")
35
+ end
36
+
37
+ def format_responded_card_types
38
+ @format_responded_card_types ||=
39
+ format_responded_response_types(responded_card_types.select do |type|
40
+ type.end_with?('_card')
41
+ end)
42
+ end
43
+
44
+ def format_responded_system_action_types
45
+ @format_responded_system_action_types ||=
46
+ format_responded_response_types(responded_card_types.select do |type|
47
+ type.end_with?('_action')
48
+ end)
49
+ end
50
+
51
+ CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX = "\n\n By attesting true, ".freeze
52
+ CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX = 'you confirm that the following '.freeze
53
+ CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_CONNECTOR = "\n\n Additionally, ".freeze
54
+ CARD_AND_SYSTEM_ACTION_DISPLAY_CARD_CLAUSE =
55
+ 'card types observed in Inferno\'s responses were displayed to users:'.freeze
56
+ CARD_AND_SYSTEM_ACTION_DISPLAY_SYSTEM_ACTION_CLAUSE =
57
+ 'system action types observed in Inferno\'s responses were displayed to users ' \
58
+ 'through standard card display, icons, flyovers, or another alternate mechanism:'.freeze
59
+
60
+ def card_and_system_action_details_display
61
+ if format_responded_card_types.present? && format_responded_system_action_types.present?
62
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
63
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_CARD_CLAUSE}\n#{format_responded_card_types}" \
64
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_CONNECTOR}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
65
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_SYSTEM_ACTION_CLAUSE}\n#{format_responded_system_action_types}"
66
+ elsif format_responded_card_types.present?
67
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
68
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_CARD_CLAUSE}\n#{format_responded_card_types}"
69
+ else
70
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_PREFIX}#{CARD_AND_SYSTEM_ACTION_DISPLAY_CLAUSE_PREFIX}" \
71
+ "#{CARD_AND_SYSTEM_ACTION_DISPLAY_SYSTEM_ACTION_CLAUSE}\n#{format_responded_system_action_types}"
38
72
  end
39
- .join("\n")
40
73
  end
41
74
 
42
75
  output :attest_true_url
43
76
  output :attest_false_url
44
77
 
45
78
  run do
46
- load_hook_requests
79
+ load_interaction_group_requests
47
80
  skip_if responded_card_types.blank?, 'No responses sent to the client.'
48
81
 
49
82
  identifier = SecureRandom.hex(32)
@@ -56,12 +89,12 @@ module DaVinciCRDTestKit
56
89
  message: <<~MESSAGE
57
90
  **Card Display Attestation**:
58
91
 
59
- I attest that the following CRD response types returned by Inferno's simulated
60
- CRD servers were processed by the client system and displayed or otherwise made
61
- available to users of the client system in an appropriate way that allows for
62
- consideration and action if warranted:
92
+ I attest that the simulated CRD responses returned by Inferno during this
93
+ group were successfully processed by the client system and that they were
94
+ made available to users of the system in an appropriate way that allowed for
95
+ user review and action if warranted.
63
96
 
64
- #{format_responded_response_types}
97
+ #{card_and_system_action_details_display}
65
98
 
66
99
  [Click here](#{attest_true_url}) if the above statement is **true**.
67
100
 
@@ -18,7 +18,7 @@ module DaVinciCRDTestKit
18
18
  DESCRIPTION
19
19
 
20
20
  run do
21
- hook_requests = load_hook_requests
21
+ hook_requests = load_interaction_group_requests
22
22
  skip_if hook_requests.blank?, "No #{hook_name} hook requests received."
23
23
 
24
24
  sorted_cards = sorted_cards_from_requests(hook_requests)
@@ -14,6 +14,8 @@ module DaVinciCRDTestKit
14
14
  During this test, Inferno will verify that each hook response built by Inferno's simulated CRD servers
15
15
  and returned to the client conforms to CDS Hooks and CRD requirements. These responses must be conformant
16
16
  for the client to demonstrate its ability to accept and process valid CRD responses.
17
+ To prevent the injection of non-standard information into Inferno responses, these validations will
18
+ strictly check that all extensions are allowed at the locations they appear by the CRD specification.
17
19
 
18
20
  If this test fails when the tester provided a custom response template, adjust the
19
21
  provided template and the submitted requests so that the response built by Inferno
@@ -29,7 +31,7 @@ module DaVinciCRDTestKit
29
31
  simulation_verification
30
32
 
31
33
  verifies_requirements 'cds-hooks_3.0.0-ballot@3', 'cds-hooks_3.0.0-ballot@222', 'cds-hooks_3.0.0-ballot@223',
32
- 'cds-hooks_3.0.0-ballot@224'
34
+ 'cds-hooks_3.0.0-ballot@224', 'hl7.fhir.us.davinci-crd_2.2.1@conf-10'
33
35
 
34
36
  input :custom_response_template, optional: true
35
37
 
@@ -45,7 +47,7 @@ module DaVinciCRDTestKit
45
47
  end
46
48
 
47
49
  run do
48
- load_hook_requests
50
+ load_interaction_group_requests
49
51
 
50
52
  skip_if request.blank?, "No #{response_label.downcase}s to verify."
51
53
 
@@ -62,7 +64,8 @@ module DaVinciCRDTestKit
62
64
  response_hash['systemActions'],
63
65
  request_hash,
64
66
  index,
65
- '2.2.1')
67
+ '2.2.1',
68
+ validator: :no_custom_extensions)
66
69
  rescue JSON::ParserError
67
70
  next
68
71
  end
@@ -35,6 +35,15 @@ module DaVinciCRDTestKit
35
35
  NutritionOrder ServiceRequest VisionPrescription
36
36
  ].freeze
37
37
 
38
+ def self.humanize_response_type(response_type)
39
+ response_type.split('_')
40
+ .map(&:capitalize)
41
+ .join(' ')
42
+ .sub('Smart', 'SMART')
43
+ .sub('Create Update', 'Create/Update')
44
+ .sub('Companions Prerequisites', 'Companions/Prerequisites')
45
+ end
46
+
38
47
  def identify_card_type(card) # rubocop:disable Metrics/CyclomaticComplexity
39
48
  return nil if card['type'].present? # action, not a card
40
49
  return ADDITIONAL_ORDERS_RESPONSE_TYPE if additional_orders_response_type?(card)
@@ -88,6 +97,14 @@ module DaVinciCRDTestKit
88
97
  [cards, actions]
89
98
  end
90
99
 
100
+ def coverage_info_system_action_response?(response_body)
101
+ return false unless response_body.is_a?(Hash)
102
+
103
+ return false unless response_body['systemActions'].is_a?(Array)
104
+
105
+ response_body['systemActions'].any? { |action| coverage_information_response_type?(action) }
106
+ end
107
+
91
108
  def coverage_info_response?(response_body)
92
109
  cards, actions = coverage_info_content(response_body)
93
110
 
@@ -123,6 +140,7 @@ module DaVinciCRDTestKit
123
140
 
124
141
  def coverage_information_response_type?(action) # rubocop:disable Metrics/CyclomaticComplexity
125
142
  return false unless action.respond_to?(:[])
143
+ return false unless action['type'] == 'update'
126
144
 
127
145
  resource = action['resource']&.to_hash
128
146
  return false unless COVERAGE_INFO_EXPECTED_RESOURCE_TYPES.include? resource&.dig('resourceType')
@@ -13,27 +13,44 @@ module DaVinciCRDTestKit
13
13
  end
14
14
  end
15
15
 
16
+ def filter_logical_model_extension_issues(issues, validator)
17
+ return if validator == :no_custom_extensions
18
+
19
+ issues.each { |issue| issue.filtered = issue.filtered || logical_model_extension_issue?(issue) }
20
+ end
21
+
22
+ # Logical models validation doesn't currently respect the `"extensions": ["any"]`
23
+ # validator flag
24
+ def logical_model_extension_issue?(issue)
25
+ issue.message.match(/\.extension: Unrecognized property/).present?
26
+ end
27
+
16
28
  # -------------------------------------------------------------------------
17
29
  # Check resource conformance outside the logical models
18
30
  # -------------------------------------------------------------------------
19
31
 
20
- def check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver)
21
- check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Coverage)
32
+ def check_resource_conformance_to_coverage_profile(resource_hash, error_prefix, ig_semver, validator: :default)
33
+ check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Coverage, validator:)
22
34
  end
23
35
 
24
- def check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver)
25
- check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Task)
36
+ def check_resource_conformance_to_questionnaire_task_profile(resource_hash, error_prefix, ig_semver,
37
+ validator: :default)
38
+ check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, FHIR::Task, validator:)
26
39
  end
27
40
 
28
- def check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver)
41
+ def check_resource_conformance_to_order_or_encounter_profile(resource_hash, request_body, error_prefix, ig_semver,
42
+ validator: :default)
29
43
  check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
44
+ validator:,
30
45
  allowed_types: ProfilesAndResourceTypes::ORDER_OR_ENCOUNTER_RESOURCE_CLASSES,
31
46
  disallowed_message: 'is not allowed as a target ' \
32
47
  'for a coverage-information action')
33
48
  end
34
49
 
35
- def check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver)
50
+ def check_resource_conformance_to_order_profile(resource_hash, request_body, error_prefix, ig_semver,
51
+ validator: :default)
36
52
  check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
53
+ validator:,
37
54
  allowed_types: ProfilesAndResourceTypes::ORDER_RESOURCE_CLASSES,
38
55
  disallowed_message: 'is not allowed for CRD orders')
39
56
  end
@@ -51,11 +68,11 @@ module DaVinciCRDTestKit
51
68
  yield resource
52
69
  end
53
70
 
54
- def check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class)
71
+ def check_resource_type_and_validate(resource_hash, error_prefix, ig_semver, expected_class, validator: :default)
55
72
  parse_action_resource(resource_hash, error_prefix) do |resource|
56
73
  if resource.is_a?(expected_class)
57
74
  resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
58
- message_prefix: error_prefix)
75
+ message_prefix: error_prefix, validator:)
59
76
  else
60
77
  add_message('error', "#{error_prefix}found resource type '#{resource.resourceType}' " \
61
78
  "expected '#{expected_class.name.split('::').last}'.")
@@ -64,14 +81,14 @@ module DaVinciCRDTestKit
64
81
  end
65
82
 
66
83
  def check_order_like_resource_conformance(resource_hash, request_body, error_prefix, ig_semver,
67
- allowed_types:, disallowed_message:)
84
+ allowed_types:, disallowed_message:, validator: :default)
68
85
  parse_action_resource(resource_hash, error_prefix) do |resource|
69
86
  case resource
70
87
  when FHIR::Appointment
71
- check_appointment_conformance(resource, request_body, error_prefix, ig_semver)
88
+ check_appointment_conformance(resource, request_body, error_prefix, ig_semver, validator:)
72
89
  when *allowed_types
73
90
  resource_is_valid?(resource:, profile_url: structure_definition_map(ig_semver)[resource.resourceType],
74
- message_prefix: error_prefix)
91
+ message_prefix: error_prefix, validator:)
75
92
  else
76
93
  add_message('error', "#{error_prefix}resource type '#{resource.resourceType}' #{disallowed_message}.")
77
94
  end
@@ -82,7 +99,7 @@ module DaVinciCRDTestKit
82
99
  # Appointment conformance (requires extra help to decide profile and check profile-based slicing)
83
100
  # -------------------------------------------------------------------------
84
101
 
85
- def check_appointment_conformance(appointment, request_body, error_prefix, ig_semver)
102
+ def check_appointment_conformance(appointment, request_body, error_prefix, ig_semver, validator: :default)
86
103
  target_appointment_profile =
87
104
  if appointment.basedOn.present?
88
105
  'http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-appointment-with-order'
@@ -92,7 +109,7 @@ module DaVinciCRDTestKit
92
109
 
93
110
  validation_issues = []
94
111
  resource_is_valid?(resource: appointment, profile_url: "#{target_appointment_profile}|#{ig_semver}",
95
- add_messages_to_runnable: false, validator_response_details: validation_issues)
112
+ add_messages_to_runnable: false, validator_response_details: validation_issues, validator:)
96
113
 
97
114
  manually_check_appointment_validation_errors(validation_issues, appointment, request_body)
98
115
  .each do |issue|
@@ -172,12 +172,16 @@ module DaVinciCRDTestKit
172
172
  end
173
173
 
174
174
  check_is_fhir_resource(prefetched_value, target_resource_type: 'Bundle')
175
- unless prefetched_value['entry'].size == 1
176
- errors << "#{error_prefix} exactly one Coverage must be provided."
177
- return
178
- end
175
+ if prefetched_value['resourceType'] == 'Bundle'
176
+ unless prefetched_value['entry']&.size == 1
177
+ errors << "#{error_prefix} exactly one Coverage must be provided."
178
+ return
179
+ end
179
180
 
180
- check_coverage(prefetched_value.dig('entry', 0, 'resource'))
181
+ check_coverage(prefetched_value.dig('entry', 0, 'resource'))
182
+ elsif prefetched_value['resourceType'] == 'Coverage'
183
+ check_coverage(prefetched_value)
184
+ end
181
185
  end
182
186
 
183
187
  def check_coverage(prefetched_coverage)
@@ -197,16 +201,26 @@ module DaVinciCRDTestKit
197
201
  "expected active, got #{prefetched_coverage['status']}."
198
202
  end
199
203
 
200
- target_patient_id = hook_request.dig('context', 'patientId')
201
- unless prefetched_coverage.dig('beneficiary', 'reference') == "Patient/#{target_patient_id}"
202
- errors << "#{error_prefix} prefetched Coverage has an unexpected beneficiary reference: " \
203
- "expected Patient/#{target_patient_id}, got #{prefetched_coverage.dig('beneficiary',
204
- 'reference')}."
205
- end
204
+ check_coverage_beneficiary(prefetched_coverage)
206
205
 
207
206
  nil
208
207
  end
209
208
 
209
+ def check_coverage_beneficiary(prefetched_coverage)
210
+ target_patient_id = hook_request.dig('context', 'patientId')
211
+ patient_id = target_patient_id&.delete_prefix('Patient/')
212
+ relative_ref = "Patient/#{patient_id}"
213
+ fhir_server = hook_request['fhirServer']&.chomp('/')
214
+ absolute_ref = "#{fhir_server}/#{relative_ref}" if fhir_server.present?
215
+
216
+ actual_ref = prefetched_coverage.dig('beneficiary', 'reference')
217
+ return if actual_ref == relative_ref || (absolute_ref && actual_ref == absolute_ref)
218
+
219
+ expected_refs = [relative_ref, absolute_ref].compact.join(' or ')
220
+ errors << "#{error_prefix} prefetched Coverage has an unexpected beneficiary reference: " \
221
+ "expected #{expected_refs}, got #{actual_ref}."
222
+ end
223
+
210
224
  def check_read(prefetched_value, instantiated_request)
211
225
  resource_type, resource_id = instantiated_request.split('/')
212
226
 
@@ -34,6 +34,8 @@ module DaVinciCRDTestKit
34
34
  profile_url: target_crd_profile,
35
35
  validator_response_details: validation_details, add_messages_to_runnable: false)
36
36
  validation_details.each do |issue|
37
+ next if issue.filtered
38
+
37
39
  prefix = prefetch_profile_error_prefix(request_index, bundle_entry_index)
38
40
  add_message(issue.severity, "#{prefix}#{issue.message}")
39
41
  end
@@ -13,15 +13,10 @@ module DaVinciCRDTestKit
13
13
  'Practitioner', 'PractitionerRole'
14
14
  ].freeze
15
15
 
16
- def validate_request_against_logical_model(request_body, request_index, ig_semver)
17
- if ig_semver == '2.2.1'
18
- check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver)
19
- else
20
- conforms_to_logical_model?(request_body, "#{CRD_CDS_HOOK_REQUEST_MODEL_URL}|#{ig_semver}",
21
- message_prefix: "(Request #{request_index + 1}) ")
22
- end
16
+ def validate_request_against_logical_model(request_body, request_index, ig_semver, validator: :default)
17
+ check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver, validator:)
23
18
 
24
- perform_version_specific_additional_verification(request_body, request_index, ig_semver)
19
+ perform_version_specific_additional_verification(request_body, request_index, ig_semver, validator:)
25
20
  end
26
21
 
27
22
  private
@@ -30,83 +25,88 @@ module DaVinciCRDTestKit
30
25
  # Additional Validation to cover areas not checked or checked incorrectly by the logical models
31
26
  # -------------------------------------------------------------------------
32
27
 
33
- def perform_version_specific_additional_verification(request_body, request_index, ig_semver)
28
+ def perform_version_specific_additional_verification(request_body, request_index, ig_semver, validator: :default)
34
29
  case ig_semver
35
30
  when '2.2.1'
36
- perform_v221_additional_verification(request_body, request_index)
31
+ perform_v221_additional_verification(request_body, request_index, validator:)
37
32
  end
38
33
  end
39
34
 
40
- def perform_v221_additional_verification(request_body, request_index)
35
+ def perform_v221_additional_verification(request_body, request_index, validator: :default)
41
36
  check_context_resources_for_ids(request_body, request_index)
42
37
  check_relative_references(request_body, request_index)
43
- check_context_resource_profiles(request_body, request_index, '2.2.1')
38
+ check_context_resource_profiles(request_body, request_index, '2.2.1', validator:)
44
39
  end
45
40
 
46
41
  # -------------------------------------------------------------------------
47
42
  # Context resources profile check (not working in v2.2.1 logical models)
48
43
  # -------------------------------------------------------------------------
49
44
 
50
- def check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver)
45
+ def check_logical_model_conformance_no_resource_checks(request_body, request_index, ig_semver, validator: :default)
51
46
  validation_issues = []
52
47
  conforms_to_logical_model?(request_body, "#{CRD_CDS_HOOK_REQUEST_MODEL_URL}|#{ig_semver}",
53
- add_messages_to_runnable: false, validator_response_details: validation_issues)
48
+ add_messages_to_runnable: false, validator_response_details: validation_issues,
49
+ validator:)
54
50
 
55
- reject_resource_issues(validation_issues).each do |issue|
51
+ filtered_issues = reject_resource_issues(validation_issues)
52
+ filter_logical_model_extension_issues(filtered_issues, validator)
53
+ filtered_issues.each do |issue|
56
54
  next if issue.filtered
57
55
 
58
56
  add_message(issue.severity, "(Request #{request_index + 1}) #{issue.message}")
59
57
  end
60
58
  end
61
59
 
62
- def check_context_resource_profiles(request_body, request_index, ig_semver)
60
+ def check_context_resource_profiles(request_body, request_index, ig_semver, validator: :default)
63
61
  case request_body['hook']
64
62
  when 'order-sign', 'order-select'
65
- draft_orders_conform_to_profiles?(request_body, request_index, ig_semver)
63
+ draft_orders_conform_to_profiles?(request_body, request_index, ig_semver, validator:)
66
64
  when 'order-dispatch'
67
65
  request_body.dig('context', 'fulfillmentTasks')&.each_with_index do |task, index|
68
66
  resource = FHIR.from_contents(task.to_json)
69
67
  resource_is_valid?(resource:, profile_url: "http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-task-dispatch|#{ig_semver}",
70
- message_prefix: "(Request #{request_index + 1}) " \
71
- "context.fulfillmentTasks entry #{index + 1} - ")
68
+ validator:, message_prefix: "(Request #{request_index + 1}) " \
69
+ "context.fulfillmentTasks entry #{index + 1} - ")
72
70
  end
73
71
  when 'appointment-book'
74
- check_appointments_profiles(request_body, request_index, ig_semver)
72
+ check_appointments_profiles(request_body, request_index, ig_semver, validator:)
75
73
  end
76
74
  rescue JSON::ParserError
77
75
  nil # no resource to validate - error found elsewhere
78
76
  end
79
77
 
80
- def draft_orders_conform_to_profiles?(request_body, request_index, ig_semver)
81
- resource = FHIR.from_contents(request_body.dig('context', 'draftOrders')&.to_json)
78
+ def draft_orders_conform_to_profiles?(request_body, request_index, ig_semver, validator: :default)
79
+ draft_orders = request_body.dig('context', 'draftOrders').to_json
80
+ resource = FHIR.from_contents(draft_orders)
82
81
  resource_is_valid?(resource:, profile_url: "http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-bundle-request|#{ig_semver}",
83
- message_prefix: "(Request #{request_index + 1}) context.draftOrders - ")
82
+ message_prefix: "(Request #{request_index + 1}) context.draftOrders - ", validator:)
84
83
  end
85
84
 
86
85
  # -------------------------------------------------------------------------
87
86
  # Appointment conformance (requires extra help to decide profile and check profile-based slicing)
88
87
  # -------------------------------------------------------------------------
89
88
 
90
- def check_appointments_profiles(request_body, request_index, ig_semver)
89
+ def check_appointments_profiles(request_body, request_index, ig_semver, validator: :default)
91
90
  resource = FHIR.from_contents(request_body.dig('context', 'appointments')&.to_json)
92
91
  return unless resource.is_a?(FHIR::Bundle)
93
92
 
94
93
  check_bundle_non_entry_resource_conformance(resource,
95
94
  "(Request #{request_index + 1}) context.appointments - ",
96
- ig_semver)
95
+ ig_semver,
96
+ validator:)
97
97
 
98
98
  resource.entry.each_with_index do |entry, entry_index|
99
99
  next unless entry.resource.present? # error caught on Bundle validation
100
100
 
101
101
  error_prefix = "(Request #{request_index + 1}) context.appointments entry #{entry_index + 1} - "
102
- check_appointment_conformance(entry.resource, request_body, error_prefix, ig_semver)
102
+ check_appointment_conformance(entry.resource, request_body, error_prefix, ig_semver, validator:)
103
103
  end
104
104
  end
105
105
 
106
- def check_bundle_non_entry_resource_conformance(bundle, error_prefix, ig_semver)
106
+ def check_bundle_non_entry_resource_conformance(bundle, error_prefix, ig_semver, validator: :default)
107
107
  validation_issues = []
108
108
  resource_is_valid?(resource: bundle, profile_url: "http://hl7.org/fhir/us/davinci-crd/StructureDefinition/profile-bundle-base|#{ig_semver}",
109
- add_messages_to_runnable: false, validator_response_details: validation_issues)
109
+ add_messages_to_runnable: false, validator_response_details: validation_issues, validator:)
110
110
 
111
111
  reject_entry_resource_issues(validation_issues).each do |issue|
112
112
  add_message(issue.severity, "#{error_prefix}#{issue.message}")