@salesforce/afv-skills 1.46.0 → 1.48.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 (317) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-observe/SKILL.md +32 -4
  3. package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
  4. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
  5. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
  6. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
  7. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
  8. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
  9. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
  10. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
  11. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
  12. package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
  13. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
  14. package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
  15. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
  16. package/skills/dx-devops-project-manage/SKILL.md +197 -0
  17. package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
  18. package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
  19. package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
  20. package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
  21. package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
  22. package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
  23. package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
  24. package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
  25. package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
  26. package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
  27. package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
  28. package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
  29. package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
  30. package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
  31. package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
  32. package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
  33. package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
  34. package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
  35. package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
  36. package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
  37. package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
  38. package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
  39. package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
  40. package/skills/education-cloud-domain-configure/SKILL.md +234 -0
  41. package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
  42. package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
  43. package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
  44. package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
  45. package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
  46. package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
  47. package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
  48. package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
  49. package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
  50. package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
  51. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
  52. package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
  53. package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
  54. package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
  55. package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
  56. package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
  57. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
  58. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
  59. package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
  60. package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
  61. package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
  62. package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
  63. package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
  64. package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
  65. package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
  66. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
  67. package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
  68. package/skills/experience-cms-brand-apply/SKILL.md +5 -5
  69. package/skills/experience-cms-brand-create/SKILL.md +2 -2
  70. package/skills/experience-cms-content-generate/SKILL.md +1 -0
  71. package/skills/experience-cms-content-render/SKILL.md +173 -0
  72. package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
  73. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
  74. package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
  75. package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
  76. package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
  77. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
  78. package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
  79. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
  80. package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
  81. package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
  82. package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
  83. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
  84. package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
  85. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
  86. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
  87. package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
  88. package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
  89. package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
  90. package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
  91. package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
  92. package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
  93. package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
  94. package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
  95. package/skills/experience-cms-content-render/references/package-api.md +106 -0
  96. package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
  97. package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
  98. package/skills/experience-cms-content-render/references/verify.md +49 -0
  99. package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
  100. package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
  101. package/skills/experience-search-coordinate/SKILL.md +198 -0
  102. package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
  103. package/skills/experience-search-coordinate/references/content-route.md +313 -0
  104. package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
  105. package/skills/experience-search-coordinate/references/media-route.md +172 -0
  106. package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
  107. package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
  108. package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
  109. package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
  110. package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
  111. package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
  112. package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
  113. package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
  114. package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
  115. package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
  116. package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
  117. package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
  118. package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
  119. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
  120. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
  121. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
  122. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
  123. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
  124. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
  125. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
  126. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
  127. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
  128. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
  129. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
  130. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
  131. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
  132. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
  133. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
  134. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
  135. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
  136. package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
  137. package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
  138. package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
  139. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
  140. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
  141. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
  142. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
  143. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
  144. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
  145. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
  146. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
  147. package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
  148. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
  149. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
  150. package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
  151. package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
  152. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
  153. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
  154. package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
  155. package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
  156. package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
  157. package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
  158. package/skills/life-sciences-territory-configure/SKILL.md +217 -0
  159. package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
  160. package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
  161. package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
  162. package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
  163. package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
  164. package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
  165. package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
  166. package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
  167. package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
  168. package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
  169. package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
  170. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
  171. package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
  172. package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
  173. package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
  174. package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
  175. package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
  176. package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
  177. package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
  178. package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
  179. package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
  180. package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
  181. package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
  182. package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
  183. package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
  184. package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
  185. package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
  186. package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
  187. package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
  188. package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
  189. package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
  190. package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
  191. package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
  192. package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
  193. package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
  194. package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
  195. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
  196. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
  197. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
  198. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
  199. package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
  200. package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
  201. package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
  202. package/skills/service-omni-agent-users-create/SKILL.md +96 -0
  203. package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
  204. package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
  205. package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
  206. package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
  207. package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
  208. package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
  209. package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
  210. package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
  211. package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
  212. package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
  213. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
  214. package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
  215. package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
  216. package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
  217. package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
  218. package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
  219. package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
  220. package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
  221. package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
  222. package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
  223. package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
  224. package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
  225. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
  226. package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
  227. package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
  228. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
  229. package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
  230. package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
  231. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
  232. package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
  233. package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
  234. package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
  235. package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
  236. package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
  237. package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
  238. package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
  239. package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
  240. package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
  241. package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
  242. package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
  243. package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
  244. package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
  245. package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
  246. package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
  247. package/skills/service-omni-queue-deploy/SKILL.md +91 -0
  248. package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
  249. package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
  250. package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
  251. package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
  252. package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
  253. package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
  254. package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
  255. package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
  256. package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
  257. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
  258. package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
  259. package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
  260. package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
  261. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
  262. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
  263. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
  264. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
  265. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
  266. package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
  267. package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
  268. package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
  269. package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
  270. package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
  271. package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
  272. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
  273. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
  274. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
  275. package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
  276. package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
  277. package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
  278. package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
  279. package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
  280. package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
  281. package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
  282. package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
  283. package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
  284. package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
  285. package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
  286. package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
  287. package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
  288. package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
  289. package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
  290. package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
  291. package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
  292. package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
  293. package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
  294. package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
  295. package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
  296. package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
  297. package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
  298. package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
  299. package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
  300. package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
  301. package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
  302. package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
  303. package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
  304. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
  305. package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
  306. package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
  307. package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
  308. package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
  309. package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
  310. package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
  311. package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
  312. package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
  313. package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
  314. package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
  315. package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
  316. package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
  317. package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
@@ -0,0 +1,1542 @@
1
+ #!/usr/bin/env bash
2
+ # integration-driver.sh - orchestrate the Omni-Channel skills in canonical order (--plan read-only detect, --run full execution requiring safe_to_write); array-based invocation (never eval), production-guarded. Modes, design, artifacts, exit codes: SKILL.md.
3
+
4
+ set -euo pipefail
5
+
6
+ MODE=""
7
+ POSITIONAL=()
8
+ while [[ $# -gt 0 ]]; do
9
+ case "$1" in
10
+ --plan|--dry-run) MODE="plan"; shift ;;
11
+ --run) MODE="run"; shift ;;
12
+ -h|--help)
13
+ cat >&2 <<'EOF'
14
+ Usage:
15
+ bash integration-driver.sh --plan <org-alias> [<count>] [<routing_targets_csv>] [<supervisor_count>]
16
+ bash integration-driver.sh --run <org-alias> [<count>] [<routing_targets_csv>] [<supervisor_count>]
17
+
18
+ Modes:
19
+ --plan Read-only. Runs only detect scripts. Emits {dry_run: true, ...}.
20
+ --run Executes deploys/POSTs. Enforces safe_to_write. Emits {dry_run: false, ...}.
21
+
22
+ Positional args (same for both modes):
23
+ org-alias sf CLI alias for the target org (required).
24
+ count Number of agents / queue members expected 1..10 (default 3).
25
+ routing_targets_csv Comma-separated sObject list. Default "Case".
26
+ Supported: Case, Incident, MessagingSession, VoiceCall.
27
+ supervisor_count Number of supervisor users expected 1..5 (default 1).
28
+ EOF
29
+ exit 0
30
+ ;;
31
+ --) shift; POSITIONAL+=("$@"); break ;;
32
+ -*)
33
+ echo "{\"error\":\"Unknown flag: $1. Use --plan or --run. See --help.\"}" >&2
34
+ exit 2
35
+ ;;
36
+ *)
37
+ POSITIONAL+=("$1"); shift
38
+ ;;
39
+ esac
40
+ done
41
+
42
+ if [ -z "$MODE" ]; then
43
+ echo '{"error":"Must specify --plan or --run. See --help."}' >&2
44
+ exit 2
45
+ fi
46
+
47
+ # Reset positional params; guard empty-array expansion for bash 3.2 nounset.
48
+ if [ "${#POSITIONAL[@]}" -gt 0 ]; then
49
+ set -- "${POSITIONAL[@]}"
50
+ else
51
+ set --
52
+ fi
53
+
54
+ if [ $# -lt 1 ]; then
55
+ echo '{"error":"Missing required <org-alias>. See --help."}' >&2
56
+ exit 2
57
+ fi
58
+
59
+ ORG="$1"
60
+ COUNT="${2:-3}"
61
+ ROUTING_TARGETS_CSV="${3:-Case}"
62
+ SUPERVISOR_COUNT="${4:-1}"
63
+
64
+ # Agent/supervisor profile: a full Salesforce + Service Cloud profile (not Platform, which fails the
65
+ # ContactCenterSupervisor assignment). Override via OMNI_AGENT_PROFILE / OMNI_SUPERVISOR_PROFILE.
66
+ # Empty here means "resolve against the org after auth" (see resolve_profile below) rather than
67
+ # hard-coding "Service Cloud User", which does not exist on every org.
68
+ AGENT_PROFILE="${OMNI_AGENT_PROFILE:-}"
69
+ SUPERVISOR_PROFILE="${OMNI_SUPERVISOR_PROFILE:-}"
70
+ SUPERVISOR_CONFIG_DN="${OMNI_SUPERVISOR_CONFIG_DEVELOPER_NAME:-Omni_Supervisor}"
71
+
72
+ # A complete WorkSkillRouting mapping is an unambiguous SBR request. Infer SkillsBased so callers do
73
+ # not need a hidden second switch; an explicit OMNI_ROUTING_TYPE still wins.
74
+ ROUTING_TYPE="${OMNI_ROUTING_TYPE:-}"
75
+ WSR_INPUT_COUNT=0
76
+ for wsr_value in "${OMNI_WSR_ENTITY:-}" "${OMNI_WSR_FIELD:-}" "${OMNI_WSR_SKILL:-}" "${OMNI_WSR_VALUE:-}"; do
77
+ [ -z "$wsr_value" ] || WSR_INPUT_COUNT=$((WSR_INPUT_COUNT + 1))
78
+ done
79
+ if [ -z "$ROUTING_TYPE" ]; then
80
+ if [ "$WSR_INPUT_COUNT" -eq 4 ]; then
81
+ ROUTING_TYPE="SkillsBased"
82
+ else
83
+ ROUTING_TYPE="QueueBased"
84
+ fi
85
+ fi
86
+ SKILL_OPTION="${OMNI_SKILL_OPTION:-RunSBRRules}"
87
+ SBR_SKILL="${OMNI_SBR_SKILL:-${OMNI_WSR_SKILL:-}}"
88
+
89
+ case "$ROUTING_TYPE" in
90
+ QueueBased|SkillsBased) ;;
91
+ *) echo "{\"error\":\"Invalid OMNI_ROUTING_TYPE '$ROUTING_TYPE'. Supported: QueueBased, SkillsBased.\"}" >&2; exit 2 ;;
92
+ esac
93
+
94
+ if [ "$WSR_INPUT_COUNT" -gt 0 ] && [ "$WSR_INPUT_COUNT" -lt 4 ]; then
95
+ echo '{"error":"Incomplete WorkSkillRouting input. Set all four of OMNI_WSR_ENTITY, OMNI_WSR_FIELD, OMNI_WSR_SKILL, and OMNI_WSR_VALUE."}' >&2
96
+ exit 2
97
+ fi
98
+
99
+ if [ "$ROUTING_TYPE" = "SkillsBased" ]; then
100
+ if [ "$WSR_INPUT_COUNT" -ne 4 ]; then
101
+ echo '{"error":"SkillsBased routing requires a complete WorkSkillRouting mapping: OMNI_WSR_ENTITY, OMNI_WSR_FIELD, OMNI_WSR_SKILL, and OMNI_WSR_VALUE."}' >&2
102
+ exit 2
103
+ fi
104
+ if [ "$SKILL_OPTION" != "RunSBRRules" ]; then
105
+ echo "{\"error\":\"The coordinator supports OMNI_SKILL_OPTION=RunSBRRules for SkillsBased routing; got '$SKILL_OPTION'.\"}" >&2
106
+ exit 2
107
+ fi
108
+ if [ -z "$SBR_SKILL" ] || [ "$SBR_SKILL" != "${OMNI_WSR_SKILL:-}" ]; then
109
+ echo '{"error":"SkillsBased routing requires OMNI_SBR_SKILL to be unset or match OMNI_WSR_SKILL, so provisioned agents receive the skill required by the WorkSkillRouting rule."}' >&2
110
+ exit 2
111
+ fi
112
+ fi
113
+
114
+ # Optional fallback assignee for Case/VoiceCall QRCs (Username or 005 Id); exported to each QRC leaf.
115
+ if [ -n "${OMNI_OVERFLOW_ASSIGNEE:-}" ]; then export QRC_OVERFLOW_ASSIGNEE="$OMNI_OVERFLOW_ASSIGNEE"; fi
116
+ if [ -n "${OMNI_QRC_PUSH_TIMEOUT:-}" ]; then export QRC_PUSH_TIMEOUT="$OMNI_QRC_PUSH_TIMEOUT"; fi
117
+ if [ -n "${OMNI_QRC_CAPACITY_PERCENTAGE:-}" ]; then export QRC_CAPACITY_PERCENTAGE="$OMNI_QRC_CAPACITY_PERCENTAGE"; fi
118
+
119
+ if ! [[ "$ORG" =~ ^[A-Za-z0-9._@-]+$ ]]; then
120
+ echo "{\"error\":\"Invalid org-alias '$ORG'. Allowed: A-Z, a-z, 0-9, . _ @ -\"}" >&2
121
+ exit 2
122
+ fi
123
+
124
+ # Agent count 1..10 - matches the leaf contracts; validate before any org write.
125
+ if ! [[ "$COUNT" =~ ^([1-9]|10)$ ]]; then
126
+ echo "{\"error\":\"Invalid count '$COUNT'. Must be integer 1..10.\"}" >&2
127
+ exit 2
128
+ fi
129
+
130
+ # Supervisor count 1..5 - matches the leaf contracts; validate before any org write.
131
+ if ! [[ "$SUPERVISOR_COUNT" =~ ^[1-5]$ ]]; then
132
+ echo "{\"error\":\"Invalid supervisor_count '$SUPERVISOR_COUNT'. Must be integer 1..5.\"}" >&2
133
+ exit 2
134
+ fi
135
+
136
+ if ! [[ "$SUPERVISOR_CONFIG_DN" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
137
+ echo "{\"error\":\"Invalid OMNI_SUPERVISOR_CONFIG_DEVELOPER_NAME '$SUPERVISOR_CONFIG_DN'. It must start with a letter and contain only A-Z, a-z, 0-9, or _ (maximum 80 characters).\"}" >&2
138
+ exit 2
139
+ fi
140
+
141
+ IFS=',' read -r -a ROUTING_TARGETS <<< "$ROUTING_TARGETS_CSV"
142
+ for target in "${ROUTING_TARGETS[@]}"; do
143
+ case "$target" in
144
+ Case|Incident|MessagingSession|VoiceCall) ;;
145
+ *)
146
+ echo "{\"error\":\"Unsupported routing target: '$target'. Supported: Case, Incident, MessagingSession, VoiceCall.\"}" >&2
147
+ exit 2
148
+ ;;
149
+ esac
150
+ done
151
+
152
+ SKILLS_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
153
+ TIMESTAMP="$(date -u +%Y%m%dT%H%M%SZ)"
154
+ # Artifacts may hold generated credentials: 0700 parent + umask 077 (files default 0600) so nothing
155
+ # is group/other readable; mktemp -d avoids same-second $TIMESTAMP collisions.
156
+ umask 077
157
+ mkdir -p "/tmp/omni-driver"
158
+ chmod 700 "/tmp/omni-driver" 2>/dev/null || true
159
+ ARTIFACTS_DIR="$(mktemp -d "/tmp/omni-driver/${TIMESTAMP}.XXXXXX")"
160
+ chmod 700 "$ARTIFACTS_DIR"
161
+
162
+ # Secrets split out of per-skill JSON into one restricted file (see redact_credentials).
163
+ CREDENTIALS_FILE="$ARTIFACTS_DIR/CREDENTIALS.json"
164
+ # The user-create leaves escrow generated passwords directly to this restricted file instead of ever
165
+ # printing plaintext to stdout; point them at the coordinator's file so all credentials land here.
166
+ # redact_credentials still runs on artifacts as defense-in-depth (it is a no-op when none are present).
167
+ export OMNI_CREDENTIALS_FILE="$CREDENTIALS_FILE"
168
+
169
+ SKILL_RESULTS='[]'
170
+ OVERALL_STATUS="green"
171
+ BLOCKING_SKILL=""
172
+ FIRST_YELLOW_SKILL=""
173
+ ATTENTION_LEAVES='[]'
174
+
175
+ # Global safe_to_write guard - enforced BEFORE any skill runs (both --plan and --run)
176
+ if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
177
+ echo "{\"error\":\"Org alias '$ORG' is not authenticated.\"}" >&2
178
+ exit 1
179
+ fi
180
+
181
+ ORG_JSON=$(sf data query --target-org "$ORG" --json \
182
+ --query "SELECT Id, IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" 2>/dev/null) || {
183
+ echo "{\"error\":\"Failed to query Organization. SOQL error: $(printf '%s' "$ORG_JSON" | head -c 400)\"}" >&2
184
+ exit 1
185
+ }
186
+
187
+ IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox')
188
+ TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
189
+ ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType')
190
+
191
+ SAFE_TO_WRITE="false"
192
+ if [ "$IS_SANDBOX" = "true" ] \
193
+ || [ "$TRIAL_EXP" != "null" ] \
194
+ || [ "$ORG_TYPE" = "Developer Edition" ] \
195
+ || [ "$ORG_TYPE" = "Base Edition" ]; then
196
+ SAFE_TO_WRITE="true"
197
+ fi
198
+
199
+ if [ "$SAFE_TO_WRITE" != "true" ]; then
200
+ jq -n \
201
+ --arg org "$ORG" \
202
+ --arg is_sandbox "$IS_SANDBOX" \
203
+ --arg trial_exp "$TRIAL_EXP" \
204
+ --arg org_type "$ORG_TYPE" \
205
+ --arg mode "$MODE" \
206
+ '{
207
+ error: "Refusing to run integration driver against a real production customer org.",
208
+ org_alias: $org,
209
+ mode: $mode,
210
+ org_shape: {
211
+ is_sandbox: ($is_sandbox == "true"),
212
+ trial_expiration_date: (if $trial_exp == "null" then null else $trial_exp end),
213
+ organization_type: $org_type
214
+ },
215
+ safe_to_write: false,
216
+ required_shape: "Target must have IsSandbox=true OR TrialExpirationDate!=null OR OrganizationType in (Developer Edition, Base Edition)."
217
+ }' >&2
218
+ exit 1
219
+ fi
220
+
221
+ echo "" >&2
222
+ echo "=== Omni Integration Driver (mode=$MODE) ===" >&2
223
+ echo "Org: $ORG" >&2
224
+ echo "Count: $COUNT" >&2
225
+ echo "Supervisor count: $SUPERVISOR_COUNT" >&2
226
+ echo "Supervisor config: $SUPERVISOR_CONFIG_DN" >&2
227
+ echo "Routing targets: ${ROUTING_TARGETS[*]}" >&2
228
+ echo "Timestamp: $TIMESTAMP" >&2
229
+ echo "Artifacts: $ARTIFACTS_DIR" >&2
230
+ echo "safe_to_write: true (IsSandbox=$IS_SANDBOX TrialExp=$TRIAL_EXP OrgType=$ORG_TYPE)" >&2
231
+
232
+ # Resolve a usable profile on THIS org rather than assuming "Service Cloud User" exists. An explicit
233
+ # override is trusted as-is (only warned if absent); otherwise prefer "Service Cloud User", then fall
234
+ # back to "Standard User" (a full-Salesforce-license profile present in every org).
235
+ ORG_PROFILES_JSON=$(sf data query --target-org "$ORG" --json \
236
+ --query "SELECT Name FROM Profile" 2>/dev/null || echo '{}')
237
+ profile_exists() { echo "$ORG_PROFILES_JSON" | jq -e --arg n "$1" 'any(.result.records[]?; .Name==$n)' >/dev/null 2>&1; }
238
+ resolve_profile() {
239
+ local want="$1" label="$2" p
240
+ if [ -n "$want" ]; then
241
+ profile_exists "$want" || echo " [!] $label profile '$want' not found on org; the create leaf may fail or fall back." >&2
242
+ printf '%s' "$want"; return 0
243
+ fi
244
+ for p in "Service Cloud User" "Standard User"; do
245
+ profile_exists "$p" && { printf '%s' "$p"; return 0; }
246
+ done
247
+ printf '%s' "Standard User"
248
+ }
249
+ AGENT_PROFILE="$(resolve_profile "$AGENT_PROFILE" "Agent")"
250
+ SUPERVISOR_PROFILE="$(resolve_profile "$SUPERVISOR_PROFILE" "Supervisor")"
251
+ echo "Agent profile: $AGENT_PROFILE" >&2
252
+ echo "Supervisor profile: $SUPERVISOR_PROFILE" >&2
253
+ echo "" >&2
254
+
255
+ echo " Preflight: entity availability" >&2
256
+ for target in "${ROUTING_TARGETS[@]}"; do
257
+ if [ "$target" = "Case" ]; then continue; fi
258
+ ENTITY_CHECK=$(sf data query --target-org "$ORG" --json \
259
+ --query "SELECT QualifiedApiName FROM EntityDefinition WHERE QualifiedApiName = '$target'" 2>/dev/null \
260
+ | jq -r '.result.totalSize // 0')
261
+ if [ "$ENTITY_CHECK" != "1" ]; then
262
+ echo " [x] Target sObject '$target' not available on this org." >&2
263
+ echo " Enable required feature (Incident Management / Enhanced Messaging) and retry." >&2
264
+ exit 2
265
+ fi
266
+ echo " [ok] $target" >&2
267
+ done
268
+ echo "" >&2
269
+
270
+ OVERALL_START=$(date +%s)
271
+
272
+ # --plan: export PLAN_ONLY so each writer self-gates to its read-only detection path.
273
+ if [ "$MODE" = "plan" ]; then export PLAN_ONLY=1; fi
274
+
275
+ # Dependency tracking: never write after a red prerequisite (green/yellow allow dependents).
276
+ BASE_OK="true"
277
+ AGENT_USERS_OK="true"
278
+ SUPERVISOR_USERS_OK="true"
279
+ SUPERVISOR_CONFIG_OK="true"
280
+ # Per-target flags without associative arrays (macOS bash 3.2 has no declare -A): namespaced
281
+ # scalars + indirect expansion.
282
+ _flag_var() { # $1=namespace $2=key -> safe scalar var name
283
+ local key; key=$(printf '%s' "$2" | tr -c 'A-Za-z0-9' '_')
284
+ printf '%s' "__FLAG_${1}_${key}"
285
+ }
286
+ set_flag() { # $1=namespace $2=key $3=value
287
+ local v; v=$(_flag_var "$1" "$2"); printf -v "$v" '%s' "$3"
288
+ }
289
+ get_flag() { # $1=namespace $2=key $3=default -> echoes value or default
290
+ # set +u guards indirect expansion of an unset key under nounset (bash 3.2); scoped to this subshell.
291
+ local v val; v=$(_flag_var "$1" "$2"); set +u; val="${!v}"; set -u; printf '%s' "${val:-$3}"
292
+ }
293
+
294
+ # Canonical QRC DeveloperName per target; only Case/VoiceCall get a QRC + created queue.
295
+ qrc_dn_for_target() {
296
+ case "$1" in
297
+ Case) printf 'Case_Routing_Config' ;;
298
+ VoiceCall) printf 'Voice_Routing_Config' ;;
299
+ Incident) printf 'Incident_Routing_Config' ;;
300
+ MessagingSession) printf 'Messaging_Routing_Config' ;;
301
+ esac
302
+ }
303
+ qrc_override_for_target() {
304
+ case "$1" in
305
+ Case) printf '%s' "${OMNI_CASE_ROUTING_CONFIG_DEVELOPER_NAME:-}" ;;
306
+ VoiceCall) printf '%s' "${OMNI_VOICE_ROUTING_CONFIG_DEVELOPER_NAME:-}" ;;
307
+ Incident) printf '%s' "${OMNI_INCIDENT_ROUTING_CONFIG_DEVELOPER_NAME:-}" ;;
308
+ MessagingSession) printf '%s' "${OMNI_MESSAGING_ROUTING_CONFIG_DEVELOPER_NAME:-}" ;;
309
+ esac
310
+ }
311
+ queue_override_for_target() {
312
+ case "$1" in
313
+ Case) printf '%s' "${OMNI_CASE_QUEUE_DEVELOPER_NAME:-}" ;;
314
+ VoiceCall) printf '%s' "${OMNI_VOICE_QUEUE_DEVELOPER_NAME:-}" ;;
315
+ Incident) printf '%s' "${OMNI_INCIDENT_QUEUE_DEVELOPER_NAME:-}" ;;
316
+ MessagingSession) printf '%s' "${OMNI_MESSAGING_QUEUE_DEVELOPER_NAME:-}" ;;
317
+ esac
318
+ }
319
+ resolve_qrc_dn_for_target() {
320
+ local target="$1" explicit queue_dn linked_json linked_id qrc_json linked_dn
321
+ explicit="$(qrc_override_for_target "$target")"
322
+ if [ -n "$explicit" ]; then
323
+ if ! [[ "$explicit" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
324
+ echo "Invalid routing-config DeveloperName '$explicit' for $target." >&2
325
+ return 1
326
+ fi
327
+ printf '%s' "$explicit"
328
+ return 0
329
+ fi
330
+
331
+ queue_dn="$(queue_override_for_target "$target")"
332
+ if [ -z "$queue_dn" ]; then
333
+ qrc_dn_for_target "$target"
334
+ return 0
335
+ fi
336
+ if ! [[ "$queue_dn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
337
+ echo "Invalid queue DeveloperName '$queue_dn' for $target." >&2
338
+ return 1
339
+ fi
340
+
341
+ linked_json=$(sf data query --target-org "$ORG" --json \
342
+ --query "SELECT QueueRoutingConfigId FROM Group WHERE Type='Queue' AND DeveloperName='$queue_dn' LIMIT 1" 2>/dev/null || echo '{}')
343
+ if ! echo "$linked_json" | jq -e '.result.records | arrays' >/dev/null 2>&1; then
344
+ echo "Queue-to-routing-config discovery was inconclusive for '$queue_dn'." >&2
345
+ return 1
346
+ fi
347
+ linked_id=$(echo "$linked_json" | jq -r '.result.records[0].QueueRoutingConfigId // ""')
348
+ if [ -z "$linked_id" ]; then
349
+ qrc_dn_for_target "$target"
350
+ return 0
351
+ fi
352
+ if ! [[ "$linked_id" =~ ^[A-Za-z0-9]{15,18}$ ]]; then
353
+ echo "Queue '$queue_dn' returned an invalid QueueRoutingConfigId." >&2
354
+ return 1
355
+ fi
356
+
357
+ qrc_json=$(sf data query --target-org "$ORG" --json \
358
+ --query "SELECT DeveloperName FROM QueueRoutingConfig WHERE Id='$linked_id' LIMIT 1" 2>/dev/null || echo '{}')
359
+ if ! echo "$qrc_json" | jq -e '.result.records | arrays' >/dev/null 2>&1; then
360
+ echo "Linked routing-config discovery was inconclusive for queue '$queue_dn'." >&2
361
+ return 1
362
+ fi
363
+ linked_dn=$(echo "$qrc_json" | jq -r '.result.records[0].DeveloperName // ""')
364
+ if [ -z "$linked_dn" ]; then
365
+ echo "Queue '$queue_dn' references a QueueRoutingConfig that could not be resolved." >&2
366
+ return 1
367
+ fi
368
+ if ! [[ "$linked_dn" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
369
+ echo "Queue '$queue_dn' returned an invalid linked QueueRoutingConfig DeveloperName." >&2
370
+ return 1
371
+ fi
372
+ printf '%s' "$linked_dn"
373
+ }
374
+ # Whether this target participates in the create-queue + QRC + routing-flow steel thread.
375
+ target_has_routing_thread() {
376
+ case "$1" in Case|VoiceCall) return 0 ;; *) return 1 ;; esac
377
+ }
378
+
379
+ # --plan readiness flags: a downstream detector runs only when its prerequisites are present on the
380
+ # org; otherwise it is deferred as pending (yellow), since probing an Omni-dependent skill before
381
+ # Omni is enabled raises INVALID_TYPE and would mislead as red. Clean --plan (all reused) => org
382
+ # already configured; any action_needed (yellow) => real setup work remains.
383
+ BASE_ENABLED="false" # Omni-Channel settings already enabled (base plan .all_enabled)
384
+ AGENT_USERS_PRESENT="false" # all agent users already exist (agent-users plan status == reused)
385
+ SUPERVISOR_USERS_PRESENT="false" # all supervisor users already exist (supervisor plan == reused)
386
+ SUPERVISOR_CONFIG_PRESENT="false" # OmniSupervisorConfig already exists (config plan == reused)
387
+
388
+ # Record a plan skill deferred because a prerequisite isn't in place yet (pending/yellow, not blocked).
389
+ record_plan_pending() {
390
+ local name="$1" key="$2" desc="$3" reason="$4"
391
+ printf " [ 0s] %-55s → pending (yellow)\n" "$key" >&2
392
+ echo " -> $reason" >&2
393
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
394
+ --arg name "$name" --arg key "$key" --arg desc "$desc" --arg reason "$reason" \
395
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: "action_needed", color: "yellow", elapsed_sec: 0, artifact_path: null, exit_code: 0, action_required: $reason}]')
396
+ ATTENTION_LEAVES=$(echo "$ATTENTION_LEAVES" | jq -c --arg k "$key" '. + [$k]')
397
+ if [ -z "$FIRST_YELLOW_SKILL" ]; then FIRST_YELLOW_SKILL="$key"; fi
398
+ if [ "$OVERALL_STATUS" = "green" ]; then OVERALL_STATUS="yellow"; fi
399
+ }
400
+
401
+ # Record a skill that was intentionally NOT run because a prerequisite failed.
402
+ record_dependency_skip() {
403
+ local name="$1" key="$2" desc="$3" reason="$4"
404
+ printf " [ 0s] %-55s → blocked_by_dependency\n" "$key" >&2
405
+ echo " -> $reason" >&2
406
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
407
+ --arg name "$name" --arg key "$key" --arg desc "$desc" --arg reason "$reason" \
408
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: "blocked_by_dependency", color: "red", elapsed_sec: 0, artifact_path: null, exit_code: 0, blocking_issue: $reason}]')
409
+ OVERALL_STATUS="red"
410
+ if [ -z "$BLOCKING_SKILL" ]; then BLOCKING_SKILL="$key"; fi
411
+ }
412
+
413
+ # Move plaintext passwords from a per-skill artifact into restricted CREDENTIALS.json (0600),
414
+ # replacing them with "[redacted - see CREDENTIALS.json]"; handles top-level and nested .create shapes.
415
+ CREDENTIALS_WRITTEN="false"
416
+ redact_credentials() {
417
+ local artifact="$1" key="$2"
418
+ [ -s "$artifact" ] || return 0
419
+ # Fast path: nothing to redact if no password field is present anywhere.
420
+ if ! jq -e '.. | objects | select(has("password")) | .password | strings' "$artifact" >/dev/null 2>&1; then
421
+ return 0
422
+ fi
423
+
424
+ # Extract [{username,password}] pairs (both shapes) with non-null passwords.
425
+ local creds
426
+ creds=$(jq -c '
427
+ [ (.created_users // []), (.create.created_users // []) ]
428
+ | add // []
429
+ | map(select(.password != null) | {username: .username, password: .password})
430
+ ' "$artifact" 2>/dev/null || echo '[]')
431
+
432
+ if [ "$(echo "$creds" | jq 'length')" -gt 0 ]; then
433
+ # Escrow credentials fail-closed: build to a temp file, verify, atomically replace, and on any
434
+ # failure delete the plaintext source artifact and abort so credentials never leak.
435
+ local existing='{}' credtmp
436
+ [ -f "$CREDENTIALS_FILE" ] && existing=$(cat "$CREDENTIALS_FILE" 2>/dev/null || echo '{}')
437
+ credtmp=$(mktemp)
438
+ if ! echo "$existing" | jq --arg k "$key" --argjson c "$creds" '. + {($k): $c}' > "$credtmp" 2>/dev/null \
439
+ || [ ! -s "$credtmp" ] \
440
+ || ! jq -e --arg k "$key" '.[$k] | length > 0' "$credtmp" >/dev/null 2>&1; then
441
+ rm -f "$credtmp"
442
+ rm -f "$artifact"
443
+ printf " [x] SECURITY: could not escrow credentials to CREDENTIALS.json; deleted plaintext artifact %s and aborting.\n" "$artifact" >&2
444
+ exit 1
445
+ fi
446
+ if ! mv "$credtmp" "$CREDENTIALS_FILE"; then
447
+ rm -f "$credtmp"
448
+ rm -f "$artifact"
449
+ printf " [x] SECURITY: could not persist CREDENTIALS.json; deleted plaintext artifact %s and aborting.\n" "$artifact" >&2
450
+ exit 1
451
+ fi
452
+ chmod 600 "$CREDENTIALS_FILE" 2>/dev/null || true
453
+ CREDENTIALS_WRITTEN="true"
454
+ fi
455
+
456
+ # Null every .password in place, fail-closed: on failure delete the plaintext original and abort.
457
+ local tmp
458
+ tmp=$(mktemp)
459
+ if ! jq '(.. | objects | select(has("password")) | .password) |= (if . == null then null else "[redacted - see CREDENTIALS.json]" end)' \
460
+ "$artifact" > "$tmp" 2>/dev/null; then
461
+ rm -f "$tmp"
462
+ rm -f "$artifact"
463
+ printf " [x] SECURITY: could not redact credentials in %s; deleted plaintext artifact and aborting.\n" "$artifact" >&2
464
+ exit 1
465
+ fi
466
+ # Verify the rewritten file is non-empty and that no original plaintext password value survived.
467
+ if [ ! -s "$tmp" ] || jq -e '.. | objects | select(has("password")) | .password
468
+ | strings | select(. != "[redacted - see CREDENTIALS.json]")' "$tmp" >/dev/null 2>&1; then
469
+ rm -f "$tmp"
470
+ rm -f "$artifact"
471
+ printf " [x] SECURITY: redaction verification failed for %s; deleted plaintext artifact and aborting.\n" "$artifact" >&2
472
+ exit 1
473
+ fi
474
+ # Atomic replace only after verification succeeded.
475
+ if ! mv "$tmp" "$artifact"; then
476
+ rm -f "$tmp"
477
+ rm -f "$artifact"
478
+ printf " [x] SECURITY: could not persist redacted artifact %s; deleted plaintext artifact and aborting.\n" "$artifact" >&2
479
+ exit 1
480
+ fi
481
+ chmod 600 "$artifact" 2>/dev/null || true
482
+ }
483
+
484
+ # Per-skill runner (array-based invocation - NO eval).
485
+ run_skill() {
486
+ local skill_name="$1"
487
+ local invocation_key="$2"
488
+ local skill_desc="$3"
489
+ local status_expr="$4"
490
+ local script_file="$5"
491
+ shift 5
492
+ local -a script_args=("$@")
493
+
494
+ local skill_dir="$SKILLS_DIR/$skill_name"
495
+ if [ ! -d "$skill_dir" ]; then
496
+ printf " [x] Skill directory missing: %s\n" "$skill_dir" >&2
497
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
498
+ --arg name "$skill_name" \
499
+ --arg key "$invocation_key" \
500
+ --arg desc "$skill_desc" \
501
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: "missing_skill", color: "red", elapsed_sec: 0, artifact_path: null, exit_code: 127}]')
502
+ OVERALL_STATUS="red"
503
+ if [ -z "$BLOCKING_SKILL" ]; then BLOCKING_SKILL="$invocation_key"; fi
504
+ return 1
505
+ fi
506
+
507
+ local script_dir="$skill_dir/scripts"
508
+ local script_path="$script_dir/$script_file"
509
+ if [ ! -x "$script_path" ] && [ ! -f "$script_path" ]; then
510
+ printf " [x] Skill script missing: %s\n" "$script_path" >&2
511
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
512
+ --arg name "$skill_name" \
513
+ --arg key "$invocation_key" \
514
+ --arg desc "$skill_desc" \
515
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: "missing_skill_script", color: "red", elapsed_sec: 0, artifact_path: null, exit_code: 127}]')
516
+ OVERALL_STATUS="red"
517
+ if [ -z "$BLOCKING_SKILL" ]; then BLOCKING_SKILL="$invocation_key"; fi
518
+ return 1
519
+ fi
520
+
521
+ local start_ts artifact stderr_log exit_code status end_ts elapsed
522
+ start_ts=$(date +%s)
523
+ artifact="$ARTIFACTS_DIR/${invocation_key}.json"
524
+ stderr_log="$ARTIFACTS_DIR/${invocation_key}.stderr.log"
525
+
526
+ exit_code=0
527
+ ( cd "$skill_dir" && bash "$script_path" "${script_args[@]}" ) > "$artifact" 2> "$stderr_log" || exit_code=$?
528
+ # umask 077 above already forces 0600, but be explicit in case the skill pre-created the file.
529
+ chmod 600 "$artifact" "$stderr_log" 2>/dev/null || true
530
+
531
+ # Move any plaintext passwords out of the artifact into CREDENTIALS.json.
532
+ redact_credentials "$artifact" "$invocation_key"
533
+
534
+ end_ts=$(date +%s)
535
+ elapsed=$((end_ts - start_ts))
536
+
537
+ if [ -s "$artifact" ]; then
538
+ status=$(jq -r "$status_expr" "$artifact" 2>/dev/null || echo "parse_error")
539
+ else
540
+ status="empty_output"
541
+ fi
542
+
543
+ # Status → traffic light. Green success states (shared enum): reused/updated/created/deployed/
544
+ # configured/bound/assigned/ok/success/skipped.
545
+ local color
546
+ case "$status" in
547
+ reused|updated|created|deployed|configured|bound|assigned|ok|success|skipped)
548
+ color="green"
549
+ ;;
550
+ action_needed|partial|unsupported_v1|not_evaluated)
551
+ color="yellow"
552
+ ;;
553
+ blocked|blocked_by_dependency|empty_output|parse_error)
554
+ color="red"
555
+ ;;
556
+ *)
557
+ color="unknown"
558
+ ;;
559
+ esac
560
+
561
+ if [ "$exit_code" -ne 0 ] && [ "$color" != "red" ]; then
562
+ printf " [!] skill exit_code=%d contradicts status='%s' - escalating to blocked\n" "$exit_code" "$status" >&2
563
+ status="crashed_after_reporting_${status}"
564
+ color="red"
565
+ fi
566
+ if [ "$color" = "unknown" ]; then
567
+ printf " [!] unrecognized skill status='%s' - treating as attention_required\n" "$status" >&2
568
+ color="yellow"
569
+ fi
570
+
571
+ printf " [%3ds] %-55s → %s (%s)\n" "$elapsed" "$invocation_key" "$status" "$color" >&2
572
+ if [ "$color" = "red" ]; then
573
+ local msg
574
+ msg=$(jq -r '.blocking_issue // .error // .message // "no message"' "$artifact" 2>/dev/null || echo "(unparseable)")
575
+ echo " -> $msg" >&2
576
+ elif [ "$color" = "yellow" ]; then
577
+ local msg
578
+ msg=$(jq -r '.action_required // .manual_actions // .blocking_issue // ""' "$artifact" 2>/dev/null || echo "")
579
+ if [ -n "$msg" ] && [ "$msg" != "null" ] && [ "$msg" != "[]" ]; then
580
+ echo " -> action needed: $msg" >&2
581
+ fi
582
+ fi
583
+
584
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
585
+ --arg name "$skill_name" \
586
+ --arg key "$invocation_key" \
587
+ --arg desc "$skill_desc" \
588
+ --arg status "$status" \
589
+ --arg color "$color" \
590
+ --argjson elapsed "$elapsed" \
591
+ --arg artifact "$artifact" \
592
+ --argjson exit_code "$exit_code" \
593
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: $status, color: $color, elapsed_sec: $elapsed, artifact_path: $artifact, exit_code: $exit_code}]')
594
+
595
+ case "$color" in
596
+ red)
597
+ if [ -z "$BLOCKING_SKILL" ]; then BLOCKING_SKILL="$invocation_key"; fi
598
+ OVERALL_STATUS="red"
599
+ return 1
600
+ ;;
601
+ yellow)
602
+ ATTENTION_LEAVES=$(echo "$ATTENTION_LEAVES" | jq -c --arg k "$invocation_key" '. + [$k]')
603
+ if [ -z "$FIRST_YELLOW_SKILL" ]; then FIRST_YELLOW_SKILL="$invocation_key"; fi
604
+ if [ "$OVERALL_STATUS" = "green" ]; then OVERALL_STATUS="yellow"; fi
605
+ ;;
606
+ green)
607
+ :
608
+ ;;
609
+ esac
610
+
611
+ return 0
612
+ }
613
+
614
+ # ---- Base settings ----
615
+ # Plan detects; run deploys the 5 OmniChannelSettings toggles + re-verifies. Hard prerequisite for
616
+ # every write skill below (INVALID_TYPE when Omni is off), so a red here fails fast.
617
+ if [ "$MODE" = "plan" ]; then
618
+ run_skill "service-omni-base-settings-configure" \
619
+ "service-omni-base-settings-configure" \
620
+ "Omni base settings (detect-existing)" \
621
+ '(if .all_enabled == true then "reused" else "action_needed" end)' \
622
+ configure-and-report.sh plan "$ORG" \
623
+ || BASE_OK="false"
624
+ base_art="$ARTIFACTS_DIR/service-omni-base-settings-configure.json"
625
+ if [ -f "$base_art" ] && [ "$(jq -r '.all_enabled // false' "$base_art" 2>/dev/null)" = "true" ]; then
626
+ BASE_ENABLED="true"
627
+ fi
628
+ else
629
+ run_skill "service-omni-base-settings-configure" \
630
+ "service-omni-base-settings-configure" \
631
+ "Omni base settings (detect → deploy → verify)" \
632
+ '.status' \
633
+ configure-and-report.sh run "$ORG" \
634
+ || BASE_OK="false"
635
+ fi
636
+
637
+ # ---- Agent users (detect-and-create.sh chains detect → run-create in --run mode) ----
638
+ # Agents provision on Service Cloud User (Omni requires a Service Cloud license).
639
+ run_skill "service-omni-agent-users-create" \
640
+ "service-omni-agent-users-create" \
641
+ "Agent users (detect + create when missing)" \
642
+ '.status' \
643
+ detect-and-create.sh "$MODE" "$ORG" "$COUNT" "$AGENT_PROFILE" \
644
+ || AGENT_USERS_OK="false"
645
+
646
+ if [ "$MODE" = "plan" ]; then
647
+ du_art="$ARTIFACTS_DIR/service-omni-agent-users-create.json"
648
+ if [ -f "$du_art" ] && [ "$(jq -r '.status // ""' "$du_art" 2>/dev/null)" = "reused" ]; then
649
+ AGENT_USERS_PRESENT="true"
650
+ fi
651
+ fi
652
+
653
+ # ---- Service channel (per target) ----
654
+ for target in "${ROUTING_TARGETS[@]}"; do
655
+ if [ "$MODE" = "plan" ]; then
656
+ if [ "$BASE_ENABLED" = "true" ]; then
657
+ run_skill "service-omni-service-channel-configure" \
658
+ "service-omni-service-channel-configure-${target}" \
659
+ "ServiceChannel for $target (plan detect)" \
660
+ '.status' \
661
+ deploy-and-report.sh "$ORG" "$target" || true
662
+ else
663
+ record_plan_pending "service-omni-service-channel-configure" \
664
+ "service-omni-service-channel-configure-${target}" \
665
+ "ServiceChannel for $target (pending)" \
666
+ "Omni-Channel is not yet enabled; the ServiceChannel for $target will be configured after base settings."
667
+ fi
668
+ set_flag SC_OK "$target" "true"
669
+ elif [ "$BASE_OK" != "true" ]; then
670
+ record_dependency_skip "service-omni-service-channel-configure" \
671
+ "service-omni-service-channel-configure-${target}" \
672
+ "ServiceChannel for $target (skipped)" \
673
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping ServiceChannel deploy for $target."
674
+ set_flag SC_OK "$target" "false"
675
+ else
676
+ if run_skill "service-omni-service-channel-configure" \
677
+ "service-omni-service-channel-configure-${target}" \
678
+ "ServiceChannel for $target (Metadata deploy)" \
679
+ '.status' \
680
+ deploy-and-report.sh "$ORG" "$target"; then
681
+ set_flag SC_OK "$target" "true"
682
+ else
683
+ set_flag SC_OK "$target" "false"
684
+ fi
685
+ fi
686
+ done
687
+
688
+ # ---- QRC (per target: Case + VoiceCall) ----
689
+ # Each routing-thread target gets its own QRC; the leaf derives naming from QRC_ROUTING_TARGET.
690
+ for target in "${ROUTING_TARGETS[@]}"; do
691
+ set_flag QRC_OK "$target" "true"
692
+ target_has_routing_thread "$target" || continue
693
+ if ! qrc_dn="$(resolve_qrc_dn_for_target "$target")"; then
694
+ record_dependency_skip "service-omni-queue-routing-config-deploy" \
695
+ "service-omni-queue-routing-config-deploy-${target}" \
696
+ "QueueRoutingConfig resolution for $target (blocked)" \
697
+ "Could not safely resolve the existing or requested QueueRoutingConfig DeveloperName for $target."
698
+ set_flag QRC_OK "$target" "false"
699
+ continue
700
+ fi
701
+ set_flag QRC_DN "$target" "$qrc_dn"
702
+ if [ "$MODE" = "plan" ]; then
703
+ if [ "$BASE_ENABLED" = "true" ]; then
704
+ QRC_ROUTING_TARGET="$target" QRC_DEVELOPER_NAME="$qrc_dn" run_skill "service-omni-queue-routing-config-deploy" \
705
+ "service-omni-queue-routing-config-deploy-${target}" \
706
+ "$qrc_dn (plan detect)" \
707
+ '.status' \
708
+ upsert-and-report.sh "$ORG" || set_flag QRC_OK "$target" "false"
709
+ else
710
+ record_plan_pending "service-omni-queue-routing-config-deploy" \
711
+ "service-omni-queue-routing-config-deploy-${target}" \
712
+ "$qrc_dn (pending)" \
713
+ "Omni-Channel is not yet enabled; the QueueRoutingConfig for $target will be created after base settings."
714
+ fi
715
+ elif [ "$BASE_OK" != "true" ]; then
716
+ record_dependency_skip "service-omni-queue-routing-config-deploy" \
717
+ "service-omni-queue-routing-config-deploy-${target}" \
718
+ "$qrc_dn (skipped)" \
719
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping QueueRoutingConfig upsert for $target."
720
+ set_flag QRC_OK "$target" "false"
721
+ else
722
+ QRC_ROUTING_TARGET="$target" QRC_DEVELOPER_NAME="$qrc_dn" run_skill "service-omni-queue-routing-config-deploy" \
723
+ "service-omni-queue-routing-config-deploy-${target}" \
724
+ "$qrc_dn (Data API upsert)" \
725
+ '.status' \
726
+ upsert-and-report.sh "$ORG" \
727
+ || set_flag QRC_OK "$target" "false"
728
+ fi
729
+ done
730
+
731
+ # ---- Queue (per target) ----
732
+ # Needs base settings; routing-thread targets also need their QRC first (the queue binds to it).
733
+ # --create-if-missing builds + binds a queue on fresh orgs instead of hard-blocking.
734
+ for target in "${ROUTING_TARGETS[@]}"; do
735
+ set_flag QUEUE_OK "$target" "true"
736
+ qrc_dn="$(get_flag QRC_DN "$target" "$(qrc_dn_for_target "$target")")"
737
+ queue_override="$(queue_override_for_target "$target")"
738
+ in_thread="false"; target_has_routing_thread "$target" && in_thread="true"
739
+ if [ "$MODE" = "plan" ]; then
740
+ if [ "$BASE_ENABLED" != "true" ]; then
741
+ record_plan_pending "service-omni-queue-deploy" \
742
+ "service-omni-queue-deploy-${target}" \
743
+ "Queue verify+align for $target (pending)" \
744
+ "Omni-Channel is not yet enabled; the queue verify+align for $target will run after base settings."
745
+ set_flag QUEUE_OK "$target" "false"
746
+ elif [ "$in_thread" = "true" ] && [ "$(get_flag QRC_OK "$target" true)" != "true" ]; then
747
+ record_plan_pending "service-omni-queue-deploy" \
748
+ "service-omni-queue-deploy-${target}" \
749
+ "Queue verify+align for $target (pending)" \
750
+ "QueueRoutingConfig is not yet present; the $target queue create/align will run after it is created."
751
+ set_flag QUEUE_OK "$target" "false"
752
+ elif [ "$in_thread" = "true" ]; then
753
+ run_skill "service-omni-queue-deploy" \
754
+ "service-omni-queue-deploy-${target}" \
755
+ "Queue create+align for $target (plan detect)" \
756
+ '.status' \
757
+ verify-and-align.sh "$ORG" "$target" "$qrc_dn" "$queue_override" --create-if-missing \
758
+ || set_flag QUEUE_OK "$target" "false"
759
+ else
760
+ run_skill "service-omni-queue-deploy" \
761
+ "service-omni-queue-deploy-${target}" \
762
+ "Queue verify+align for $target (plan detect)" \
763
+ '.status' \
764
+ verify-and-align.sh "$ORG" "$target" "" "$queue_override" \
765
+ || set_flag QUEUE_OK "$target" "false"
766
+ fi
767
+ elif [ "$BASE_OK" != "true" ]; then
768
+ record_dependency_skip "service-omni-queue-deploy" \
769
+ "service-omni-queue-deploy-${target}" \
770
+ "Queue verify+align for $target (skipped)" \
771
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping queue verify+align for $target."
772
+ set_flag QUEUE_OK "$target" "false"
773
+ elif [ "$in_thread" = "true" ] && [ "$(get_flag QRC_OK "$target" true)" != "true" ]; then
774
+ record_dependency_skip "service-omni-queue-deploy" \
775
+ "service-omni-queue-deploy-${target}" \
776
+ "Queue verify+align for $target (skipped)" \
777
+ "Prerequisite service-omni-queue-routing-config-deploy did not succeed for $target; cannot bind the queue to $qrc_dn."
778
+ set_flag QUEUE_OK "$target" "false"
779
+ else
780
+ if [ "$in_thread" = "true" ]; then
781
+ run_skill "service-omni-queue-deploy" \
782
+ "service-omni-queue-deploy-${target}" \
783
+ "Queue create+align for $target (Metadata + Data API)" \
784
+ '.status' \
785
+ verify-and-align.sh "$ORG" "$target" "$qrc_dn" "$queue_override" --create-if-missing \
786
+ || set_flag QUEUE_OK "$target" "false"
787
+ else
788
+ run_skill "service-omni-queue-deploy" \
789
+ "service-omni-queue-deploy-${target}" \
790
+ "Queue verify+align for $target (Data API)" \
791
+ '.status' \
792
+ verify-and-align.sh "$ORG" "$target" "" "$queue_override" \
793
+ || set_flag QUEUE_OK "$target" "false"
794
+ fi
795
+ fi
796
+ done
797
+
798
+ # ---- Queue members (per target - chained to queue-deploy's discovered queue DN) ----
799
+ # Needs both the queue (queue-deploy ok) and the agent users to exist.
800
+ for target in "${ROUTING_TARGETS[@]}"; do
801
+ if [ "$MODE" = "plan" ]; then
802
+ if [ "$(get_flag QUEUE_OK "$target" true)" != "true" ]; then
803
+ record_plan_pending "service-omni-queue-members-assign" \
804
+ "service-omni-queue-members-assign-${target}" \
805
+ "Queue members for $target (pending)" \
806
+ "The $target queue is not yet in place; member binding will run after the queue is aligned."
807
+ continue
808
+ fi
809
+ if [ "$AGENT_USERS_PRESENT" != "true" ]; then
810
+ record_plan_pending "service-omni-queue-members-assign" \
811
+ "service-omni-queue-members-assign-${target}" \
812
+ "Queue members for $target (pending)" \
813
+ "Agent users are not yet created; member binding will run after they exist."
814
+ continue
815
+ fi
816
+ q_artifact="$ARTIFACTS_DIR/service-omni-queue-deploy-${target}.json"
817
+ queue_dn=""
818
+ [ -f "$q_artifact" ] && queue_dn=$(jq -r '.developer_name // ""' "$q_artifact" 2>/dev/null || true)
819
+ if [ -z "$queue_dn" ] || [ "$queue_dn" = "null" ] || ! [[ "$queue_dn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
820
+ record_plan_pending "service-omni-queue-members-assign" \
821
+ "service-omni-queue-members-assign-${target}" \
822
+ "Queue members for $target (pending)" \
823
+ "Queue DeveloperName was not resolved in plan; member binding will run once the queue is confirmed."
824
+ continue
825
+ fi
826
+ run_skill "service-omni-queue-members-assign" \
827
+ "service-omni-queue-members-assign-${target}" \
828
+ "Queue members for $target (plan detect, queue $queue_dn)" \
829
+ '.status' \
830
+ verify-and-bind.sh "$ORG" "$queue_dn" "$COUNT" \
831
+ || true
832
+ # Plan: only "reused" means members are already bound; else a --run would bind them first.
833
+ mem_status=$(jq -r '.status // ""' "$ARTIFACTS_DIR/service-omni-queue-members-assign-${target}.json" 2>/dev/null || echo "")
834
+ if [ "$mem_status" = "reused" ]; then
835
+ set_flag QUEUE_MEMBERS_OK "$target" "true"
836
+ else
837
+ set_flag QUEUE_MEMBERS_OK "$target" "false"
838
+ fi
839
+ continue
840
+ fi
841
+ if [ "$(get_flag QUEUE_OK "$target" true)" != "true" ]; then
842
+ record_dependency_skip "service-omni-queue-members-assign" \
843
+ "service-omni-queue-members-assign-${target}" \
844
+ "Queue members for $target (skipped)" \
845
+ "Prerequisite service-omni-queue-deploy did not succeed for $target; skipping member binding."
846
+ continue
847
+ fi
848
+ if [ "$AGENT_USERS_OK" != "true" ]; then
849
+ record_dependency_skip "service-omni-queue-members-assign" \
850
+ "service-omni-queue-members-assign-${target}" \
851
+ "Queue members for $target (skipped)" \
852
+ "Prerequisite service-omni-agent-users-create did not succeed; no agent users to bind as queue members."
853
+ continue
854
+ fi
855
+
856
+ q_artifact="$ARTIFACTS_DIR/service-omni-queue-deploy-${target}.json"
857
+ queue_dn=""
858
+ if [ -f "$q_artifact" ]; then
859
+ queue_dn=$(jq -r '.developer_name // ""' "$q_artifact" 2>/dev/null || true)
860
+ fi
861
+
862
+ if [ -z "$queue_dn" ] || [ "$queue_dn" = "null" ]; then
863
+ printf " [ 0s] %-55s → skipped (queue not resolved)\n" "service-omni-queue-members-assign-${target}" >&2
864
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
865
+ --arg name "service-omni-queue-members-assign" \
866
+ --arg key "service-omni-queue-members-assign-${target}" \
867
+ --arg desc "Queue members for $target (skipped)" \
868
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: "skipped", color: "green", elapsed_sec: 0, artifact_path: null, exit_code: 0}]')
869
+ continue
870
+ fi
871
+
872
+ if ! [[ "$queue_dn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
873
+ printf " [ 0s] %-55s → blocked (invalid queue_dn: %q)\n" "service-omni-queue-members-assign-${target}" "$queue_dn" >&2
874
+ SKILL_RESULTS=$(echo "$SKILL_RESULTS" | jq -c \
875
+ --arg name "service-omni-queue-members-assign" \
876
+ --arg key "service-omni-queue-members-assign-${target}" \
877
+ --arg desc "Queue members for $target (blocked: bad DN)" \
878
+ '. + [{name: $name, invocation_key: $key, description: $desc, status: "blocked", color: "red", elapsed_sec: 0, artifact_path: null, exit_code: 1}]')
879
+ OVERALL_STATUS="red"
880
+ if [ -z "$BLOCKING_SKILL" ]; then BLOCKING_SKILL="service-omni-queue-members-assign-${target}"; fi
881
+ continue
882
+ fi
883
+
884
+ run_skill "service-omni-queue-members-assign" \
885
+ "service-omni-queue-members-assign-${target}" \
886
+ "Queue members bind to $queue_dn for $target" \
887
+ '.status' \
888
+ verify-and-bind.sh "$ORG" "$queue_dn" "$COUNT" \
889
+ || true
890
+ # Record whether member binding fully succeeded; a partial bind blocks the routing-flow gate below.
891
+ mem_status=$(jq -r '.status // ""' "$ARTIFACTS_DIR/service-omni-queue-members-assign-${target}.json" 2>/dev/null || echo "")
892
+ case "$mem_status" in
893
+ bound|reused) set_flag QUEUE_MEMBERS_OK "$target" "true" ;;
894
+ *) set_flag QUEUE_MEMBERS_OK "$target" "false" ;;
895
+ esac
896
+ done
897
+
898
+ # Resolve provisioned and reused agent usernames once. SkillsBased routing needs these bindings before
899
+ # the WorkSkillRouting rule and routing flow are activated; rep-experience stages reuse the same list.
900
+ REP_AGENT_USERNAMES=""
901
+ au_art="$ARTIFACTS_DIR/service-omni-agent-users-create.json"
902
+ if [ -f "$au_art" ]; then
903
+ REP_AGENT_USERNAMES=$(jq -r '
904
+ [ (.created_users // []), (.create.created_users // []), (.reused_users // []),
905
+ (.detect.existing_users // []), (.users // []) ]
906
+ | add // [] | map(.username // empty) | unique | join(",")
907
+ ' "$au_art" 2>/dev/null || echo "")
908
+ fi
909
+
910
+ # ---- SkillsBased prerequisites ----
911
+ # The Skill/agent bindings and WorkSkillRouting rule must exist before the trigger flow is activated.
912
+ # Otherwise an immediate record insert can produce a PSR with no SkillRequirement rows.
913
+ SBR_READY="true"
914
+ WSR_READY="true"
915
+ if [ "$ROUTING_TYPE" = "SkillsBased" ]; then
916
+ SBR_READY="false"
917
+ WSR_READY="false"
918
+
919
+ if [ "$MODE" = "plan" ]; then
920
+ if [ "$BASE_ENABLED" = "true" ] && [ "$AGENT_USERS_PRESENT" = "true" ] && [ -n "$REP_AGENT_USERNAMES" ]; then
921
+ run_skill "service-omni-skills-based-routing-configure" \
922
+ "service-omni-skills-based-routing-configure" \
923
+ "Skills-based routing for $SBR_SKILL (plan detect)" \
924
+ '.status' \
925
+ configure-and-report.sh "$ORG" "$SBR_SKILL" "$REP_AGENT_USERNAMES" || true
926
+ else
927
+ record_plan_pending "service-omni-skills-based-routing-configure" \
928
+ "service-omni-skills-based-routing-configure" \
929
+ "Skills-based routing for $SBR_SKILL (pending)" \
930
+ "Skills-based routing will run after Omni is enabled and agent users exist."
931
+ fi
932
+ elif [ "$BASE_OK" != "true" ] || [ "$AGENT_USERS_OK" != "true" ]; then
933
+ record_dependency_skip "service-omni-skills-based-routing-configure" \
934
+ "service-omni-skills-based-routing-configure" \
935
+ "Skills-based routing (skipped)" \
936
+ "Prerequisites (base settings + agent users) did not succeed; skipping agent bindings for $SBR_SKILL."
937
+ elif [ -z "$REP_AGENT_USERNAMES" ]; then
938
+ record_dependency_skip "service-omni-skills-based-routing-configure" \
939
+ "service-omni-skills-based-routing-configure" \
940
+ "Skills-based routing (skipped)" \
941
+ "No active agent usernames were resolved; refusing to activate SkillsBased routing without a skilled agent."
942
+ else
943
+ run_skill "service-omni-skills-based-routing-configure" \
944
+ "service-omni-skills-based-routing-configure" \
945
+ "Skills-based routing bind $SBR_SKILL" \
946
+ '.status' \
947
+ configure-and-report.sh "$ORG" "$SBR_SKILL" "$REP_AGENT_USERNAMES" || true
948
+ sbr_status=$(jq -r '.status // ""' "$ARTIFACTS_DIR/service-omni-skills-based-routing-configure.json" 2>/dev/null || echo "")
949
+ case "$sbr_status" in configured|reused) SBR_READY="true" ;; esac
950
+ fi
951
+
952
+ WSR_LABEL="${OMNI_WSR_LABEL:-}"
953
+ if [ "$MODE" = "plan" ]; then
954
+ if [ "$BASE_ENABLED" = "true" ]; then
955
+ run_skill "service-omni-work-skill-routing-configure" \
956
+ "service-omni-work-skill-routing-configure" \
957
+ "Field-based skill routing $OMNI_WSR_ENTITY.$OMNI_WSR_FIELD=$OMNI_WSR_VALUE -> $OMNI_WSR_SKILL (plan detect)" \
958
+ '.status' \
959
+ configure-and-report.sh "$ORG" "$OMNI_WSR_ENTITY" "$OMNI_WSR_FIELD" "$OMNI_WSR_SKILL" "$OMNI_WSR_VALUE" "$WSR_LABEL" || true
960
+ else
961
+ record_plan_pending "service-omni-work-skill-routing-configure" \
962
+ "service-omni-work-skill-routing-configure" \
963
+ "Field-based skill routing (pending)" \
964
+ "Omni-Channel is not yet enabled; the WorkSkillRouting rule will be authored after base settings."
965
+ fi
966
+ elif [ "$SBR_READY" != "true" ]; then
967
+ record_dependency_skip "service-omni-work-skill-routing-configure" \
968
+ "service-omni-work-skill-routing-configure" \
969
+ "Field-based skill routing (skipped)" \
970
+ "Agent skill provisioning did not succeed; refusing to activate a WorkSkillRouting rule with no eligible skilled agents."
971
+ else
972
+ run_skill "service-omni-work-skill-routing-configure" \
973
+ "service-omni-work-skill-routing-configure" \
974
+ "Field-based skill routing $OMNI_WSR_ENTITY.$OMNI_WSR_FIELD=$OMNI_WSR_VALUE -> $OMNI_WSR_SKILL" \
975
+ '.status' \
976
+ configure-and-report.sh "$ORG" "$OMNI_WSR_ENTITY" "$OMNI_WSR_FIELD" "$OMNI_WSR_SKILL" "$OMNI_WSR_VALUE" "$WSR_LABEL" || true
977
+ wsr_status=$(jq -r '.status // ""' "$ARTIFACTS_DIR/service-omni-work-skill-routing-configure.json" 2>/dev/null || echo "")
978
+ case "$wsr_status" in created|updated|reused) WSR_READY="true" ;; esac
979
+ fi
980
+ fi
981
+
982
+ # ---- Routing flow (per routing-thread target: Case + VoiceCall) ----
983
+ # Record-triggered flow, last leg of the thread; runs only after queue + members exist. Passes the
984
+ # discovered ServiceChannel/Queue/QRC DeveloperNames so the flow binds the same resources, not its
985
+ # defaults. OMNI_RUNTIME_PROOF=1 fires the trigger (fail-soft); OMNI_RUNTIME_PROOF_REQUIRED=1 blocks on failure.
986
+ RF_FLAGS=(--routing-type "$ROUTING_TYPE")
987
+ [ "$ROUTING_TYPE" != "SkillsBased" ] || RF_FLAGS+=(--skill-option "$SKILL_OPTION")
988
+ [ "${OMNI_RUNTIME_PROOF:-0}" = "1" ] && RF_FLAGS+=(--runtime-proof)
989
+ [ "${OMNI_RUNTIME_PROOF_REQUIRED:-0}" = "1" ] && RF_FLAGS+=(--require-proof)
990
+ for target in "${ROUTING_TARGETS[@]}"; do
991
+ target_has_routing_thread "$target" || continue
992
+ rf_qrc="$(get_flag QRC_DN "$target" "$(qrc_dn_for_target "$target")")"
993
+ # Resolve the queue + channel DeveloperNames this run established, from artifacts.
994
+ rf_queue=""
995
+ q_art="$ARTIFACTS_DIR/service-omni-queue-deploy-${target}.json"
996
+ [ -f "$q_art" ] && rf_queue=$(jq -r '.developer_name // ""' "$q_art" 2>/dev/null || true)
997
+ rf_channel=""
998
+ sc_art="$ARTIFACTS_DIR/service-omni-service-channel-configure-${target}.json"
999
+ [ -f "$sc_art" ] && rf_channel=$(jq -r '.channel_developer_name // ""' "$sc_art" 2>/dev/null || true)
1000
+ if [ "$MODE" = "plan" ]; then
1001
+ if [ "$(get_flag QUEUE_OK "$target" true)" != "true" ]; then
1002
+ record_plan_pending "service-omni-routing-flow-deploy" \
1003
+ "service-omni-routing-flow-deploy-${target}" \
1004
+ "Routing flow for $target (pending)" \
1005
+ "The $target queue is not yet in place; the routing flow will deploy after the queue + members."
1006
+ elif [ "$(get_flag QUEUE_MEMBERS_OK "$target" false)" != "true" ]; then
1007
+ record_plan_pending "service-omni-routing-flow-deploy" \
1008
+ "service-omni-routing-flow-deploy-${target}" \
1009
+ "Routing flow for $target (pending)" \
1010
+ "The $target queue has no members bound yet; the routing flow will deploy after queue members are assigned (routing to an empty queue is refused)."
1011
+ elif [ "$ROUTING_TYPE" = "SkillsBased" ]; then
1012
+ record_plan_pending "service-omni-routing-flow-deploy" \
1013
+ "service-omni-routing-flow-deploy-${target}" \
1014
+ "SkillsBased routing flow for $target (pending)" \
1015
+ "The Skill bindings and WorkSkillRouting rule will be created before the SkillsBased trigger flow is activated."
1016
+ else
1017
+ SERVICE_CHANNEL_DEVELOPER_NAME="$rf_channel" QUEUE_DEVELOPER_NAME="$rf_queue" ROUTING_CONFIG_DEVELOPER_NAME="$rf_qrc" \
1018
+ run_skill "service-omni-routing-flow-deploy" \
1019
+ "service-omni-routing-flow-deploy-${target}" \
1020
+ "Routing flow for $target (plan detect)" \
1021
+ '.status' \
1022
+ deploy-and-report.sh "$ORG" --target "$target" --trigger "${RF_FLAGS[@]}" \
1023
+ || true
1024
+ fi
1025
+ elif [ "$(get_flag QUEUE_OK "$target" true)" != "true" ]; then
1026
+ record_dependency_skip "service-omni-routing-flow-deploy" \
1027
+ "service-omni-routing-flow-deploy-${target}" \
1028
+ "Routing flow for $target (skipped)" \
1029
+ "Prerequisite service-omni-queue-deploy did not succeed for $target; the routing flow has no queue to route to."
1030
+ elif [ "$(get_flag QUEUE_MEMBERS_OK "$target" false)" != "true" ]; then
1031
+ # Never route to an empty queue: members must be fully bound (bound/reused) first.
1032
+ record_dependency_skip "service-omni-routing-flow-deploy" \
1033
+ "service-omni-routing-flow-deploy-${target}" \
1034
+ "Routing flow for $target (skipped)" \
1035
+ "Prerequisite service-omni-queue-members-assign did not fully succeed for $target; refusing to deploy the routing flow to a queue with no bound members."
1036
+ elif [ "$ROUTING_TYPE" = "SkillsBased" ] && { [ "$SBR_READY" != "true" ] || [ "$WSR_READY" != "true" ]; }; then
1037
+ record_dependency_skip "service-omni-routing-flow-deploy" \
1038
+ "service-omni-routing-flow-deploy-${target}" \
1039
+ "SkillsBased routing flow for $target (skipped)" \
1040
+ "SkillsBased prerequisites did not complete: agent skill bindings ready=$SBR_READY, WorkSkillRouting ready=$WSR_READY. Refusing to activate a flow that could create a PSR without SkillRequirement rows."
1041
+ else
1042
+ SERVICE_CHANNEL_DEVELOPER_NAME="$rf_channel" QUEUE_DEVELOPER_NAME="$rf_queue" ROUTING_CONFIG_DEVELOPER_NAME="$rf_qrc" \
1043
+ run_skill "service-omni-routing-flow-deploy" \
1044
+ "service-omni-routing-flow-deploy-${target}" \
1045
+ "Routing flow for $target (record-triggered deploy)" \
1046
+ '.status' \
1047
+ deploy-and-report.sh "$ORG" --target "$target" --trigger "${RF_FLAGS[@]}" \
1048
+ || true
1049
+ fi
1050
+ done
1051
+
1052
+ # ---- Presence status (per target - chained to service-channel's discovered channel DN) ----
1053
+ # Needs base settings + the target's ServiceChannel (the Available_<X> status binds to it).
1054
+ for target in "${ROUTING_TARGETS[@]}"; do
1055
+ if [ "$MODE" = "plan" ]; then
1056
+ if [ "$BASE_ENABLED" != "true" ]; then
1057
+ record_plan_pending "service-omni-presence-status-deploy" \
1058
+ "service-omni-presence-status-deploy-${target}" \
1059
+ "Presence statuses for $target (pending)" \
1060
+ "Omni-Channel is not yet enabled; presence statuses for $target will deploy after base settings."
1061
+ else
1062
+ sc_artifact="$ARTIFACTS_DIR/service-omni-service-channel-configure-${target}.json"
1063
+ channel_dn=""
1064
+ [ -f "$sc_artifact" ] && channel_dn=$(jq -r '.channel_developer_name // ""' "$sc_artifact" 2>/dev/null || true)
1065
+ if [ -n "$channel_dn" ] && [[ "$channel_dn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
1066
+ run_skill "service-omni-presence-status-deploy" \
1067
+ "service-omni-presence-status-deploy-${target}" \
1068
+ "Presence statuses for $target (plan detect, channel $channel_dn)" \
1069
+ '.status' \
1070
+ deploy-and-report.sh "$ORG" "$target" "$channel_dn" || true
1071
+ else
1072
+ run_skill "service-omni-presence-status-deploy" \
1073
+ "service-omni-presence-status-deploy-${target}" \
1074
+ "Presence statuses for $target (plan detect, default channel)" \
1075
+ '.status' \
1076
+ deploy-and-report.sh "$ORG" "$target" || true
1077
+ fi
1078
+ fi
1079
+ elif [ "$BASE_OK" != "true" ]; then
1080
+ record_dependency_skip "service-omni-presence-status-deploy" \
1081
+ "service-omni-presence-status-deploy-${target}" \
1082
+ "Presence statuses for $target (skipped)" \
1083
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping presence-status deploy for $target."
1084
+ elif [ "$(get_flag SC_OK "$target" true)" != "true" ]; then
1085
+ record_dependency_skip "service-omni-presence-status-deploy" \
1086
+ "service-omni-presence-status-deploy-${target}" \
1087
+ "Presence statuses for $target (skipped)" \
1088
+ "Prerequisite service-omni-service-channel-configure did not succeed for $target; the Available_$target status has no channel to bind to."
1089
+ else
1090
+ sc_artifact="$ARTIFACTS_DIR/service-omni-service-channel-configure-${target}.json"
1091
+ channel_dn=""
1092
+ if [ -f "$sc_artifact" ]; then
1093
+ channel_dn=$(jq -r '.channel_developer_name // ""' "$sc_artifact" 2>/dev/null || true)
1094
+ fi
1095
+ if [ -n "$channel_dn" ] && [[ "$channel_dn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
1096
+ run_skill "service-omni-presence-status-deploy" \
1097
+ "service-omni-presence-status-deploy-${target}" \
1098
+ "Presence statuses for $target (bound to $channel_dn)" \
1099
+ '.status' \
1100
+ deploy-and-report.sh "$ORG" "$target" "$channel_dn" \
1101
+ || true
1102
+ else
1103
+ run_skill "service-omni-presence-status-deploy" \
1104
+ "service-omni-presence-status-deploy-${target}" \
1105
+ "Presence statuses for $target (channel DN unresolved, using skill default)" \
1106
+ '.status' \
1107
+ deploy-and-report.sh "$ORG" "$target" \
1108
+ || true
1109
+ fi
1110
+ fi
1111
+ done
1112
+
1113
+ # Optional rep-experience stages (opt-in OMNI_REP_EXPERIENCE=1, default off): presence-user-config
1114
+ # and sidebar-enable. SkillsBased prerequisites run earlier whenever SkillsBased routing is requested.
1115
+ if [ "${OMNI_REP_EXPERIENCE:-0}" = "1" ]; then
1116
+ # ---- Presence user config (decline + decline reason + ACW), once ----
1117
+ if [ "$MODE" = "plan" ]; then
1118
+ if [ "$BASE_ENABLED" = "true" ]; then
1119
+ AGENT_USERNAMES_CSV="$REP_AGENT_USERNAMES" run_skill "service-omni-presence-user-config-deploy" \
1120
+ "service-omni-presence-user-config-deploy" \
1121
+ "Presence user config (plan detect)" \
1122
+ '.status' \
1123
+ deploy-and-report.sh "$ORG" || true
1124
+ else
1125
+ record_plan_pending "service-omni-presence-user-config-deploy" \
1126
+ "service-omni-presence-user-config-deploy" \
1127
+ "Presence user config (pending)" \
1128
+ "Omni-Channel is not yet enabled; the presence user config will deploy after base settings."
1129
+ fi
1130
+ elif [ "$BASE_OK" != "true" ]; then
1131
+ record_dependency_skip "service-omni-presence-user-config-deploy" \
1132
+ "service-omni-presence-user-config-deploy" \
1133
+ "Presence user config (skipped)" \
1134
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping presence user config."
1135
+ else
1136
+ AGENT_USERNAMES_CSV="$REP_AGENT_USERNAMES" run_skill "service-omni-presence-user-config-deploy" \
1137
+ "service-omni-presence-user-config-deploy" \
1138
+ "Presence user config (Metadata deploy)" \
1139
+ '.status' \
1140
+ deploy-and-report.sh "$ORG" \
1141
+ || true
1142
+ fi
1143
+
1144
+ # ---- Omni sidebar ---- (empty OMNI_CONSOLE_APP => leaf auto-detects the console app)
1145
+ SIDEBAR_APP="${OMNI_CONSOLE_APP:-}"
1146
+ if [ "$MODE" = "plan" ]; then
1147
+ if [ "$BASE_ENABLED" = "true" ]; then
1148
+ run_skill "service-omni-sidebar-configure" \
1149
+ "service-omni-sidebar-configure" \
1150
+ "Omni sidebar (plan detect)" \
1151
+ '.status' \
1152
+ enable-and-report.sh "$ORG" "$SIDEBAR_APP" || true
1153
+ else
1154
+ record_plan_pending "service-omni-sidebar-configure" \
1155
+ "service-omni-sidebar-configure" \
1156
+ "Omni sidebar (pending)" \
1157
+ "Omni-Channel is not yet enabled; the sidebar will be enabled after base settings."
1158
+ fi
1159
+ elif [ "$BASE_OK" != "true" ]; then
1160
+ record_dependency_skip "service-omni-sidebar-configure" \
1161
+ "service-omni-sidebar-configure" \
1162
+ "Omni sidebar (skipped)" \
1163
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping sidebar enablement."
1164
+ else
1165
+ run_skill "service-omni-sidebar-configure" \
1166
+ "service-omni-sidebar-configure" \
1167
+ "Omni sidebar (CustomApplication deploy)" \
1168
+ '.status' \
1169
+ enable-and-report.sh "$ORG" "$SIDEBAR_APP" \
1170
+ || true
1171
+ fi
1172
+ fi
1173
+
1174
+ # ---- Permission set assign (Omni_Agent) ----
1175
+ # Needs agent users to exist.
1176
+ if [ "$MODE" = "plan" ]; then
1177
+ if [ "$AGENT_USERS_PRESENT" = "true" ]; then
1178
+ run_skill "service-omni-permission-set-assign" \
1179
+ "service-omni-permission-set-assign" \
1180
+ "Omni_Agent PSA (plan detect)" \
1181
+ '.status' \
1182
+ verify-and-assign.sh "$ORG" "$COUNT" "Omni_Agent" || true
1183
+ else
1184
+ record_plan_pending "service-omni-permission-set-assign" \
1185
+ "service-omni-permission-set-assign" \
1186
+ "Omni_Agent PSA (pending)" \
1187
+ "Agent users are not yet created; the Omni_Agent permission-set assignment will run after they exist."
1188
+ fi
1189
+ elif [ "$AGENT_USERS_OK" != "true" ]; then
1190
+ record_dependency_skip "service-omni-permission-set-assign" \
1191
+ "service-omni-permission-set-assign" \
1192
+ "Omni_Agent PSA (skipped)" \
1193
+ "Prerequisite service-omni-agent-users-create did not succeed; no agent users to assign Omni_Agent to."
1194
+ else
1195
+ run_skill "service-omni-permission-set-assign" \
1196
+ "service-omni-permission-set-assign" \
1197
+ "Omni_Agent to users (Data API POST)" \
1198
+ '.status' \
1199
+ verify-and-assign.sh "$ORG" "$COUNT" "Omni_Agent" \
1200
+ || true
1201
+ fi
1202
+
1203
+ # ---- Supervisor users (create N; independent of routing targets) ----
1204
+ # On Service Cloud User (Omni Supervisor is a Service Cloud feature; the Salesforce license pool is
1205
+ # often exhausted on demo orgs). detect-and-create runs a real read-only detector in --plan.
1206
+ run_skill "service-omni-supervisor-users-create" \
1207
+ "service-omni-supervisor-users-create" \
1208
+ "Supervisor users (detect + create when missing)" \
1209
+ '.status' \
1210
+ detect-and-create.sh "$MODE" "$ORG" "$SUPERVISOR_COUNT" "$SUPERVISOR_PROFILE" \
1211
+ || SUPERVISOR_USERS_OK="false"
1212
+
1213
+ if [ "$MODE" = "plan" ]; then
1214
+ su_art="$ARTIFACTS_DIR/service-omni-supervisor-users-create.json"
1215
+ if [ -f "$su_art" ] && [ "$(jq -r '.status // ""' "$su_art" 2>/dev/null)" = "reused" ]; then
1216
+ SUPERVISOR_USERS_PRESENT="true"
1217
+ fi
1218
+ fi
1219
+
1220
+ # ---- Supervisor permset (ContactCenterSupervisor to supervisor users) ----
1221
+ # Needs supervisor users. Use standard ContactCenterSupervisor, not custom Omni_Supervisor (which
1222
+ # re-declares license-gated perms that fail on standard licenses); the leaf already defaults to it.
1223
+ if [ "$MODE" = "plan" ]; then
1224
+ if [ "$SUPERVISOR_USERS_PRESENT" = "true" ]; then
1225
+ run_skill "service-omni-supervisor-permset-assign" \
1226
+ "service-omni-supervisor-permset-assign" \
1227
+ "ContactCenterSupervisor PSA (plan detect)" \
1228
+ '.status' \
1229
+ verify-and-assign.sh "$ORG" "$SUPERVISOR_COUNT" "ContactCenterSupervisor" || true
1230
+ else
1231
+ record_plan_pending "service-omni-supervisor-permset-assign" \
1232
+ "service-omni-supervisor-permset-assign" \
1233
+ "ContactCenterSupervisor PSA (pending)" \
1234
+ "Supervisor users are not yet created; the ContactCenterSupervisor permission-set assignment will run after they exist."
1235
+ fi
1236
+ elif [ "$SUPERVISOR_USERS_OK" != "true" ]; then
1237
+ record_dependency_skip "service-omni-supervisor-permset-assign" \
1238
+ "service-omni-supervisor-permset-assign" \
1239
+ "ContactCenterSupervisor PSA (skipped)" \
1240
+ "Prerequisite service-omni-supervisor-users-create did not succeed; no supervisor users to assign ContactCenterSupervisor to."
1241
+ else
1242
+ run_skill "service-omni-supervisor-permset-assign" \
1243
+ "service-omni-supervisor-permset-assign" \
1244
+ "ContactCenterSupervisor to supervisor users (Data API POST)" \
1245
+ '.status' \
1246
+ verify-and-assign.sh "$ORG" "$SUPERVISOR_COUNT" "ContactCenterSupervisor" \
1247
+ || true
1248
+ fi
1249
+
1250
+ # ---- Command Center surface analysis (read-only advisory) ----
1251
+ # Reports whether the org is on the classic Omni Supervisor surface or Command Center V2 (Enhanced
1252
+ # Omni-Channel). Advisory only: this coordinator configures the classic surface; V2 enablement is an
1253
+ # org-preference flip outside the Metadata API. It never writes, so it runs identically in plan and
1254
+ # run and never hard-blocks - ambiguous detection surfaces as yellow, a clean read as green.
1255
+ run_skill "service-omni-command-center-analyze" \
1256
+ "service-omni-command-center-analyze" \
1257
+ "Command Center surface analysis (classic vs V2, read-only)" \
1258
+ '(if .status=="detected" then "ok" else "action_needed" end)' \
1259
+ analyze.sh "$ORG" \
1260
+ || true
1261
+
1262
+ # ---- Supervisor config (deploy OmniSupervisorConfig binding supervisors + queues) ----
1263
+ # Needs base settings + supervisor users. Pass the actual discovered queue DeveloperNames so it
1264
+ # binds the real (possibly reused) queues, not the skill's canonical defaults.
1265
+ if [ "$MODE" = "plan" ]; then
1266
+ if [ "$BASE_ENABLED" != "true" ]; then
1267
+ record_plan_pending "service-omni-supervisor-config-deploy" \
1268
+ "service-omni-supervisor-config-deploy" \
1269
+ "OmniSupervisorConfig (pending)" \
1270
+ "Omni-Channel is not yet enabled; the OmniSupervisorConfig will deploy after base settings."
1271
+ elif [ "$SUPERVISOR_USERS_PRESENT" != "true" ]; then
1272
+ record_plan_pending "service-omni-supervisor-config-deploy" \
1273
+ "service-omni-supervisor-config-deploy" \
1274
+ "OmniSupervisorConfig (pending)" \
1275
+ "Supervisor users are not yet created; the OmniSupervisorConfig will deploy after they exist."
1276
+ else
1277
+ DISCOVERED_QUEUES_CSV=""
1278
+ for target in "${ROUTING_TARGETS[@]}"; do
1279
+ [ "$(get_flag QUEUE_OK "$target" true)" = "true" ] || continue
1280
+ q_artifact="$ARTIFACTS_DIR/service-omni-queue-deploy-${target}.json"
1281
+ [ -f "$q_artifact" ] || continue
1282
+ qdn=$(jq -r '.developer_name // ""' "$q_artifact" 2>/dev/null || true)
1283
+ if [ -n "$qdn" ] && [ "$qdn" != "null" ] && [[ "$qdn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
1284
+ if [ -z "$DISCOVERED_QUEUES_CSV" ]; then
1285
+ DISCOVERED_QUEUES_CSV="$qdn"
1286
+ else
1287
+ DISCOVERED_QUEUES_CSV="$DISCOVERED_QUEUES_CSV,$qdn"
1288
+ fi
1289
+ fi
1290
+ done
1291
+ run_skill "service-omni-supervisor-config-deploy" \
1292
+ "service-omni-supervisor-config-deploy" \
1293
+ "OmniSupervisorConfig (plan detect)" \
1294
+ '.status' \
1295
+ deploy-and-report.sh "$ORG" "$SUPERVISOR_COUNT" "$DISCOVERED_QUEUES_CSV" "" "" "$SUPERVISOR_CONFIG_DN" || true
1296
+ fi
1297
+ elif [ "$BASE_OK" != "true" ]; then
1298
+ record_dependency_skip "service-omni-supervisor-config-deploy" \
1299
+ "service-omni-supervisor-config-deploy" \
1300
+ "OmniSupervisorConfig (skipped)" \
1301
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping OmniSupervisorConfig deploy."
1302
+ elif [ "$SUPERVISOR_USERS_OK" != "true" ]; then
1303
+ record_dependency_skip "service-omni-supervisor-config-deploy" \
1304
+ "service-omni-supervisor-config-deploy" \
1305
+ "OmniSupervisorConfig (skipped)" \
1306
+ "Prerequisite service-omni-supervisor-users-create did not succeed; no supervisor users to bind into the OmniSupervisorConfig."
1307
+ else
1308
+ # Actual queue DeveloperNames from succeeded queue-deploy artifacts (authoritative over defaults).
1309
+ DISCOVERED_QUEUES_CSV=""
1310
+ for target in "${ROUTING_TARGETS[@]}"; do
1311
+ [ "$(get_flag QUEUE_OK "$target" true)" = "true" ] || continue
1312
+ q_artifact="$ARTIFACTS_DIR/service-omni-queue-deploy-${target}.json"
1313
+ [ -f "$q_artifact" ] || continue
1314
+ qdn=$(jq -r '.developer_name // ""' "$q_artifact" 2>/dev/null || true)
1315
+ if [ -n "$qdn" ] && [ "$qdn" != "null" ] && [[ "$qdn" =~ ^[A-Za-z0-9_]{1,80}$ ]]; then
1316
+ if [ -z "$DISCOVERED_QUEUES_CSV" ]; then
1317
+ DISCOVERED_QUEUES_CSV="$qdn"
1318
+ else
1319
+ DISCOVERED_QUEUES_CSV="$DISCOVERED_QUEUES_CSV,$qdn"
1320
+ fi
1321
+ fi
1322
+ done
1323
+
1324
+ run_skill "service-omni-supervisor-config-deploy" \
1325
+ "service-omni-supervisor-config-deploy" \
1326
+ "OmniSupervisorConfig deploy (Metadata API)" \
1327
+ '.status' \
1328
+ deploy-and-report.sh "$ORG" "$SUPERVISOR_COUNT" "$DISCOVERED_QUEUES_CSV" "" "" "$SUPERVISOR_CONFIG_DN" \
1329
+ || true
1330
+ fi
1331
+
1332
+ # Derive the supervisor-config disposition (from its artifact) to gate the surface stage below.
1333
+ # plan: PRESENT iff the config already exists (status reused); run: OK iff it now exists.
1334
+ supcfg_art="$ARTIFACTS_DIR/service-omni-supervisor-config-deploy.json"
1335
+ supcfg_status=""
1336
+ SUPERVISOR_CONFIG_RESOLVED_DN="$SUPERVISOR_CONFIG_DN"
1337
+ [ -f "$supcfg_art" ] && supcfg_status=$(jq -r '.status // ""' "$supcfg_art" 2>/dev/null || echo "")
1338
+ if [ -f "$supcfg_art" ]; then
1339
+ artifact_config_dn=$(jq -r '.config_developer_name // ""' "$supcfg_art" 2>/dev/null || echo "")
1340
+ if [[ "$artifact_config_dn" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
1341
+ SUPERVISOR_CONFIG_RESOLVED_DN="$artifact_config_dn"
1342
+ fi
1343
+ fi
1344
+ if [ "$MODE" = "plan" ]; then
1345
+ [ "$supcfg_status" = "reused" ] && SUPERVISOR_CONFIG_PRESENT="true"
1346
+ else
1347
+ case "$supcfg_status" in created|updated|reused) SUPERVISOR_CONFIG_OK="true" ;; *) SUPERVISOR_CONFIG_OK="false" ;; esac
1348
+ fi
1349
+
1350
+ # ---- Supervisor surface (Action + Tab companions on the OmniSupervisorConfig) ----
1351
+ # Needs base settings + an OmniSupervisorConfig. Inserts the standard action/tab rows via the Data
1352
+ # API against the parent config's Id - independent of the config metadata file and idempotent.
1353
+ if [ "$MODE" = "plan" ]; then
1354
+ if [ "$BASE_ENABLED" != "true" ]; then
1355
+ record_plan_pending "service-omni-supervisor-surface-deploy" \
1356
+ "service-omni-supervisor-surface-deploy" \
1357
+ "Supervisor action/tab surface (pending)" \
1358
+ "Omni-Channel is not yet enabled; the supervisor action/tab surface will be populated after base settings."
1359
+ elif [ "$SUPERVISOR_CONFIG_PRESENT" != "true" ]; then
1360
+ record_plan_pending "service-omni-supervisor-surface-deploy" \
1361
+ "service-omni-supervisor-surface-deploy" \
1362
+ "Supervisor action/tab surface (pending)" \
1363
+ "OmniSupervisorConfig is not yet present; the supervisor action/tab surface will be populated after the config is deployed."
1364
+ else
1365
+ run_skill "service-omni-supervisor-surface-deploy" \
1366
+ "service-omni-supervisor-surface-deploy" \
1367
+ "Supervisor action/tab surface (plan detect)" \
1368
+ '.status' \
1369
+ deploy-and-report.sh "$ORG" "$SUPERVISOR_CONFIG_RESOLVED_DN" || true
1370
+ fi
1371
+ elif [ "$BASE_OK" != "true" ]; then
1372
+ record_dependency_skip "service-omni-supervisor-surface-deploy" \
1373
+ "service-omni-supervisor-surface-deploy" \
1374
+ "Supervisor action/tab surface (skipped)" \
1375
+ "Prerequisite service-omni-base-settings-configure did not succeed; skipping the supervisor action/tab surface."
1376
+ elif [ "$SUPERVISOR_CONFIG_OK" != "true" ]; then
1377
+ record_dependency_skip "service-omni-supervisor-surface-deploy" \
1378
+ "service-omni-supervisor-surface-deploy" \
1379
+ "Supervisor action/tab surface (skipped)" \
1380
+ "Prerequisite service-omni-supervisor-config-deploy did not succeed; no OmniSupervisorConfig to populate the action/tab surface on."
1381
+ else
1382
+ run_skill "service-omni-supervisor-surface-deploy" \
1383
+ "service-omni-supervisor-surface-deploy" \
1384
+ "Supervisor action/tab surface (Data API inserts)" \
1385
+ '.status' \
1386
+ deploy-and-report.sh "$ORG" "$SUPERVISOR_CONFIG_RESOLVED_DN" || true
1387
+ fi
1388
+
1389
+ OVERALL_END=$(date +%s)
1390
+ TOTAL_ELAPSED=$((OVERALL_END - OVERALL_START))
1391
+
1392
+ # Run artifacts (run mode only): run.log (sanitized sequence + result), deploy_ids.json (provenance
1393
+ # manifest per skill), teardown.sh (dry-run reverse-order plan; makes NO changes - removal is manual).
1394
+ RUN_LOG=""
1395
+ DEPLOY_IDS=""
1396
+ TEARDOWN=""
1397
+ if [ "$MODE" = "run" ]; then
1398
+ RUN_LOG="$ARTIFACTS_DIR/run.log"
1399
+ DEPLOY_IDS="$ARTIFACTS_DIR/deploy_ids.json"
1400
+ TEARDOWN="$ARTIFACTS_DIR/teardown.sh"
1401
+
1402
+ {
1403
+ echo "# Omni coordinator run log"
1404
+ echo "# org=$ORG mode=$MODE timestamp=$TIMESTAMP"
1405
+ echo "# overall_status=$OVERALL_STATUS elapsed=${TOTAL_ELAPSED}s"
1406
+ echo "#"
1407
+ echo "$SKILL_RESULTS" | jq -r '.[] | "[\(.elapsed_sec)s] \(.invocation_key) → \(.status) (\(.color))"'
1408
+ } > "$RUN_LOG"
1409
+ chmod 600 "$RUN_LOG" 2>/dev/null || true
1410
+
1411
+ DEPLOY_MANIFEST='[]'
1412
+ while IFS= read -r res; do
1413
+ [ -z "$res" ] && continue
1414
+ key=$(echo "$res" | jq -r '.invocation_key')
1415
+ skill=$(echo "$res" | jq -r '.name')
1416
+ status=$(echo "$res" | jq -r '.status')
1417
+ color=$(echo "$res" | jq -r '.color')
1418
+ art=$(echo "$res" | jq -r '.artifact_path // ""')
1419
+ case "$status" in
1420
+ created|deployed) prov="created" ;;
1421
+ updated) prov="updated" ;;
1422
+ reused|assigned|bound|configured|ok|success|skipped) prov="reused" ;;
1423
+ *) prov="none" ;;
1424
+ esac
1425
+ ids='[]'; before='null'; devname='null'
1426
+ # Require a NON-EMPTY artifact (-s, not -f): a leaf that crashed can leave a 0-byte file, and
1427
+ # jq over empty input yields an empty string (not "null"), which would make --argjson below throw
1428
+ # and abort the whole run under set -e before the final report. Empty-string results are also
1429
+ # normalized back to valid JSON as a second guard.
1430
+ if [ -n "$art" ] && [ "$art" != "null" ] && [ -s "$art" ]; then
1431
+ ids=$(jq -c '[ .id?, .developer_name?, .channel_developer_name?,
1432
+ (.created_users[]?.id), (.agents[]?.service_resource_id),
1433
+ (.assignments[]?.id) ] | map(select(. != null and . != "" and . != "null"))' "$art" 2>/dev/null || echo '[]')
1434
+ before=$(jq -c '.before? // null' "$art" 2>/dev/null || echo 'null')
1435
+ devname=$(jq -r '.developer_name // "null"' "$art" 2>/dev/null || echo 'null')
1436
+ [ -z "$ids" ] && ids='[]'
1437
+ [ -z "$before" ] && before='null'
1438
+ [ -z "$devname" ] && devname='null'
1439
+ fi
1440
+ DEPLOY_MANIFEST=$(echo "$DEPLOY_MANIFEST" | jq -c \
1441
+ --arg key "$key" --arg skill "$skill" --arg status "$status" --arg color "$color" \
1442
+ --arg prov "$prov" --arg art "$art" --arg dn "$devname" \
1443
+ --argjson ids "$ids" --argjson before "$before" \
1444
+ '. + [{invocation_key:$key, skill:$skill, status:$status, color:$color, provenance:$prov, developer_name:(if $dn=="null" then null else $dn end), resource_ids:$ids, before:$before, artifact_path:(if $art=="" then null else $art end)}]')
1445
+ done < <(echo "$SKILL_RESULTS" | jq -c '.[]')
1446
+
1447
+ jq -n --arg org "$ORG" --arg ts "$TIMESTAMP" --argjson entries "$DEPLOY_MANIFEST" \
1448
+ '{coordinator:"service-omni-channel-setup-coordinate", org_alias:$org, timestamp:$ts, note:"Provenance manifest. Per-skill artifacts remain the source of truth. Only entries with provenance=created were created by this run.", entries:$entries}' > "$DEPLOY_IDS"
1449
+ chmod 600 "$DEPLOY_IDS" 2>/dev/null || true
1450
+
1451
+ cat > "$TEARDOWN" <<TDEOF
1452
+ #!/usr/bin/env bash
1453
+ # AUTO-GENERATED teardown plan for the Omni coordinator run at $TIMESTAMP (org $ORG).
1454
+ # DRY-RUN: prints a reverse-order plan, makes NO changes. Removal is manual - metadata components
1455
+ # need destructiveChanges deploys, only run-created resources may be removed, reused records never.
1456
+ set -euo pipefail
1457
+ ORG="$ORG"
1458
+ MANIFEST="\$(cd "\$(dirname "\$0")" && pwd)/deploy_ids.json"
1459
+ if [ ! -f "\$MANIFEST" ]; then echo "manifest not found: \$MANIFEST" >&2; exit 1; fi
1460
+ echo "Teardown plan (reverse dependency order) for org \$ORG"
1461
+ echo "Source manifest: \$MANIFEST"
1462
+ echo ""
1463
+ jq -r '.entries | reverse | .[]
1464
+ | if .provenance=="created" then
1465
+ "REMOVE \(.invocation_key): created \((.developer_name // (.resource_ids | join(", "))) // "-") [artifact: \(.artifact_path // "-")]"
1466
+ elif .provenance=="updated" then
1467
+ "RESTORE \(.invocation_key): restore prior values \(.before | tojson) [artifact: \(.artifact_path // "-")]"
1468
+ else
1469
+ "KEEP \(.invocation_key): \(.status) - adopted/reused, do not delete"
1470
+ end' "\$MANIFEST"
1471
+ echo ""
1472
+ echo "Plan only - no changes made. Reverse each REMOVE using its per-skill artifact; metadata"
1473
+ echo "components need a destructiveChanges deploy; data records use sf api request rest --method DELETE."
1474
+ TDEOF
1475
+ chmod 700 "$TEARDOWN" 2>/dev/null || true
1476
+ fi
1477
+
1478
+ echo "" >&2
1479
+ # The create leaves now escrow directly into CREDENTIALS_FILE, so recognise that too (redact_credentials
1480
+ # only flips CREDENTIALS_WRITTEN when it finds plaintext to move, which no longer happens).
1481
+ if [ "$CREDENTIALS_WRITTEN" != "true" ] && [ -s "$CREDENTIALS_FILE" ]; then
1482
+ chmod 600 "$CREDENTIALS_FILE" 2>/dev/null || true
1483
+ CREDENTIALS_WRITTEN="true"
1484
+ fi
1485
+ if [ "$CREDENTIALS_WRITTEN" = "true" ]; then
1486
+ echo " Generated credentials written to (mode 0600): $CREDENTIALS_FILE" >&2
1487
+ echo " Read them once, distribute securely, then delete: rm -f \"$CREDENTIALS_FILE\"" >&2
1488
+ echo " Passwords have been redacted from all other artifacts." >&2
1489
+ echo "" >&2
1490
+ fi
1491
+ jq -n \
1492
+ --arg org "$ORG" \
1493
+ --arg mode "$MODE" \
1494
+ --arg timestamp "$TIMESTAMP" \
1495
+ --arg artifacts_dir "$ARTIFACTS_DIR" \
1496
+ --arg overall_status "$OVERALL_STATUS" \
1497
+ --arg blocking_skill "$BLOCKING_SKILL" \
1498
+ --arg first_yellow_skill "$FIRST_YELLOW_SKILL" \
1499
+ --argjson attention_leaves "$ATTENTION_LEAVES" \
1500
+ --argjson total_elapsed "$TOTAL_ELAPSED" \
1501
+ --argjson results "$SKILL_RESULTS" \
1502
+ --argjson requested_count "$COUNT" \
1503
+ --argjson requested_supervisor_count "$SUPERVISOR_COUNT" \
1504
+ --arg routing_targets "$ROUTING_TARGETS_CSV" \
1505
+ --argjson safe_to_write true \
1506
+ --arg credentials_file "$CREDENTIALS_FILE" \
1507
+ --arg credentials_written "$CREDENTIALS_WRITTEN" \
1508
+ --arg run_log "$RUN_LOG" \
1509
+ --arg deploy_ids_file "$DEPLOY_IDS" \
1510
+ --arg teardown_script "$TEARDOWN" \
1511
+ '{
1512
+ coordinator: "service-omni-channel-setup-coordinate",
1513
+ mode: $mode,
1514
+ dry_run: ($mode == "plan"),
1515
+ org_alias: $org,
1516
+ timestamp: $timestamp,
1517
+ safe_to_write: $safe_to_write,
1518
+ run_log: (if $run_log == "" then null else $run_log end),
1519
+ deploy_ids_file: (if $deploy_ids_file == "" then null else $deploy_ids_file end),
1520
+ teardown_script: (if $teardown_script == "" then null else $teardown_script end),
1521
+ requested_count: $requested_count,
1522
+ requested_supervisor_count: $requested_supervisor_count,
1523
+ routing_targets: ($routing_targets | split(",")),
1524
+ artifacts_dir: $artifacts_dir,
1525
+ credentials_file: (if $credentials_written == "true" then $credentials_file else null end),
1526
+ credentials_note: (if $credentials_written == "true" then "Generated passwords are in credentials_file (mode 0600) and redacted from all other artifacts. Read once, distribute securely, then delete." else null end),
1527
+ overall_status: $overall_status,
1528
+ blocking_skill: (if $blocking_skill == "" then null else $blocking_skill end),
1529
+ first_attention_skill: (if $first_yellow_skill == "" then null else $first_yellow_skill end),
1530
+ attention_leaves: $attention_leaves,
1531
+ total_elapsed_sec: $total_elapsed,
1532
+ leaves_executed: ($results | length),
1533
+ skills: $results,
1534
+ exit_semantics: "exit 0 iff overall_status == green. yellow exits 2 (setup work still required). red exits 1 (blocked)."
1535
+ }'
1536
+
1537
+ case "$OVERALL_STATUS" in
1538
+ green) exit 0 ;;
1539
+ yellow) exit 2 ;;
1540
+ red) exit 1 ;;
1541
+ *) exit 1 ;;
1542
+ esac