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