@salesforce/afv-skills 1.45.0 → 1.47.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 (171) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-observe/SKILL.md +32 -4
  3. package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
  4. package/skills/automation-flow-generate/SKILL.md +11 -5
  5. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
  6. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
  7. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
  8. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
  9. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
  10. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
  11. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
  12. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
  13. package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
  14. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
  15. package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
  16. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
  17. package/skills/dx-code-analyzer-configure/scripts/validate-config.sh +14 -10
  18. package/skills/dx-code-analyzer-run/scripts/apply-fixes.js +45 -4
  19. package/skills/dx-code-analyzer-run/scripts/describe-rule.js +52 -32
  20. package/skills/dx-devops-project-manage/SKILL.md +197 -0
  21. package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
  22. package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
  23. package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
  24. package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
  25. package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
  26. package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
  27. package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
  28. package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
  29. package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
  30. package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
  31. package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
  32. package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
  33. package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
  34. package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
  35. package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
  36. package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
  37. package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
  38. package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
  39. package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
  40. package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
  41. package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
  42. package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
  43. package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
  44. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +3 -5
  45. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
  46. package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
  47. package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
  48. package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
  49. package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
  50. package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
  51. package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
  52. package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
  53. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
  54. package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
  55. package/skills/experience-cms-brand-apply/SKILL.md +5 -5
  56. package/skills/experience-cms-brand-create/SKILL.md +2 -2
  57. package/skills/experience-cms-content-generate/SKILL.md +1 -0
  58. package/skills/experience-cms-content-render/SKILL.md +173 -0
  59. package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
  60. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
  61. package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
  62. package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
  63. package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
  64. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
  65. package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
  66. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
  67. package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
  68. package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
  69. package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
  70. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
  71. package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
  72. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
  73. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
  74. package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
  75. package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
  76. package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
  77. package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
  78. package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
  79. package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
  80. package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
  81. package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
  82. package/skills/experience-cms-content-render/references/package-api.md +106 -0
  83. package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
  84. package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
  85. package/skills/experience-cms-content-render/references/verify.md +49 -0
  86. package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
  87. package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
  88. package/skills/experience-search-coordinate/SKILL.md +198 -0
  89. package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
  90. package/skills/experience-search-coordinate/references/content-route.md +313 -0
  91. package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
  92. package/skills/experience-search-coordinate/references/media-route.md +172 -0
  93. package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
  94. package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
  95. package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
  96. package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
  97. package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
  98. package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
  99. package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
  100. package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
  101. package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
  102. package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
  103. package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
  104. package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
  105. package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
  106. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
  107. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
  108. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
  109. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
  110. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
  111. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
  112. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
  113. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
  114. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
  115. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
  116. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
  117. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
  118. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
  119. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
  120. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
  121. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
  122. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
  123. package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
  124. package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
  125. package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
  126. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
  127. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
  128. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
  129. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
  130. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
  131. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
  132. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
  133. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
  134. package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
  135. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
  136. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
  137. package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
  138. package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
  139. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
  140. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
  141. package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
  142. package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
  143. package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
  144. package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
  145. package/skills/life-sciences-territory-configure/SKILL.md +217 -0
  146. package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
  147. package/skills/platform-apex-logs-debug/SKILL.md +7 -7
  148. package/skills/platform-custom-application-generate/SKILL.md +4 -4
  149. package/skills/platform-custom-object-generate/SKILL.md +7 -7
  150. package/skills/platform-custom-tab-generate/SKILL.md +1 -1
  151. package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
  152. package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
  153. package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
  154. package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
  155. package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
  156. package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
  157. package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
  158. package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
  159. package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
  160. package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
  161. package/skills/platform-flexipage-generate/SKILL.md +4 -0
  162. package/skills/platform-list-view-generate/SKILL.md +1 -0
  163. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
  164. package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
  165. package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
  166. package/skills/platform-soql-query/SKILL.md +8 -8
  167. package/skills/platform-value-set-generate/SKILL.md +2 -2
  168. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
  169. package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
  170. package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
  171. package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
@@ -78,11 +78,10 @@ Generates flow metadata element by element. This step is **mandatory** and must
78
78
  - **result** (STRING): Result of the flow element generation. Contains the final flow metadata **only when `isComplete` is `true`**.
79
79
 
80
80
  **MANDATORY: Loop until complete. NEVER pause or ask the user to confirm continuation.**
81
- - A flow can have **any number of elements** (10, 15, or more). Each call generates one element at a time, so you may need **many** iterations. This is expected and normal.
81
+ - A flow can have **any number of elements**. Each call generates one element at a time, so you may need **many** iterations.
82
82
  - Call `flowElementGeneration` with the `operationId` from Step 2 and `requestSource` (use `"A4V"` for XML output, empty string or other value for JSON).
83
83
  - Check the `isComplete` output and the `result` field after each call.
84
- - If `isComplete` is `false` **and no errors are returned**, you **MUST** call `flowElementGeneration` again with the **same `operationId`** from Step 2. **Do NOT ask the user if they want to continue. Do NOT pause. Do NOT summarize progress mid-loop. Just keep calling.**
85
- - **Do NOT stop** until `isComplete` is `true` **or** the invocable action returns errors. There is **no maximum** number of iterations — keep going regardless of how many calls it takes.
84
+ - If `isComplete` is `false` **and no errors are returned**, you **MUST** call `flowElementGeneration` again with the **same `operationId`** from Step 2. **Do NOT ask the user if they want to continue. Do NOT pause. Do NOT summarize progress mid-loop. Just keep calling** — there is **no maximum** number of iterations.
86
85
  - When `isComplete` is `true`, extract the flow metadata from the `result` field.
87
86
  - If errors are returned, stop the loop and surface the error to the user.
88
87
 
@@ -91,6 +90,8 @@ Generates flow metadata element by element. This step is **mandatory** and must
91
90
  - DO NOT add new nodes, tags, attributes, or text (do not add missing labels, X/Y coordinates, etc.).
92
91
  - DO NOT remove any existing nodes.
93
92
 
93
+ **Canvas mode.** A net-new canvas-based flow needs a `<processMetadataValues>` `CanvasMode`=`AUTO_LAYOUT_CANVAS` entry (independent of `BuilderType`) or it opens in **Free-Form**. STRICT CONSTRAINTS forbid hand-editing the XML, so this must come from the generation pipeline — if the returned XML lacks it, surface a pipeline gap; do NOT hand-edit. (Non-canvas types like `CustomerLifecycle` carry none — don't flag those.)
94
+
94
95
  ## inflightMetadata Format
95
96
  **DATA TYPE: ARRAY (not string)**
96
97
 
@@ -208,6 +209,10 @@ To select records for a scheduled flow, put the record criteria in the **start e
208
209
 
209
210
  When the prompt says "runs once a week / every Sunday / daily at a time", the `userPrompt` should state: scheduled trigger, the frequency, the start time, and the record filter on the triggering object.
210
211
 
212
+ ### "Autolaunched" is literal
213
+
214
+ An **"autolaunched"**/"no-trigger" request builds a **no-trigger** flow (no `triggerType`, `object`, or `<schedule>` on start) — put "for each…/when X"/timing intent in the **body**, not the trigger; add one only if asked.
215
+
211
216
  ### Counting related records ("count all related X", "number of X")
212
217
 
213
218
  To store a count of records, use a **single** assignment element with `<operator>AssignCount</operator>`, assigning from the collection (the record-lookup result) into a Number variable. Do NOT emit two assignment elements for one count, and do NOT give two assignment elements the same `name` — duplicate assignment names, or two assignments doing one logical count, fail deployment. One lookup → one `AssignCount` assignment → one record update.
@@ -299,7 +304,7 @@ Then call Step 2 and Step 3 for this flow.
299
304
  "requestSource": "A4V"
300
305
  }
301
306
  ```
302
- Call repeatedly with the same `operationId` until `isComplete` is `true` or errors are returned. A flow can have any number of elements, so expect multiple iterations. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
307
+ Loop as in Step 3 (same `operationId`, `requestSource: "A4V"`) until `isComplete` is `true` or errors return; extract the XML from `result` then.
303
308
 
304
309
  **Example 2: With custom objects from local sfdx project**
305
310
 
@@ -348,7 +353,7 @@ Call repeatedly with the same `operationId` until `isComplete` is `true` or erro
348
353
  "requestSource": "A4V"
349
354
  }
350
355
  ```
351
- Call repeatedly with the same `operationId` until `isComplete` is `true` or errors are returned. A flow can have any number of elements, so expect multiple iterations. When `isComplete` is `true`, extract the flow metadata from the `result` field. Use `"requestSource": "A4V"` to get flow metadata in XML format.
356
+ Loop exactly as in Example 1's Step 3.
352
357
 
353
358
  ## CRITICAL Verification Checklist (MUST VERIFY BEFORE AND AFTER EVERY FLOW GENERATION)
354
359
 
@@ -361,3 +366,4 @@ Call repeatedly with the same `operationId` until `isComplete` is `true` or erro
361
366
  - [ ] **groundingMetadata** from Step 1 output is passed directly to Step 2 input (already a string — do NOT serialize it again).
362
367
  - [ ] **Step 3** is called in a loop with the same `operationId` from Step 2, `requestSource` always `"A4V"`, until `isComplete` is `true` or errors are returned — no pausing, no asking the user to continue, no matter how many iterations. Extract the XML from `result` only when `isComplete` is `true`.
363
368
  - [ ] **Multi-flow**: Each flow's full pipeline is completed before starting the next, SEQUENTIALLY (never in parallel, never interleaved), and ALL requested flows are generated — do NOT stop after the first.
369
+ - [ ] **Canvas mode (read-only)**: a canvas-based net-new flow's XML carries a `<processMetadataValues>` `CanvasMode`=`AUTO_LAYOUT_CANVAS` entry. If absent, surface a pipeline gap — do NOT hand-edit (STRICT CONSTRAINTS forbid it). Non-canvas types like `CustomerLifecycle` carry none — don't flag those.
@@ -0,0 +1,275 @@
1
+ ---
2
+ name: consumer-goods-promotion-bo-api-deploy
3
+ description: "Use to enable the Consumer Goods Cloud TPM Promotion Business Object (BO) API framework in a Salesforce org, interview the user for the custom Workflow Step they want to add, generate and deploy that Apex class, wire it into the create/update/copy workflows, and prove it end-to-end via headless REST ingest. Ships a worked reference example (SetCommentValue) but the customization is user-supplied per org. Triggers on \"deploy the promotion BO API\", \"set up promotion BO API\", \"headless promotion create\", \"add a BO API workflow step\"."
4
+ metadata:
5
+ version: "1.0"
6
+ domains: ["Consumer Goods"]
7
+ minApiVersion: "60.0"
8
+ cliTools:
9
+ - tool: ["jq"]
10
+ semver: ">=1.6"
11
+ - tool: ["node"]
12
+ semver: ">=20.17.0"
13
+ - tool: ["sf"]
14
+ semver: ">=2.0.0"
15
+ accessCheck:
16
+ - type: license
17
+ value: "CGCRetailAndTPMMgmtPsl"
18
+ ---
19
+
20
+ # Deploy the Promotion BO API
21
+
22
+ ## What this does
23
+
24
+ Turns on the Consumer Goods Cloud TPM Promotion Business Object (BO) API in a
25
+ Salesforce org, interviews the user for **one custom Workflow Step** to add to
26
+ the promotion pipeline, generates and deploys that Apex class, registers it
27
+ against a BO API entity, wires it into the customer's chosen subset of
28
+ `{create, update, copy}` workflows, then proves the whole path end-to-end via
29
+ three REST endpoints:
30
+
31
+ - `POST /services/apexrest/<prefix>/promotions/initialize`
32
+ - `POST /services/apexrest/<prefix>/promotions/ingest`
33
+ - `GET /services/apexrest/<prefix>/promotions/status?importId=...`
34
+
35
+ It is meant for **headless delivery**: point it at an org, answer the interview,
36
+ and the skill installs the step and verifies it. The Promotion BO API ships in
37
+ the CGCloud **managed package** (namespace `cgcloud` released, `cgcloud_dev`
38
+ dev/beta). Every Apex identifier, object/field API name, and REST URL is derived
39
+ from the detected package prefix at runtime — this skill never hard-codes it.
40
+
41
+ `derive` is out of scope here (a follow-up skill covers it). Detailed runbooks
42
+ for the write and smoke phases live under `references/`:
43
+
44
+ - `references/conventions-and-payload-rules.md` — sales-org partitioning, schema-first contract derivation, and the eight cross-workflow payload rules R1–R8.
45
+ - `references/generate-and-wire.md` — Phase 5a (generate + deploy the class) and Phase 5b (register + wire the step), with the full `register-step.apex`.
46
+ - `references/smoke-and-verify.md` — Phase 6 (contract derivation, payload materialization, create/update/copy legs + verification).
47
+ - `references/reference-example-set-comment-value.md` — the shipped `SetCommentValue` preset (assets at `assets/set-comment-value/`).
48
+
49
+ ## Inputs to collect first
50
+
51
+ Ask before starting; do not guess.
52
+
53
+ 1. **Org alias** — the `sf` alias of the target org. Required.
54
+ 2. **Sales org** — required pre-parameter, default `0001`. Must be 4 chars, uppercase (mirrors `TPMSetupData.validateSalesOrg`). **Every** downstream lookup (promotion template, tactic template, anchor account, product filter criteria, SKUs) is scoped to this sales org — see `references/conventions-and-payload-rules.md` ("Sales-org partitioning").
55
+ 3. **Yes to seeding?** — Phase 3a fetches the packaged BO API seed and asks for confirmation before inserting ~232 rows across 8 objects. Offer `--yes` for CI callers.
56
+ 4. **Dry run?** — `--dry-run` runs Phases 1, 2, 3a-preview, the Phase 5 interview, and a validate-only deploy. Writes nothing.
57
+
58
+ What the customization *is* gets collected in Phase 5's interview, once the
59
+ framework state is known.
60
+
61
+ ## Find the namespace first
62
+
63
+ The prefix depends on the installed build — detect it, do not guess. Released
64
+ package → `cgcloud`; dev/beta → `cgcloud_dev`; source-deployed → no prefix.
65
+
66
+ ```bash
67
+ sf data query --target-org <alias> \
68
+ --query "SELECT NamespacePrefix FROM ApexClass WHERE Name = 'TPMSetupData'" --json
69
+ ```
70
+
71
+ Read `records[0].NamespacePrefix` and bind three variables used everywhere below:
72
+
73
+ - `PREFIX_DOT` — Apex references: `cgcloud.` / `cgcloud_dev.` / empty.
74
+ - `PREFIX_UNDER` — object/field API names: `cgcloud__` / `cgcloud_dev__` / empty.
75
+ - `URL_NS` — REST paths: `/cgcloud` / `/cgcloud_dev` / empty.
76
+
77
+ Every Apex call is `${PREFIX_DOT}ClassName.method(...)`; every SOQL object/field is
78
+ `${PREFIX_UNDER}Object__c`; every REST URL is
79
+ `/services/apexrest${URL_NS}/promotions/<endpoint>`. Substitute at run time.
80
+
81
+ ## Phase 1 — Preflight
82
+
83
+ Run these and stop on the first failure.
84
+
85
+ 1. `sf --version`; `node --version` (only if running shipped scripts).
86
+ 2. `sf org display --target-org <alias> --json` — confirm reachable, capture `instanceUrl` + org id (for display/logging only). Every REST call in later phases goes through `sf api request rest`, which uses the CLI's stored session — this skill never extracts the access token.
87
+ 3. Validate the sales org locally: length 4, uppercase, non-blank (mirrors `TPMSetupData.validateSalesOrg`). Fail fast instead of surfacing an Apex stack trace.
88
+ 4. **TPM entitlement (Permission Set License)** — the TPM app is licensed through the **`CGCRetailAndTPMMgmtPsl`** Permission Set License ("CGC Retail and Trade Promotion Management"); there is **no** `TPM App` UserLicense. Check the running user's PSL assignment:
89
+ ```sql
90
+ SELECT Id FROM PermissionSetLicenseAssign
91
+ WHERE Assignee.Username = '<running-user-name>'
92
+ AND PermissionSetLicense.DeveloperName = 'CGCRetailAndTPMMgmtPsl'
93
+ ```
94
+ On scratch/dev-hub the PSL may be legitimately unassigned; detect env with `SELECT OrganizationType, IsSandbox FROM Organization`:
95
+ - `OrganizationType = 'Developer Edition'` OR `IsSandbox = true` and empty → **warn** ("permset gate below is the real check"), continue.
96
+ - Any other org shape and empty → **stop** ("TPM Permission Set License `CGCRetailAndTPMMgmtPsl` not assigned to the running user").
97
+ 5. **Running-user permission set (TPM Admin persona)** — the setup needs a permission set granting the *TPM Master Data Admin* persona. The shipped permset is `TPM_Master_Data_Admin` (packaged; `PermissionSet.Name` is the bare name regardless of namespace), but **customers frequently clone it** under their own name — so treat a miss as "ask", not "fail". Take `<running-user-name>` from `sf org display ... .result.username`:
98
+ ```sql
99
+ SELECT PermissionSet.Name, PermissionSet.Label FROM PermissionSetAssignment
100
+ WHERE Assignee.Username = '<running-user-name>'
101
+ AND (PermissionSet.Name = 'TPM_Master_Data_Admin'
102
+ OR PermissionSet.Label LIKE '%TPM%Master Data Admin%')
103
+ ```
104
+ Match → proceed. No match → **do not hard-fail on the name**: show `sf org assign permset --name TPM_Master_Data_Admin --target-org <alias>` as the default option, but also ask the user to confirm whether they already hold an equivalent (possibly cloned/renamed) permission set for the TPM admin persona. Pause, wait for a cloned-permset confirmation or the assignment, then re-query. Proceed only once admin access is confirmed.
105
+ 6. Namespace discovery (above).
106
+
107
+ ## Phase 2 — Verify BO API framework state
108
+
109
+ Read-only; tells you whether Phase 3 needs to run. All three counts MUST be
110
+ scoped to `<salesOrg>` — the framework rows are sales-org-partitioned.
111
+
112
+ ```bash
113
+ # 1. Workflow rows for this sales org (expect 4: create/update/copy/derive)
114
+ sf data query --target-org <alias> --json --query "
115
+ SELECT Name FROM ${PREFIX_UNDER}BO_API_Workflow__c
116
+ WHERE ${PREFIX_UNDER}BO_API__r.Name = 'Promotion'
117
+ AND ${PREFIX_UNDER}BO_API__r.${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
118
+
119
+ # 2. Step rows for this sales org
120
+ sf data query --target-org <alias> --json --query "
121
+ SELECT COUNT(Id) FROM ${PREFIX_UNDER}BO_API_Workflow_Step__c
122
+ WHERE ${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
123
+
124
+ # 3. Junction rows whose parent workflow belongs to a Promotion BO API for this sales org
125
+ sf data query --target-org <alias> --json --query "
126
+ SELECT COUNT(Id) FROM ${PREFIX_UNDER}BO_API_Workflow_Workflow_Step__c
127
+ WHERE ${PREFIX_UNDER}BO_API_Workflow__r.${PREFIX_UNDER}BO_API__r.Name = 'Promotion'
128
+ AND ${PREFIX_UNDER}BO_API_Workflow__r.${PREFIX_UNDER}BO_API__r.${PREFIX_UNDER}Sales_Org__c = '<salesOrg>'"
129
+ ```
130
+
131
+ - Zero workflow rows → fresh for this sales org, Phase 3 will seed.
132
+ - Full state (4 workflow rows `create/update/copy/derive`; >0 step + junction rows) → Phase 3 is a no-op for this sales org (still run for idempotency; 3a shows zero net-new).
133
+ - Partial state → **error**; stop, report which rows are missing. A human inspects first.
134
+
135
+ ## Phase 3a — Preview the default BO API seed (fetch + confirm)
136
+
137
+ The workflows/steps/junctions/entities/input-structures ship as CSVs inside the
138
+ packaged `TPMSetupData` static resource; `TPMSetupData.setupBOApi` reads and
139
+ upserts them. Before running it, show the user what will land.
140
+
141
+ 1. Locate the resource:
142
+ ```bash
143
+ sf data query --target-org <alias> --json --query "
144
+ SELECT Id, Name, NamespacePrefix, SystemModStamp, BodyLength
145
+ FROM StaticResource WHERE Name = 'TPMSetupData'"
146
+ ```
147
+ Zero rows → stop ("TPMSetupData static resource not found — is the CGCloud package installed?"). Multiple rows → prefer the one whose `NamespacePrefix` matches Phase 1; if prefixes disagree, stop and ask.
148
+ 2. **Retrieve + expand the resource cross-platform via SFDX** — use `sf project retrieve`, which unpacks a zip StaticResource into a folder on every OS (no `curl`, no `unzip` — `unzip` isn't present on Windows by default). Retrieve into a **dedicated `setup-data/` subproject** so the read-only managed resource never mixes into the Phase-5a deploy tree (`./.promotion-bo-api-deploy/force-app`, which is what gets deployed back):
149
+ ```bash
150
+ mkdir -p ./.promotion-bo-api-deploy/setup-data/force-app/main/default
151
+ printf '{ "packageDirectories": [{ "path": "force-app", "default": true }], "sourceApiVersion": "60.0" }' \
152
+ > ./.promotion-bo-api-deploy/setup-data/sfdx-project.json
153
+ ( cd ./.promotion-bo-api-deploy/setup-data && sf project retrieve start \
154
+ --metadata "StaticResource:${PREFIX_UNDER}TPMSetupData" --target-org <alias> --json )
155
+ ```
156
+ Non-success → stop, print `result` errors.
157
+ 3. SFDX expands the zip to `./.promotion-bo-api-deploy/setup-data/force-app/main/default/staticresources/${PREFIX_UNDER}TPMSetupData/BOApi/`. `ls` it. Expected: `0_BO_API__c.csv`, `0_BO_API_Entity__c.csv`, `0_BO_API_Output_Entity__c.csv`, `0_BO_API_Workflow__c.csv`, `0_BO_API_Workflow_Entity__c.csv`, `0_BO_API_Workflow_Step__c.csv`, `0_BO_API_Workflow_Workflow_Step__c.csv`, `0_BO_API_Step_Input_Structure__c.csv`, `import.json`. Any missing → stop, print the delta.
158
+ 4. Confirm each target sObject exists: `sf sobject describe --sobject '${PREFIX_UNDER}BO_API_Workflow__c' --target-org <alias> > /dev/null` (etc.). Any describe failure → stop (package partially installed).
159
+ 5. Show a summary (row counts per object; total ~232 rows across 8 objects; "upsert on Unique_Key__c — re-running is idempotent"), plus the first 3 rows of `0_BO_API_Workflow__c.csv` and `0_BO_API_Workflow_Step__c.csv` with `{{NS}}`/`{{SALES_ORG}}` substituted.
160
+ 6. Wait for confirmation unless `--yes`. "no"/blank → abort clean, point at `./.promotion-bo-api-deploy/setup-data/force-app/main/default/staticresources/${PREFIX_UNDER}TPMSetupData/BOApi/`.
161
+ 7. On `--dry-run` → stop here; do not run 3b.
162
+
163
+ ## Phase 3b — Apply the default seed
164
+
165
+ Only after 3a confirmation.
166
+
167
+ 1. `PHASE3_START=$(date -u +%FT%TZ)`.
168
+ 2. Run metadata-wizard setup via anon Apex: `${PREFIX_DOT}TPMSetupData.setupMetadataWizard('<salesOrg>');` → `sf apex run --target-org <alias> --file ./.promotion-bo-api-deploy/setup-metadata-wizard.apex`.
169
+ 3. Wait for the whole batch chain. `GenericDemoSetupDataBatch` self-chains via `finish()`; polling a single job id misses children. Poll by class:
170
+ ```bash
171
+ sf data query --target-org <alias> --json --query "
172
+ SELECT COUNT() FROM AsyncApexJob
173
+ WHERE ApexClass.Name = 'GenericDemoSetupDataBatch'
174
+ AND CreatedDate >= ${PHASE3_START}
175
+ AND Status NOT IN ('Completed','Failed','Aborted')"
176
+ ```
177
+ Sleep 5s between polls; break when count is zero for **two consecutive** polls (covers the gap between a parent's `finish()` and the child's `AsyncApexJob` row appearing).
178
+ 4. Final check — every child succeeded (`SELECT Id, Status, NumberOfErrors, ExtendedStatus FROM AsyncApexJob WHERE ApexClass.Name = 'GenericDemoSetupDataBatch' AND CreatedDate >= ${PHASE3_START}`). Any `Failed`/`Aborted`/`NumberOfErrors > 0` → stop, print `ExtendedStatus`.
179
+ 5. Repeat 2–4 for `${PREFIX_DOT}TPMSetupData.setupBOApi('<salesOrg>');`.
180
+
181
+ ## Phase 4 — Verify the BO API framework is on
182
+
183
+ Re-run the Phase 2 queries. Expect 4 `BO_API_Workflow__c` rows
184
+ (`create`/`update`/`copy`/`derive`, uniqueness on `Unique_Key__c`), ≥46
185
+ `BO_API_Workflow_Step__c` rows, ≥54 `BO_API_Workflow_Workflow_Step__c` junction
186
+ rows. Anything short → stop, print what's missing. Never claim success while a
187
+ component failed.
188
+
189
+ ## Phase 5 — Interview: what does the user want to customize?
190
+
191
+ The framework is now on. The customization is **user-supplied** — do not skip
192
+ the interview; do not invent an answer. Print:
193
+
194
+ ```text
195
+ The Promotion BO API framework is installed. To add a custom step, tell me:
196
+ - Which BO API entity does it target? (e.g. Promotion or Tactic — I'll list the exact entities registered in your org)
197
+ - Which workflows should it fire in? (create, update, copy)
198
+ - What does it read from the ingest input?
199
+ - What does it write on the SObject or elsewhere?
200
+ - Any preconditions or side effects I should know about?
201
+ If you want a worked example, say "use the SetCommentValue reference" —
202
+ it copies the tactic input Comment onto Tactic.Comment__c.
203
+ ```
204
+
205
+ ### 5.1 — Answers to collect
206
+
207
+ | Field | Required | Notes |
208
+ |---|---|---|
209
+ | `stepName` | yes | PascalCase Apex class name + BO API Workflow Step `Name`. Unique across `ApexClass`. |
210
+ | `entity` | yes | BO API entity `Name` — MUST match a row queried live from `${PREFIX_UNDER}BO_API_Entity__c` (shared, not sales-org-partitioned); do not assume a fixed list. In a current org these include `Promotion`, `Tactic`, `ProductFilter`, `ManualInputs`, `CustomState` (plus the `TPM_Promotion.*` structures). Query the org and offer the actual rows. |
211
+ | `workflows` | yes | Non-empty subset of `{create, update, copy}`. `derive` is out of scope. |
212
+ | `actionName` | yes | Symbolic action string the class receives in `call(String action, ...)`. Convention: lowerCamelCase of `stepName`. |
213
+ | `inputPaths` | yes (≥1) | JSON paths to read from `currentInput`, e.g. `["Comment"]`. |
214
+ | `inputPathType` | yes | JSON type of the input paths — `String`, `Array`, `Boolean`, `Number`, `Object`. Drives the `RecordType` on each SIS row; a wrong value causes `TransformationError: Expected <Type>` at ingest. Different types → run Phase 5b once per type group. |
215
+ | `outputWrites` | yes (≥1) | `{field, source}` pairs; `field` is the target API name **without** the namespace prefix (skill adds `${PREFIX_UNDER}`), `source` references an `inputPaths` value / literal / computed expression. |
216
+ | `description` | yes | One-sentence step description → `${PREFIX_UNDER}Description__c` + class docstring. |
217
+ | `preconditions` | optional | Free-text guards before writing. |
218
+ | `sortAfter` | optional | Named packaged step to sort after; default `max(Sort__c) + 10`. |
219
+
220
+ ### 5.2 — Interview flow
221
+
222
+ 1. **Preset shortcut.** If `--preset set-comment-value` or the user says "use the SetCommentValue reference", load the answers from `references/reference-example-set-comment-value.md` (`assets/set-comment-value/interview-answers.json`) and skip the interactive interview. `--interview-file <path>` loads answers from a JSON file mirroring the 5.1 table (wins over `--preset`).
223
+ 2. **Ask the questions** one at a time (or as a block); do not proceed until every required field has a concrete value.
224
+ 3. **Show the entity's writeable fields** after the user names the entity:
225
+ ```bash
226
+ sf sobject describe --target-org <alias> --sobject '${PREFIX_UNDER}<Entity>__c' --json \
227
+ | jq -r '.fields[] | select(.updateable == true and .createable == true) | .name'
228
+ ```
229
+ Any `outputWrites` field must appear here; otherwise stop and ask for a different one (FLS is caller-scoped).
230
+ 4. **Confirm the plan** (recap Class/Entity/Workflows/Reads/Writes/Sort/Description → "Deploy + wire? [y/N]"). `n`/blank → save answers to `./.promotion-bo-api-deploy/interview.json` and exit. `y` → Phase 5a.
231
+
232
+ ## Phases 5a / 5b — Generate + deploy, register + wire
233
+
234
+ Runbook in `references/generate-and-wire.md`. In short: guard the class name for
235
+ idempotency; confirm target-field writeability; generate a namespace-agnostic
236
+ `Callable` from the interview (reads `currentInput`/`currentOutput`, derives the
237
+ prefix from `getSObjectType()` at runtime); show + confirm; `sf project deploy
238
+ start`. Then, in one anon-Apex transaction, upsert one `BO_API_Workflow_Step__c`
239
+ (bare class name in `Classname__c`), one `BO_API_Workflow_Workflow_Step__c`
240
+ junction per chosen workflow (never assume all three; `derive` dropped), and the
241
+ `BO_API_Step_Input_Structure__c` rows (RecordType per `inputPathType`). Both
242
+ upserts key on `Unique_Key__c` (idempotent).
243
+
244
+ ## Phase 6 — Smoke test through the REST endpoints
245
+
246
+ Runbook in `references/smoke-and-verify.md`. Derive the payload contract per
247
+ invoked workflow from `BO_API_Step_Input_Structure__c` (schema-first), apply
248
+ rules R1–R8 from `references/conventions-and-payload-rules.md`, resolve every
249
+ reference in-sales-org, materialize `./out/smoke/{create,update,copy}.json`, then
250
+ run `initialize → ingest → poll status` for each chosen workflow. Verify via a
251
+ direct `BO_API_Transaction_Log__c` query (any row `!= 'Calculated'` → fail) plus
252
+ an `outputWrites` field assertion against `./out/smoke/expected.json`. Update and
253
+ copy legs run only if the interview included them.
254
+
255
+ ## Phase 7 — Report
256
+
257
+ Short status: org, sales org, dry-run flag; namespace prefix; BO API seed rows
258
+ before/after (Phases 2 & 4); interview (preset name or resolved `stepName`,
259
+ `entity`, `workflows`, `inputPaths`, `outputWrites`); class deploy result +
260
+ workflow-step id + junction ids (1–3); smoke import ids per invoked workflow, all
261
+ `Calculated` with matching write assertions — or exactly which failed (SOQL,
262
+ expected, actual); manual follow-ups (`none` if clean).
263
+
264
+ ## Rules
265
+
266
+ - Never claim success while any `BO_API_Transaction_Log__c` row for the smoke import ids has `Status__c != 'Calculated'`. `Calculated` is the terminal success state (R2); `Processed` does NOT exist in the picklist.
267
+ - Never claim success while any `outputWrites` assertion fails, or while any `GenericDemoSetupDataBatch` `AsyncApexJob` since the phase start is still running or failed.
268
+ - Stop on the first failed preflight or verify check; report the exact failure.
269
+ - Never hard-code the namespace prefix — compose every Apex/SOQL/REST reference from the Phase-1 detection.
270
+ - Never invent Apex method signatures — use what the packaged `TPMSetupData` / `BO_API_*` objects and the shipping REST endpoints declare.
271
+ - The seeded BO API metadata is packaged content — preview it and get user confirmation before invoking `TPMSetupData.setupBOApi`.
272
+ - Never invent an interview answer. Absent a user and any `--preset`/`--interview-file`, stop and print the questions.
273
+ - Never wire a workflow the interview did not include. `create`/`update`/`copy` are individually opt-in; `derive` is always excluded.
274
+ - Never compose a smoke payload from a hard-coded template — derive accepted paths per workflow from `BO_API_Step_Input_Structure__c` and validate user input against that contract first.
275
+ - Never resolve a reference value without the sales-org filter. A record under a different sales org is not valid for this invocation.
@@ -0,0 +1,32 @@
1
+ # preset: `set-comment-value`
2
+
3
+ Worked reference example for the `consumer-goods-promotion-bo-api-deploy` skill. The
4
+ skill's default flow interviews the user for the customization; this
5
+ preset short-circuits the interview with a pre-canned answer set.
6
+
7
+ Invoke with:
8
+
9
+ ```text
10
+ --preset set-comment-value
11
+ ```
12
+
13
+ or, during the interview, tell the skill "use the SetCommentValue
14
+ reference".
15
+
16
+ ## What it does
17
+
18
+ Copies the top-level `Comment` value from a tactic ingest payload onto
19
+ `Tactic__c.Comment__c` on the resulting Tactic record. Wired into all
20
+ three workflows (`create`, `update`, `copy`).
21
+
22
+ ## Files
23
+
24
+ - `interview-answers.json` — Phase 5 interview answers the skill loads
25
+ when this preset is chosen. Also a starting template for customers who
26
+ want to hand-edit before running `--interview-file`.
27
+ - `SetCommentValue.cls` + `.cls-meta.xml` — the class the skill deploys.
28
+ Namespace-agnostic: the tactic-namespace prefix is derived from
29
+ `SObject.getSObjectType()` at runtime.
30
+ - `payloads/create.json`, `update.json`, `copy.json` — Phase 6 smoke-test
31
+ fixtures with `REPLACED-AT-RUNTIME` markers for the ids captured
32
+ during the run.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Custom BO API Workflow Step: lifts a top-level `Comment` value out of
3
+ * the ingest tactic input onto Tactic.Comment__c.
4
+ *
5
+ * Registered against the `Tactic` BO API entity and wired into the
6
+ * `create`, `update`, and `copy` workflows by the
7
+ * deploy-promotion-bo-api skill.
8
+ *
9
+ * Namespace-agnostic: derives the Tactic namespace from the passed
10
+ * SObject at runtime and never types `<ns>__Tactic__c` at compile time,
11
+ * so the same class works under `cgcloud`, `cgcloud_dev`, and
12
+ * source-deployed (no prefix) orgs.
13
+ *
14
+ * Context keys `currentInput` / `currentOutput` are seeded by the
15
+ * packaged BoTransformationEngine (verified at
16
+ * force-app/main/default/classes/BoTransformationEngine.cls:825-864).
17
+ */
18
+ global with sharing class SetCommentValue implements Callable {
19
+
20
+ public class SetCommentValueException extends Exception {}
21
+
22
+ // MUST be global: on a managed-INSTALLED org the packaged CallableManager
23
+ // resolves/invokes this subscriber Callable across the namespace boundary via
24
+ // Type.forName. A `public` class is invisible there and fails at runtime with
25
+ // `Type "<class>" does not exist` (the ATL row lands in Error). `global` works
26
+ // on both managed-install and same-namespace source/dev orgs.
27
+ global Object call(String action, Map<String, Object> context) {
28
+ if (action != 'setCommentValue') {
29
+ throw new SetCommentValueException('Unsupported action: ' + action);
30
+ }
31
+ if (context == null) {
32
+ throw new SetCommentValueException('null context');
33
+ }
34
+
35
+ Object rawInput = context.get('currentInput');
36
+ Object rawOutput = context.get('currentOutput');
37
+
38
+ if (!(rawInput instanceof Map<String, Object>)) {
39
+ return null;
40
+ }
41
+ Map<String, Object> tacticInput = (Map<String, Object>) rawInput;
42
+
43
+ Object commentValue = tacticInput.get('Comment');
44
+ if (commentValue == null) {
45
+ return null;
46
+ }
47
+
48
+ // The engine wraps Tactic__c in TPM_Promotion.TacticRecord (see
49
+ // BoTransformationEngineEntityTree.generateObject:696 —
50
+ // promo.addTactic(new Tactic__c()) returns a TacticRecord wrapper).
51
+ // Unwrap to the underlying SObject via getRecord() before writing;
52
+ // a raw SObject cast fails silently and the write never persists.
53
+ SObject tactic;
54
+ if (rawOutput instanceof SObject) {
55
+ tactic = (SObject) rawOutput;
56
+ } else if (rawOutput instanceof TPM_Promotion.Record) {
57
+ tactic = ((TPM_Promotion.Record) rawOutput).getRecord();
58
+ } else {
59
+ return null;
60
+ }
61
+
62
+ String tacticType = String.valueOf(tactic.getSObjectType());
63
+ String prefix = '';
64
+ if (tacticType.contains('__') && tacticType.endsWith('__c')) {
65
+ Integer firstUnderscoreUnderscore = tacticType.indexOf('__');
66
+ Integer lastUnderscoreUnderscore = tacticType.lastIndexOf('__');
67
+ if (firstUnderscoreUnderscore < lastUnderscoreUnderscore) {
68
+ prefix = tacticType.substring(0, firstUnderscoreUnderscore) + '__';
69
+ }
70
+ }
71
+
72
+ tactic.put(prefix + 'Comment__c', String.valueOf(commentValue));
73
+ return null;
74
+ }
75
+ }
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
3
+ <apiVersion>60.0</apiVersion>
4
+ <status>Active</status>
5
+ </ApexClass>
@@ -0,0 +1,13 @@
1
+ {
2
+ "stepName": "SetCommentValue",
3
+ "entity": "Tactic",
4
+ "workflows": ["create", "update", "copy"],
5
+ "actionName": "setCommentValue",
6
+ "inputPaths": ["Comment"],
7
+ "outputWrites": [
8
+ { "field": "Comment__c", "source": "Comment" }
9
+ ],
10
+ "description": "Copies the tactic input Comment to Tactic.Comment__c.",
11
+ "preconditions": null,
12
+ "sortAfter": null
13
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "importId": "REPLACED-AT-RUNTIME",
3
+ "workflow": "copy",
4
+ "salesOrg": "0001",
5
+ "promotions": [
6
+ {
7
+ "SourceId": "REPLACED-AT-RUNTIME-PROMOTION-ID"
8
+ }
9
+ ]
10
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "importId": "REPLACED-AT-RUNTIME",
3
+ "workflow": "create",
4
+ "salesOrg": "0001",
5
+ "promotions": [
6
+ {
7
+ "AnchorAccount": "Safeway_South",
8
+ "PromotionTemplate": "Regional Promotion",
9
+ "DateFrom": "2026-08-01",
10
+ "DateThru": "2026-08-31",
11
+ "Slogan": "Skill smoke create",
12
+ "Tactics": [
13
+ {
14
+ "TacticTemplate": "PrmDisplay",
15
+ "Comment": "Set by SetCommentValue skill test"
16
+ }
17
+ ]
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "importId": "REPLACED-AT-RUNTIME",
3
+ "workflow": "update",
4
+ "salesOrg": "0001",
5
+ "promotions": [
6
+ {
7
+ "Id": "REPLACED-AT-RUNTIME-PROMOTION-ID",
8
+ "Tactics": [
9
+ {
10
+ "Id": "REPLACED-AT-RUNTIME-TACTIC-ID",
11
+ "Comment": "Updated by SetCommentValue skill test"
12
+ }
13
+ ]
14
+ }
15
+ ]
16
+ }