@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.
- package/package.json +1 -1
- package/skills/agentforce-observe/SKILL.md +32 -4
- package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
- package/skills/dx-devops-project-manage/SKILL.md +197 -0
- package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
- package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
- package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
- package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
- package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
- package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
- package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
- package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
- package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
- package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
- package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
- package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
- package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
- package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
- package/skills/education-cloud-domain-configure/SKILL.md +234 -0
- package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
- package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
- package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
- package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
- package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
- package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
- package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
- package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +7 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +1 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
- package/skills/experience-cms-brand-apply/SKILL.md +5 -5
- package/skills/experience-cms-brand-create/SKILL.md +2 -2
- package/skills/experience-cms-content-generate/SKILL.md +1 -0
- package/skills/experience-cms-content-render/SKILL.md +173 -0
- package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
- package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
- package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
- package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
- package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
- package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
- package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
- package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
- package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
- package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
- package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
- package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
- package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
- package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
- package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
- package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
- package/skills/experience-cms-content-render/references/package-api.md +106 -0
- package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
- package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
- package/skills/experience-cms-content-render/references/verify.md +49 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
- package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
- package/skills/experience-search-coordinate/SKILL.md +198 -0
- package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
- package/skills/experience-search-coordinate/references/content-route.md +313 -0
- package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
- package/skills/experience-search-coordinate/references/media-route.md +172 -0
- package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
- package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
- package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
- package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
- package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
- package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
- package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
- package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
- package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
- package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
- package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
- package/skills/life-sciences-territory-configure/SKILL.md +217 -0
- package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
- package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
- package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
- package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
- package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
- package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
- package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
- package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
- package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
- package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
- package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
- package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
- package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
- package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
- package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
- package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
- package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
- package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
- package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
- package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
- package/skills/service-omni-agent-users-create/SKILL.md +96 -0
- package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
- package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
- package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
- package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
- package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
- package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
- package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
- package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
- package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
- package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
- package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
- package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
- package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
- package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
- package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
- package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
- package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
- package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
- package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
- package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
- package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
- package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
- package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
- package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
- package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
- package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
- package/skills/service-omni-queue-deploy/SKILL.md +91 -0
- package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
- package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
- package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
- package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
- package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
- package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
- package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
- package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
- package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
- package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
- package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
- package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
- package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
- package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
- package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
- package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
- package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
- package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
- package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
- package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
- package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
- package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
- package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
- package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
- package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
- package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
- package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
- package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
- package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
- package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
- package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
- package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
- package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
- package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
- package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
- package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# upsert-and-report.sh - detect + POST (create) or PATCH (align) a QueueRoutingConfig via the Data API; an omitted routing-model preserves an existing record, idempotent (SOQL pre/post-check). Args, env overrides, output contract: SKILL.md.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
if [ $# -lt 1 ]; then
|
|
7
|
+
echo '{"error":"Missing required argument: org-alias","usage":"bash upsert-and-report.sh <org-alias> [routing-model] [developer-name] [master-label] [capacity-weight] [routing-priority]"}' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
ORG="$1"
|
|
12
|
+
|
|
13
|
+
# Optional routing-model input: 2nd positional arg, or ROUTING_MODEL_INPUT env var (coordinator path).
|
|
14
|
+
# Empty => preserve existing / use default-on-create (see header).
|
|
15
|
+
REQUESTED_ROUTING_MODEL="${2:-${ROUTING_MODEL_INPUT:-}}"
|
|
16
|
+
ALLOWED_ROUTING_MODELS="MostAvailable LeastActive"
|
|
17
|
+
if [ -n "$REQUESTED_ROUTING_MODEL" ]; then
|
|
18
|
+
case " $ALLOWED_ROUTING_MODELS " in
|
|
19
|
+
*" $REQUESTED_ROUTING_MODEL "*) : ;;
|
|
20
|
+
*) echo "{\"error\":\"Invalid routing-model '$REQUESTED_ROUTING_MODEL'. Allowed: $ALLOWED_ROUTING_MODELS\"}" >&2; exit 1 ;;
|
|
21
|
+
esac
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
# Routing target drives the canonical naming defaults when no explicit developer-name/label is given.
|
|
25
|
+
ROUTING_TARGET="${QRC_ROUTING_TARGET:-Case}"
|
|
26
|
+
case "$ROUTING_TARGET" in
|
|
27
|
+
Case) TARGET_DEFAULT_DN="Case_Routing_Config"; TARGET_DEFAULT_LABEL="Case Routing Config" ;;
|
|
28
|
+
VoiceCall) TARGET_DEFAULT_DN="Voice_Routing_Config"; TARGET_DEFAULT_LABEL="Voice Routing Config" ;;
|
|
29
|
+
Incident) TARGET_DEFAULT_DN="Incident_Routing_Config"; TARGET_DEFAULT_LABEL="Incident Routing Config" ;;
|
|
30
|
+
MessagingSession) TARGET_DEFAULT_DN="Messaging_Routing_Config"; TARGET_DEFAULT_LABEL="Messaging Routing Config" ;;
|
|
31
|
+
*) echo "{\"error\":\"Invalid QRC_ROUTING_TARGET '$ROUTING_TARGET'. Allowed: Case, VoiceCall, Incident, MessagingSession\"}" >&2; exit 1 ;;
|
|
32
|
+
esac
|
|
33
|
+
|
|
34
|
+
# Resolve DeveloperName / MasterLabel / capacity / priority from positional args, then env, then
|
|
35
|
+
# create-time defaults. On an existing record, omitted values are replaced with persisted values
|
|
36
|
+
# after the pre-check so a targeted rerun does not reset operator-managed configuration.
|
|
37
|
+
DEVELOPER_NAME="${3:-${QRC_DEVELOPER_NAME:-$TARGET_DEFAULT_DN}}"
|
|
38
|
+
MASTER_LABEL="${4:-${QRC_MASTER_LABEL:-$TARGET_DEFAULT_LABEL}}"
|
|
39
|
+
CAPACITY_WEIGHT="${5:-${QRC_CAPACITY_WEIGHT:-5}}"
|
|
40
|
+
ROUTING_PRIORITY="${6:-${QRC_ROUTING_PRIORITY:-1}}"
|
|
41
|
+
MASTER_LABEL_EXPLICIT=false
|
|
42
|
+
{ [ -n "${4:-}" ] || [ -n "${QRC_MASTER_LABEL:-}" ]; } && MASTER_LABEL_EXPLICIT=true
|
|
43
|
+
ROUTING_PRIORITY_EXPLICIT=false
|
|
44
|
+
{ [ -n "${6:-}" ] || [ -n "${QRC_ROUTING_PRIORITY:-}" ]; } && ROUTING_PRIORITY_EXPLICIT=true
|
|
45
|
+
# Optional fallback/overflow assignee (QueueRoutingConfig.OverflowAssigneeId): a Username or 005 Id.
|
|
46
|
+
# Empty preserves an existing value (never cleared). 7th positional, or QRC_OVERFLOW_ASSIGNEE env.
|
|
47
|
+
OVERFLOW_ASSIGNEE_INPUT="${7:-${QRC_OVERFLOW_ASSIGNEE:-}}"
|
|
48
|
+
|
|
49
|
+
# CapacityWeight and CapacityPercentage are mutually exclusive. The steel-thread
|
|
50
|
+
# contract uses percentage mode (100%); callers that omit it keep weight mode.
|
|
51
|
+
CAPACITY_PERCENTAGE_INPUT="${QRC_CAPACITY_PERCENTAGE:-}"
|
|
52
|
+
WEIGHT_EXPLICIT=false
|
|
53
|
+
{ [ -n "${5:-}" ] || [ -n "${QRC_CAPACITY_WEIGHT:-}" ]; } && WEIGHT_EXPLICIT=true
|
|
54
|
+
CAPACITY_MODE="weight"
|
|
55
|
+
if [ -n "$CAPACITY_PERCENTAGE_INPUT" ]; then
|
|
56
|
+
CAPACITY_MODE="percentage"
|
|
57
|
+
if [ "$WEIGHT_EXPLICIT" = "true" ]; then
|
|
58
|
+
echo '{"error":"capacity-weight and capacity-percentage are mutually exclusive; provide only one."}' >&2
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
if ! [[ "$CAPACITY_PERCENTAGE_INPUT" =~ ^[0-9]+([.][0-9]+)?$ ]] \
|
|
62
|
+
|| awk "BEGIN{exit !($CAPACITY_PERCENTAGE_INPUT < 0 || $CAPACITY_PERCENTAGE_INPUT > 100)}"; then
|
|
63
|
+
echo "{\"error\":\"Invalid capacity-percentage '$CAPACITY_PERCENTAGE_INPUT'. Must be a number 0..100.\"}" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# PushTimeout is opt-in. Omitted input preserves an existing value and is left
|
|
69
|
+
# out of a create; an explicit value is written and verified after the write.
|
|
70
|
+
PUSH_TIMEOUT_INPUT="${8:-${QRC_PUSH_TIMEOUT:-}}"
|
|
71
|
+
if [ -n "$PUSH_TIMEOUT_INPUT" ] \
|
|
72
|
+
&& { ! [[ "$PUSH_TIMEOUT_INPUT" =~ ^[0-9]+$ ]] || [ "$PUSH_TIMEOUT_INPUT" -gt 3600 ]; }; then
|
|
73
|
+
echo "{\"error\":\"Invalid push-timeout '$PUSH_TIMEOUT_INPUT'. Must be integer seconds 0..3600.\"}" >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Validate DeveloperName grammar (interpolated into SOQL below - reject injection-shaped input).
|
|
78
|
+
if ! [[ "$DEVELOPER_NAME" =~ ^[A-Za-z][A-Za-z0-9_]{0,79}$ ]]; then
|
|
79
|
+
echo "{\"error\":\"Invalid developer-name '$DEVELOPER_NAME'. Must start with a letter, only letters/digits/underscore (max 80).\"}" >&2
|
|
80
|
+
exit 1
|
|
81
|
+
fi
|
|
82
|
+
if ! [[ "$CAPACITY_WEIGHT" =~ ^[0-9]+$ ]] || [ "$CAPACITY_WEIGHT" -lt 1 ] || [ "$CAPACITY_WEIGHT" -gt 100 ]; then
|
|
83
|
+
echo "{\"error\":\"Invalid capacity-weight '$CAPACITY_WEIGHT'. Must be integer 1..100.\"}" >&2
|
|
84
|
+
exit 1
|
|
85
|
+
fi
|
|
86
|
+
if ! [[ "$ROUTING_PRIORITY" =~ ^[0-9]+$ ]] || [ "$ROUTING_PRIORITY" -lt 1 ]; then
|
|
87
|
+
echo "{\"error\":\"Invalid routing-priority '$ROUTING_PRIORITY'. Must be integer >= 1.\"}" >&2
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
# Validate the overflow assignee (005 Id or Username) before any sf call; it's interpolated into SOQL.
|
|
91
|
+
if [ -n "$OVERFLOW_ASSIGNEE_INPUT" ] \
|
|
92
|
+
&& ! [[ "$OVERFLOW_ASSIGNEE_INPUT" =~ ^(005[A-Za-z0-9]{12}([A-Za-z0-9]{3})?|[A-Za-z0-9._@+-]{3,240})$ ]]; then
|
|
93
|
+
echo "{\"error\":\"Invalid overflow-assignee '$OVERFLOW_ASSIGNEE_INPUT'. Provide an active User Username or a 005 User Id.\"}" >&2
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
DEFAULT_ROUTING_MODEL="MostAvailable"
|
|
98
|
+
# Model for a new record; recomputed below to preserve an existing record's model when unspecified.
|
|
99
|
+
ROUTING_MODEL="${REQUESTED_ROUTING_MODEL:-$DEFAULT_ROUTING_MODEL}"
|
|
100
|
+
# RoutingPriority is required on QueueRoutingConfig (omitting it fails with REQUIRED_FIELD_MISSING);
|
|
101
|
+
# 1 = highest, the canonical demo default.
|
|
102
|
+
IS_ATTRIBUTE_BASED=false
|
|
103
|
+
API_VERSION="v66.0"
|
|
104
|
+
|
|
105
|
+
if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
|
|
106
|
+
echo "{\"error\":\"Org alias '$ORG' is not authenticated.\"}" >&2
|
|
107
|
+
exit 1
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# Write guard: POSTs/PATCHes a QueueRoutingConfig, so self-enforce the production-org guard (directly invokable).
|
|
111
|
+
ORG_GUARD_JSON=$(sf data query --target-org "$ORG" \
|
|
112
|
+
--query "SELECT IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" \
|
|
113
|
+
--json 2>/dev/null) || {
|
|
114
|
+
echo "{\"error\":\"Failed to query Organization for safe_to_write guard: $(printf '%s' "$ORG_GUARD_JSON" | head -c 300)\"}" >&2
|
|
115
|
+
exit 1
|
|
116
|
+
}
|
|
117
|
+
IS_SANDBOX=$(echo "$ORG_GUARD_JSON" | jq -r '.result.records[0].IsSandbox')
|
|
118
|
+
TRIAL_EXP=$(echo "$ORG_GUARD_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
|
|
119
|
+
ORG_TYPE=$(echo "$ORG_GUARD_JSON" | jq -r '.result.records[0].OrganizationType')
|
|
120
|
+
|
|
121
|
+
SAFE_TO_WRITE=false
|
|
122
|
+
if [ "$IS_SANDBOX" = "true" ] \
|
|
123
|
+
|| [ "$TRIAL_EXP" != "null" ] \
|
|
124
|
+
|| [ "$ORG_TYPE" = "Developer Edition" ] \
|
|
125
|
+
|| [ "$ORG_TYPE" = "Base Edition" ]; then
|
|
126
|
+
SAFE_TO_WRITE=true
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
if [ "$SAFE_TO_WRITE" != "true" ]; then
|
|
130
|
+
echo "{\"skill\":\"service-omni-queue-routing-config-deploy\",\"status\":\"blocked\",\"blocking_issue\":\"Refusing to upsert a QueueRoutingConfig on a real production customer org (IsSandbox=$IS_SANDBOX, TrialExpirationDate=$TRIAL_EXP, OrganizationType=$ORG_TYPE). Target a sandbox, trial CDO, or Developer Edition org.\",\"safe_to_write\":false}" >&2
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
# Resolve the optional overflow assignee to an active-User Id (read-only). OVERFLOW_REQUESTED
|
|
135
|
+
# distinguishes "set/change overflow" from "leave it alone".
|
|
136
|
+
OVERFLOW_REQUESTED=false
|
|
137
|
+
DESIRED_OVERFLOW_ID=""
|
|
138
|
+
if [ -n "$OVERFLOW_ASSIGNEE_INPUT" ]; then
|
|
139
|
+
OVERFLOW_REQUESTED=true
|
|
140
|
+
if [[ "$OVERFLOW_ASSIGNEE_INPUT" =~ ^005[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$ ]]; then
|
|
141
|
+
OV_JSON=$(sf data query --target-org "$ORG" --json \
|
|
142
|
+
--query "SELECT Id, IsActive FROM User WHERE Id='$OVERFLOW_ASSIGNEE_INPUT'" 2>/dev/null || true)
|
|
143
|
+
else
|
|
144
|
+
OV_JSON=$(sf data query --target-org "$ORG" --json \
|
|
145
|
+
--query "SELECT Id, IsActive FROM User WHERE Username='$OVERFLOW_ASSIGNEE_INPUT'" 2>/dev/null || true)
|
|
146
|
+
fi
|
|
147
|
+
if ! echo "$OV_JSON" | jq -e '.result.totalSize | numbers' >/dev/null 2>&1; then
|
|
148
|
+
echo "{\"skill\":\"service-omni-queue-routing-config-deploy\",\"status\":\"blocked\",\"blocking_issue\":\"Overflow-assignee lookup was inconclusive for '$OVERFLOW_ASSIGNEE_INPUT'. Raw: $(printf '%s' "$OV_JSON" | head -c 200)\"}" >&2
|
|
149
|
+
exit 1
|
|
150
|
+
fi
|
|
151
|
+
if [ "$(echo "$OV_JSON" | jq -r '.result.totalSize')" != "1" ]; then
|
|
152
|
+
echo "{\"skill\":\"service-omni-queue-routing-config-deploy\",\"status\":\"blocked\",\"blocking_issue\":\"Overflow assignee '$OVERFLOW_ASSIGNEE_INPUT' did not resolve to exactly one User.\"}" >&2
|
|
153
|
+
exit 1
|
|
154
|
+
fi
|
|
155
|
+
if [ "$(echo "$OV_JSON" | jq -r '.result.records[0].IsActive')" != "true" ]; then
|
|
156
|
+
echo "{\"skill\":\"service-omni-queue-routing-config-deploy\",\"status\":\"blocked\",\"blocking_issue\":\"Overflow assignee '$OVERFLOW_ASSIGNEE_INPUT' is not an active User; overflow work would never be delivered.\"}" >&2
|
|
157
|
+
exit 1
|
|
158
|
+
fi
|
|
159
|
+
DESIRED_OVERFLOW_ID=$(echo "$OV_JSON" | jq -r '.result.records[0].Id')
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# emit read-only plan preview JSON and exit 0 (PLAN_ONLY mode)
|
|
163
|
+
emit_plan() {
|
|
164
|
+
local status="$1"
|
|
165
|
+
local detail="$2"
|
|
166
|
+
jq -n \
|
|
167
|
+
--arg status "$status" \
|
|
168
|
+
--arg detail "$detail" \
|
|
169
|
+
--arg dn "$DEVELOPER_NAME" \
|
|
170
|
+
'{
|
|
171
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
172
|
+
status: $status,
|
|
173
|
+
plan_mode: true,
|
|
174
|
+
plan_detail: $detail,
|
|
175
|
+
developer_name: $dn,
|
|
176
|
+
manual_actions: [],
|
|
177
|
+
blocking_issue: null
|
|
178
|
+
}'
|
|
179
|
+
exit 0
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Step 1 - Pre-check: query existing record by DeveloperName
|
|
183
|
+
QUERY_JSON=$(sf data query --target-org "$ORG" \
|
|
184
|
+
--query "SELECT Id, DeveloperName, MasterLabel, RoutingModel, RoutingPriority, IsAttributeBased, CapacityWeight, CapacityPercentage, PushTimeout, OverflowAssigneeId FROM QueueRoutingConfig WHERE DeveloperName='$DEVELOPER_NAME'" \
|
|
185
|
+
--json 2>/dev/null || true)
|
|
186
|
+
|
|
187
|
+
# Check for OmniChannelSettings-not-enabled error (would surface as INVALID_TYPE)
|
|
188
|
+
if echo "$QUERY_JSON" | jq -e '.name == "INVALID_TYPE"' >/dev/null 2>&1; then
|
|
189
|
+
jq -n '{
|
|
190
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
191
|
+
status: "blocked",
|
|
192
|
+
developer_name: "'"$DEVELOPER_NAME"'",
|
|
193
|
+
id: null,
|
|
194
|
+
manual_actions: [],
|
|
195
|
+
blocking_issue: "OmniChannelSettings is not enabled on this org. Run service-omni-base-settings-configure first."
|
|
196
|
+
}'
|
|
197
|
+
exit 1
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# Only a numeric totalSize is authoritative; an inconclusive read must not be treated as absence
|
|
201
|
+
# (which would trigger a spurious create).
|
|
202
|
+
if ! echo "$QUERY_JSON" | jq -e '.result.totalSize | numbers' >/dev/null 2>&1; then
|
|
203
|
+
jq -n --arg raw "$(printf '%s' "$QUERY_JSON" | head -c 400)" '{
|
|
204
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
205
|
+
status: "blocked",
|
|
206
|
+
developer_name: "'"$DEVELOPER_NAME"'",
|
|
207
|
+
id: null,
|
|
208
|
+
manual_actions: [],
|
|
209
|
+
blocking_issue: ("QueueRoutingConfig pre-check query was inconclusive (not a clean zero-record result). Not creating on an inconclusive read. Raw: " + $raw)
|
|
210
|
+
}'
|
|
211
|
+
exit 1
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
RECORD_COUNT=$(echo "$QUERY_JSON" | jq -r '.result.totalSize // 0' 2>/dev/null || echo "0")
|
|
215
|
+
|
|
216
|
+
# Step 2 - Decide action based on existing state
|
|
217
|
+
EXISTING_ID=""
|
|
218
|
+
BEFORE_CAPACITY_WEIGHT="null"
|
|
219
|
+
BEFORE_CAPACITY_PERCENTAGE="null"
|
|
220
|
+
BEFORE_PUSH_TIMEOUT="null"
|
|
221
|
+
BEFORE_ROUTING_MODEL="null"
|
|
222
|
+
BEFORE_MASTER_LABEL="null"
|
|
223
|
+
BEFORE_ROUTING_PRIORITY="null"
|
|
224
|
+
BEFORE_IS_ATTRIBUTE_BASED="false"
|
|
225
|
+
BEFORE_OVERFLOW_ASSIGNEE="null"
|
|
226
|
+
BEFORE_EXISTED=false
|
|
227
|
+
NEEDS_UPDATE=false
|
|
228
|
+
|
|
229
|
+
if [ "$RECORD_COUNT" -gt 0 ]; then
|
|
230
|
+
BEFORE_EXISTED=true
|
|
231
|
+
EXISTING_ID=$(echo "$QUERY_JSON" | jq -r '.result.records[0].Id')
|
|
232
|
+
BEFORE_CAPACITY_WEIGHT=$(echo "$QUERY_JSON" | jq -r '.result.records[0].CapacityWeight // "null"')
|
|
233
|
+
BEFORE_ROUTING_MODEL=$(echo "$QUERY_JSON" | jq -r '.result.records[0].RoutingModel // "null"')
|
|
234
|
+
BEFORE_MASTER_LABEL=$(echo "$QUERY_JSON" | jq -r '.result.records[0].MasterLabel // "null"')
|
|
235
|
+
BEFORE_ROUTING_PRIORITY=$(echo "$QUERY_JSON" | jq -r '.result.records[0].RoutingPriority // "null"')
|
|
236
|
+
BEFORE_IS_ATTRIBUTE_BASED=$(echo "$QUERY_JSON" | jq -r '.result.records[0].IsAttributeBased // false')
|
|
237
|
+
BEFORE_OVERFLOW_ASSIGNEE=$(echo "$QUERY_JSON" | jq -r '.result.records[0].OverflowAssigneeId // "null"')
|
|
238
|
+
BEFORE_CAPACITY_PERCENTAGE=$(echo "$QUERY_JSON" | jq -r '.result.records[0].CapacityPercentage // "null"')
|
|
239
|
+
BEFORE_PUSH_TIMEOUT=$(echo "$QUERY_JSON" | jq -r '.result.records[0].PushTimeout // "null"')
|
|
240
|
+
|
|
241
|
+
# Preserve the org's routing model when none was explicitly requested (an explicit request wins).
|
|
242
|
+
if [ -z "$REQUESTED_ROUTING_MODEL" ] && [ "$BEFORE_ROUTING_MODEL" != "null" ]; then
|
|
243
|
+
ROUTING_MODEL="$BEFORE_ROUTING_MODEL"
|
|
244
|
+
fi
|
|
245
|
+
if [ "$MASTER_LABEL_EXPLICIT" = "false" ] && [ "$BEFORE_MASTER_LABEL" != "null" ]; then
|
|
246
|
+
MASTER_LABEL="$BEFORE_MASTER_LABEL"
|
|
247
|
+
fi
|
|
248
|
+
if [ "$ROUTING_PRIORITY_EXPLICIT" = "false" ] && [ "$BEFORE_ROUTING_PRIORITY" != "null" ]; then
|
|
249
|
+
ROUTING_PRIORITY="$BEFORE_ROUTING_PRIORITY"
|
|
250
|
+
fi
|
|
251
|
+
# This leaf does not expose an attribute-based-routing input; preserve that independent setting.
|
|
252
|
+
IS_ATTRIBUTE_BASED="$BEFORE_IS_ATTRIBUTE_BASED"
|
|
253
|
+
if [ -z "$CAPACITY_PERCENTAGE_INPUT" ] && [ "$WEIGHT_EXPLICIT" = "false" ]; then
|
|
254
|
+
if [ "$BEFORE_CAPACITY_PERCENTAGE" != "null" ]; then
|
|
255
|
+
CAPACITY_MODE="percentage"
|
|
256
|
+
CAPACITY_PERCENTAGE_INPUT="$BEFORE_CAPACITY_PERCENTAGE"
|
|
257
|
+
elif [ "$BEFORE_CAPACITY_WEIGHT" != "null" ]; then
|
|
258
|
+
CAPACITY_MODE="weight"
|
|
259
|
+
CAPACITY_WEIGHT="$BEFORE_CAPACITY_WEIGHT"
|
|
260
|
+
fi
|
|
261
|
+
fi
|
|
262
|
+
|
|
263
|
+
# Compare each field against the effective target values; if any differ, mark for PATCH
|
|
264
|
+
if [ "$BEFORE_ROUTING_MODEL" != "$ROUTING_MODEL" ] \
|
|
265
|
+
|| [ "$BEFORE_MASTER_LABEL" != "$MASTER_LABEL" ] \
|
|
266
|
+
|| [ "$BEFORE_ROUTING_PRIORITY" != "$ROUTING_PRIORITY" ] \
|
|
267
|
+
|| [ "$BEFORE_IS_ATTRIBUTE_BASED" != "$IS_ATTRIBUTE_BASED" ]; then
|
|
268
|
+
NEEDS_UPDATE=true
|
|
269
|
+
fi
|
|
270
|
+
if [ "$CAPACITY_MODE" = "percentage" ]; then
|
|
271
|
+
if [ "$BEFORE_CAPACITY_PERCENTAGE" = "null" ] \
|
|
272
|
+
|| awk "BEGIN{exit !($BEFORE_CAPACITY_PERCENTAGE != $CAPACITY_PERCENTAGE_INPUT)}"; then
|
|
273
|
+
NEEDS_UPDATE=true
|
|
274
|
+
fi
|
|
275
|
+
elif [ "$BEFORE_CAPACITY_WEIGHT" != "$CAPACITY_WEIGHT" ]; then
|
|
276
|
+
NEEDS_UPDATE=true
|
|
277
|
+
fi
|
|
278
|
+
if [ -n "$PUSH_TIMEOUT_INPUT" ] && [ "$BEFORE_PUSH_TIMEOUT" != "$PUSH_TIMEOUT_INPUT" ]; then
|
|
279
|
+
NEEDS_UPDATE=true
|
|
280
|
+
fi
|
|
281
|
+
# Overflow drift counts only when an explicit assignee differs from the persisted one.
|
|
282
|
+
if [ "$OVERFLOW_REQUESTED" = "true" ] && [ "$BEFORE_OVERFLOW_ASSIGNEE" != "$DESIRED_OVERFLOW_ID" ]; then
|
|
283
|
+
NEEDS_UPDATE=true
|
|
284
|
+
fi
|
|
285
|
+
fi
|
|
286
|
+
|
|
287
|
+
OVERFLOW_PLAN_NOTE=""
|
|
288
|
+
[ "$OVERFLOW_REQUESTED" = "true" ] && OVERFLOW_PLAN_NOTE=" Fallback/overflow assignee would be set to User $DESIRED_OVERFLOW_ID."
|
|
289
|
+
[ "$CAPACITY_MODE" = "percentage" ] && OVERFLOW_PLAN_NOTE=" Capacity model: percentage=${CAPACITY_PERCENTAGE_INPUT}%.$OVERFLOW_PLAN_NOTE"
|
|
290
|
+
[ -n "$PUSH_TIMEOUT_INPUT" ] && OVERFLOW_PLAN_NOTE=" PushTimeout=${PUSH_TIMEOUT_INPUT}s.$OVERFLOW_PLAN_NOTE"
|
|
291
|
+
|
|
292
|
+
# PLAN_ONLY: Steps 1-2 are read-only. Report what a --run would do and stop before any write.
|
|
293
|
+
if [ "${PLAN_ONLY:-}" = "1" ]; then
|
|
294
|
+
if [ "$BEFORE_EXISTED" = "false" ]; then
|
|
295
|
+
emit_plan "action_needed" "Would create QueueRoutingConfig '$DEVELOPER_NAME' with v1-canonical values.$OVERFLOW_PLAN_NOTE"
|
|
296
|
+
elif [ "$NEEDS_UPDATE" = "true" ]; then
|
|
297
|
+
emit_plan "action_needed" "QueueRoutingConfig '$DEVELOPER_NAME' exists but drifts from v1-canonical values; would PATCH.$OVERFLOW_PLAN_NOTE"
|
|
298
|
+
else
|
|
299
|
+
emit_plan "reused" "QueueRoutingConfig '$DEVELOPER_NAME' already matches v1-canonical values.$OVERFLOW_PLAN_NOTE"
|
|
300
|
+
fi
|
|
301
|
+
fi
|
|
302
|
+
|
|
303
|
+
# Step 3 - POST (create) or PATCH (update) or no-op (reused)
|
|
304
|
+
STATUS="reused"
|
|
305
|
+
FINAL_ID="$EXISTING_ID"
|
|
306
|
+
|
|
307
|
+
if [ "$BEFORE_EXISTED" = "false" ]; then
|
|
308
|
+
# POST new record
|
|
309
|
+
STATUS="created"
|
|
310
|
+
POST_BODY=$(jq -n \
|
|
311
|
+
--arg dn "$DEVELOPER_NAME" \
|
|
312
|
+
--arg ml "$MASTER_LABEL" \
|
|
313
|
+
--arg rm "$ROUTING_MODEL" \
|
|
314
|
+
--argjson rp "$ROUTING_PRIORITY" \
|
|
315
|
+
--argjson iab "$IS_ATTRIBUTE_BASED" \
|
|
316
|
+
'{
|
|
317
|
+
DeveloperName: $dn,
|
|
318
|
+
MasterLabel: $ml,
|
|
319
|
+
RoutingModel: $rm,
|
|
320
|
+
RoutingPriority: $rp,
|
|
321
|
+
IsAttributeBased: $iab
|
|
322
|
+
}')
|
|
323
|
+
if [ "$CAPACITY_MODE" = "percentage" ]; then
|
|
324
|
+
POST_BODY=$(echo "$POST_BODY" | jq --argjson cp "$CAPACITY_PERCENTAGE_INPUT" '. + {CapacityPercentage: $cp}')
|
|
325
|
+
else
|
|
326
|
+
POST_BODY=$(echo "$POST_BODY" | jq --argjson cw "$CAPACITY_WEIGHT" '. + {CapacityWeight: $cw}')
|
|
327
|
+
fi
|
|
328
|
+
if [ -n "$PUSH_TIMEOUT_INPUT" ]; then
|
|
329
|
+
POST_BODY=$(echo "$POST_BODY" | jq --argjson pt "$PUSH_TIMEOUT_INPUT" '. + {PushTimeout: $pt}')
|
|
330
|
+
fi
|
|
331
|
+
# Only set OverflowAssigneeId when explicitly requested (never write a null on create).
|
|
332
|
+
if [ "$OVERFLOW_REQUESTED" = "true" ]; then
|
|
333
|
+
POST_BODY=$(echo "$POST_BODY" | jq --arg oa "$DESIRED_OVERFLOW_ID" '. + {OverflowAssigneeId: $oa}')
|
|
334
|
+
fi
|
|
335
|
+
|
|
336
|
+
# sf api request rest (beta) writes a warning to STDERR; redirect it so stdout is pure JSON.
|
|
337
|
+
POST_STDERR="$(mktemp)"
|
|
338
|
+
POST_RESULT=$(sf api request rest --target-org "$ORG" \
|
|
339
|
+
"/services/data/$API_VERSION/sobjects/QueueRoutingConfig" \
|
|
340
|
+
--method POST \
|
|
341
|
+
--body "$POST_BODY" 2>"$POST_STDERR" || true)
|
|
342
|
+
rm -f "$POST_STDERR"
|
|
343
|
+
|
|
344
|
+
POST_SUCCESS=$(echo "$POST_RESULT" | jq -r '.success // false' 2>/dev/null || echo "false")
|
|
345
|
+
if [ "$POST_SUCCESS" != "true" ]; then
|
|
346
|
+
ERR_MSG=$(echo "$POST_RESULT" | jq -r '.[0].message // .message // "Unknown POST error"' 2>/dev/null)
|
|
347
|
+
jq -n \
|
|
348
|
+
--arg blocking "POST failed: $ERR_MSG" \
|
|
349
|
+
--arg dn "$DEVELOPER_NAME" \
|
|
350
|
+
'{
|
|
351
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
352
|
+
status: "blocked",
|
|
353
|
+
developer_name: $dn,
|
|
354
|
+
id: null,
|
|
355
|
+
manual_actions: [],
|
|
356
|
+
blocking_issue: $blocking
|
|
357
|
+
}'
|
|
358
|
+
exit 1
|
|
359
|
+
fi
|
|
360
|
+
FINAL_ID=$(echo "$POST_RESULT" | jq -r '.id')
|
|
361
|
+
|
|
362
|
+
elif [ "$NEEDS_UPDATE" = "true" ]; then
|
|
363
|
+
# PATCH existing record
|
|
364
|
+
STATUS="updated"
|
|
365
|
+
PATCH_BODY=$(jq -n \
|
|
366
|
+
--arg ml "$MASTER_LABEL" \
|
|
367
|
+
--arg rm "$ROUTING_MODEL" \
|
|
368
|
+
--argjson rp "$ROUTING_PRIORITY" \
|
|
369
|
+
--argjson iab "$IS_ATTRIBUTE_BASED" \
|
|
370
|
+
'{
|
|
371
|
+
MasterLabel: $ml,
|
|
372
|
+
RoutingModel: $rm,
|
|
373
|
+
RoutingPriority: $rp,
|
|
374
|
+
IsAttributeBased: $iab
|
|
375
|
+
}')
|
|
376
|
+
if [ "$CAPACITY_MODE" = "percentage" ]; then
|
|
377
|
+
PATCH_BODY=$(echo "$PATCH_BODY" | jq --argjson cp "$CAPACITY_PERCENTAGE_INPUT" '. + {CapacityPercentage: $cp}')
|
|
378
|
+
[ "${BEFORE_CAPACITY_WEIGHT:-null}" = "null" ] || PATCH_BODY=$(echo "$PATCH_BODY" | jq '. + {CapacityWeight: null}')
|
|
379
|
+
else
|
|
380
|
+
PATCH_BODY=$(echo "$PATCH_BODY" | jq --argjson cw "$CAPACITY_WEIGHT" '. + {CapacityWeight: $cw}')
|
|
381
|
+
[ "${BEFORE_CAPACITY_PERCENTAGE:-null}" = "null" ] || PATCH_BODY=$(echo "$PATCH_BODY" | jq '. + {CapacityPercentage: null}')
|
|
382
|
+
fi
|
|
383
|
+
if [ -n "$PUSH_TIMEOUT_INPUT" ]; then
|
|
384
|
+
PATCH_BODY=$(echo "$PATCH_BODY" | jq --argjson pt "$PUSH_TIMEOUT_INPUT" '. + {PushTimeout: $pt}')
|
|
385
|
+
fi
|
|
386
|
+
# Only touch OverflowAssigneeId when explicitly requested - an omitted input leaves it as-is.
|
|
387
|
+
if [ "$OVERFLOW_REQUESTED" = "true" ]; then
|
|
388
|
+
PATCH_BODY=$(echo "$PATCH_BODY" | jq --arg oa "$DESIRED_OVERFLOW_ID" '. + {OverflowAssigneeId: $oa}')
|
|
389
|
+
fi
|
|
390
|
+
|
|
391
|
+
# PATCH returns 204 (empty) on success; detect success by the absence of an error field.
|
|
392
|
+
PATCH_STDERR="$(mktemp)"
|
|
393
|
+
PATCH_RESULT=$(sf api request rest --target-org "$ORG" \
|
|
394
|
+
"/services/data/$API_VERSION/sobjects/QueueRoutingConfig/$EXISTING_ID" \
|
|
395
|
+
--method PATCH \
|
|
396
|
+
--body "$PATCH_BODY" 2>"$PATCH_STDERR" || true)
|
|
397
|
+
rm -f "$PATCH_STDERR"
|
|
398
|
+
|
|
399
|
+
# PATCH success = empty result OR result contains success:true. Error = has errorCode or message with error.
|
|
400
|
+
if echo "$PATCH_RESULT" | jq -e 'if type == "array" then .[0].errorCode else .errorCode end' >/dev/null 2>&1; then
|
|
401
|
+
ERR_MSG=$(echo "$PATCH_RESULT" | jq -r 'if type == "array" then .[0].message else .message end' 2>/dev/null)
|
|
402
|
+
jq -n \
|
|
403
|
+
--arg blocking "PATCH failed: $ERR_MSG" \
|
|
404
|
+
--arg dn "$DEVELOPER_NAME" \
|
|
405
|
+
--arg id "$EXISTING_ID" \
|
|
406
|
+
'{
|
|
407
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
408
|
+
status: "blocked",
|
|
409
|
+
developer_name: $dn,
|
|
410
|
+
id: $id,
|
|
411
|
+
manual_actions: [],
|
|
412
|
+
blocking_issue: $blocking
|
|
413
|
+
}'
|
|
414
|
+
exit 1
|
|
415
|
+
fi
|
|
416
|
+
fi
|
|
417
|
+
|
|
418
|
+
# Step 4 - Post-check: re-query to confirm final state
|
|
419
|
+
FINAL_JSON=$(sf data query --target-org "$ORG" \
|
|
420
|
+
--query "SELECT Id, DeveloperName, MasterLabel, RoutingModel, RoutingPriority, IsAttributeBased, CapacityWeight, CapacityPercentage, PushTimeout, OverflowAssigneeId FROM QueueRoutingConfig WHERE Id='$FINAL_ID'" \
|
|
421
|
+
--json 2>/dev/null)
|
|
422
|
+
|
|
423
|
+
if [ "$(echo "$FINAL_JSON" | jq -r '.result.totalSize')" != "1" ]; then
|
|
424
|
+
jq -n \
|
|
425
|
+
--arg blocking "Post-check re-query returned unexpected count. Expected 1, got $(echo "$FINAL_JSON" | jq -r '.result.totalSize')." \
|
|
426
|
+
--arg dn "$DEVELOPER_NAME" \
|
|
427
|
+
--arg id "$FINAL_ID" \
|
|
428
|
+
'{
|
|
429
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
430
|
+
status: "blocked",
|
|
431
|
+
developer_name: $dn,
|
|
432
|
+
id: $id,
|
|
433
|
+
manual_actions: [],
|
|
434
|
+
blocking_issue: $blocking
|
|
435
|
+
}'
|
|
436
|
+
exit 1
|
|
437
|
+
fi
|
|
438
|
+
|
|
439
|
+
# Read the persisted values back (reporting input constants would mask a silently coerced field).
|
|
440
|
+
ACTUAL_DN=$(echo "$FINAL_JSON" | jq -r '.result.records[0].DeveloperName // "null"')
|
|
441
|
+
ACTUAL_ML=$(echo "$FINAL_JSON" | jq -r '.result.records[0].MasterLabel // "null"')
|
|
442
|
+
ACTUAL_RM=$(echo "$FINAL_JSON" | jq -r '.result.records[0].RoutingModel // "null"')
|
|
443
|
+
ACTUAL_CW=$(echo "$FINAL_JSON" | jq -r '.result.records[0].CapacityWeight // "null"')
|
|
444
|
+
ACTUAL_CP=$(echo "$FINAL_JSON" | jq -r '.result.records[0].CapacityPercentage // "null"')
|
|
445
|
+
ACTUAL_RP=$(echo "$FINAL_JSON" | jq -r '.result.records[0].RoutingPriority // "null"')
|
|
446
|
+
# IsAttributeBased is boolean; read it explicitly since jq's // would map a genuine false to "null".
|
|
447
|
+
ACTUAL_IAB=$(echo "$FINAL_JSON" | jq -r '.result.records[0].IsAttributeBased | if . == null then "null" else tostring end')
|
|
448
|
+
ACTUAL_PT=$(echo "$FINAL_JSON" | jq -r '.result.records[0].PushTimeout')
|
|
449
|
+
ACTUAL_OAI=$(echo "$FINAL_JSON" | jq -r '.result.records[0].OverflowAssigneeId')
|
|
450
|
+
|
|
451
|
+
# For a create/update, the persisted values must equal what we wrote; a mismatch blocks.
|
|
452
|
+
if [ "$STATUS" = "created" ] || [ "$STATUS" = "updated" ]; then
|
|
453
|
+
MISMATCHES=""
|
|
454
|
+
[ "$ACTUAL_ML" = "$MASTER_LABEL" ] || MISMATCHES="$MISMATCHES MasterLabel(got='$ACTUAL_ML' want='$MASTER_LABEL')"
|
|
455
|
+
[ "$ACTUAL_RM" = "$ROUTING_MODEL" ] || MISMATCHES="$MISMATCHES RoutingModel(got='$ACTUAL_RM' want='$ROUTING_MODEL')"
|
|
456
|
+
if [ "$CAPACITY_MODE" = "percentage" ]; then
|
|
457
|
+
if [ "$ACTUAL_CP" = "null" ] \
|
|
458
|
+
|| awk "BEGIN{exit !($ACTUAL_CP != $CAPACITY_PERCENTAGE_INPUT)}"; then
|
|
459
|
+
MISMATCHES="$MISMATCHES CapacityPercentage(got='$ACTUAL_CP' want='$CAPACITY_PERCENTAGE_INPUT')"
|
|
460
|
+
fi
|
|
461
|
+
else
|
|
462
|
+
[ "$ACTUAL_CW" = "$CAPACITY_WEIGHT" ] || MISMATCHES="$MISMATCHES CapacityWeight(got='$ACTUAL_CW' want='$CAPACITY_WEIGHT')"
|
|
463
|
+
fi
|
|
464
|
+
if [ -n "$PUSH_TIMEOUT_INPUT" ]; then
|
|
465
|
+
[ "$ACTUAL_PT" = "$PUSH_TIMEOUT_INPUT" ] || MISMATCHES="$MISMATCHES PushTimeout(got='$ACTUAL_PT' want='$PUSH_TIMEOUT_INPUT')"
|
|
466
|
+
fi
|
|
467
|
+
[ "$ACTUAL_RP" = "$ROUTING_PRIORITY" ] || MISMATCHES="$MISMATCHES RoutingPriority(got='$ACTUAL_RP' want='$ROUTING_PRIORITY')"
|
|
468
|
+
[ "$ACTUAL_IAB" = "$IS_ATTRIBUTE_BASED" ] || MISMATCHES="$MISMATCHES IsAttributeBased(got='$ACTUAL_IAB' want='$IS_ATTRIBUTE_BASED')"
|
|
469
|
+
if [ "$OVERFLOW_REQUESTED" = "true" ]; then
|
|
470
|
+
[ "$ACTUAL_OAI" = "$DESIRED_OVERFLOW_ID" ] || MISMATCHES="$MISMATCHES OverflowAssigneeId(got='$ACTUAL_OAI' want='$DESIRED_OVERFLOW_ID')"
|
|
471
|
+
fi
|
|
472
|
+
if [ -n "$MISMATCHES" ]; then
|
|
473
|
+
jq -n \
|
|
474
|
+
--arg dn "$DEVELOPER_NAME" \
|
|
475
|
+
--arg id "$FINAL_ID" \
|
|
476
|
+
--arg blocking "Post-write verification failed - persisted QueueRoutingConfig does not match intended values:$MISMATCHES" \
|
|
477
|
+
'{
|
|
478
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
479
|
+
status: "blocked",
|
|
480
|
+
developer_name: $dn,
|
|
481
|
+
id: $id,
|
|
482
|
+
manual_actions: [],
|
|
483
|
+
blocking_issue: $blocking
|
|
484
|
+
}'
|
|
485
|
+
exit 1
|
|
486
|
+
fi
|
|
487
|
+
fi
|
|
488
|
+
|
|
489
|
+
# Step 5 - Emit report JSON (actual persisted values, read back from the org)
|
|
490
|
+
jq -n \
|
|
491
|
+
--arg status "$STATUS" \
|
|
492
|
+
--arg id "$FINAL_ID" \
|
|
493
|
+
--arg actual_dn "$ACTUAL_DN" \
|
|
494
|
+
--arg actual_ml "$ACTUAL_ML" \
|
|
495
|
+
--arg actual_rm "$ACTUAL_RM" \
|
|
496
|
+
--arg actual_cw "$ACTUAL_CW" \
|
|
497
|
+
--arg actual_cp "$ACTUAL_CP" \
|
|
498
|
+
--arg capacity_mode "$CAPACITY_MODE" \
|
|
499
|
+
--arg actual_rp "$ACTUAL_RP" \
|
|
500
|
+
--arg actual_iab "$ACTUAL_IAB" \
|
|
501
|
+
--arg actual_pt "$ACTUAL_PT" \
|
|
502
|
+
--arg actual_oai "$ACTUAL_OAI" \
|
|
503
|
+
--argjson before_existed "$BEFORE_EXISTED" \
|
|
504
|
+
--arg before_cw "$BEFORE_CAPACITY_WEIGHT" \
|
|
505
|
+
--arg before_rm "$BEFORE_ROUTING_MODEL" \
|
|
506
|
+
--arg routing_target "$ROUTING_TARGET" \
|
|
507
|
+
--argjson overflow_requested "$OVERFLOW_REQUESTED" \
|
|
508
|
+
'{
|
|
509
|
+
skill: "service-omni-queue-routing-config-deploy",
|
|
510
|
+
status: $status,
|
|
511
|
+
routing_target: $routing_target,
|
|
512
|
+
overflow_assignee_requested: $overflow_requested,
|
|
513
|
+
developer_name: $actual_dn,
|
|
514
|
+
id: $id,
|
|
515
|
+
master_label: $actual_ml,
|
|
516
|
+
routing_model: $actual_rm,
|
|
517
|
+
capacity_mode: $capacity_mode,
|
|
518
|
+
capacity_weight: (if $actual_cw == "null" then null else ($actual_cw | tonumber?) end),
|
|
519
|
+
capacity_percentage: (if $actual_cp == "null" then null else ($actual_cp | tonumber?) end),
|
|
520
|
+
routing_priority: (if $actual_rp == "null" then null else ($actual_rp | tonumber?) end),
|
|
521
|
+
is_attribute_based: (if $actual_iab == "true" then true elif $actual_iab == "false" then false else null end),
|
|
522
|
+
push_timeout: (if $actual_pt == "null" then null else ($actual_pt | tonumber?) end),
|
|
523
|
+
overflow_assignee_id: (if $actual_oai == "null" then null else $actual_oai end),
|
|
524
|
+
values_source: "requeried",
|
|
525
|
+
before: (
|
|
526
|
+
if $before_existed then
|
|
527
|
+
{
|
|
528
|
+
existed: true,
|
|
529
|
+
capacity_weight: (if $before_cw == "null" then null else ($before_cw | tonumber?) end),
|
|
530
|
+
routing_model: (if $before_rm == "null" then null else $before_rm end)
|
|
531
|
+
}
|
|
532
|
+
else
|
|
533
|
+
{existed: false, capacity_weight: null, routing_model: null}
|
|
534
|
+
end
|
|
535
|
+
),
|
|
536
|
+
manual_actions: [],
|
|
537
|
+
blocking_issue: null
|
|
538
|
+
}'
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: service-omni-routing-flow-deploy
|
|
3
|
+
description: "Use to deploy an Omni-Channel routing Flow for Case or VoiceCall and verify it routes work items to a queue or by skill. Supports the Case and VoiceCall steel thread with an autolaunched dryRun-gated variant and record-triggered QueueBased and SkillsBased variants. --trigger deploys a record-triggered variant; --routing-type SkillsBased evaluates WorkSkillRouting rules with a non-null skillOption; --runtime-proof checks routing evidence and, for SkillsBased, SkillRequirement rows. Triggers: deploy Omni routing flow for Case, deploy Omni routing flow for VoiceCall, wire VoiceCall routing, create a record-triggered Omni routing flow, verify a Case routing flow, deploy skills-based routing flow. Do not use for screen flows, platform-event-triggered flows, or Flow Tests."
|
|
4
|
+
allowed-tools: Bash Read Write Edit Glob Grep
|
|
5
|
+
metadata:
|
|
6
|
+
version: "1.0"
|
|
7
|
+
domains: ["Service"]
|
|
8
|
+
minApiVersion: "66.0"
|
|
9
|
+
relatedSkills:
|
|
10
|
+
- "service-omni-base-settings-configure"
|
|
11
|
+
- "service-omni-channel-setup-coordinate"
|
|
12
|
+
- "service-omni-presence-status-deploy"
|
|
13
|
+
- "service-omni-queue-deploy"
|
|
14
|
+
- "service-omni-queue-routing-config-deploy"
|
|
15
|
+
- "service-omni-service-channel-configure"
|
|
16
|
+
accessCheck:
|
|
17
|
+
- type: license
|
|
18
|
+
value: ServiceCloud
|
|
19
|
+
cliTools:
|
|
20
|
+
- tool: ["jq"]
|
|
21
|
+
semver: ">=1.6"
|
|
22
|
+
- tool: ["sf"]
|
|
23
|
+
semver: ">=2.139.6"
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
# service-omni-routing-flow-deploy
|
|
27
|
+
|
|
28
|
+
> Voice runtime proof depends on a provisioned Contact Center. Flow redeploys report `Changed` even when the deployed configuration is unchanged.
|
|
29
|
+
|
|
30
|
+
Deploy an Omni-Channel routing Flow via the Metadata API, confirm it lands Active, and prove it routes work — either by CLI invocation (the autolaunched dry-run) or by observing real routing side effects (the record-triggered variant). `service-omni-channel-setup-coordinate` wires it in after `service-omni-queue-deploy` has produced the target queue and its members are bound.
|
|
31
|
+
|
|
32
|
+
## Variants
|
|
33
|
+
|
|
34
|
+
Four assets ship under `assets/force-app/main/default/flows/` — an autolaunched and a record-triggered variant for each of Case and VoiceCall. Selection is by `--target Case|VoiceCall` (default Case) and `--trigger`.
|
|
35
|
+
|
|
36
|
+
| Variant | Shape | Proves | Routes real work? |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| `Omni_Route_Cases` / `Omni_Route_VoiceCalls` (default) | Autolaunched, `dryRun` gate | CLI invocability via Actions REST, no side effects | No |
|
|
39
|
+
| `Omni_Route_Case_Trigger` / `Omni_Route_VoiceCall_Trigger` (`--trigger`) | Record-triggered on insert, calls `routeWork` | Real routing — the insert creates `PendingServiceRouting`/`AgentWork` | Yes |
|
|
40
|
+
|
|
41
|
+
The autolaunched variant de-risks the headless CLI path cheaply; the record-triggered variant proves real records reach a queue. A full steel thread deploys the record-triggered variant.
|
|
42
|
+
|
|
43
|
+
With `--trigger --routing-type SkillsBased`, the record-triggered variant is drawn from a sibling `<FlowDN>.SkillsBased.flow-meta.xml` asset that emits `routingType=SkillsBased` plus a non-null `skillOption`. The base trigger flows hardcode `routingType=QueueBased`, so a skills-based-routing org config never takes effect at runtime — this variant is what makes it route by skill (W-24069467).
|
|
44
|
+
|
|
45
|
+
## Inputs
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
bash scripts/deploy-and-report.sh <org-alias> [flow_developer_name] [--target Case|VoiceCall] [--trigger] [--routing-type QueueBased|SkillsBased] [--skill-option RunSBRRules|DefineSkillRequirements|Both] [--runtime-proof] [--require-proof] [--skip-invoke]
|
|
49
|
+
# autolaunched smoke test (Case):
|
|
50
|
+
bash scripts/deploy-and-report.sh myorg
|
|
51
|
+
# record-triggered Voice routing with runtime proof:
|
|
52
|
+
bash scripts/deploy-and-report.sh myorg --target VoiceCall --trigger --runtime-proof
|
|
53
|
+
# skills-based Case routing (platform evaluates WorkSkillRouting rules):
|
|
54
|
+
bash scripts/deploy-and-report.sh myorg --target Case --trigger --routing-type SkillsBased --runtime-proof
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
- `--target Case|VoiceCall` (default Case) — selects the flow pair.
|
|
58
|
+
- `--trigger` — deploy the record-triggered variant with dynamic token resolution.
|
|
59
|
+
- `--routing-type QueueBased|SkillsBased` (default QueueBased; env `OMNI_ROUTING_TYPE`) — `SkillsBased` deploys the `<FlowDN>.SkillsBased` variant that calls `routeWork` with `routingType=SkillsBased`, so the org's skills-based routing config takes effect at runtime instead of being inert. It deploys under the same DeveloperName, replacing the QueueBased trigger (never two triggers on one object).
|
|
60
|
+
- `--skill-option RunSBRRules|DefineSkillRequirements|Both` (default `RunSBRRules`; env `OMNI_SKILL_OPTION`; SkillsBased only) — `RunSBRRules`/`Both` make the platform evaluate the org's `WorkSkillRouting` rules server-side and attach matching `SkillRequirement` rows to the PSR; `DefineSkillRequirements` uses only skills the flow itself passes. A SkillsBased deploy with an empty/invalid skillOption is **refused** (exit 2): a null skillOption NPEs the platform `routeWork` action and rolls back the triggering insert (W-24069761), so the asset always carries a non-null value.
|
|
61
|
+
- `--runtime-proof` (implies `--trigger`) — after deploy, insert a target record. QueueBased accepts a `PendingServiceRouting` or `AgentWork` row. SkillsBased requires a `PendingServiceRouting` with at least one `SkillRequirement`, proving that `RunSBRRules` evaluated the active `WorkSkillRouting` rule. Proof is fail-soft unless required. The record is always deleted afterward.
|
|
62
|
+
- `--require-proof` (implies `--runtime-proof`; also `OMNI_RUNTIME_PROOF_REQUIRED=1`) — makes that proof blocking; the release gate for the Voice steel thread.
|
|
63
|
+
- `--skip-invoke` (autolaunched only) — deploy + verify Active, no Actions REST call.
|
|
64
|
+
- Token overrides (`--trigger`): `QUEUE_DEVELOPER_NAME`, `ROUTING_CONFIG_DEVELOPER_NAME`, `SERVICE_CHANNEL_DEVELOPER_NAME`. The coordinator always supplies these three from the resources it established for the target, so the flow binds the same channel/queue/QRC rather than a guessed default.
|
|
65
|
+
|
|
66
|
+
## Preconditions and safety
|
|
67
|
+
|
|
68
|
+
- Target org authenticated via `sf` CLI, Service Cloud license, `sf` CLI ≥ 2.139.6, `jq` ≥ 1.6.
|
|
69
|
+
- Omni-Channel base settings enabled (`service-omni-base-settings-configure`) — the routing action resolves only when Omni is on; the flow deploys without it but fails at runtime.
|
|
70
|
+
- Record-triggered runs need a deployed ServiceChannel (`service-omni-service-channel-configure`), Queue (`service-omni-queue-deploy`), and QueueRoutingConfig (`service-omni-queue-routing-config-deploy`) to resolve their tokenized IDs; runtime proof additionally needs an agent online with a compatible presence status (`service-omni-presence-status-deploy`).
|
|
71
|
+
- The three-way `safe_to_write` production guard applies.
|
|
72
|
+
|
|
73
|
+
## Run
|
|
74
|
+
|
|
75
|
+
`deploy-and-report.sh` deploys, verifies, and (per variant) proves routing:
|
|
76
|
+
|
|
77
|
+
1. **Resolve tokens** (record-triggered only) — the committed asset carries `__QUEUE_ID__`, `__ROUTING_CONFIG_ID__`, `__SERVICE_CHANNEL_ID__`, and channel name/label tokens. The skill resolves each by DeveloperName into a materialized copy that is never committed (the asset stays tokenized for portability). A ServiceChannel that does not resolve fails closed rather than embedding an empty id.
|
|
78
|
+
2. **Deploy** — `sf project deploy start --metadata "Flow:<FlowDN>"`; the `files[].state` maps to `reused`/`updated`/`created`.
|
|
79
|
+
3. **Verify Active** — retrieve the Flow and confirm its resource IDs, `routingType`, and SkillsBased `skillOption` still match; otherwise redeploy. Confirm an active version exists.
|
|
80
|
+
4. **Prove routing** — autolaunched: POST to `/actions/custom/flow/<FlowDN>` with `{"dryRun":true}` and assert `isSuccess` and `dryRunOk`. Record-triggered: insert a target record and observe routing. SkillsBased success additionally requires one or more `SkillRequirement` rows on the PSR.
|
|
81
|
+
|
|
82
|
+
## Behavior
|
|
83
|
+
|
|
84
|
+
**Two shapes, two verification paths.** "Can we invoke a routing flow headlessly?" and "do real records route to a queue?" are different questions. The autolaunched `dryRun` flow answers the first in one side-effect-free HTTP call — the flow must have a code path that succeeds without prerequisites, so `dryRun=true` takes an assignment step and returns `dryRunOk=true`. Record-triggered flows are not exposed as custom actions, so the only headless proof is inserting the source record and observing `PendingServiceRouting`/`AgentWork`. The PSR row alone proves the flow routed; agent assignment depends on Omni distribution, not the flow.
|
|
85
|
+
|
|
86
|
+
**Active-version round trip.** For the record-triggered variant, component deploy state proves only the transaction, not that an active version exists. The skill prefers `FlowDefinitionView.ActiveVersionId` and falls back to the active Tooling `Flow` row when that view is unavailable. It fails closed when neither source proves an active version, so `flow_active:true` is never reported without evidence.
|
|
87
|
+
|
|
88
|
+
**Binding-drift redeploy.** `reused` means an active version already exists and its embedded queue/QRC/ServiceChannel IDs, routing type, and SkillsBased skill option match the request. If a binding or routing mode drifted, the skill redeploys instead of reusing. `OMNI_FLOW_FORCE=1` forces a redeploy.
|
|
89
|
+
|
|
90
|
+
**Idempotency quirk.** Unlike most metadata types, `Flow` deploys are not byte-idempotent — Salesforce reports `Changed` on identical redeploys because every deploy is a potential version bump. Treat `updated` from this skill as "successfully redeployed," not "operator modified the flow."
|
|
91
|
+
|
|
92
|
+
**Safety.** Deploys use explicit `--metadata "Flow:<DN>"` (never `--source-dir`) so sibling flows are untouched, and the skill never overwrites a Flow it did not author — the coordinator supplies `flow_developer_name` deliberately. `flow_developer_name` is validated against `^[A-Za-z][A-Za-z0-9_]{0,79}$` because it is interpolated into the Actions REST URL path.
|
|
93
|
+
|
|
94
|
+
## Output contract
|
|
95
|
+
|
|
96
|
+
A single JSON object with `status` ∈ `created` | `reused` | `updated` | `blocked`, `flow_developer_name`, `flow_state`, `flow_active`, `deploy_id`, `manual_actions`, and `blocking_issue`. The autolaunched variant adds `verification_path` and `invocation_smoke_test` (`attempted`, `success`, `endpoint`, `dry_run_ok`). The record-triggered variant adds `mode: "record_triggered"`, `target`, `flow_active_version_id`, `routing_type`, `skill_option` (the skillOption when SkillsBased, else null), `resolved` (queue/routing_config/service_channel), and `runtime_proof` (`attempted`, `required`, `success`, `record_id`, `pending_service_routing`, `agent_work`, `reason`). Under `--routing-type SkillsBased`, `runtime_proof` also carries `skill_requirement_count` and `skill_requirements` — the `SkillRequirement` rows the platform attached to the PSR (the skills-based acceptance signal).
|
|
97
|
+
|
|
98
|
+
- `blocked` — deploy failed; the flow is not Active; the autolaunched invocation failed; a queue/QRC could not be resolved; the ServiceChannel did not resolve; or `--require-proof` was set and the mode-specific proof was not observed. For SkillsBased, a PSR with zero `SkillRequirement` rows is not a passing proof. A plain `--runtime-proof` failure remains fail-soft.
|
|
99
|
+
- `runtime_proof.attempted` is `false` unless `--runtime-proof`/`--require-proof` was passed; the throwaway record is always cleaned up.
|
|
100
|
+
|
|
101
|
+
## Limitations
|
|
102
|
+
|
|
103
|
+
- Case and VoiceCall only; extend by forking the closest asset and preserving the `dryRun`/`dryRunOk` contract (autolaunched) or the `routeWork` + tokenized-ID pattern (record-triggered).
|
|
104
|
+
- Does not register the Flow on a queue's routing configuration — that binding belongs to `service-omni-queue-routing-config-deploy`.
|
|
105
|
+
- Does not handle screen flows, platform-event-triggered flows, or Flow Tests.
|
|
106
|
+
|
|
107
|
+
## References
|
|
108
|
+
|
|
109
|
+
| File | When to read |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `references/api-notes.md` | On unexpected deploy or verification behavior — variant comparison, the token-resolution table, Flow schema, the Actions REST response shape, and record-DML verification |
|