@salesforce/afv-skills 1.55.0 → 1.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-generate/SKILL.md +8 -0
  3. package/skills/agentforce-generate/references/actions-reference.md +2 -2
  4. package/skills/agentforce-generate/references/voice-modality-reference.md +51 -15
  5. package/skills/agentforce-generate/references/voice-telephony-cli.md +193 -0
  6. package/skills/agentforce-observe/SKILL.md +13 -9
  7. package/skills/agentforce-observe/references/ahm-alerts.md +193 -14
  8. package/skills/automation-sandbox-post-copy-config-generate/SKILL.md +24 -15
  9. package/skills/automation-sandbox-post-copy-config-generate/assets/config_template.json +11 -0
  10. package/skills/automation-sandbox-post-copy-config-generate/assets/json_schema.json +33 -1
  11. package/skills/automation-sandbox-post-copy-config-generate/references/configuration_catalog.md +32 -1
  12. package/skills/automation-sandbox-post-copy-configure/SKILL.md +75 -71
  13. package/skills/automation-sandbox-post-copy-configure/assets/scheduled_apex_template.apex +13 -0
  14. package/skills/automation-sandbox-post-copy-configure/examples/sample_scheduled_apex_config.json +13 -0
  15. package/skills/automation-sandbox-post-copy-configure/references/scheduled_apex_path.md +155 -0
  16. package/skills/automation-sandbox-post-copy-configure/scripts/build-scheduled-apex.mjs +62 -0
  17. package/skills/automation-sandbox-post-copy-configure/scripts/soql-escape-job-name.mjs +28 -0
  18. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +383 -0
  19. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +324 -0
  20. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +63 -0
  21. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +100 -0
  22. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -0
  23. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +46 -0
  24. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/04-deploy-engine.js +198 -0
  25. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/05-deploy-tpm-accruals.js +231 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +82 -0
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +517 -0
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +416 -0
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -0
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +514 -0
  31. package/skills/consumer-goods-sync-management-configure/SKILL.md +137 -0
  32. package/skills/consumer-goods-sync-management-configure/examples/scenarios.md +294 -0
  33. package/skills/consumer-goods-sync-management-configure/references/act1-setup-sync.md +333 -0
  34. package/skills/consumer-goods-sync-management-configure/references/act2-assign-users.md +324 -0
  35. package/skills/consumer-goods-sync-management-configure/references/act3-plan-verify.md +113 -0
  36. package/skills/consumer-goods-sync-management-configure/references/readiness-and-enablement.md +164 -0
  37. package/skills/consumer-goods-sync-management-configure/references/sync-management-app-install-backbone.md +159 -0
  38. package/skills/consumer-goods-sync-management-configure/references/transports-and-namespace.md +220 -0
  39. package/skills/consumer-goods-sync-management-configure/references/verify-and-smoke.md +175 -0
  40. package/skills/dx-org-analyze/SKILL.md +0 -1
  41. package/skills/dx-org-analyze/scripts/collect_org_data.py +2 -2
  42. package/skills/dx-org-analyze/scripts/compute_diff.py +2 -4
  43. package/skills/dx-org-analyze/scripts/introspect_org.py +4 -40
  44. package/skills/dx-org-manage/SKILL.md +6 -50
  45. package/skills/dx-org-manage/examples/README.md +10 -16
  46. package/skills/dx-org-manage/references/scratch-org-create.md +1 -1
  47. package/skills/dx-org-manage/references/scratch-org-operations.md +1 -1
  48. package/skills/dx-org-shape-manage/SKILL.md +4 -3
  49. package/skills/education-cloud-domain-configure/references/gotchas-extended.md +1 -0
  50. package/skills/experience-accessibility-validate/SKILL.md +8 -5
  51. package/skills/experience-accessibility-validate/references/reviewers/sc-1-1-1-non-text-content.md +1 -4
  52. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-i-lists.md +0 -4
  53. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-ii-tables.md +20 -85
  54. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iii-form-labels.md +4 -4
  55. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-iv-regions.md +0 -4
  56. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-1-v-groups.md +2 -4
  57. package/skills/experience-accessibility-validate/references/reviewers/sc-1-3-5-identify-input.md +0 -4
  58. package/skills/experience-accessibility-validate/references/reviewers/sc-1-4-3-contrast.md +4 -14
  59. package/skills/experience-accessibility-validate/references/reviewers/sc-2-1-1-keyboard.md +9 -5
  60. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-4-link-purpose.md +1 -4
  61. package/skills/experience-accessibility-validate/references/reviewers/sc-2-4-6-headings-labels.md +0 -4
  62. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-1-pointer-gestures.md +0 -4
  63. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-2-pointer-cancellation.md +0 -4
  64. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-3-label-in-name.md +0 -4
  65. package/skills/experience-accessibility-validate/references/reviewers/sc-2-5-7-dragging-movement.md +0 -4
  66. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-1-on-focus.md +0 -4
  67. package/skills/experience-accessibility-validate/references/reviewers/sc-3-2-2-on-input.md +1 -4
  68. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-1-error-identification.md +1 -4
  69. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-2-labels-instructions.md +1 -4
  70. package/skills/experience-accessibility-validate/references/reviewers/sc-3-3-3-error-suggestion.md +1 -4
  71. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-i-name.md +4 -5
  72. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-ii-role.md +1 -4
  73. package/skills/experience-accessibility-validate/references/reviewers/sc-4-1-2-iii-value.md +0 -4
  74. package/skills/experience-lwc-accessibility-jest-run/SKILL.md +104 -25
  75. package/skills/experience-lwc-accessibility-jest-run/assets/jest.config.js +4 -0
  76. package/skills/experience-lwc-accessibility-jest-run/assets/sa11y-jest-setup.js +5 -0
  77. package/skills/experience-ui-bundle-app-coordinate/SKILL.md +5 -5
  78. package/skills/experience-ui-bundle-features-generate/SKILL.md +65 -36
  79. package/skills/experience-ui-bundle-features-generate/references/angular/features.md +74 -0
  80. package/skills/experience-ui-bundle-features-generate/references/react/features.md +59 -0
  81. package/skills/experience-ui-bundle-features-generate/scripts/detect-framework.sh +73 -0
  82. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -2
  83. package/skills/experience-ui-bundle-metadata-generate/SKILL.md +26 -14
  84. package/skills/experience-ui-bundle-metadata-generate/references/angular-metadata-generate.md +37 -0
  85. package/skills/experience-ui-bundle-metadata-generate/references/react-metadata-generate.md +32 -0
  86. package/skills/experience-ui-bundle-metadata-generate/scripts/detect-framework.sh +73 -0
  87. package/skills/experience-ui-bundle-metadata-generate/scripts/verify-bundle-location.sh +30 -8
  88. package/skills/field-service-data-capture-migrate/SKILL.md +391 -0
  89. package/skills/field-service-data-capture-migrate/assets/sample-legacy-flow.flow-meta.xml +163 -0
  90. package/skills/field-service-data-capture-migrate/references/architecture-notes.md +40 -0
  91. package/skills/field-service-data-capture-migrate/references/component-mapping.md +278 -0
  92. package/skills/field-service-data-capture-migrate/references/converter-builder-contract.md +183 -0
  93. package/skills/field-service-data-capture-migrate/references/dc-platform-rules.md +130 -0
  94. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +215 -0
  95. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +36 -0
  96. package/skills/field-service-data-capture-migrate/references/migration-checklist.md +121 -0
  97. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +132 -0
  98. package/skills/field-service-data-capture-migrate/scripts/analyze_flow.py +277 -0
  99. package/skills/field-service-data-capture-migrate/scripts/convert_to_dc_spec.py +1311 -0
  100. package/skills/field-service-data-capture-migrate/scripts/cud_analysis.py +402 -0
  101. package/skills/field-service-data-capture-migrate/scripts/deploy_flow.sh +230 -0
  102. package/skills/field-service-data-capture-migrate/scripts/discover_subflow_tree.py +230 -0
  103. package/skills/field-service-data-capture-migrate/scripts/flow_input.py +186 -0
  104. package/skills/field-service-data-capture-migrate/scripts/flow_xml_utils.py +39 -0
  105. package/skills/field-service-data-capture-migrate/scripts/fsm_dc_catalog.py +413 -0
  106. package/skills/field-service-data-capture-migrate/scripts/generate_summary.py +199 -0
  107. package/skills/field-service-data-capture-migrate/scripts/json_to_flow_xml.py +146 -0
  108. package/skills/field-service-data-capture-migrate/scripts/manual_review_flags.py +42 -0
  109. package/skills/field-service-data-capture-migrate/scripts/retrieve_flow_rest.sh +155 -0
  110. package/skills/field-service-data-capture-migrate/scripts/subflow_names.py +49 -0
  111. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +1661 -0
  112. package/skills/field-service-data-capture-migrate/scripts/validate_flow.sh +147 -0
  113. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +1258 -0
  114. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +1 -1
  115. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +1 -1
  116. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +14 -1
  117. package/skills/mobile-apps-create/SKILL.md +1 -1
  118. package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/SKILL.md +7 -7
  119. package/skills/platform-sandbox-configure/SKILL.md +108 -133
  120. package/skills/platform-sandbox-configure/references/api-response-shapes.md +39 -0
  121. package/skills/platform-sandbox-configure/references/definition-file-approach.md +73 -0
  122. package/skills/platform-widget-generate/SKILL.md +26 -18
  123. package/skills/platform-widget-generate/examples/formulas.json +191 -0
  124. package/skills/platform-widget-generate/references/widget-formulas.md +123 -0
  125. package/skills/platform-widget-generate/references/widget-meta-directives.md +4 -4
  126. package/skills/sales-call-scoring-configure/SKILL.md +318 -0
  127. package/skills/sales-call-scoring-configure/assets/best-practice-competencies.json +67 -0
  128. package/skills/sales-call-scoring-configure/assets/ootb-competencies.json +58 -0
  129. package/skills/sales-call-scoring-configure/references/admin-communication.md +79 -0
  130. package/skills/sales-call-scoring-configure/references/competency-crud.md +130 -0
  131. package/skills/sales-call-scoring-configure/scripts/create-competency.sh +166 -0
  132. package/skills/sales-call-scoring-configure/scripts/create-custom-competency.sh +166 -0
  133. package/skills/sales-call-scoring-configure/scripts/edit-competency.sh +185 -0
  134. package/skills/sales-call-scoring-configure/scripts/edit-custom-competency.sh +190 -0
  135. package/skills/sales-call-scoring-configure/scripts/enable-call-scoring.sh +436 -0
  136. package/skills/sales-call-scoring-configure/scripts/get-competency.sh +72 -0
  137. package/skills/sales-call-scoring-configure/scripts/get-custom-competency.sh +78 -0
  138. package/skills/sales-call-scoring-configure/scripts/install-best-practice-competencies.sh +223 -0
  139. package/skills/sales-call-scoring-configure/scripts/install-ootb-competencies.sh +237 -0
  140. package/skills/sales-call-scoring-configure/scripts/list-competencies.sh +99 -0
  141. package/skills/sales-call-scoring-configure/scripts/shared/auth.sh +116 -0
  142. package/skills/sales-call-scoring-configure/scripts/shared/cap.sh +86 -0
  143. package/skills/sales-call-scoring-configure/scripts/shared/soap.sh +267 -0
  144. package/skills/sales-call-scoring-configure/scripts/shared/soql.sh +25 -0
  145. package/skills/sales-call-scoring-configure/scripts/toggle-competency.sh +112 -0
  146. package/skills/sales-call-scoring-configure/scripts/toggle-custom-competency.sh +125 -0
  147. package/skills/sales-call-scoring-configure/scripts/verify-ootb-competencies.sh +116 -0
  148. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1 -0
  149. package/skills/service-de-channel-activate/SKILL.md +6 -5
  150. package/skills/service-de-channel-create/SKILL.md +3 -3
  151. package/skills/service-de-channel-routing-configure/SKILL.md +3 -3
  152. package/skills/service-de-channel-settings-configure/SKILL.md +195 -0
  153. package/skills/service-de-channel-settings-configure/references/channel-settings-facets.md +152 -0
  154. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/gotchas.md +3 -3
  155. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/language-keywords.md +4 -4
  156. package/skills/{service-de-channel-consent-configure → service-de-channel-settings-configure}/references/worked-examples.md +14 -14
  157. package/skills/service-de-headless-channel-configure/SKILL.md +6 -6
  158. package/skills/service-de-headless-channel-configure/references/worked-examples.md +1 -1
  159. package/skills/service-helpagent-coordinate/SKILL.md +3 -2
  160. package/skills/service-helpagent-coordinate/references/channel-voice.md +15 -4
  161. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +49 -31
  162. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +71 -6
  163. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +26 -4
  164. package/skills/service-itsm-agentic-setup-cmdb-access-assign/SKILL.md +35 -10
  165. package/skills/service-itsm-agentic-setup-cmdb-access-assign/references/mcp-invocation.md +25 -5
  166. package/skills/service-itsm-agentic-setup-cmdb-bundle-deploy/SKILL.md +1 -1
  167. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +5 -5
  168. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +1 -1
  169. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +2 -2
  170. package/skills/service-itsm-agentic-setup-cmdb-coordinate/examples/output-templates.md +1 -1
  171. package/skills/service-itsm-agentic-setup-cmdb-discovery-configure/SKILL.md +25 -15
  172. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -5
  173. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +4 -2
  174. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +1 -1
  175. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +29 -16
  176. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -5
  177. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +4 -2
  178. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +1 -1
  179. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +29 -16
  180. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +138 -68
  181. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +31 -15
  182. package/skills/service-itsm-agentic-setup-incident-management/references/incident-persona-psg.md +156 -0
  183. package/skills/service-itsm-agentic-setup-incident-management/references/incident-preferences.md +148 -0
  184. package/skills/service-itsm-agentic-setup-incident-management/references/major-incident-management.md +162 -0
  185. package/skills/service-itsm-agentic-setup-incident-management/references/service-mgmt-privilege.md +149 -0
  186. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +103 -89
  187. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +2 -2
  188. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +21 -39
  189. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +3 -3
  190. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +18 -1
  191. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +63 -37
  192. package/skills/service-omni-attribute-routing-configure/SKILL.md +53 -0
  193. package/skills/service-omni-attribute-routing-configure/references/api-notes.md +7 -0
  194. package/skills/service-omni-attribute-routing-configure/scripts/configure-and-report.sh +224 -0
  195. package/skills/service-omni-attribute-routing-configure/scripts/tests/test_attribute_routing_contracts.py +137 -0
  196. package/skills/service-omni-channel-inventory-analyze/SKILL.md +96 -0
  197. package/skills/service-omni-channel-inventory-analyze/references/api-notes.md +52 -0
  198. package/skills/service-omni-channel-inventory-analyze/scripts/analyze.sh +185 -0
  199. package/skills/service-omni-channel-inventory-analyze/scripts/tests/_bootstrap.py +84 -0
  200. package/skills/service-omni-channel-inventory-analyze/scripts/tests/test_inventory_analyze_contracts.py +141 -0
  201. package/skills/service-omni-channel-limits-analyze/SKILL.md +70 -0
  202. package/skills/service-omni-channel-limits-analyze/references/api-notes.md +22 -0
  203. package/skills/service-omni-channel-limits-analyze/scripts/analyze.sh +159 -0
  204. package/skills/service-omni-channel-limits-analyze/scripts/tests/_bootstrap.py +87 -0
  205. package/skills/service-omni-channel-limits-analyze/scripts/tests/test_limits_analyze_contracts.py +99 -0
  206. package/skills/service-omni-channel-setup-coordinate/SKILL.md +12 -20
  207. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +55 -5
  208. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +38 -0
  209. package/skills/service-omni-command-center-analyze/SKILL.md +2 -2
  210. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  211. package/skills/dx-org-manage/examples/snapshots/error_output.json +0 -9
  212. package/skills/dx-org-manage/examples/snapshots/success_output.json +0 -15
  213. package/skills/dx-org-manage/references/cli_flags.md +0 -67
  214. package/skills/dx-org-manage/references/creating-snapshot.md +0 -103
  215. package/skills/dx-org-manage/references/snapshot_usage.md +0 -74
  216. package/skills/experience-accessibility-validate/scripts/contrast-ratio.py +0 -153
  217. package/skills/experience-lwc-accessibility-jest-run/references/running-sa11y-jest-tests.md +0 -205
  218. package/skills/experience-ui-bundle-features-generate/scripts/verify-react-bundle.sh +0 -14
  219. /package/skills/experience-ui-bundle-features-generate/references/{conflict-resolution-schema.json → common/conflict-resolution-schema.json} +0 -0
  220. /package/skills/{platform-agentsetup-categories-fetch → platform-agenticsetup-categories-get}/references/api-response-schema.md +0 -0
@@ -0,0 +1,324 @@
1
+ # Act 2 of 3 — Assign Users to the Sync Configuration
2
+
3
+ Part of the [`consumer-goods-sync-management-configure`](../SKILL.md) skill — the **second of three acts**.
4
+ Runs after [Act 1](act1-setup-sync.md) (it binds a user to the config Act 1
5
+ installed — the admin-confirmed baseline root, e.g. the Retail Execution baseline); followed by
6
+ [Act 3](act3-plan-verify.md).
7
+
8
+ **This Act verifies, checks a precondition, then writes.** It verifies the user (read), confirms
9
+ the user's IOU membership (read — a **precondition**, not a write; the package doesn't own IOU
10
+ membership and this skill does **not** create it), then writes the config assignment. **The IOU
11
+ precondition (A2.2) only matters under the `Default_IOU` strategy** — see below.
12
+
13
+ **Detect the resolution strategy first, because it decides whether the IOU precondition even
14
+ applies.** Read the target config root's `Business_Area_Resolution_Strategy__c` (via
15
+ `GET /v1/syncconfig/configs`). The runtime resolver (`ClientAppMapperService.mapClientApp`) picks
16
+ the root *by strategy, gated on IOU presence*, then resolves the binding **User → Role → Profile**
17
+ from the mapping rows:
18
+ - **`Custom_User_Field` (the default strategy) — no IOU needed.** This root is selected for
19
+ users **without** a default mobility IOU, and the binding is resolved entirely from the
20
+ `Sync_Client_App_Profile_Mapping__c` rows. So a **Role (00E)** or **Profile (00e)** mapping —
21
+ written by A2.3's endpoint — binds every user with that role/profile in one shot, with **no IOU
22
+ membership involved at all**. A2.2 does not apply here — skip it.
23
+ - **`Default_IOU` — IOU membership is the binding, and it's a precondition.** This root is selected
24
+ **only** for users who *have* a default mobility IOU, so that membership is what routes them.
25
+ **This skill reads it, it does not create it:** if the user already has a default mobility IOU,
26
+ proceed; **if the user has no IOU membership, report and stop** — there are no further steps, and
27
+ the skill does **not** write IOU membership (that's a CG Cloud core / RE concern). A Role/Profile
28
+ mapping alone won't rescue an IOU-less user, because this root isn't even evaluated for them.
29
+
30
+ **A missing default mobility IOU is a stop *only* under `Default_IOU` — never under
31
+ `Custom_User_Field`.** Under `Custom_User_Field` the strategy doesn't route by IOU at all, so a user
32
+ without one is **not** blocked: they bind through the mapping row with the **all-scope default**
33
+ (`businessArea` = `'All'`, i.e. all business areas — the `Custom_User_Field` analog of "all IOUs").
34
+ Only under `Default_IOU`, where the IOU *is* the binding, does an absent IOU leave nothing to bind —
35
+ so report-and-stop applies **there and only there**. Do not generalize the `Default_IOU` stop into a
36
+ blanket "no IOU ⇒ stop" rule; that would wrongly halt every `Custom_User_Field` assignment.
37
+
38
+ Choosing the wrong path produces a user who "looks assigned" but whose app resolves no config.
39
+
40
+ ## What this Act does
41
+
42
+ ### 1. A2.1 — Verify the target user exists — `core`
43
+
44
+ - Generic SOQL via `execute_api`:
45
+ `GET /query?q=SELECT Id, Name, IsActive, Profile.Name FROM User
46
+ WHERE Name = '<name>'` (or by username/email — prefer a unique key). **Versionless** — the
47
+ `execute_api` transport supplies the `/services/data/<api_version>/` base itself (see the
48
+ orchestrator's session state); don't pin a version into a `core` call. This is a
49
+ first-class `purpose: validate` pre-flight query, **not** a bespoke API. `User` is a core object —
50
+ namespace is irrelevant.
51
+ - No package code needed. Report the resolved `User.Id` (and `IsActive`) into session state;
52
+ if the user doesn't exist or is inactive, stop and report — don't assign into a phantom.
53
+
54
+ ### 2. A2.2 — Confirm the user's Mobility IOU membership — `core` (read-only precondition)
55
+
56
+ - **Package boundary:** `Sync Management App` only **reads** `InternalOrgUnitUser` /
57
+ `InternalOrganizationUnit` — `ClientAppMapperService` queries the user's default mobility IOU
58
+ (`IsDfltMobIntrOrgUnit = true`) to route them to a `core`-mode `Sync_Config` root, and is
59
+ written (dynamic SOQL + `QueryException` catch) to even *compile* in pure managed-package
60
+ orgs where `InternalOrgUnitUser` doesn't exist. It **never writes** IOU membership, and
61
+ **neither does this skill** — IOU membership is a CG Cloud **core Retail Execution** concern,
62
+ provisioned outside this flow.
63
+ - **Under `Custom_User_Field` (the default) — skip this step entirely.** The binding is resolved
64
+ from the `Sync_Client_App_Profile_Mapping__c` row with no IOU involved, so there is nothing to
65
+ check here; go straight to A2.3. A user with **no** default mobility IOU is **not** a blocker under
66
+ this strategy — the mapping row binds them with the all-scope default (`businessArea` = `'All'`).
67
+ The "report and stop on a missing IOU" rule below is **`Default_IOU`-only**.
68
+ - **Under `Default_IOU` — check the membership as a precondition (read-only):** run the same query
69
+ `ClientAppMapperService.getDefaultIouId()` uses —
70
+ `SELECT InternalOrganizationUnitId FROM InternalOrgUnitUser WHERE UserId = :userId AND
71
+ IsDfltMobIntrOrgUnit = true LIMIT 1` (`core`; `InternalOrgUnitUser` is a core RE object, no
72
+ namespace prefix).
73
+ - **If it returns a row:** the user has a default mobility IOU — carry that
74
+ `InternalOrganizationUnitId` forward (it's the `iouId` scope A2.3 needs) and proceed.
75
+ - **If it returns no row:** the user has **no** Mobility IOU membership — **report and stop.**
76
+ Under `Default_IOU` the config root isn't even evaluated for an IOU-less user, so there is
77
+ nothing further to do: do **not** attempt to create IOU membership, and do **not** proceed to
78
+ A2.3. Say plainly that the user must be given a default mobility IOU (a core / RE task) before
79
+ sync assignment can continue.
80
+ - **In a pure managed-package org** the query throws `QueryException` (the object is absent); the
81
+ package treats that as the MP path (`null`) — degrade the same way rather than hard-failing.
82
+
83
+ ### 3. A2.3 — Assign the user to the sync configuration installed in Act 1 — `Sync Management App` (mapping row) / `core` (IOU-implicit)
84
+
85
+ **An assignment binds a User, a Role, OR a Profile to the config** — `mappedRecordId`'s key-prefix
86
+ picks which (`005` = User, `00E` = Role, `00e` = Profile), and the runtime resolver matches in
87
+ priority **User → Role → Profile**. A **Role or Profile** mapping is the high-leverage choice: one
88
+ row binds everyone with that role/profile, no per-user write. Two paths, chosen by the
89
+ `resolution_strategy` you detected up front:
90
+
91
+ #### A2.3 elicitation flow (admin-facing) — what to gather, in order
92
+
93
+ When an admin says "assign the Sync config to a profile / role / user," walk this flow. The
94
+ endpoint (`POST /v1/syncconfig/assignment`) takes a resolved **Id** in `mappedRecordId`
95
+ (`005`=User, `00E`=Role, `00e`=Profile) — never a name — so every "name" the admin gives is
96
+ **resolved to an Id via a `core` SOQL** before the call. Do not skip a confirmation to save a
97
+ round-trip: a wrong config name or an unresolved profile silently produces a no-op binding.
98
+
99
+ 1. **Establish the Config to assign to.**
100
+ - If the admin **didn't name a config**, list the deployed ones — `GET /v1/syncconfig/configs`
101
+ (`SyncConfigListEndpoint`) returns `{name, clientAppId, resolutionStrategy}` — and present the
102
+ **`name`** values for the admin to pick. Do not guess.
103
+ - If the admin **named a config**, confirm it's actually deployed: match the name against
104
+ `/configs`. If it's not there, say so and stop (nothing to bind to) — offer the deployed list.
105
+ - Carry forward the chosen config's **`name`** (→ `configPath`) **and** its
106
+ `resolutionStrategy` — the strategy decides whether the second field is an **IOU** (`iouId`,
107
+ `Default_IOU`) or a **Business Area** (`businessArea`, `Custom_User_Field`). `name` ≠
108
+ `clientAppId`; pass the `name`.
109
+ 2. **Identify the mapping option: Profile, Role, or User.** Ask the admin if it's not stated.
110
+ 3. **Resolve the target name → Id and gather the scope, per option:**
111
+ - **Profile:** get the profile name; if the admin didn't give one, list available profiles
112
+ (`SELECT Id, Name FROM Profile ORDER BY Name`) and ask. Resolve the name → **`00e` Id**
113
+ (`SELECT Id FROM Profile WHERE Name = '<name>'`). Gather the scope (IOU/Business Area — see
114
+ below). Invoke the endpoint with the `00e` Id as `mappedRecordId`.
115
+ - **Role:** get the role name; if not given, list available roles
116
+ (`SELECT Id, Name FROM UserRole ORDER BY Name`) and ask. Resolve the name → **`00E` Id**.
117
+ Gather the scope. Invoke with the `00E` Id as `mappedRecordId`.
118
+ - *Grounding & core-vs-Sync Management App:* these mirror the package's own list fetchers —
119
+ `ClientProfileEditExtension.getProfileIds()` (`SELECT Id, Name FROM Profile`) and
120
+ `getRoleIds()` (`SELECT Id, Name FROM UserRole`) — but `Profile`/`UserRole` are standard
121
+ platform objects (no namespace prefix) and those accessors are VF-controller/`protected`
122
+ (namespace-private, not headless-reachable), so the agent runs the SOQL **directly as `core`**,
123
+ exactly like the IOU reads. Both require the caller to have **view access** on the object.
124
+ - *Role name nuance:* pass the resolved **Id** forward, not the picked name. The service derives
125
+ the persisted mapping-row name itself — and for **roles it uses `DeveloperName`** (the API
126
+ name), not the display `Name` you listed (`SyncConfigAssignmentService.buildRecordName`);
127
+ profiles use `Name` for both. Passing the Id avoids the display-vs-API-name mismatch.
128
+ - **User:** identify the user and get the **`005` Id** from the org
129
+ (`SELECT Id, IsActive FROM User WHERE Username/Email/Name = '<value>'` — reuse A2.1). **If the
130
+ user isn't in the org, inform the admin and stop** — do not bind a phantom. Under `Default_IOU`,
131
+ the user's default mobility IOU was already confirmed in **A2.2** (the precondition check): pass
132
+ that confirmed `InternalOrganizationUnitId` as the scope (`iouId`). Invoke with the `005` Id as
133
+ `mappedRecordId`. (A2.2's read used the exact query `ClientAppMapperService.getDefaultIouId()`
134
+ runs: `SELECT InternalOrganizationUnitId FROM InternalOrgUnitUser WHERE UserId = :userId AND
135
+ IsDfltMobIntrOrgUnit = true LIMIT 1`.)
136
+ - *`IsDfltMobIntrOrgUnit = true` is load-bearing:* it matches **only** the user's *default
137
+ mobility* IOU. A secondary/non-default IOU the user belongs to is deliberately **not**
138
+ matched (the resolver routes such users via the MP path), so do **not** substitute another
139
+ IOU row.
140
+ - *No default mobility IOU → already stopped in A2.2:* if the user had no such membership, the
141
+ A2.2 precondition already reported-and-stopped under `Default_IOU`, so you never reach this
142
+ write. This skill does not create IOU membership to unblock it.
143
+ 4. **The "scope" field is strategy-dependent — this is the key reconciliation:**
144
+ - Under **`Default_IOU`**, the admin's "IOU" is passed as **`iouId`** (the InternalOrganizationUnit
145
+ Id). For the **User** path this is exactly the IOU fetched from `InternalOrgUnitUser` in step 3.
146
+ For **Profile/Role**, there is no single user to derive it from, so **list the org's IOUs and
147
+ ask** — `SELECT Id, OrganizationCode, OrganizationName FROM InternalOrganizationUnit ORDER BY
148
+ OrganizationCode` (present **`OrganizationName` alongside `OrganizationCode`** so the admin can
149
+ identify the right record — a bare code like `IOU-001` is hard to recognize; the admin's pick →
150
+ `iouId`; blank = all IOUs). The package's own `ClientProfileEditExtension.getIouOptions()`
151
+ selects only `OrganizationCode` for its VF label; add `OrganizationName` here for the
152
+ admin-facing pick. **Both IOU
153
+ reads are `core`, not `Sync Management App`** — `InternalOrgUnitUser` and `InternalOrganizationUnit` are CG
154
+ Cloud core RE objects (no namespace prefix), and the package's IOU accessors are
155
+ VF-controller/`protected` (namespace-private, not headless-reachable), so the agent runs the
156
+ SOQL directly. In a **pure managed-package org** these objects don't exist — the query throws
157
+ `QueryException` (the package catches it and degrades to "All"); the agent should treat a
158
+ missing-object error the same way, not as a hard failure.
159
+ - *Object confusion to avoid:* `InternalOrganizationUnit` = the IOU **record** (carries
160
+ `OrganizationCode`, what you *list from*); `InternalOrgUnitUser` = the user↔IOU **junction**
161
+ (carries `IsDfltMobIntrOrgUnit`, what you query for *one user's default*).
162
+ - Under **`Custom_User_Field`** (the default), there is **no IOU** — the second field is a
163
+ **Business Area picklist value** passed as **`businessArea`** (blank / `'All'` = all areas).
164
+ So "assign to profile X with IOU Y" under this strategy means: bind profile X, and Y is a
165
+ Business Area value, not an IOU. If the admin insists on an IOU under `Custom_User_Field`, that
166
+ signals the config's strategy doesn't match the request — surface the mismatch rather than
167
+ stuffing an IOU Id into `businessArea`.
168
+ 5. **Invoke and verify.** `POST /v1/syncconfig/assignment` with the body in step (a).2 below, then
169
+ read back with `GET /v1/syncconfig/assignment?configId=<config name>`.
170
+ - **On failure, report and STOP.** If the POST returns a failure status (or throws), surface the
171
+ error message to the admin and **halt — no read-back, no further processing.** A failure body
172
+ inside an HTTP 200 is still a failure; never treat it as success and never fall back to a raw
173
+ `Sync_Client_App_Profile_Mapping__c` write to "recover." In particular an **unknown/unresolvable
174
+ `configPath` is a hard rejection** (the endpoint throws — it no longer round-trips the value or
175
+ silently defaults the strategy to `Custom_User_Field`), so a bad config name fails loudly here
176
+ rather than writing a mis-keyed row: confirm the config is in the `/configs` list before POSTing.
177
+ Only a success response proceeds to the read-back.
178
+
179
+ The mechanics of each transport path:
180
+
181
+ - **(a) `Custom_User_Field` (the default) — a mapping-row write via the `Sync Management App` endpoint, NO IOU
182
+ needed.** The binding is resolved entirely from the `Sync_Client_App_Profile_Mapping__c` row, so
183
+ write a **User/Role/Profile** row through `SyncConfigAssignmentEndpoint` and you're done — **A2.2
184
+ is not on the critical path here.** Steps:
185
+ 1. **Pick the config.** `GET /v1/syncconfig/configs` (`SyncConfigListEndpoint`) lists the
186
+ deployed configs as `{name, clientAppId, resolutionStrategy}`. Pass the config's **`name`** (or
187
+ its `clientAppId`) as `configPath` — the endpoint accepts **either** and normalises to the
188
+ canonical `clientAppId` before storing (see the `configPath` note below). The **stored** row is
189
+ always keyed by `clientAppId`, which is what the admin **Assignments page** displays.
190
+ 2. **Assign.** `POST /v1/syncconfig/assignment` with
191
+ `{"req":{"configPath":"<config name or clientAppId>","mappedRecordId":"<005|00E|00e id>","businessArea":"<value or 'All'>"}}`.
192
+ Pass a **Role (`00E`)** or **Profile (`00e`)** id to bind by role/profile; a **User (`005`)** id
193
+ to bind one user. `mappedRecordId`'s key-prefix picks the type automatically.
194
+ 3. **Verify by read-back.** `GET /v1/syncconfig/assignment?configId=<name or clientAppId>` returns
195
+ the row with the resolved target name and the stored `businessAreaStored`/`resolutionStrategy`.
196
+ The returned `clientAppId`/`clientAppProfile` are the **canonical** short `clientAppId` (not
197
+ the config's longer custom-setting `name`); confirm they match, since that canonical value is
198
+ what makes the row appear on the admin Assignments page (which filters by `clientAppId`). For
199
+ example, passing a config `name` like `<AppId>__Default` as `configPath` stores the short
200
+ `clientAppId` `<AppId>`, and the row is visible on that app's Assignments page.
201
+ - **(b) `Default_IOU` — IOU-implicit; gated on the A2.2 precondition.** This root is selected
202
+ **only** for users with a default mobility IOU, so the user is bound via that IOU, not a
203
+ standalone mapping row. **A2.2 must have already confirmed the membership** — if the user had no
204
+ default mobility IOU, A2.2 already reported-and-stopped and you never reach here. Given the
205
+ confirmed membership, verify by re-reading it and confirming `ClientAppMapperService`'s route
206
+ resolves to the installed config root. (A Role/Profile row alone won't bind an IOU-less user here, because
207
+ this root isn't evaluated for them.) When you do write a row under this strategy, pass
208
+ `"iouId":"<id>"` (the IOU confirmed in A2.2) instead of `businessArea` so the endpoint stores the
209
+ IOU id correctly.
210
+ - **Why NOT a raw `POST /sobjects/<ns>Sync_Client_App_Profile_Mapping__c`:** the mapping's
211
+ `Business_Area_Name__c` field is **overloaded** — it holds an InternalOrganizationUnit **Id**
212
+ under the `Default_IOU` strategy but a Business Area **picklist value** under
213
+ `Custom_User_Field`. A raw sObject write that doesn't derive this from the config's
214
+ `Business_Area_Resolution_Strategy__c` **silently corrupts the field**, and also skips the
215
+ cross-config duplicate check and the unique-Name convention. The endpoint centralizes all three
216
+ (it's the same `SyncConfigAssignmentService` the VF `ClientProfileEditExtension.Save()` uses), so
217
+ the headless agent and the admin UI produce identical rows. This is why A2.3's explicit path is
218
+ **`Sync Management App`, not `core`** — the raw-sObject shortcut is unsafe.
219
+ - **Use the endpoint, never a raw write.** The assignment + configs endpoints are the supported
220
+ path here — never fall back to a raw `Sync_Client_App_Profile_Mapping__c` write (it corrupts the
221
+ overloaded `Business_Area_Name__c` and skips dedup + the unique-Name convention).
222
+
223
+ #### A2.3 re-assignment, update & removal — upsert semantics (do this, don't guess)
224
+
225
+ The POST is an **upsert**, and re-binding an already-mapped user/role/profile is a first-class
226
+ case — not the same call twice. Get it wrong and you either hit a duplicate error or write a second
227
+ conflicting row. The rules the endpoint enforces server-side (`SyncConfigAssignmentService`):
228
+
229
+ - **Create vs. update is decided by the `recordId` param, not by the config.** The POST body
230
+ carries an optional **`recordId`** (an *existing* `Sync_Client_App_Profile_Mapping__c` Id):
231
+ - **Omit `recordId` → create a new row.**
232
+ - **Pass `recordId` → update that existing row in place** (the endpoint SELECTs it and throws
233
+ `No assignment found for recordId ...` if the Id doesn't exist — so read it back first).
234
+
235
+ Body shape (add `recordId` to the step-(a).2 body when updating):
236
+ `{"req":{"configPath":"<name|clientAppId>","mappedRecordId":"<005|00E|00e>","businessArea":"<value|'All'>","recordId":"<existing mapping Id>"}}`
237
+ (under `Default_IOU`, `"iouId":"<id>"` replaces `businessArea`, as above).
238
+
239
+ - **The create path is NOT idempotent — re-POSTing the same binding is rejected, not a no-op.**
240
+ Within one config the dedup key is the **triple** `(configPath, businessArea, mappedRecordId)`
241
+ (i.e. `Client_App_ID__c` + `Business_Area_Name__c` + `Mapped_Record_Id__c`). A create (`recordId`
242
+ omitted) whose triple already exists **throws a duplicate error**
243
+ (`ASSIGNMENT_EDIT_DUPLICATE_MAPPING`). So **before re-assigning, `GET
244
+ ?configId=<name>` first**: if the exact row already exists, the user/role/profile is *already*
245
+ bound as requested — report done, don't re-POST. Only the row being updated is excluded from this
246
+ check, and only when you pass its Id as `recordId` — that's what lets you change an existing
247
+ binding's scope (e.g. move a profile from `businessArea='All'` to a specific area) without it
248
+ colliding with itself: fetch the row's Id via GET, then re-POST with `recordId` = that Id.
249
+
250
+ - **Moving a binding between configs = DELETE-then-POST, not a re-POST.** A *second* guard,
251
+ `assertNoDuplicateAcrossStrategyRoots`, blocks the same `(mappedRecordId, businessArea)` from
252
+ being bound to a **different config that shares the same resolution strategy** (a blank strategy
253
+ normalises to `Custom_User_Field`, so it's in that bucket) — and this guard has **no exclude
254
+ param**, so you **cannot** simply POST the binding to the new config while the old row still
255
+ exists; it throws `This mapping is already used in the configuration "..."`. To move a binding
256
+ across same-strategy configs: **DELETE the old mapping first, then POST to the new config.** (Two
257
+ configs with *different* strategies don't collide, so this only bites same-strategy roots — but
258
+ DELETE-then-POST is the safe default regardless.)
259
+
260
+ - **Remove a binding via DELETE.** `DELETE /v1/syncconfig/assignment?id=<name-or-id>` removes one
261
+ mapping. The `id` param accepts **either the row's `Name` or its `Id`** (the service resolves Name
262
+ first, then Id) — use the value the `GET ?configId=<name>` read-back returns; it throws
263
+ `No assignment found for "..."` if neither matches. **Verify with a follow-up GET** showing the row
264
+ gone, exactly like any other write. (Deleting only removes the explicit mapping row; under
265
+ `Default_IOU`, where the IOU *is* the binding, there is no mapping row to delete — revoking that
266
+ user is an IOU-membership change, a core / RE task outside this flow.)
267
+
268
+ - **Every update/delete ends with a read-back too.** The upsert and the DELETE are writes — verify
269
+ each with `GET /v1/syncconfig/assignment?configId=<name>` (row present with the new scope after an
270
+ update; row absent after a delete). Never infer success from the 200 alone.
271
+
272
+ ## Act 2 end state
273
+
274
+ - Target user confirmed to exist and active (`User.Id` captured).
275
+ - Under `Default_IOU`: the user's default mobility IOU membership **confirmed** by read
276
+ (`IsDfltMobIntrOrgUnit = true`) — **or**, if the user has no such membership, **reported and the
277
+ Act stopped** (the skill does not create IOU membership; that's a core / RE task). Under
278
+ `Custom_User_Field`: no IOU check applies.
279
+ - The user bound to the config installed in Act 1: implicitly (via the confirmed IOU, strategy
280
+ `Default_IOU`) or explicitly (a verified `Sync_Client_App_Profile_Mapping__c` row written via the
281
+ `/v1/syncconfig/assignment` endpoint under `Custom_User_Field`) — or, if the `Default_IOU`
282
+ precondition failed, reported as stopped-at-A2.2 with no binding.
283
+
284
+ ## Gotchas
285
+
286
+ - **`Default_IOU` gates A2.3 on the A2.2 IOU precondition — but `Custom_User_Field` does NOT.**
287
+ Under `Default_IOU` there is no separate "assign to config" write — the IOU *is* the assignment,
288
+ so an IOU-less user cannot be bound: A2.2 reads the membership and, if it's absent, **reports and
289
+ stops** (the skill never creates IOU membership). Under `Custom_User_Field` (the default strategy)
290
+ the binding is a `Sync_Client_App_Profile_Mapping__c` row keyed by **User/Role/Profile** with **no
291
+ IOU involved** — a Role or Profile row binds a whole cohort in one write, and the IOU check does
292
+ **not** apply. Detect the strategy before deciding whether the IOU precondition is even on the
293
+ critical path; don't invent a mapping row a `Default_IOU` root ignores, and don't run the IOU
294
+ check under `Custom_User_Field`.
295
+ - **Assignment granularity: User / Role / Profile.** `mappedRecordId` chooses by key-prefix
296
+ (`005`/`00E`/`00e`) and the resolver matches **User → Role → Profile** in priority order. Prefer a
297
+ **Role or Profile** mapping when the intent is "everyone in this role/profile" — it's one row, not
298
+ one-per-user, and it's the same lever the admin VF page pulls.
299
+ - **The package's read-only IOU stance is deliberate — and so is this skill's.** `ClientAppMapperService`
300
+ is built to survive orgs where `InternalOrgUnitUser` doesn't exist — that's the signal the package
301
+ doesn't own IOU writes. This skill mirrors that: it **reads** IOU membership as a precondition and,
302
+ if it's missing under `Default_IOU`, reports and stops. It does **not** write IOU membership or
303
+ treat the absence as a Sync Management App task — provisioning a user's default mobility IOU is a core / RE
304
+ responsibility, done outside this flow.
305
+ - **Don't write the mapping row raw — the `Business_Area_Name__c` field is overloaded.** It was
306
+ tempting to treat A2.3's explicit path as a plain custom-setting sObject POST (no VF page needed),
307
+ but that skips the strategy-driven derivation of `Business_Area_Name__c` (IOU Id vs Business Area
308
+ picklist value), the cross-config duplicate check, and the unique-Name convention — silently
309
+ corrupting the row. Use `POST /v1/syncconfig/assignment` (`SyncConfigAssignmentEndpoint`), which
310
+ wraps the same `SyncConfigAssignmentService` the VF page uses. This is why A2.3's explicit path is
311
+ `Sync Management App`, not `core`.
312
+ - **`configPath` accepts either the `name` or the `clientAppId`; the row is stored under the
313
+ `clientAppId`.** `GET /v1/syncconfig/configs` returns both (e.g. `{name:"<AppId>__Default",
314
+ clientAppId:"<AppId>"}`), and they differ when a config's custom-setting Name isn't its short
315
+ `clientAppId`. The endpoint **normalises** `configPath` to the canonical `clientAppId` before
316
+ writing `Client_App_ID__c`/`Client_App_Profile__c`, so passing either works. This matters because
317
+ the package keys everything off `clientAppId`: the admin **Assignments page**
318
+ (`ClientProfilesController`) only shows rows whose `Client_App_Id__c` equals the config's
319
+ `clientAppId`, and the runtime resolver (`ClientAppMapperService`) returns `Client_App_Profile__c`
320
+ verbatim as the resolved client-app id. **Storing the Name (e.g. `<AppId>__Default`) instead of the
321
+ short `clientAppId` (`<AppId>`) makes the assignment invisible on the admin page and unresolvable at
322
+ runtime** — `SyncConfigAssignmentService` normalises Name↔clientAppId, storing the canonical
323
+ `clientAppId` so this doesn't happen. Always confirm the read-back's `clientAppId` is the short
324
+ canonical value.
@@ -0,0 +1,113 @@
1
+ # Act 3 of 3 — Plan & Verify
2
+
3
+ Part of the [`consumer-goods-sync-management-configure`](../SKILL.md) skill — the **third and final act**.
4
+ Runs after [Act 1](act1-setup-sync.md) (install) and [Act 2](act2-assign-users.md)
5
+ (assignment). For an unqualified "set up sync" request you must reach this Act's end state —
6
+ finishing the install is not finishing the setup.
7
+
8
+ **This Act is read + assert (no writes).** It lists the activated config, verifies it across
9
+ the four objects, and checks it against the baseline files. **This is the authoritative
10
+ verification** — Act 1's post-install step ran only the cheap **aggregate** landing check (sum of
11
+ the four counts == `InstallResponse.recordsInserted`, no manifest fetch); the **per-object** count
12
+ comparison against the baseline manifest (A3.2) happens **here, once** — Act 1 deliberately does
13
+ not run it, so the two acts do not duplicate each other.
14
+
15
+ ## What this Act does
16
+
17
+ ### 1. A3.1 — List + verify the activated config across the four objects — **`Sync Management App` (list) + `core` (counts)**
18
+
19
+ - **List the deployed configs via the `Sync Management App` endpoint.** `GET /v1/syncconfig/configs`
20
+ (`SyncConfigListEndpoint`) returns every deployed `Sync_Config__c` as
21
+ `{name, clientAppId, resolutionStrategy}` in one package-blessed call — the source of truth for
22
+ *which* configs are activated and each one's Business Area Resolution Strategy. This is also the
23
+ discovery step Act 2's explicit assignment (A2.3) consumes: the `name` it returns is the
24
+ `configPath` the assignment endpoint expects. This is the supported list surface (a `core` SOQL
25
+ list — see below — is an equivalent fallback for reading deployed configs).
26
+ - **Get per-object counts + installed version via generic `core` reads — works today, no package
27
+ change.** The four sync objects + `Sync_Config_State__c` are **List custom settings**, so they're
28
+ queryable via generic SOQL. **Batch all five `COUNT()`s into ONE anonymous-Apex call that
29
+ `System.debug`s a JSON blob — do NOT loop `sf data query` per object.** Each `sf` cold-start is
30
+ ~15–20s, so a 4–5-object loop blows the 2-min timeout; one `sf apex run` returns every count in a
31
+ single invocation (see the batched form + the **grep `USER_DEBUG` first** parse rule in
32
+ [transports-and-namespace.md](transports-and-namespace.md) — `sf apex run` echoes the block as
33
+ `Execute Anonymous:` source, so a bare marker grep matches twice). A single
34
+ `GET /query?q=SELECT COUNT() FROM <ns>Sync_Config__c` via `execute_api` is fine only for a one-off
35
+ read. Return row counts + keys + installed `configId`/`version` in one summary. **This covers the
36
+ count/verify half** — it is **not** blocked.
37
+ - **What does *not* work (and why):** the package's own state readers
38
+ `SyncConfigOrchestrator.buildStateMap()` / `readCurrentVersion()` are **`public static`,
39
+ unannotated** (and their internal helper `installedVersionMap()` is **`private static`**) —
40
+ either way namespace-private, so **not** callable via anonymous Apex or REST from a subscriber org
41
+ (a `private` member is even less reachable than a `public` one). `BatchSoqlEndpoint` (`/v1/batchsoql`) is gated by
42
+ `isClientAppIdAvailable()` (needs a pre-existing `Sync_Config__c`). So the package's
43
+ *purpose-built* count/version reader isn't reachable — but generic core SOQL covers the need.
44
+ - **No additional Sync Management App call is needed for A3.1 — record counts ARE the verification.** The
45
+ per-object `core` COUNT() reads above are the complete, intended verification method (the objects
46
+ are custom settings, directly queryable). Do **not** propose, wait on, or build a
47
+ `/v1/syncconfig/verify` endpoint — Act 3 adds **no** Sync Management App calls; verification is done through
48
+ record counts, full stop, not a workaround for a missing surface. The package's `public static`
49
+ state readers being namespace-private is therefore moot — their output (counts + version) is
50
+ already covered by generic `core` SOQL.
51
+ - **Adopt the verify-step convention** when documenting the result — end the workflow with
52
+ an explicit assert step (a distinct step with `id: verify`, a read `api`, and an `assert:` predicate).
53
+ **Assert against the installed baseline's own
54
+ expected count — never a hardcoded literal** (`<expected>` is resolved at runtime per the bullet
55
+ below; it varies by which baseline was installed):
56
+ ```yaml
57
+ - id: verify
58
+ description: "Confirm activated sync config"
59
+ api: "GET /query?q=SELECT COUNT() FROM <ns>Sync_Tracked_Object_Config__c"
60
+ assert: "count == <expected tracked-object count for the installed baseline>"
61
+ ```
62
+
63
+ ### 2. A3.2 — Verify against the baseline static-resource files — `core`
64
+
65
+ - **Baseline count check (record counts only — the intended verification):**
66
+ 1. **Derive the expected per-object counts from the installed baseline — do NOT hardcode them.**
67
+ The expected numbers depend on *which* baseline was installed (a different baseline, or a newer
68
+ release of the same one, has different counts), so resolve them at runtime, not from literals in
69
+ this doc:
70
+ - Identify the installed baseline from the activated config (the `staticResourceName`/version
71
+ captured at **install time** in Act 1's A1.3 — carry it forward in session state — or the
72
+ installed `configId`/`version` read back in A3.1).
73
+ - Read that baseline's own manifest/CSVs (the installed baseline's static-resource name came
74
+ from the discovery endpoint / the install captured in A1.3) and count the
75
+ rows it declares per object — that row count **is** the expected value. The per-object numbers
76
+ differ by edition and release, so **derive them from the installed baseline's manifest at
77
+ runtime — never assert against a constant in this doc.**
78
+ 2. Get the installed counts from A3.1's `core` COUNT() reads.
79
+ 3. Report a single pass / fail verdict per object: installed count == the installed baseline's
80
+ declared count.
81
+ - **Verification is through record counts.** The count comparison against the installed baseline
82
+ is the complete server-side check — a count match confirms the config's artefacts installed as
83
+ that baseline specifies.
84
+ - **These reads hit Core directly, bypassing the package dispatcher** — the same generic `core`
85
+ SOQL A3.1 runs (the **batched anon-Apex `COUNT()` call**); A3.2 **reuses those counts** rather
86
+ than re-querying per object.
87
+ - **No Sync Management App work here.** There is no `/v1/syncconfig/verify` (or `mode=smoke`) proposal — Act 3
88
+ adds no calls to the package; counts cover the need.
89
+ - **A green count check proves the server side installed the baseline's artefacts — say exactly
90
+ what was verified.** Matching counts confirm the cloud-side install; report the verification as a
91
+ server-side count check so no one reads a green count as more than that.
92
+
93
+ ## Act 3 end state
94
+
95
+ - Activated config **listed** via the `/v1/syncconfig/configs` `Sync Management App` endpoint (deployed configs +
96
+ strategy) and **verified** across the four objects + `Sync_Config_State__c` **through per-object
97
+ record counts** via generic `core` SOQL (the objects are custom settings). Act 3 requires **no new
98
+ Sync Management App call** — counts are the verification.
99
+ - **Baseline count check** run: installed per-object counts match the **installed baseline's own
100
+ declared counts** (derived at runtime from that baseline's manifest/CSVs — not hardcoded) — or the
101
+ discrepancy reported.
102
+
103
+ ## Gotchas
104
+
105
+ - **The package's config *count/version* readers are namespace-private, but you don't need them.**
106
+ They're `public static` (unreachable headlessly) — but the four objects are **custom settings**,
107
+ so generic `core` SOQL reads them today. Listing the deployed
108
+ configs + strategy has a blessed surface (`/v1/syncconfig/configs`). No further Sync Management
109
+ App endpoint is needed for Act 3 — verification is record counts via `core` SOQL, and that is the
110
+ intended method, not a workaround.
111
+ - **A green count check proves the server side installed the baseline's artefacts.** Matching
112
+ record counts confirm the cloud-side install. Say exactly what was verified — report it as a
113
+ server-side count check, nothing more.
@@ -0,0 +1,164 @@
1
+ # Reference — Namespace Gate & Retail-Execution Enablement (core precedents)
2
+
3
+ Background for [Act 1](act1-setup-sync.md) sub-acts A1.1, A1.2. Both are **core**
4
+ concerns — resolving the namespace and toggling Retail Execution both go through generic Salesforce
5
+ capabilities, not a package endpoint — so the pattern is a generic `core` transport; where no
6
+ automatable path exists, the skill reports what's needed and who provides it, then stops that step.
7
+
8
+ ## A1.1 — Verify the Sync metadata is present and resolve its namespace (hard gate) — `core`
9
+
10
+ ### A1.1.0 — Namespace-resolution gate (the hard prerequisite) — `core` Tooling API + `core` Organization query
11
+
12
+ The whole skill presupposes the Sync Management App sync objects exist and their namespace is
13
+ known — no sync object, `/v1/*` endpoint, or namespace reference works otherwise. The namespace
14
+ resolves from **one of two sources**: a **managed-package install** (`InstalledSubscriberPackage`,
15
+ below) or the **org's own registered namespace** (`Organization.NamespacePrefix`, for a
16
+ source-deployed DE/packaging org). **Verify presence and resolve the namespace as the very first
17
+ thing A1.1 does.** Start with the package check via the generic **Tooling API** (Option 2):
18
+
19
+ ```text
20
+ GET /services/data/vXX.0/tooling/query?q=
21
+ SELECT SubscriberPackage.Name, SubscriberPackage.NamespacePrefix,
22
+ SubscriberPackageVersion.MajorVersion, SubscriberPackageVersion.MinorVersion,
23
+ SubscriberPackageVersion.IsBeta
24
+ FROM InstalledSubscriberPackage
25
+ ```
26
+
27
+ - **Why Tooling, not the package's own endpoint.** The package *does* ship
28
+ `ManagedPackageEndpoint` (`GET /v1/managedpackage?method=isManagedPackageInstalled&namespacePrefix=<ns>`,
29
+ which internally does `UserInfo.isCurrentUserLicensed(ns)` and treats "didn't throw" as
30
+ installed). But that route is *served by* the package — **if the package is absent the route
31
+ 404s**, so it cannot report absence (chicken-and-egg). The Tooling query is generic `core` and
32
+ answers definitively either way, **and** returns the installed *version* — which the package
33
+ endpoint does not. Use `/v1/managedpackage` only as an optional confirming call once Tooling has
34
+ already said the package is present.
35
+ - **No server-side filter.** `InstalledSubscriberPackage` doesn't support a `WHERE` on the
36
+ packaging fields — query all rows and match client-side by `SubscriberPackage.Name` (the Sync
37
+ Management App package), then **read the matched row's `NamespacePrefix` off the result** — don't
38
+ assume a fixed prefix; the org carries whatever the installed package was built with.
39
+ - **Two outputs from a match:** (a) the **namespace prefix** (read from the matched row) that every
40
+ later package-object reference resolves to, and (b) the **installed version**, recorded for
41
+ reference. (The
42
+ `/v1/syncconfig/*` endpoints' package-release rollout is a separate workstream — no
43
+ version-gating logic is needed here.)
44
+ - **Transport:** `sf data query --use-tooling-api --query "..."` when the CLI is present, else a
45
+ plain REST `GET /services/data/vXX.0/tooling/query?q=<url-encoded SOQL>` (via `execute_api` or any
46
+ session-token client — the CLI is only sugar over this).
47
+
48
+ #### The namespace has two sources — package OR org (don't stop at an empty package list)
49
+
50
+ A match on `InstalledSubscriberPackage` is the **managed-package install** case — a subscriber org.
51
+ But the sync metadata can also be **source-deployed under the org's *own* registered namespace** (a
52
+ Developer Edition / packaging / dev org), and then `InstalledSubscriberPackage` returns **no row at
53
+ all** even though the sync objects are present. An empty package list is therefore **not** an
54
+ automatic hard miss — fall back to the org namespace:
55
+
56
+ - **Org-namespace fallback (`core`, no Tooling needed):** `SELECT Id, NamespacePrefix FROM
57
+ Organization`. A non-null `NamespacePrefix` is the org's own namespace; treat it (with the
58
+ trailing `__`) as `ns_prefix`. **Confirm** the sync metadata actually lives under it by resolving
59
+ one known object — `GET /sobjects/<orgNs>Sync_Config__c/describe` or `SELECT COUNT() FROM
60
+ <orgNs>Sync_Config__c` — a success means the run proceeds under the org namespace. There is **no
61
+ `SubscriberPackageVersion`** in this case; endpoint-version gating doesn't apply to a
62
+ source-deployed org (the `/v1/syncconfig/*` classes are either deployed or not — probe the route).
63
+ - *Worked example:* a DE org with a non-null `Organization.NamespacePrefix` (say `<orgNs>`) and
64
+ zero `InstalledSubscriberPackage` rows resolves `ns_prefix = <orgNs>__`, and
65
+ `<orgNs>Sync_Config__c` describes successfully — the run proceeds; the empty package list was a
66
+ red herring.
67
+
68
+ - **Describe fallback (no Tooling access):** `GET /sobjects/<ns>Sync_Config__c/describe` across the
69
+ candidate prefixes — the package's known namespace(s), **the org namespace** from `SELECT
70
+ NamespacePrefix FROM Organization` (a plain `core` query, no Tooling), and the bare (unmanaged)
71
+ name — 200 = that prefix is the live one (no version); `NOT_FOUND` = that prefix absent.
72
+ `NOT_FOUND` on all probed = absent. Prefer the Tooling query when available — only it returns the
73
+ installed *version* for the package case.
74
+ - **Hard miss = STOP the whole run — only when NEITHER source resolves.** If no
75
+ `InstalledSubscriberPackage` row matches **and** the org has no namespace (or `<orgNs>Sync_Config__c`
76
+ doesn't describe) **and** the object isn't present unmanaged (bare `Sync_Config__c`), report "the
77
+ Sync Management App metadata is not present in this org (no managed package, no org-namespaced
78
+ deploy)" and halt — **installing/deploying it is org setup outside this skill's scope**, not a
79
+ step the skill performs. This is the outermost admission gate, ahead of RE enablement (A1.2),
80
+ which presupposes the sync objects exist.
81
+
82
+ ### A1.1.1 — No license read, no permission-set gate (readiness is exactly two checks)
83
+
84
+ Installing and using the Sync Management App is **not** license-gated: there is no license or SKU
85
+ to detect, and package install is not entitlement-gated. So A1.1 runs **no** license query and the
86
+ skill declares no license `accessCheck`.
87
+
88
+ **Equally, do NOT verify the existence of any named permission set as a readiness gate.**
89
+ Permission-set names are unstable — renamed or folded into another set with a different name across
90
+ releases and orgs — so a name-based existence check would false-block a perfectly capable org.
91
+
92
+ The Act-1 readiness gates are therefore **exactly two**: **(1) the Sync Management objects are
93
+ present** (the namespace gate, A1.1.0 above) and **(2) Retail Execution is enabled** (A1.2 below) —
94
+ RE is an org *preference*, not a license, so it is enabled in A1.2 rather than probed for as an
95
+ entitlement here. Access to the install/assignment endpoints is granted by *some* permission set in
96
+ the target org whose name is the admin's concern, not a value the skill asserts; a denied call is
97
+ reported as an access boundary (with who grants it), never gated on a specific permission-set name.
98
+
99
+ ## A1.2 — Enable Retail Execution — `core` read + `core` write
100
+
101
+ - Enabling base RE is a **working `core` write**: an MDAPI `Settings:RetailExecution` deploy flips
102
+ `enableRetailExecution` **false → true**. Dry-run first, then re-read to confirm.
103
+ - **What the read shows depends on the transport:**
104
+ - **Dispatcher (Headless-360)** `getRetailExecutionEnableSetting` returns **5 prefs**
105
+ (`isRetailExecutionEnablePrefEnable`, `isProductHierarchyPrefEnabled`,
106
+ `isVisitSharingPrefEnabled`, `isCGAgentsPrefEnabled`, `isCGGenAIPrefEnabled`) + 4 read-only
107
+ echo flags.
108
+ - **MDAPI `Settings:RetailExecution`** exposes **3** booleans (`enableRetailExecution`,
109
+ `enableProductHierarchy`, `enableVisitSharing`). Same org pref
110
+ (`ORG_PREFERENCE_RETAIL_EXECUTION_ENABLED` via `PermAndPrefUtil.setOrgPref`, controller
111
+ `RetailExecutionEnableController`), narrower projection. Don't claim "5 sub-prefs" when reading
112
+ via CLI — you'll only see 3.
113
+ - **Two write transports:**
114
+ - **Dispatcher present:** `RetailExecutionSettings` has **5 separate PATCH setters, not one
115
+ combined call** — `set-retail-execution-enable-setting`, `set-product-hierarchy-pref`,
116
+ `set-visit-sharing-pref`, `set-cgagent-pref`, `set-cgcgen-aipref` (each body
117
+ `{enable:boolean}`). Guards: all need View Setup & Configuration; `set-visit-sharing-pref`
118
+ needs Visit Share Pilot perm on enable=true; the CG-agent/GenAI setters are gated by
119
+ `RetailExecution.userCanUseCGGenAI`. Route base:
120
+ `PATCH /headless/invoke/platform/retail-execution-settings/...`. The batch endpoint harness only
121
+ probes no-param GETs, so these PATCH-with-body routes are structurally un-probed.
122
+ - **Dispatcher-absent (`sf` CLI):** deploy `settings/RetailExecution.settings` (the member takes
123
+ the bare type name `RetailExecution`, no `Settings` suffix) via
124
+ `sf project deploy start --metadata "Settings:RetailExecution"`. Generic `core` MDAPI Settings
125
+ deploy — read pref → write settings file → deploy → re-verify. `sourceApiVersion` must be ≥ the
126
+ field's minApiVersion.
127
+ - **Dependent-default side effect:** enabling `enableRetailExecution` also auto-set
128
+ `enableVisitSharing` true (was absent before), even though the deploy file carried only the one
129
+ field. Always re-read after the write; the org may set dependent sub-prefs for you.
130
+ - **The advanced-pilot GA gate is a pure dependency, not a blocker:**
131
+ `RetailExecution.orgHasAdvncdRetailExecutionPilot` appears **only** as a Core-source setup-tree
132
+ admission-gate expression — no step in this skill (or its spec) reads or writes it, and it's not an OrgPreference/PSL row,
133
+ so it has no read/write API anywhere. **Base RE enablement does not require it** — it gates only
134
+ *advanced* RE. Report it as a CG Cloud platform / Retail Execution concern only when an
135
+ advanced-RE feature needs it — never as a blocker on the base toggle.
136
+
137
+ ### REST endpoints — the CLI-free path
138
+
139
+ The `sf` CLI is only sugar over these REST resources. **When the `sf` CLI plugin is not available,
140
+ use the REST APIs directly** (any HTTP client with a valid session token — `execute_api` in a
141
+ dispatcher context, or a bearer-token `curl`). Same operations, same results.
142
+
143
+ | Step | REST call | CLI equivalent | Notes |
144
+ |---|---|---|---|
145
+ | **A1.2** read (Tooling) | **Primary: `GET /tooling/query?q=SELECT+IsRetailExecutionEnabled,IsProductHierarchyEnabled,IsVisitSharingEnabled+FROM+RetailExecutionSettings`** (SOQL projection — returns the values directly). Fallback: the two-call singleton `SELECT Id …` → `GET /tooling/sobjects/RetailExecutionSettings/<id>` | `sf project retrieve start --metadata "Settings:RetailExecution"` | Tooling field names: `Is{RetailExecution,ProductHierarchy,VisitSharing}Enabled`. Prefer the SOQL projection — the singleton `GET /sobjects/RetailExecutionSettings/<id>` can return `UNKNOWN_EXCEPTION` |
146
+ | **A1.2** read (dispatcher) | `POST /headless/invoke` (op in body) | — | Dispatcher route only where Headless-360 is provisioned; 404 otherwise |
147
+ | **A1.2** write | `POST /metadata/deployRequest` (base64 zip; `checkOnly:true` to dry-run) → poll `GET /metadata/deployRequest/<id>`; repeat `checkOnly:false` for real | `sf project deploy start --metadata "Settings:RetailExecution"` | This is what `sf project deploy` wraps |
148
+ | **A1.2** write (unsupported) | `PATCH /tooling/sobjects/RetailExecutionSettings/<id>` `{"IsRetailExecutionEnabled":true}` | — | Settings sObjects reject a direct Tooling PATCH (`METADATA_FIELD_UPDATE_ERROR`) — use the metadata-deploy path instead |
149
+
150
+ **Field-name drift by transport (same underlying pref, three spellings) — don't cross them:**
151
+ dispatcher `is…PrefEnable(d)` · MDAPI Settings `enable…` · Tooling sObject `Is…Enabled`.
152
+
153
+ **Net:** A1.2 **read** has a clean CLI-free REST path (Tooling GET, works with no dispatcher);
154
+ A1.2 **write** is REST too but *only* via `/metadata/deployRequest` (or the dispatcher PATCH where
155
+ present) — never a direct Tooling field PATCH.
156
+
157
+ ## Ordering consequence
158
+
159
+ Namespace gate (A1.1 — package **or** org namespace resolves) → RE enablement (A1.2, the effective
160
+ base-provisioning gate) → then anything RE-gated: the RE-flavored install baseline. There is **no
161
+ license read** in this ordering — A1.1 is the namespace gate alone, since installing/using the app
162
+ is not license-gated. If the sync metadata is absent (neither package nor org namespace resolves) or
163
+ RE can't be enabled, the RE-gated steps are reported blocked rather than attempted-and-faked, with
164
+ the owner of the missing piece named.