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
@@ -39,21 +39,24 @@ module DaVinciCRDTestKit
39
39
  "#{CRD_LOGICAL_MODEL_BASE}/#{profile_name}"
40
40
  end
41
41
 
42
- def perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver)
42
+ def perform_response_logical_model_validation(cards, system_actions, request_body, response_index, ig_semver,
43
+ validator: :default)
43
44
  if cards.is_a?(Array)
44
45
  cards.each_with_index do |card, card_index|
45
- validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
46
+ validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver, validator:)
46
47
  end
47
48
  end
48
49
 
49
50
  return unless system_actions.is_a?(Array)
50
51
 
51
52
  system_actions.each_with_index do |action, action_index|
52
- validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
53
+ validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver,
54
+ validator:)
53
55
  end
54
56
  end
55
57
 
56
- def validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver)
58
+ def validate_card_against_logical_model(card, response_index, request_body, card_index, ig_semver,
59
+ validator: :default)
57
60
  label = logical_model_entity_label(response_index, card_index, 'card')
58
61
  unless card.is_a?(Hash)
59
62
  add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
@@ -71,15 +74,62 @@ module DaVinciCRDTestKit
71
74
 
72
75
  validation_issues = []
73
76
  conforms_to_logical_model?({ 'cards' => [card] }, logical_model_url(profile_name),
74
- add_messages_to_runnable: false, validator_response_details: validation_issues)
77
+ add_messages_to_runnable: false, validator_response_details: validation_issues,
78
+ validator:)
79
+
80
+ validate_summary_length(card, label)
81
+ validate_suggestion_uuid_presence(card, label) unless ['v201', '2.0.1'].include? ig_semver
82
+ validate_no_smart_suggestions(card, label) if card_type == CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE
75
83
 
76
84
  error_prefix = "#{label} (#{card_type || 'uncategorized'}): "
77
85
  filtered_issues = manually_check_card_specific_errors(card, validation_issues, card_type,
78
- request_body, error_prefix, ig_semver)
86
+ request_body, error_prefix, ig_semver, validator:)
87
+ filter_logical_model_extension_issues(filtered_issues, validator)
79
88
  add_messages_not_excluded(filtered_issues, error_prefix)
80
89
  end
81
90
 
82
- def validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver)
91
+ def validate_summary_length(card, label)
92
+ return unless card['summary'].is_a? String
93
+
94
+ summary_length = card['summary'].length
95
+
96
+ return if summary_length < 140
97
+
98
+ add_message(
99
+ 'error',
100
+ "#{label} summary length of #{summary_length} characters is longer than " \
101
+ 'the maximum allowed of 139 characters.'
102
+ )
103
+ end
104
+
105
+ def validate_suggestion_uuid_presence(card, label)
106
+ return unless card['suggestions'].presence.is_a? Array
107
+ return unless card['suggestions'].all?(Hash)
108
+
109
+ card['suggestions'].each_with_index do |suggestion, index|
110
+ next if suggestion['uuid'].present?
111
+
112
+ add_message(
113
+ 'error',
114
+ "#{label} suggestion #{index + 1} does not contain a `uuid`"
115
+ )
116
+ end
117
+ end
118
+
119
+ def validate_no_smart_suggestions(card, label)
120
+ suggestion_count = card['suggestions']&.length || 0
121
+
122
+ return if suggestion_count.zero?
123
+
124
+ add_message(
125
+ 'error',
126
+ "#{label} CDSHooksResponse.cards.suggestions: max allowed = 0, but found #{suggestion_count} " \
127
+ '(from http://hl7.org/fhir/us/davinci-crd/StructureDefinition/CRDHooksResponse-launchSMART|2.2.1)'
128
+ )
129
+ end
130
+
131
+ def validate_system_action_against_logical_model(action, response_index, request_body, action_index, ig_semver,
132
+ validator: :default)
83
133
  label = logical_model_entity_label(response_index, action_index, 'systemAction')
84
134
  unless action.is_a?(Hash)
85
135
  add_message('error', "#{label} is not a JSON object; skipping logical model validation.")
@@ -102,17 +152,116 @@ module DaVinciCRDTestKit
102
152
 
103
153
  validation_issues = []
104
154
  conforms_to_logical_model?({ 'systemActions' => [action] }, logical_model_url(profile_name),
105
- add_messages_to_runnable: false, validator_response_details: validation_issues)
155
+ add_messages_to_runnable: false, validator_response_details: validation_issues,
156
+ validator:)
106
157
 
158
+ validation_issues
159
+ .reject! do |issue|
160
+ issue.message.match?(%r{CDSHooksResponse\.systemActions\[\d+\]\.extension: The extension definition http://hl7\.org/fhir/us/davinci-crd/StructureDefinition/CDSHookServiceResponseExtensionIfNoneExist\|2\.2\.1 defines the contexts of use as}) # rubocop:disable Layout/LineLength
161
+ end
162
+
163
+ if action_type == CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
164
+ check_multiple_coverage_info_extension_conformance(action)
165
+ end
107
166
  error_prefix = "#{label} (#{action_type || 'uncategorized'}): "
108
167
  filtered_issues = manually_check_action_specific_errors(action, validation_issues, action_type,
109
- request_body, error_prefix, ig_semver)
168
+ request_body, error_prefix, ig_semver, validator:)
169
+ filter_logical_model_extension_issues(filtered_issues, validator)
110
170
  add_messages_not_excluded(filtered_issues, error_prefix)
111
171
  end
112
172
 
173
+ def check_multiple_coverage_info_extension_conformance(action)
174
+ resource = FHIR.from_contents(action['resource'].to_json)
175
+
176
+ grouped_coverage_info = extract_and_group_coverage_info(resource)
177
+ multiple_extensions_conformance_check(grouped_coverage_info, resource)
178
+ end
179
+
180
+ def extract_and_group_coverage_info(resource)
181
+ resource.extension.each_with_object({}) do |extension, grouped_extensions|
182
+ next unless extension.url == COVERAGE_INFO_EXT_URL
183
+
184
+ coverage_key = find_extension_value(extension, 'coverage', 'valueReference', 'reference')
185
+ grouped_extensions[coverage_key] ||= []
186
+ grouped_extensions[coverage_key] << extension
187
+ end
188
+ end
189
+
190
+ # For the same coverage, ensure coverage-assertion-ids and satisfied-pa-ids are the same.
191
+ # For different coverages, ensure coverage-assertion-ids and satisfied-pa-ids are distinct.
192
+ def multiple_extensions_conformance_check(grouped_coverage_info, resource)
193
+ resource_ref = "#{resource.resourceType}/#{resource.id}"
194
+ assertion_ids_across_coverages = Set.new
195
+ pa_ids_across_coverages = Set.new
196
+
197
+ grouped_coverage_info.each do |coverage, extensions|
198
+ coverage_assertion_ids = collect_extensions_id(extensions, 'coverage-assertion-id', 'valueString').uniq
199
+ satisfied_pa_ids = collect_extensions_id(extensions, 'satisfied-pa-id', 'valueString').uniq.compact
200
+ if coverage_assertion_ids.length != 1
201
+ add_message(
202
+ 'error',
203
+ same_coverage_conformance_error_msg(resource_ref, coverage, 'coverage-assertion-ids')
204
+ )
205
+ end
206
+
207
+ if satisfied_pa_ids.length > 1
208
+ add_message(
209
+ 'error',
210
+ same_coverage_conformance_error_msg(resource_ref, coverage, 'satisfied-pa-ids')
211
+ )
212
+ end
213
+
214
+ assertion_id = coverage_assertion_ids.first
215
+ if assertion_ids_across_coverages.include?(assertion_id)
216
+ add_message(
217
+ 'error',
218
+ different_coverage_conformance_error_msg(resource_ref, 'coverage-assertion-ids')
219
+ )
220
+ end
221
+
222
+ assertion_ids_across_coverages.add(assertion_id)
223
+ pa_id = satisfied_pa_ids.first
224
+ next unless pa_id
225
+
226
+ if pa_ids_across_coverages.include?(pa_id)
227
+ add_message(
228
+ 'error',
229
+ different_coverage_conformance_error_msg(resource_ref, 'satisfied-pa-ids')
230
+ )
231
+ end
232
+
233
+ pa_ids_across_coverages.add(pa_id)
234
+ end
235
+ end
236
+
237
+ def collect_extensions_id(extensions, url, *properties)
238
+ extensions.map do |extension|
239
+ find_extension_value(extension, url, *properties)
240
+ end
241
+ end
242
+
243
+ def find_extension_value(extension, url, *properties)
244
+ found_extension = extension.extension.find { |ext| ext.url == url }
245
+ return nil unless found_extension
246
+
247
+ properties.reduce(found_extension) do |current, prop|
248
+ return current unless current.respond_to?(prop)
249
+
250
+ current.send(prop)
251
+ end
252
+ end
253
+
254
+ def same_coverage_conformance_error_msg(resource_ref, coverage, id_name)
255
+ "#{resource_ref}: extension has multiple repetitions of coverage `#{coverage}` with different #{id_name}."
256
+ end
257
+
258
+ def different_coverage_conformance_error_msg(resource_ref, id_name)
259
+ "#{resource_ref}: extensions referencing differing coverage SHALL have distinct #{id_name}."
260
+ end
261
+
113
262
  def add_messages_not_excluded(issues, error_prefix)
114
263
  issues.each do |issue|
115
- next if issue.filtered || logical_model_extension_issue?(issue)
264
+ next if issue.filtered
116
265
 
117
266
  add_message(issue.severity, "#{error_prefix}#{issue.message}")
118
267
  end
@@ -126,47 +275,45 @@ module DaVinciCRDTestKit
126
275
  # Validator Filtering and Manual Checks Depending on the Card Type
127
276
  # -------------------------------------------------------------------------
128
277
 
129
- def logical_model_extension_issue?(issue)
130
- issue.message.match(/\.extension: Unrecognized property/).present?
131
- end
132
-
133
278
  def manually_check_card_specific_errors(card, validation_issues, card_type, request_body, error_prefix,
134
- ig_semver)
279
+ ig_semver, validator: :default)
135
280
  case card_type
136
- when DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
137
- manually_check_form_completion_errors(card, validation_issues, error_prefix)
138
- when DaVinciCRDTestKit::CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE
281
+ when CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
282
+ manually_check_form_completion_errors(card, validation_issues, error_prefix, validator:)
283
+ when CardsIdentification::PROPOSE_ALTERNATIVE_REQUEST_RESPONSE_TYPE
139
284
  manually_check_propose_alternative_errors(card, validation_issues, request_body,
140
- error_prefix, ig_semver)
141
- when DaVinciCRDTestKit::CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE
285
+ error_prefix, ig_semver, validator:)
286
+ when CardsIdentification::ADDITIONAL_ORDERS_RESPONSE_TYPE
142
287
  manually_check_additional_orders_errors(card, validation_issues, request_body,
143
- error_prefix, ig_semver)
288
+ error_prefix, ig_semver, validator:)
289
+ when CardsIdentification::LAUNCH_SMART_APP_RESPONSE_TYPE
290
+ manually_check_launch_smart_app_errors(validation_issues)
144
291
  else
145
292
  validation_issues
146
293
  end
147
294
  end
148
295
 
149
296
  def manually_check_action_specific_errors(action, validation_issues, action_type, request_body,
150
- error_prefix, ig_semver)
297
+ error_prefix, ig_semver, validator: :default)
151
298
  case action_type
152
299
  when DaVinciCRDTestKit::CardsIdentification::COVERAGE_INFORMATION_RESPONSE_TYPE
153
300
  manually_check_coverage_information_errors(action, validation_issues, request_body,
154
- error_prefix, ig_semver)
301
+ error_prefix, ig_semver, validator:)
155
302
  when DaVinciCRDTestKit::CardsIdentification::CREATE_OR_UPDATE_COVERAGE_RESPONSE_TYPE
156
303
  manually_check_update_coverage_action_errors(action, validation_issues,
157
- error_prefix, ig_semver)
304
+ error_prefix, ig_semver, validator:)
158
305
  when DaVinciCRDTestKit::CardsIdentification::FORM_COMPLETION_RESPONSE_TYPE
159
306
  manually_check_form_completion_action_errors(action, validation_issues,
160
- error_prefix, ig_semver)
307
+ error_prefix, ig_semver, validator:)
161
308
  else
162
309
  validation_issues
163
310
  end
164
311
  end
165
312
 
166
- def manually_check_form_completion_errors(card, validation_issues, error_prefix)
313
+ def manually_check_form_completion_errors(card, validation_issues, error_prefix, validator: :default)
167
314
  validation_issues.reject do |issue|
168
315
  if issue.message.match?(/The type 'Questionnaire' is not valid - must be Task/)
169
- check_questionnaire_actions(card, issue.message, error_prefix)
316
+ check_questionnaire_actions(card, issue.message, error_prefix, validator:)
170
317
  true
171
318
  else
172
319
  false
@@ -174,7 +321,7 @@ module DaVinciCRDTestKit
174
321
  end
175
322
  end
176
323
 
177
- def check_questionnaire_actions(card, error_message, error_prefix)
324
+ def check_questionnaire_actions(card, error_message, error_prefix, validator: :default)
178
325
  extracted_indexes =
179
326
  error_message.match(/CDSHooksResponse\.cards\[0\]\.suggestions\[(\d+)\]\.actions\[(\d+)\]\.resource/)
180
327
  unless extracted_indexes
@@ -187,43 +334,63 @@ module DaVinciCRDTestKit
187
334
 
188
335
  message_prefix = "#{error_prefix} suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
189
336
  resource = FHIR.from_contents(card['suggestions'][suggestion_index]['actions'][action_index]['resource'].to_json)
190
- resource_is_valid?(resource:, message_prefix:) # no questionnaire profile applied per CRD
337
+ resource_is_valid?(resource:, message_prefix:, validator:) # no questionnaire profile applied per CRD
191
338
 
192
339
  return if resource.id.present?
193
340
 
194
341
  add_message('error', "#{message_prefix}Questionnaire must have an id.")
195
342
  end
196
343
 
344
+ def manually_check_launch_smart_app_errors(validation_issues)
345
+ validation_issues.reject do |issue|
346
+ issue.message.match?(/CDSHooksResponse.cards.suggestions: minimum required = 1, but only found 0/)
347
+ end
348
+ end
349
+
197
350
  def manually_check_propose_alternative_errors(card, validation_issues, request_body,
198
- error_prefix, ig_semver)
351
+ error_prefix, ig_semver, validator: :default)
199
352
  no_resource_issues = manually_check_action_resources_for_order_profile_conformance(card,
200
353
  validation_issues,
201
354
  request_body,
202
355
  error_prefix,
203
- ig_semver)
356
+ ig_semver,
357
+ validator:)
358
+
359
+ no_resource_issues.each do |issue|
360
+ next unless issue.message.match?(/Constraint failed: crd-respar-1/)
361
+
362
+ new_message =
363
+ "#{issue.message}. In some cases [additional create " \
364
+ 'actions](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#propose-alternate-request-response-type) ' \
365
+ 'are permitted. Manually check the suggestion to verify whether this is an actual error.'
366
+ issue.instance_variable_set(:@severity, 'warning')
367
+ issue.instance_variable_set(:@message, new_message)
368
+ end
369
+
204
370
  no_resource_issues.reject do |issue|
205
371
  issue.message.match?(/but is fixed to 'create' in the profile/)
206
372
  end
207
373
  end
208
374
 
209
375
  def manually_check_additional_orders_errors(card, validation_issues, request_body,
210
- error_prefix, ig_semver)
376
+ error_prefix, ig_semver, validator: :default)
211
377
  manually_check_action_resources_for_order_profile_conformance(card,
212
378
  validation_issues,
213
379
  request_body,
214
380
  error_prefix,
215
- ig_semver)
381
+ ig_semver,
382
+ validator:)
216
383
  end
217
384
 
218
385
  def manually_check_action_resources_for_order_profile_conformance(card, validation_issues, request_body,
219
- error_prefix, ig_semver)
386
+ error_prefix, ig_semver, validator: :default)
220
387
  if card['suggestions'].present?
221
388
  card['suggestions'].each_with_index do |suggestion, suggestion_index|
222
389
  next unless suggestion['actions'].present?
223
390
 
224
391
  suggestion['actions'].each_with_index do |action, action_index|
225
392
  action_error_prefix = "#{error_prefix}suggestion #{suggestion_index + 1}, action #{action_index + 1} - "
226
- check_action_target(action, request_body, action_error_prefix, ig_semver)
393
+ check_action_target(action, request_body, action_error_prefix, ig_semver, validator:)
227
394
  end
228
395
  end
229
396
  end
@@ -231,36 +398,37 @@ module DaVinciCRDTestKit
231
398
  reject_resource_issues(validation_issues)
232
399
  end
233
400
 
234
- def check_action_target(action, request_body, error_prefix, ig_semver)
401
+ def check_action_target(action, request_body, error_prefix, ig_semver, validator: :default)
235
402
  local_reference?(action['resourceId'], error_prefix) if action['resourceId'].present?
236
403
  return unless action['resource'].present?
237
404
 
238
- check_resource_conformance_to_order_profile(action['resource'], request_body, error_prefix, ig_semver)
405
+ check_resource_conformance_to_order_profile(action['resource'], request_body, error_prefix, ig_semver, validator:)
239
406
  end
240
407
 
241
408
  def manually_check_coverage_information_errors(action, validation_issues, request_body,
242
- error_prefix, ig_semver)
409
+ error_prefix, ig_semver, validator: :default)
243
410
  if action['resource'].present?
244
411
  check_resource_conformance_to_order_or_encounter_profile(action['resource'], request_body,
245
- error_prefix, ig_semver)
412
+ error_prefix, ig_semver, validator:)
246
413
  end
247
414
  reject_resource_issues(validation_issues)
248
415
  end
249
416
 
250
417
  def manually_check_update_coverage_action_errors(action, validation_issues,
251
- error_prefix, ig_semver)
418
+ error_prefix, ig_semver, validator: :default)
252
419
  check_required_action_type(action, 'update', error_prefix, 'coverage update action response type')
253
420
  if action['resource'].present?
254
- check_resource_conformance_to_coverage_profile(action['resource'], error_prefix, ig_semver)
421
+ check_resource_conformance_to_coverage_profile(action['resource'], error_prefix, ig_semver, validator:)
255
422
  end
256
423
  reject_resource_issues(validation_issues)
257
424
  end
258
425
 
259
426
  def manually_check_form_completion_action_errors(action, validation_issues,
260
- error_prefix, ig_semver)
427
+ error_prefix, ig_semver, validator: :default)
261
428
  check_required_action_type(action, 'create', error_prefix, 'form completion action response type')
262
429
  if action['resource'].present?
263
- check_resource_conformance_to_questionnaire_task_profile(action['resource'], error_prefix, ig_semver)
430
+ check_resource_conformance_to_questionnaire_task_profile(action['resource'], error_prefix, ig_semver,
431
+ validator:)
264
432
  end
265
433
  reject_resource_issues(validation_issues)
266
434
  end
@@ -7,14 +7,21 @@ module DaVinciCRDTestKit
7
7
  ORDER_SIGN_TAG = 'order-sign'.freeze
8
8
  DISCOVERY_TAG = 'cds_discovery'.freeze
9
9
  ANY_HOOK_TAG = 'any_hook'.freeze
10
+ CROSS_HOOK_ANALYSIS_TAG = 'cross_hook'.freeze
10
11
  DATA_FETCH_TAG = 'data_fetch'.freeze
11
12
  PAYER_ORG_FETCH_TAG = 'payer_org'.freeze
12
13
  PARENT_LOCATION_FETCH_TAG = 'parent_location'.freeze
13
14
  HOOK_INSTANCE_TAG_PREFIX = 'hi_'.freeze
14
15
  HOOK_INSTANCE_DATA_FETCH_TAG_PREFIX = 'hi_data_fetch_'.freeze
15
16
  LONG_RUNNING_GROUP_TAG = 'long_running_request'.freeze
17
+ UNKNOWN_CONTENT_GROUP_TAG = 'unknown_content_request'.freeze
18
+ SELF_PAY_GROUP_TAG = 'self_pay_request'.freeze
16
19
  DUPLICATED_HOOK_INSTANCE_TAG = 'duplicate_hook_instance'.freeze
17
20
  COVERAGE_INFO_DISABLED_TAG = 'coverage-info-disabled'.freeze
21
+ TECHNICAL_ISSUES_TAG = 'technical-issues'.freeze
22
+ NO_MEMBER_FOUND_TAG = 'no-member-found'.freeze
23
+ COVERAGE_NOT_FOUND_TAG = 'coverage-not-found'.freeze
24
+ NO_ACTIVE_COVERAGE_TAG = 'no-active-coverage'.freeze
18
25
  UNKNOWN_CONFIGURATION_TAG = 'unknown-configuration'.freeze
19
26
  UNKNOWN_CONTEXT_TAG = 'unknown-context'.freeze
20
27
  UNKNOWN_ELEMENT_TAG = 'unknown-element'.freeze
@@ -1216,7 +1216,7 @@ hl7.fhir.us.davinci-crd_2.2.1,hook-35,https://hl7.org/fhir/us/davinci-crd/2.2.1/
1216
1216
  hl7.fhir.us.davinci-crd_2.2.1,hook-36,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-36,"Where CRD Clients have an appropriate workflow and data capture mechanism, the order-select hook **MAY** be used in scenarios that do not involve creating a true order.",MAY,CRD Client,,true,,
1217
1217
  hl7.fhir.us.davinci-crd_2.2.1,hook-37,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-37,The 'context' field in invocations of the order-sign hook **SHALL** conform to the logical model specified [here](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-CRDOrderSignContext.html),SHALL,CRD Client,,true,,
1218
1218
  hl7.fhir.us.davinci-crd_2.2.1,hook-38,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-38,CRD Servers **MAY** use the order-sign hook as a basis for associating a patient with a particular practitioner from a payer attribution perspective.,MAY,CRD Server,,false,,
1219
- hl7.fhir.us.davinci-crd_2.2.1,hook-39,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-39,"CRD clients and servers **SHALL**, at minimum, support returning and processing the [Coverage Information](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-ext-coverage-information.html) system action for all invocations of the order-select hook.",SHALL,"CRD Client, CRD Server",,false,,
1219
+ hl7.fhir.us.davinci-crd_2.2.1,hook-39,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/hooks.html#ci-c-hook-39,"CRD clients and servers **SHALL**, at minimum, support returning and processing the [Coverage Information](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-ext-coverage-information.html) system action for all invocations of the order-sign hook.",SHALL,"CRD Client, CRD Server",,false,,
1220
1220
  hl7.fhir.us.davinci-crd_2.2.1,impl-1,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/implementation.html#ci-c-impl-1,Clients that suppress 'default presumption' coverage-information of messages **SHALL** mitigate the potential for misinterpretation in the event CRD is unavailable.,SHALL,CRD Client,,false,,
1221
1221
  hl7.fhir.us.davinci-crd_2.2.1,impl-2,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/implementation.html#ci-c-impl-2,CRD servers **SHOULD** strive to achieve a minimum of 3-9s availability for their services and strive to provide at least some level of useful response to CRD clients even if some of their back-end systems are unavailable.,SHOULD,CRD Server,,false,,
1222
1222
  hl7.fhir.us.davinci-crd_2.2.1,impl-3,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/implementation.html#ci-c-impl-3,"Even if implemented using multiple components, there is still a requirement that the exchanges between the provider HIT (including any ePA coordinators) and the payer HIT (including any ePA coordinators) **SHALL** replicate all of the defined exchanges between provider and payer.",SHALL,"CRD Client, CRD Server",,false,,
@@ -1311,7 +1311,11 @@ hl7.fhir.us.davinci-crd_2.2.1,resp-69,https://hl7.org/fhir/us/davinci-crd/2.2.1/
1311
1311
  hl7.fhir.us.davinci-crd_2.2.1,resp-70,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#ci-c-resp-70,"If returning a card rather than a system action, the ""Update Coverage Records"" response type **SHOULD NOT** be returned for hook types that are likely to be triggered by clinical users rather than administrative staff. Cards of this type would be appropriate for hooks such as encounter-start or appointment-book but would not be appropriate for order-select or order-sign.",SHOULD NOT,CRD Server,,true,,
1312
1312
  hl7.fhir.us.davinci-crd_2.2.1,resp-71,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#ci-c-resp-71,Adjust Coverage responses **SHALL** comply with the logical model profile found [here](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-CRDHooksResponse-adjustCoverage.html).,SHALL,CRD Server,,false,,
1313
1313
  hl7.fhir.us.davinci-crd_2.2.1,resp-72,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/cards.html#ci-c-resp-72,Launch SMART responses **SHALL** comply with the logical model profile found [here](https://hl7.org/fhir/us/davinci-crd/2.2.1/en/StructureDefinition-CRDHooksResponse-launchSMART.html).,SHALL,CRD Server,,false,,
1314
- hl7.fhir.us.davinci-crd_2.2.1,sec-1,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-1,Implementers SHALL adhere to inherited security and privacy rules,SHALL,"CRD Client, CRD Server",,false,,
1314
+ hl7.fhir.us.davinci-crd_2.2.1,sec-1,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-1,"Implementers SHALL adhere to any security and privacy rules defined by:
1315
+ - FHIR Core: [Security & Privacy Module](http://hl7.org/fhir/R4/secpriv-module.html), [Security Principles](http://hl7.org/fhir/R4/security.html) and [Implementer's Checklist](http://hl7.org/fhir/R4/safety.html)
1316
+ - HRex: [Privacy & Security](http://hl7.org/fhir/us/davinci-hrex/1.2.0/security.html)
1317
+ - CDS Hooks: [Security & Safety](http://cds-hooks.hl7.org/2026Jan/index.html#security-and-safety)
1318
+ - SMART on FHIR: [SMART App Launch](http://www.hl7.org/fhir/smart-app-launch)",SHALL,"CRD Client, CRD Server",,false,,
1315
1319
  hl7.fhir.us.davinci-crd_2.2.1,sec-2,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-2,"As per the CDS Hooks specification, communications between CRD clients and CRD servers **SHALL** use TLS.",SHALL,"CRD Client, CRD Server",,false,,
1316
1320
  hl7.fhir.us.davinci-crd_2.2.1,sec-3,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-3,CRD servers and CRD clients **SHOULD** enforce a minimum version and other TLS configuration requirements based on [HRex rules](http://hl7.org/fhir/us/davinci-hrex/1.2.0/security.html#exchange-security) for PHI exchange.,SHOULD,"CRD Client, CRD Server",,false,,
1317
1321
  hl7.fhir.us.davinci-crd_2.2.1,sec-4,https://hl7.org/fhir/us/davinci-crd/2.2.1/en/security.html#ci-c-sec-4,CRD clients that support the Launch SMART Application Response Type **SHALL** support running applications that adhere to the SMART on FHIR [confidential app](https://hl7.org/fhir/smart-app-launch/STU2.2/app-launch.html#support-for-public-and-confidential-apps) profile.,SHALL,CRD Client,,true,,