@salesforce/afv-skills 1.55.0 → 1.57.0

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 (274) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-generate/SKILL.md +8 -0
  3. package/skills/agentforce-generate/references/actions-reference.md +2 -2
  4. package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
  5. package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
  6. package/skills/agentforce-observe/SKILL.md +13 -9
  7. package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
  8. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
  9. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
  10. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
  11. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
  12. package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
  13. package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
  14. package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
  15. package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
  16. package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
  17. package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
  18. package/skills/commerce-b2b-open-code-components-integrate/SKILL.md +1 -1
  19. package/skills/commerce-b2b-open-code-components-replace/SKILL.md +16 -16
  20. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
  21. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
  22. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
  23. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
  24. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
  25. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
  31. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
  32. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
  33. package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
  34. package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
  35. package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
  36. package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
  37. package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
  38. package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
  39. package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
  40. package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
  41. package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
  42. package/skills/dx-devops-project-manage/SKILL.md +1 -1
  43. package/skills/dx-org-analyze/SKILL.md +0 -1
  44. package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
  45. package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
  46. package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
  47. package/skills/dx-org-manage/SKILL.md +6 -50
  48. package/skills/dx-org-manage/examples/README.md +10 -16
  49. package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
  50. package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
  51. package/skills/dx-org-shape-manage/SKILL.md +4 -3
  52. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
  53. package/skills/experience-accessibility-validate/SKILL.md +8 -5
  54. package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
  55. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
  56. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
  57. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
  58. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
  59. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
  60. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
  61. package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
  62. package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
  63. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
  64. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
  65. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
  66. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
  67. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
  68. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
  69. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
  70. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
  71. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
  72. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
  73. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
  74. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
  75. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
  76. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
  77. package/skills/experience-cms-content-generate/SKILL.md +1 -1
  78. package/skills/experience-cms-content-render/SKILL.md +3 -3
  79. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +35 -7
  80. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +13 -4
  81. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +30 -8
  82. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +14 -4
  83. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +11 -1
  84. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +1 -1
  85. package/skills/experience-cms-content-render/assets/shared/mediaLabels.ts +62 -0
  86. package/skills/experience-cms-content-render/references/init-scaffold.md +13 -6
  87. package/skills/experience-content-media-stock-image-search/SKILL.md +0 -1
  88. package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
  89. package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
  90. package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
  91. package/skills/experience-lwc-design-generate/SKILL.md +1 -1
  92. package/skills/experience-lwc-legacy-migrate/SKILL.md +212 -0
  93. package/skills/experience-lwc-legacy-migrate/assets/lo20-host-page-template.html +192 -0
  94. package/skills/experience-lwc-legacy-migrate/references/aura-to-lwc-completeness-checklist.md +188 -0
  95. package/skills/experience-lwc-legacy-migrate/references/lightning-out-2-system-reference.md +495 -0
  96. package/skills/experience-lwc-legacy-migrate/references/lightning-out-beta-to-2-migration.md +824 -0
  97. package/skills/experience-lwc-legacy-migrate/scripts/convert-lo-names.py +94 -0
  98. package/skills/experience-lwc-legacy-migrate/scripts/validate-lo20-page.py +133 -0
  99. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
  100. package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
  101. package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
  102. package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
  103. package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
  104. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
  105. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
  106. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
  107. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
  108. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
  109. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
  110. package/skills/experience-ui-bundle-project-generate/SKILL.md +1 -1
  111. package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
  112. package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
  113. package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
  114. package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
  115. package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
  116. package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
  117. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
  118. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
  119. package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
  120. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
  121. package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
  122. package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
  123. package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
  124. package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
  125. package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
  126. package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
  127. package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
  128. package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
  129. package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
  130. package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
  131. package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
  132. package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
  133. package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
  134. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
  135. package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
  136. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
  137. package/skills/integration-connectivity-connected-app-configure/SKILL.md +5 -5
  138. package/skills/integration-connectivity-generate/SKILL.md +10 -10
  139. package/skills/integration-connectivity-generate/scripts/configure-named-credential.sh +2 -2
  140. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
  141. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
  142. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
  143. package/skills/mobile-apps-create/SKILL.md +1 -1
  144. package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
  145. package/skills/platform-apex-test-generate/SKILL.md +1 -1
  146. package/skills/platform-apex-test-run/SKILL.md +1 -1
  147. package/skills/platform-custom-field-generate/SKILL.md +1 -1
  148. package/skills/platform-custom-lightning-type-generate/SKILL.md +1 -1
  149. package/skills/platform-custom-metadata-type-generate/SKILL.md +1 -1
  150. package/skills/platform-custom-object-generate/SKILL.md +1 -1
  151. package/skills/platform-custom-report-type-generate/SKILL.md +1 -1
  152. package/skills/platform-custom-setting-generate/SKILL.md +1 -1
  153. package/skills/platform-data-and-tooling-api-context-get/SKILL.md +1 -1
  154. package/skills/platform-data-manage/SKILL.md +10 -10
  155. package/skills/platform-flexipage-generate/SKILL.md +1 -1
  156. package/skills/platform-lightning-app-coordinate/SKILL.md +1 -1
  157. package/skills/platform-metadata-api-context-get/SKILL.md +1 -1
  158. package/skills/platform-metadata-retrieve/SKILL.md +1 -1
  159. package/skills/platform-permission-set-generate/SKILL.md +1 -1
  160. package/skills/platform-report-generate/SKILL.md +1 -1
  161. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +4 -4
  162. package/skills/platform-sandbox-configure/SKILL.md +108 -133
  163. package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
  164. package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
  165. package/skills/platform-sharing-owd-configure/SKILL.md +1 -1
  166. package/skills/platform-sharing-rules-generate/SKILL.md +1 -1
  167. package/skills/platform-soql-query/SKILL.md +1 -1
  168. package/skills/platform-value-set-generate/SKILL.md +1 -1
  169. package/skills/platform-widget-generate/SKILL.md +26 -18
  170. package/skills/platform-widget-generate/examples/formulas.json +191 -0
  171. package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
  172. package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
  173. package/skills/sales-call-scoring-configure/SKILL.md +318 -0
  174. package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
  175. package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
  176. package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
  177. package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
  178. package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
  179. package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
  180. package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
  181. package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
  182. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
  183. package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
  184. package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
  185. package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
  186. package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
  187. package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
  188. package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
  189. package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
  190. package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
  191. package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
  192. package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
  193. package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
  194. package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
  195. package/skills/service-agentforce-human-escalation-configure/SKILL.md +4 -0
  196. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +26 -6
  197. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +5 -4
  198. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
  199. package/skills/service-de-channel-activate/SKILL.md +6 -5
  200. package/skills/service-de-channel-create/SKILL.md +3 -3
  201. package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
  202. package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
  203. package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
  204. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
  205. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
  206. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
  207. package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
  208. package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
  209. package/skills/service-de-waba-integrate/SKILL.md +2 -1
  210. package/skills/service-digital-engagement-deployment-configure/SKILL.md +2 -0
  211. package/skills/service-digital-engagement-deployment-configure/scripts/check-api-version.sh +29 -0
  212. package/skills/service-email-to-case-configure/SKILL.md +1 -1
  213. package/skills/service-helpagent-coordinate/SKILL.md +3 -2
  214. package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
  215. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
  216. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
  217. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
  218. package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
  219. package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
  220. package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
  221. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
  222. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
  223. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
  224. package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
  225. package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
  226. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
  227. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
  228. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
  229. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
  230. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
  231. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
  232. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
  233. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
  234. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
  235. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
  236. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
  237. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
  238. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
  239. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
  240. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
  241. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
  242. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
  243. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
  244. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
  245. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
  246. package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
  247. package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
  248. package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
  249. package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
  250. package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
  251. package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
  252. package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
  253. package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
  254. package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
  255. package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
  256. package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
  257. package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
  258. package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
  259. package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
  260. package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
  261. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
  262. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
  263. package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
  264. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  265. package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
  266. package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
  267. package/skills/dx-org-manage/references/cli_flags.md +0 -67
  268. package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
  269. package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
  270. package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
  271. package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
  272. package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
  273. /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
  274. /package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/references/api-response-schema.md +0 -0
@@ -396,10 +396,10 @@ mcp__headless-360__dispatch({
396
396
  "businessHoursId": "<BusinessHoursId>",
397
397
  "timeTrigger": 60,
398
398
  "order": 1,
399
- "startTimeBasedOn": "SlaProcessCreatedDate",
399
+ "startTimeBasedOn": "MILESTONE_CRITERIA",
400
400
  "milestoneCriteria": [
401
401
  {
402
- "milestoneState": "Active",
402
+ "milestoneState": "ACTIVE",
403
403
  "milestoneAgreementType": "SLA",
404
404
  "filterType": "RuleFilter",
405
405
  "filterItems": [
@@ -411,7 +411,11 @@ mcp__headless-360__dispatch({
411
411
  })
412
412
  ```
413
413
 
414
- `milestoneCriteria` is mandatory. `milestoneAgreementType` is mandatory per the UI and lives **inside each `milestoneCriteria[]` item** (it maps to the `MilestoneCriteria.MilestoneAgreementType` sub-entity field — sending it at the top level of the milestone body returns `JSON_PARSER_ERROR: Unrecognized field`). Valid UI values are **`SLA`** (customer-facing agreement) or **`OLA`** (internal / operational). The API accepts any string because the underlying field is plain `Text(40)` with no server-side picklist enforcement — an unrecognized value persists to the DB but the UI treats it as blank; omitting the field entirely also succeeds silently but leaves the record's Milestone Agreement Type null (W-23959162). Success: `body.id` is the new Milestone Id.
414
+ **`startTimeBasedOn`** controls when the timer starts. Send **`"MILESTONE_CRITERIA"`** (matches OOB and every default pattern) to start the clock when the milestone's ACTIVE criteria first match. It is a free string with two recognized values — **`MILESTONE_CRITERIA`** (criteria-based / dynamic start) and **`SLA_PROCESS`** (the fallback); any other value silently defaults to the `SLA_PROCESS` behavior (no error). The fallback anchors the timer to the **Incident's `SlaStartDate`** (→ its `CreatedDate` if `SlaStartDate` is null) — **not** the SLA-process record's created date. **This choice is not runtime-verifiable in the standard verify flow:** for a test Incident that matches the SLA at creation, both settings compute the same `TargetDate` (≈ `SlaStartDate + timeTrigger`) — they diverge only when the criteria first match *after* creation (e.g. a later field change), which the Phase 3 test does not exercise. So send the OOB value and rely on it; there is no Phase-3 assertion that distinguishes the two (unlike the ACTIVE criterion, whose effect **is** observable via tiering — see below). `milestoneCriteria` is mandatory. `milestoneAgreementType` is mandatory per the UI and lives **inside each `milestoneCriteria[]` item** (it maps to the `MilestoneCriteria.MilestoneAgreementType` sub-entity field — sending it at the top level of the milestone body returns `JSON_PARSER_ERROR: Unrecognized field`). Valid UI values are **`SLA`** (customer-facing agreement) or **`OLA`** (internal / operational). The API accepts any string because the underlying field is plain `Text(40)` with no server-side picklist enforcement — an unrecognized value persists to the DB but the UI treats it as blank; omitting the field entirely also succeeds silently but leaves the record's Milestone Agreement Type null (W-23959162). Success: `body.id` is the new Milestone Id.
415
+
416
+ **`milestoneState` MUST be UPPERCASE and comes from a fixed set — this is load-bearing.** Valid values are **`ACTIVE`**, **`COMPLETE`**, **`PAUSE`**, **`UNPAUSE`** (there is **no `CANCEL`** state). `MilestoneService` compares `milestoneState` against `"ACTIVE"` with a **case-sensitive** `String.equals`, so `"Active"`/`"active"` (or any wrong case) **silently fails to register the ACTIVE criterion** — the create still returns `201 success:true`, but the priority/entry filter is never written and the Setup UI shows blank Activation Criteria. The milestone loses its Priority gate, so **in practice every Incident engages the lowest-`order` milestone of that MilestoneType regardless of its Priority** — a priority-tiered policy collapses to one tier. Use `ACTIVE` for the engagement (Priority) criterion and `COMPLETE` for the completion (Status) criterion; never send `CANCEL` (once the `ACTIVE` Priority criterion is correct it gates engagement, making a cancel criterion redundant).
417
+
418
+ **The create response NEVER echoes `milestoneCriteria` — it always returns `milestoneCriteria: []` even on success** (the create handler only sets `id`/`success`; only a subsequent GET/read-back populates criteria). So the `201` proves nothing about whether criteria persisted. **Verify by runtime, not the response:** after seeding, create a **non-matching-tier** test Incident (e.g. a Moderate/Low one for a Critical-first policy) and confirm its `EntityMilestone.TargetResponseInMins` matches that tier's `timeTrigger`, not order-1's. A Critical-only test can't catch a dropped criterion because Critical maps to order-1, which is also the collapse fallback.
415
419
 
416
420
  ---
417
421
 
@@ -573,11 +577,15 @@ Success: `body.id`.
573
577
  ## Predefined Incident Policy (Phase 0.6 detect + Phase 2-OOB seed)
574
578
 
575
579
  The out-of-box **"Standard Support for Incidents"** policy is what Salesforce seeds from Setup's
576
- "Create Predefined Policies" step (Incident process type only). That step is Aura-only and not
577
- headless-reachable, but it seeds via the **same backend** as the public `/connect/sla-management/*`
578
- routes above — so the skill replicates it exactly with routes it already uses. The full template
579
- (policy flags, 2 milestone types, 8 priority-tiered milestones, entitlement criterion) lives in
580
- **`assets/predefined-incident-policy.json`** — load and follow it; the values below are the shape.
580
+ "Create Predefined Policies" step (Incident process type only). That step is **Aura-only** — the
581
+ checkbox calls `DefaultSlaPolicyController.saveSelectedOptions`, an `@AuraEnabled` controller whose
582
+ only caller of `DefaultSlaService.createDefaultSlaPolicies` is that Aura action; there is **no public
583
+ REST/Connect wrapper**, so the headless MCP cannot invoke the seeder directly. But the seeder just
584
+ reads core's `default-sla-policies/incident.json` and writes it through the **same backend** as the
585
+ public `/connect/sla-management/*` routes above — so the skill replicates that resource's exact
586
+ output with routes it already uses. The full template (policy flags, 2 milestone types, **6
587
+ milestones**, entitlement criterion) lives in **`assets/predefined-incident-policy.json`** — load
588
+ and follow it; the values below are the shape.
581
589
 
582
590
  ### Detect first (no server idempotency → mandatory)
583
591
 
@@ -607,17 +615,24 @@ Uses the Phase 2 routes above, in this order:
607
615
  First read the catalog — SOQL `SELECT Id, Name FROM MilestoneType WHERE Name IN ('Acknowledge
608
616
  Within', 'Resolve Within')` (or `GET .../milestone-types`) — **reuse any match by id**, and `POST
609
617
  .../milestone-types` (`recurrenceType: OneTime`) **only** for the name(s) not already present.
610
- Capture both ids (whether reused or created).
618
+ Capture both ids (whether reused or created) **for chaining only — when you narrate the reuse,
619
+ name the type ("reusing the existing First Response type"); never append its queried Id, masked or
620
+ full (Output contract).**
611
621
  2. Create the SLA Policy `Standard Support for Incidents` (`POST .../sla-policies`) with
612
622
  `processType: "Incident"`, `businessHourId` = default, `createdDateEntryCriteria: true`,
613
623
  `closedExitCriteria: true`, `versionDefault: true`, **`active: true`** (create it active directly —
614
- do **not** use the Connect activate PATCH, which can 500 headless). Verify via SOQL on `SlaProcess`.
615
- 3. Attach the **8 priority-tiered milestones** (`POST .../sla-policies/<id>/milestones`, one per row).
616
- Reuse `assets/attach-milestone.json`. Validate each Priority/Status value against the live
617
- `Incident` picklist first. For the **mid tier**, map to the org's actual mid-priority label —
618
- `Moderate` on the standard ITSM picklist, but many orgs label it `Medium` — and seed orders 5–6
619
- with whichever the live picklist carries rather than dropping the tier. Drop a row **only** when its
620
- value is genuinely absent from the picklist, and note any dropped or relabeled tier in the report.
624
+ do **not** use the Connect activate PATCH, which can 500 headless). Verify via SOQL on `SlaProcess`,
625
+ then narrate the policy by that verified name — never the captured/queried Id (Output contract).
626
+ 3. Attach the **6 milestones** (`POST .../sla-policies/<id>/milestones`, one per row). Reuse
627
+ `assets/attach-milestone.json`. Send **`startTimeBasedOn: "MILESTONE_CRITERIA"`** on every
628
+ milestone — the OOB timer starts when the milestone criteria first match; the public route reads
629
+ this exact token (`MilestoneService` treats `MILESTONE_CRITERIA` as the criteria-based/dynamic
630
+ start; its only other recognized value, `SLA_PROCESS`, anchors to the Incident's `SlaStartDate`).
631
+ Validate each Priority/Status value against the live `Incident` picklist first. For the **mid
632
+ tier**, map to the org's actual mid-priority label — `Moderate` on the standard ITSM picklist, but
633
+ many orgs label it `Medium` — inside the merged orders 5–6 `Priority` OR rows (keep the `Low` row).
634
+ Drop a row from the OR **only** when its value is genuinely absent from the picklist, and note any
635
+ dropped or relabeled tier in the report.
621
636
 
622
637
  | order | milestoneType | timeTrigger | active criterion (Priority) | completion (Status) |
623
638
  |-------|---------------|-------------|-----------------------------|---------------------|
@@ -625,23 +640,31 @@ Uses the Phase 2 routes above, in this order:
625
640
  | 2 | Resolve Within | 120 | `Equals Critical` | `In [Resolved, Completed, Closed]` |
626
641
  | 3 | Acknowledge Within | 60 | `Equals High` | `NotEqual New` |
627
642
  | 4 | Resolve Within | 240 | `Equals High` | `In [Resolved, Completed, Closed]` |
628
- | 5 | Acknowledge Within | 240 | `Equals Moderate` | `NotEqual New` |
629
- | 6 | Resolve Within | 960 | `Equals Moderate` | `In [Resolved, Completed, Closed]` |
630
- | 7 | Acknowledge Within | 240 | `Equals Low` | `NotEqual New` |
631
- | 8 | Resolve Within | 960 | `Equals Low` | `In [Resolved, Completed, Closed]` |
632
-
633
- The **active** criterion (`Priority Equals <tier>`) is the one the public Attach-Milestone
634
- `milestoneCriteria` carries (`milestoneState: Active`, `milestoneAgreementType: SLA`,
635
- `filterType: RuleFilter`) — seeding it engages each milestone. The OOB also carries completion
636
- (`Status In …`) and cancel (`Priority NotEqual <tier>`) criteria (`milestoneState` Complete / Cancel);
637
- confirm the route accepts those states in the smoke test — if not, the active criterion alone is
638
- sufficient for engagement (matches `examples/milestone-patterns.md` Pattern 3).
639
-
640
- **OR / In have no single-row form.** `filterItems` AND by default and there is no `In` operator. The
641
- Priority tiers are already pre-split above (each row is a single `Priority Equals <tier>`, Moderate and
642
- Low as separate rows 5–8), so no OR is needed for Priority. For the `Status In` completion sets, either
643
- use `filterLogic: "1 OR 2"` if the route accepts it (verify live) or split into one row per value —
644
- behavior-equivalent.
643
+ | 5 | Acknowledge Within | 240 | `Equals Moderate` OR `Equals Low` (`filterLogic: "1 OR 2"`) | `NotEqual New` |
644
+ | 6 | Resolve Within | 960 | `Equals Moderate` OR `Equals Low` (`filterLogic: "1 OR 2"`) | `In [Resolved, Completed, Closed]` |
645
+
646
+ This mirrors core's `default-sla-policies/incident.json` shape: Critical and High are per-tier;
647
+ **Moderate and Low are one merged tier** (ACTIVE `filterLogic "1 OR 2"` over two Priority Equals rows).
648
+
649
+ The **active** criterion is the one the public Attach-Milestone `milestoneCriteria` carries
650
+ (`milestoneState: ACTIVE`, `milestoneAgreementType: SLA`, `filterType: RuleFilter`) — seeding it
651
+ **is what tiers the policy**: it gates which Incidents engage each milestone. Because all three tiers
652
+ reuse the same two MilestoneTypes, if the ACTIVE criterion fails to persist (e.g. a wrong-case
653
+ `milestoneState` — see the case-sensitivity warning above), the milestone loses its Priority gate and
654
+ in practice every Incident engages the lowest-`order` milestone (Critical, 30/120) regardless of Priority. Each milestone also carries a
655
+ completion criterion (`milestoneState: COMPLETE`, `Status In …`). **The core template's per-tier
656
+ CANCEL criterion is intentionally dropped** — the public route has no `CANCEL` state, and once the
657
+ ACTIVE Priority criterion is correct it already prevents off-tier engagement, so a cancel criterion is
658
+ redundant. **Verify tiering by runtime** (see the case-sensitivity warning): seed, then create a
659
+ **Moderate/Low** test Incident and confirm its `EntityMilestone` lands on the 240/960 tier, not 30/120.
660
+
661
+ **Priority OR is expressed via `filterLogic`, not an `In` operator.** `filterItems` AND by default and
662
+ there is no `In` operator, but the merged Moderate-or-Low tier's ACTIVE criterion uses
663
+ `filterLogic: "1 OR 2"` across two `Priority Equals` rows — the same `customLogicExpression` the
664
+ public milestone-criteria route consumes, so no separate per-value milestone is needed. If the live
665
+ route rejects `filterLogic`, fall back to two separate `Priority Equals` milestones (Moderate, Low)
666
+ with the same timeTriggers — behavior-equivalent. The `Status In` completion sets use the same
667
+ `filterLogic: "1 OR 2 OR 3"` pattern (split into one row per value if the route rejects it).
645
668
 
646
669
  4. Create the Entitlement (`POST /sobjects/Entitlement`) on the resolved Account, `SlaProcessId` =
647
670
  the seeded policy, backdated `StartDate`. **Attempt** the OOB always-match entitlement criterion
@@ -658,8 +681,11 @@ behavior-equivalent.
658
681
  fields (a wrong-payload `400`, not a platform gap), and misattributing it reads as a bug. Never
659
682
  surface the raw sentinel string or a `JSON_PARSER_ERROR` in the customer report. The date-windowed
660
683
  Entitlement plus the milestone criteria are still enough to engage the Phase 3 test Incident.
661
- 5. Verify per **Verify SLA engagement** below with a **Critical** test Incident (a Critical Priority
662
- matches orders 1–2, so an `EntityMilestone` spawns), then **STOP** — do not offer custom.
684
+ Narrate the Entitlement by name/Account, never its returned Id (Output contract).
685
+ 5. Verify per **Verify SLA engagement** below with a **Moderate/Low** test Incident — its
686
+ `EntityMilestone` must land on the 240/960 tier, not 30/120. Use a non-lowest tier: Critical is
687
+ order-1, the collapse fallback, so it spawns a milestone even when the ACTIVE criterion was dropped
688
+ and can't detect the bug. Then **STOP** — do not offer custom.
663
689
 
664
690
  ---
665
691
 
@@ -705,10 +731,10 @@ mcp__headless-360__dispatch_readonly({
705
731
  | `dispatch` shape is raw HTTP | Pass `{url, method, body?, query_params?}` — NOT `{operation_id, arguments}`. |
706
732
  | Response wrapper | Connect/`/sobjects`/`/query` are singly wrapped — read `body`. Aura `/headless/invoke/…` routes (not used here) are doubly wrapped. |
707
733
  | Corpus vs. registry drift | `discover` may not rank the SLA POST routes at the top — the routes are still known-good; `describe` + `dispatch` on the canonical path works either way. |
708
- | SLA Policy create response is null | `POST /sla-policies` echoes most fields as `null` — always verify via SOQL on `SlaProcess`. |
734
+ | SLA Policy create response is null | `POST /sla-policies` echoes most fields as `null` — always verify via SOQL on `SlaProcess`; then narrate the policy by the **verified name**, never the captured/queried Id (Output contract). |
709
735
  | Milestone filter payload | `milestoneCriteria` is mandatory (omitting it → `400: Criteria details cannot be empty`). Use `filterType: "RuleFilter"` with concrete `filterItems[]` — `filterType: "Formula"` triggers a 500. Operators are `Equals` (not `Equal`) and `NotEqual` (not `NotEquals`/`NotEqualTo`/`!=`); wrong forms → `POST_BODY_PARSE_ERROR: Invalid value for Filter Operation Enum`. |
710
736
  | `slaProcessId` rejected in body | The server returns `JSON_PARSER_ERROR: Unrecognized field "slaProcessId"` — the id is in the path only. |
711
737
  | Entitlement create + status | Create via `POST /sobjects/Entitlement` (standard sObject, not the Connect surface). Status is date-computed: a future `StartDate` → `Inactive`; backdate `StartDate` to yesterday for immediate SLA engagement in testing. |
712
738
  | OOB seed has no idempotency | `POST .../sla-policies` does not dedupe — re-seeding "Standard Support for Incidents" duplicates every artifact. Detect first via `GET .../sla-policies?processTypes=Incident` and name-match before seeding. |
713
739
  | Connect activate PATCH may 500 | The OOB backend flips the policy active via a Connect activate call; headless, that PATCH can 500. Create the SlaProcess with `active: true` directly instead (as the custom flow does). |
714
- | Never leak a record Id | In **every** user-facing message (interim narration *and* the final report, incl. "created milestone …" progress lines), refer to the Phase-3 test Incident by its **`IncidentNumber`** (the verify SOQL selects it) and milestones by their **`MilestoneType.Name`** — never the 15/18-char record Id. |
740
+ | Never leak a record Id (SKILL.md Output contract) | In **every** user-facing message — interim narration *and* the final report, incl. "created milestone …" / "policy created …" / "reusing existing … →" progress lines — refer to the Phase-3 test Incident by its **`IncidentNumber`** (the verify SOQL selects it) and milestones/policy by their **name** — never the 15/18-char record Id (full **or masked**, e.g. `557VW…R3XVYA0`) or a `triggerId`. This covers **detected/reused** artifacts too — no `→ <Id>` "proof of reuse". Ids stay internal (chaining only). |
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: service-omni-attribute-routing-configure
3
+ description: "Use to manage Omni-Channel attribute-based routing rules through the Tooling API. TRIGGER when: list attribute-routing rules, inspect a WorkSkillRouting rule, create an inactive routing rule, add a field-to-skill mapping, activate a complete routing rule, deactivate a routing rule, or delete a routing rule or attribute. Do not use to create agent skills or routing flows."
4
+ allowed-tools: Bash Read Write Grep Glob
5
+ metadata:
6
+ version: "1.0"
7
+ domains: ["Service"]
8
+ minApiVersion: "68.0"
9
+ relatedSkills:
10
+ - "service-omni-work-skill-routing-configure"
11
+ accessCheck:
12
+ - type: license
13
+ value: ServiceCloud
14
+ cliTools:
15
+ - tool: ["jq"]
16
+ semver: ">=1.6"
17
+ - tool: ["python3"]
18
+ semver: ">=3.10"
19
+ - tool: ["sf"]
20
+ semver: ">=2.139.6"
21
+ ---
22
+
23
+ # service-omni-attribute-routing-configure
24
+
25
+ Manage the full `WorkSkillRouting` and `WorkSkillRoutingAttribute` lifecycle using the approved Tooling API contract. Create rules inactive, add all mappings, and activate only after at least one mapping exists.
26
+
27
+ ## Usage
28
+
29
+ ```bash
30
+ bash scripts/configure-and-report.sh <org> list
31
+ bash scripts/configure-and-report.sh <org> get <rule-id>
32
+ bash scripts/configure-and-report.sh <org> create <developer-name> <label> <related-entity>
33
+ bash scripts/configure-and-report.sh <org> add-attribute <rule-id> <Entity.Field> <value> <skill-id> <level> <related-entity> [additional] [priority]
34
+ bash scripts/configure-and-report.sh <org> set-active <rule-id> true|false
35
+ CONFIRM_DELETE=1 bash scripts/configure-and-report.sh <org> delete-attribute <attribute-id>
36
+ CONFIRM_DELETE=1 bash scripts/configure-and-report.sh <org> delete-rule <rule-id>
37
+ ```
38
+
39
+ ## Safety and behavior
40
+
41
+ - Reads require View Setup. Writes require Customize Application and are refused on production customer orgs.
42
+ - Omni-Channel and Skills-Based Routing must be enabled, and referenced `Skill` records must already exist.
43
+ - Creation always starts inactive. Activation reads the complete Metadata compound, preserves all mappings, removes the read-only `urls` member, changes only `isActive`, and verifies with a cold read.
44
+ - The script rejects activation without mappings, invalid skill levels, invalid additional-skill priorities, and deletion without `CONFIRM_DELETE=1`.
45
+ - Existing identical mappings are reused.
46
+
47
+ ## Boundaries
48
+
49
+ Use `service-omni-work-skill-routing-configure` for the simpler one-mapping Metadata API workflow. Use this skill when the user explicitly needs Tooling CRUD, multiple mappings, activation control, or deletion.
50
+
51
+ ## Reference
52
+
53
+ Read [references/api-notes.md](references/api-notes.md) before changing the payload or compound Metadata handling.
@@ -0,0 +1,7 @@
1
+ # Attribute-routing Tooling API contract
2
+
3
+ Live API v68 describe validation confirms Tooling API CRUD for `WorkSkillRouting` and `WorkSkillRoutingAttribute` on orgs provisioned for Skills-Based Routing. A rule is created inactive, populated with one or more child mappings, and activated only when complete.
4
+
5
+ `PATCH WorkSkillRouting/{Id}` replaces the entire Metadata compound. Always read first, preserve `masterLabel`, `relatedEntity`, and `workSkillRoutingAttributes`, remove the read-only `urls` member, then write the complete compound. Omitting the nested mappings deletes them.
6
+
7
+ Eligible related entities are Case, ChangeRequest, ContactRequest, FlowOrchestrationWorkItem, Incident, Lead, LiveChatTranscript, MessagingSession, Order, Problem, SocialPost, VoiceCall, and WorkOrder. `SkillLevel` is 0–10. `SkillPriority` is 0–4 and is valid only when `IsAdditionalSkill=true`.
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat >&2 <<'EOF'
6
+ Usage:
7
+ bash configure-and-report.sh <org> list
8
+ bash configure-and-report.sh <org> get <rule-id>
9
+ bash configure-and-report.sh <org> create <developer-name> <label> <related-entity>
10
+ bash configure-and-report.sh <org> add-attribute <rule-id> <field> <value> <skill-id> <level> <related-entity> [additional] [priority]
11
+ bash configure-and-report.sh <org> set-active <rule-id> true|false
12
+ CONFIRM_DELETE=1 bash configure-and-report.sh <org> delete-attribute <attribute-id>
13
+ CONFIRM_DELETE=1 bash configure-and-report.sh <org> delete-rule <rule-id>
14
+ EOF
15
+ }
16
+
17
+ emit_blocked() {
18
+ jq -n --arg operation "$OPERATION" --arg issue "$1" \
19
+ '{skill:"service-omni-attribute-routing-configure",status:"blocked",operation:$operation,result:null,blocking_issue:$issue}'
20
+ exit 1
21
+ }
22
+
23
+ valid_id() { [[ "$1" =~ ^[A-Za-z0-9]{15,18}$ ]]; }
24
+ valid_name() { [[ "$1" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; }
25
+ valid_entity() {
26
+ case "$1" in
27
+ Case|ChangeRequest|ContactRequest|FlowOrchestrationWorkItem|Incident|Lead|LiveChatTranscript|MessagingSession|Order|Problem|SocialPost|VoiceCall|WorkOrder) return 0 ;;
28
+ *) return 1 ;;
29
+ esac
30
+ }
31
+
32
+ safe_to_write() {
33
+ local org_json is_sandbox trial_exp org_type
34
+ org_json=$(sf data query --target-org "$ORG" --json \
35
+ --query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" 2>/dev/null) \
36
+ || emit_blocked "Failed to establish the target org safety boundary."
37
+ is_sandbox=$(jq -r '.result.records[0].IsSandbox // false' <<<"$org_json")
38
+ trial_exp=$(jq -r '.result.records[0].TrialExpirationDate // "null"' <<<"$org_json")
39
+ org_type=$(jq -r '.result.records[0].OrganizationType // ""' <<<"$org_json")
40
+ if [ "$is_sandbox" != "true" ] && [ "$trial_exp" = "null" ] \
41
+ && [ "$org_type" != "Developer Edition" ] && [ "$org_type" != "Base Edition" ]; then
42
+ emit_blocked "Refusing to change attribute routing in a production customer org. Use a sandbox, trial, Developer Edition, or Base Edition org."
43
+ fi
44
+ }
45
+
46
+ request() {
47
+ local method="$1" endpoint="$2" body="${3:-}"
48
+ if [ -n "$body" ]; then
49
+ REQUEST_OUTPUT=$(FORCE_COLOR=0 sf api request rest "$endpoint" --method "$method" --body "$body" --target-org "$ORG" 2>/dev/null) \
50
+ || emit_blocked "The Tooling API $method operation failed."
51
+ else
52
+ REQUEST_OUTPUT=$(FORCE_COLOR=0 sf api request rest "$endpoint" --method "$method" --target-org "$ORG" 2>/dev/null) \
53
+ || emit_blocked "The Tooling API $method operation failed."
54
+ fi
55
+ if [ -z "$REQUEST_OUTPUT" ] && { [ "$method" = "PATCH" ] || [ "$method" = "DELETE" ]; }; then
56
+ REQUEST_OUTPUT='{}'
57
+ fi
58
+ jq -e . >/dev/null 2>&1 <<<"$REQUEST_OUTPUT" \
59
+ || emit_blocked "The Tooling API $method operation returned malformed JSON."
60
+ }
61
+
62
+ delete_tooling_record() {
63
+ local object="$1" record_id="$2"
64
+ DELETE_OUTPUT=$(FORCE_COLOR=0 sf data delete record --target-org "$ORG" --json --api-version "$API_VERSION" --use-tooling-api \
65
+ --sobject "$object" --record-id "$record_id" 2>/dev/null) \
66
+ || emit_blocked "The Tooling API DELETE operation failed."
67
+ jq -e '.status == 0 and .result.success == true' >/dev/null 2>&1 <<<"$DELETE_OUTPUT" \
68
+ || emit_blocked "The Tooling API DELETE operation returned an unsuccessful result."
69
+ }
70
+
71
+ ensure_available() {
72
+ request GET "$BASE/sobjects/WorkSkillRouting/describe"
73
+ [ "$(jq -r '.createable // false' <<<"$REQUEST_OUTPUT")" = "true" ] \
74
+ || emit_blocked "WorkSkillRouting is unavailable or read-only. Enable Omni-Channel and Skills-Based Routing, then retry."
75
+ }
76
+
77
+ [ $# -ge 2 ] || { usage; exit 2; }
78
+ ORG="$1"
79
+ OPERATION="$2"
80
+ shift 2
81
+ API_VERSION="${SF_API_VERSION:-68.0}"
82
+ BASE="/services/data/v${API_VERSION}/tooling"
83
+
84
+ sf org display --target-org "$ORG" --json >/dev/null 2>&1 \
85
+ || emit_blocked "The target org is not authenticated or cannot be reached."
86
+
87
+ case "$OPERATION" in
88
+ list)
89
+ [ $# -eq 0 ] || { usage; exit 2; }
90
+ result=$(sf data query --target-org "$ORG" --use-tooling-api --json \
91
+ --query "SELECT Id, DeveloperName, MasterLabel, IsActive, RelatedEntity FROM WorkSkillRouting ORDER BY DeveloperName" 2>/dev/null) \
92
+ || emit_blocked "WorkSkillRouting is unavailable. Enable Omni-Channel and Skills-Based Routing, then retry."
93
+ jq -n --argjson records "$(jq -c '.result.records // []' <<<"$result")" \
94
+ '{skill:"service-omni-attribute-routing-configure",status:"analyzed",operation:"list",result:{records:$records,count:($records|length)},blocking_issue:null}'
95
+ ;;
96
+ get)
97
+ [ $# -eq 1 ] && valid_id "$1" || emit_blocked "get requires a valid 15- or 18-character WorkSkillRouting Id."
98
+ request GET "$BASE/sobjects/WorkSkillRouting/$1"
99
+ result="$REQUEST_OUTPUT"
100
+ jq -n --argjson record "$result" \
101
+ '{skill:"service-omni-attribute-routing-configure",status:"analyzed",operation:"get",result:{record:$record},blocking_issue:null}'
102
+ ;;
103
+ create)
104
+ [ $# -eq 3 ] || emit_blocked "create requires developer-name, label, and related-entity."
105
+ developer_name="$1"; label="$2"; entity="$3"
106
+ valid_name "$developer_name" || emit_blocked "Invalid routing-rule developer name."
107
+ valid_entity "$entity" || emit_blocked "Unsupported related entity '$entity'."
108
+ ensure_available
109
+ existing_query=$(sf data query --target-org "$ORG" --use-tooling-api --json \
110
+ --query "SELECT Id FROM WorkSkillRouting WHERE DeveloperName = '$developer_name' LIMIT 1" 2>/dev/null) \
111
+ || emit_blocked "Failed to check for an existing attribute-routing rule."
112
+ existing_id=$(jq -r '.result.records[0].Id // empty' <<<"$existing_query")
113
+ if [ -n "$existing_id" ]; then
114
+ request GET "$BASE/sobjects/WorkSkillRouting/$existing_id"
115
+ existing="$REQUEST_OUTPUT"
116
+ existing_label=$(jq -r '.Metadata.masterLabel // .MasterLabel // empty' <<<"$existing")
117
+ existing_entity=$(jq -r '.Metadata.relatedEntity // .RelatedEntity // empty' <<<"$existing")
118
+ existing_active=$(jq -r '.Metadata.isActive // .IsActive // false' <<<"$existing")
119
+ [ "$existing_label" = "$label" ] && [ "$existing_entity" = "$entity" ] && [ "$existing_active" = "false" ] \
120
+ || emit_blocked "A routing rule named '$developer_name' already exists with different label, entity, or activation state."
121
+ jq -n --argjson record "$existing" \
122
+ '{skill:"service-omni-attribute-routing-configure",status:"reused",operation:"create",result:{record:$record},blocking_issue:null}'
123
+ exit 0
124
+ fi
125
+ safe_to_write
126
+ body=$(jq -cn --arg fullName "$developer_name" --arg label "$label" --arg entity "$entity" \
127
+ '{FullName:$fullName,Metadata:{masterLabel:$label,relatedEntity:$entity,isActive:false}}')
128
+ request POST "$BASE/sobjects/WorkSkillRouting" "$body"
129
+ created="$REQUEST_OUTPUT"
130
+ id=$(jq -r '.id // empty' <<<"$created")
131
+ [ -n "$id" ] || emit_blocked "The Tooling API did not return the created routing-rule Id."
132
+ request GET "$BASE/sobjects/WorkSkillRouting/$id"
133
+ verified="$REQUEST_OUTPUT"
134
+ jq -n --argjson create "$created" --argjson record "$verified" \
135
+ '{skill:"service-omni-attribute-routing-configure",status:"created",operation:"create",result:{create:$create,record:$record},blocking_issue:null}'
136
+ ;;
137
+ add-attribute)
138
+ [ $# -ge 6 ] && [ $# -le 8 ] || emit_blocked "add-attribute requires rule-id, field, value, skill-id, level, related-entity, and optional additional/priority."
139
+ rule_id="$1"; field="$2"; value="$3"; skill_id="$4"; level="$5"; entity="$6"; additional="${7:-false}"; priority="${8:-}"
140
+ valid_id "$rule_id" && valid_id "$skill_id" || emit_blocked "Rule Id and Skill Id must be valid Salesforce record Ids."
141
+ [[ "$field" =~ ^[A-Za-z][A-Za-z0-9_]*\.[A-Za-z][A-Za-z0-9_]*$ ]] || emit_blocked "Field must be entity-qualified, for example Case.Priority."
142
+ jq -en --arg level "$level" '($level|tonumber) >= 0 and ($level|tonumber) <= 10' >/dev/null 2>&1 \
143
+ || emit_blocked "Skill level must be between 0 and 10."
144
+ valid_entity "$entity" || emit_blocked "Unsupported related entity '$entity'."
145
+ field_entity="${field%%.*}"; field_name="${field#*.}"
146
+ [ "$field_entity" = "$entity" ] || emit_blocked "Field '$field' does not belong to related entity '$entity'."
147
+ [ "$additional" = "true" ] || [ "$additional" = "false" ] || emit_blocked "additional must be true or false."
148
+ if [ -n "$priority" ]; then
149
+ [ "$additional" = "true" ] || emit_blocked "Skill priority is valid only for an additional skill."
150
+ [[ "$priority" =~ ^[0-4]$ ]] || emit_blocked "Skill priority must be between 0 and 4."
151
+ fi
152
+ ensure_available
153
+ request GET "$BASE/sobjects/WorkSkillRoutingAttribute/describe"
154
+ [ "$(jq -r '.createable // false' <<<"$REQUEST_OUTPUT")" = "true" ] \
155
+ || emit_blocked "WorkSkillRoutingAttribute is unavailable or read-only on this org."
156
+ safe_to_write
157
+ request GET "$BASE/sobjects/WorkSkillRouting/$rule_id"
158
+ existing="$REQUEST_OUTPUT"
159
+ parent_entity=$(jq -r '.Metadata.relatedEntity // empty' <<<"$existing")
160
+ [ "$parent_entity" = "$entity" ] || emit_blocked "Related entity '$entity' does not match the parent rule ('$parent_entity')."
161
+ request GET "/services/data/v${API_VERSION}/sobjects/${entity}/describe"
162
+ field_describe="$REQUEST_OUTPUT"
163
+ field_type=$(jq -r --arg field "$field_name" '.fields[] | select(.name==$field) | .type' <<<"$field_describe" | head -1)
164
+ case "$field_type" in picklist|boolean|reference|id) ;; *) emit_blocked "Field '$field' is not an eligible picklist, boolean, reference, or id field." ;; esac
165
+ if [ "$field_type" = "picklist" ]; then
166
+ jq -e --arg field "$field_name" --arg value "$value" \
167
+ '.fields[] | select(.name==$field) | [.picklistValues[]? | select(.active==true and .value==$value)] | length > 0' \
168
+ <<<"$field_describe" >/dev/null || emit_blocked "Value '$value' is not an active value for '$field'."
169
+ elif [ "$field_type" = "boolean" ]; then
170
+ [ "$value" = "true" ] || [ "$value" = "false" ] || emit_blocked "Boolean field '$field' requires value true or false."
171
+ fi
172
+ distinct_fields=$(jq '[.Metadata.workSkillRoutingAttributes[]?.field] | unique | length' <<<"$existing")
173
+ field_already_used=$(jq --arg field "$field" '[.Metadata.workSkillRoutingAttributes[]? | select(.field==$field)] | length' <<<"$existing")
174
+ [ "$distinct_fields" -lt 10 ] || [ "$field_already_used" -gt 0 ] \
175
+ || emit_blocked "A routing rule supports at most 10 distinct fields."
176
+ existing_count=$(jq --arg field "$field" --arg value "$value" --arg skill "$skill_id" \
177
+ '[.Metadata.workSkillRoutingAttributes[]? | select(.field==$field and .value==$value and ((.skillId // .skill) == $skill))] | length' <<<"$existing")
178
+ if [ "$existing_count" -gt 0 ]; then
179
+ jq -n --argjson record "$existing" \
180
+ '{skill:"service-omni-attribute-routing-configure",status:"reused",operation:"add-attribute",result:{record:$record},blocking_issue:null}'
181
+ exit 0
182
+ fi
183
+ body=$(jq -cn --arg rule "$rule_id" --arg field "$field" --arg value "$value" --arg skill "$skill_id" \
184
+ --arg entity "$entity" --arg level "$level" --arg additional "$additional" --arg priority "$priority" \
185
+ '{WorkSkillRoutingId:$rule,Field:$field,Value:$value,SkillId:$skill,SkillLevel:($level|tonumber),RelatedEntity:$entity,IsAdditionalSkill:($additional=="true")}
186
+ + (if $priority=="" then {} else {SkillPriority:($priority|tonumber)} end)')
187
+ request POST "$BASE/sobjects/WorkSkillRoutingAttribute" "$body"
188
+ created="$REQUEST_OUTPUT"
189
+ attribute_id=$(jq -r '.id // empty' <<<"$created")
190
+ [ -n "$attribute_id" ] || emit_blocked "The Tooling API did not return the created attribute Id."
191
+ request GET "$BASE/sobjects/WorkSkillRoutingAttribute/$attribute_id"
192
+ verified="$REQUEST_OUTPUT"
193
+ jq -n --argjson create "$created" --argjson record "$verified" \
194
+ '{skill:"service-omni-attribute-routing-configure",status:"created",operation:"add-attribute",result:{create:$create,record:$record},blocking_issue:null}'
195
+ ;;
196
+ set-active)
197
+ [ $# -eq 2 ] && valid_id "$1" || emit_blocked "set-active requires a valid rule Id and true or false."
198
+ [ "$2" = "true" ] || [ "$2" = "false" ] || emit_blocked "set-active value must be true or false."
199
+ ensure_available
200
+ safe_to_write
201
+ request GET "$BASE/sobjects/WorkSkillRouting/$1"
202
+ current="$REQUEST_OUTPUT"
203
+ metadata=$(jq -c --argjson active "$2" '.Metadata | del(.urls) | .isActive=$active' <<<"$current")
204
+ [ "$2" = "false" ] || [ "$(jq '.workSkillRoutingAttributes // [] | length' <<<"$metadata")" -gt 0 ] \
205
+ || emit_blocked "Refusing to activate a routing rule without attribute mappings."
206
+ request PATCH "$BASE/sobjects/WorkSkillRouting/$1" "$(jq -cn --argjson metadata "$metadata" '{Metadata:$metadata}')"
207
+ request GET "$BASE/sobjects/WorkSkillRouting/$1"
208
+ verified="$REQUEST_OUTPUT"
209
+ [ "$(jq -r '.Metadata.isActive' <<<"$verified")" = "$2" ] || emit_blocked "Activation verification did not match the requested state."
210
+ jq -n --argjson record "$verified" \
211
+ '{skill:"service-omni-attribute-routing-configure",status:"updated",operation:"set-active",result:{record:$record},blocking_issue:null}'
212
+ ;;
213
+ delete-attribute|delete-rule)
214
+ [ $# -eq 1 ] && valid_id "$1" || emit_blocked "$OPERATION requires a valid Salesforce record Id."
215
+ [ "${CONFIRM_DELETE:-0}" = "1" ] || emit_blocked "Deletion requires explicit CONFIRM_DELETE=1."
216
+ ensure_available
217
+ safe_to_write
218
+ object="WorkSkillRoutingAttribute"; [ "$OPERATION" = "delete-rule" ] && object="WorkSkillRouting"
219
+ delete_tooling_record "$object" "$1"
220
+ jq -n --arg operation "$OPERATION" --arg id "$1" \
221
+ '{skill:"service-omni-attribute-routing-configure",status:"deleted",operation:$operation,result:{id:$id},blocking_issue:null}'
222
+ ;;
223
+ *) usage; exit 2 ;;
224
+ esac
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import subprocess
7
+ import tempfile
8
+ import unittest
9
+ from pathlib import Path
10
+
11
+ SKILLS_ROOT = Path(__file__).resolve().parents[3]
12
+ SCRIPT = SKILLS_ROOT / "service-omni-attribute-routing-configure/scripts/configure-and-report.sh"
13
+ DEFAULT_SF = """#!/usr/bin/env bash
14
+ [ "$1 $2" = "org display" ] && echo '{"status":0,"result":{}}' && exit 0
15
+ echo unexpected >&2
16
+ exit 1
17
+ """
18
+
19
+
20
+ def run_script(*args: str, sf_source: str = DEFAULT_SF, extra_env: dict[str, str] | None = None):
21
+ with tempfile.TemporaryDirectory(prefix="attribute-routing-sf-") as directory:
22
+ sf = Path(directory) / "sf"
23
+ sf.write_text(sf_source)
24
+ sf.chmod(0o755)
25
+ env = dict(os.environ)
26
+ env["PATH"] = directory + os.pathsep + env.get("PATH", "")
27
+ env.update(extra_env or {})
28
+ return subprocess.run(["bash", str(SCRIPT), *args], capture_output=True, text=True, env=env)
29
+
30
+
31
+ class AttributeRoutingContracts(unittest.TestCase):
32
+ def test_script_syntax(self):
33
+ result = subprocess.run(["bash", "-n", str(SCRIPT)], capture_output=True, text=True)
34
+ self.assertEqual(result.returncode, 0, result.stderr)
35
+
36
+ def test_create_rejects_unsupported_entity_before_write(self):
37
+ result = run_script("org", "create", "Rule", "Rule", "Account")
38
+ self.assertEqual(result.returncode, 1)
39
+ self.assertIn("Unsupported related entity", result.stdout)
40
+
41
+ def test_create_reuses_matching_inactive_rule(self):
42
+ sf_source = """#!/usr/bin/env bash
43
+ case "$1 $2" in
44
+ "org display") echo '{"status":0,"result":{}}' ;;
45
+ "api request")
46
+ if [[ " $* " == *"WorkSkillRouting/describe"* ]]; then
47
+ echo '{"createable":true}'
48
+ elif [[ " $* " == *"WorkSkillRouting/0Xaxx0000000001AAA"* ]]; then
49
+ echo '{"Id":"0Xaxx0000000001AAA","Metadata":{"masterLabel":"Eval Case Routing","relatedEntity":"Case","isActive":false,"workSkillRoutingAttributes":[]}}'
50
+ else
51
+ exit 1
52
+ fi
53
+ ;;
54
+ "data query")
55
+ if [[ " $* " == *" FROM WorkSkillRouting WHERE DeveloperName = 'Eval_Case_Routing' "* ]]; then
56
+ echo '{"status":0,"result":{"records":[{"Id":"0Xaxx0000000001AAA"}]}}'
57
+ else
58
+ exit 1
59
+ fi
60
+ ;;
61
+ *) exit 1 ;;
62
+ esac
63
+ """
64
+ result = run_script(
65
+ "org", "create", "Eval_Case_Routing", "Eval Case Routing", "Case",
66
+ sf_source=sf_source,
67
+ )
68
+ self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
69
+ response = json.loads(result.stdout)
70
+ self.assertEqual(response["status"], "reused")
71
+ self.assertEqual(response["result"]["record"]["Id"], "0Xaxx0000000001AAA")
72
+
73
+ def test_supported_service_entities_include_voice_and_incident(self):
74
+ source = SCRIPT.read_text()
75
+ self.assertIn("Incident", source)
76
+ self.assertIn("VoiceCall", source)
77
+ self.assertIn("ChangeRequest", source)
78
+ self.assertIn("Problem", source)
79
+
80
+ def test_add_attribute_rejects_level_above_ten(self):
81
+ result = run_script(
82
+ "org", "add-attribute", "0Xaxx0000000001AAA", "Case.Priority", "High",
83
+ "0C5xx0000000001AAA", "10.1", "Case",
84
+ )
85
+ self.assertEqual(result.returncode, 1)
86
+ self.assertIn("between 0 and 10", result.stdout)
87
+
88
+ def test_activation_preserves_complete_metadata(self):
89
+ source = SCRIPT.read_text()
90
+ self.assertIn(".Metadata | del(.urls) | .isActive=$active", source)
91
+ self.assertIn("workSkillRoutingAttributes", source)
92
+
93
+ def test_failures_are_emitted_from_parent_shell(self):
94
+ source = SCRIPT.read_text()
95
+ self.assertNotIn("$(request ", source)
96
+ self.assertIn('REQUEST_OUTPUT=$(FORCE_COLOR=0 sf api request rest', source)
97
+
98
+ def test_deletes_require_explicit_confirmation(self):
99
+ result = run_script("org", "delete-rule", "0Xaxx0000000001AAA")
100
+ self.assertEqual(result.returncode, 1)
101
+ self.assertIn("CONFIRM_DELETE=1", result.stdout)
102
+
103
+ def test_deletes_use_tooling_data_delete_instead_of_bodyless_rest(self):
104
+ cases = (
105
+ ("delete-rule", "WorkSkillRouting", "0Xaxx0000000001AAA"),
106
+ ("delete-attribute", "WorkSkillRoutingAttribute", "0Yaxx0000000001AAA"),
107
+ )
108
+ for operation, object_name, record_id in cases:
109
+ with self.subTest(operation=operation):
110
+ sf_source = f"""#!/usr/bin/env bash
111
+ case "$1 $2" in
112
+ "org display") echo '{{"status":0,"result":{{}}}}' ;;
113
+ "api request")
114
+ case " $* " in *" --method DELETE "*) exit 23 ;; *) echo '{{"createable":true}}' ;; esac
115
+ ;;
116
+ "data query") echo '{{"status":0,"result":{{"records":[{{"IsSandbox":true,"TrialExpirationDate":null,"OrganizationType":"Enterprise Edition"}}]}}}}' ;;
117
+ "data delete")
118
+ [ "$3" = "record" ] || exit 1
119
+ [[ " $* " == *" --sobject {object_name} "* ]] || exit 1
120
+ [[ " $* " == *" --record-id {record_id} "* ]] || exit 1
121
+ [[ " $* " == *" --use-tooling-api "* ]] || exit 1
122
+ echo '{{"status":0,"result":{{"id":"{record_id}","success":true,"errors":[]}}}}'
123
+ ;;
124
+ *) exit 1 ;;
125
+ esac
126
+ """
127
+ result = run_script(
128
+ "org", operation, record_id,
129
+ sf_source=sf_source,
130
+ extra_env={"CONFIRM_DELETE": "1"},
131
+ )
132
+ self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
133
+ self.assertEqual("deleted", json.loads(result.stdout)["status"])
134
+
135
+
136
+ if __name__ == "__main__":
137
+ unittest.main(verbosity=2)