@salesforce/afv-skills 1.65.0 → 1.65.1

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 (349) hide show
  1. package/package.json +1 -1
  2. package/skills/field-service-data-capture-form-deployer-configure/SKILL.md +2 -23
  3. package/skills/field-service-data-capture-form-designer-configure/SKILL.md +3 -13
  4. package/skills/field-service-mobile-branding-configure/SKILL.md +3 -17
  5. package/skills/field-service-objective-designer-configure/SKILL.md +379 -72
  6. package/skills/field-service-prework-brief-deployer-configure/SKILL.md +537 -10
  7. package/skills/field-service-scheduling-policy-designer-query/SKILL.md +1093 -65
  8. package/skills/field-service-sobject-create-configure/SKILL.md +213 -13
  9. package/skills/field-service-voice-to-form-configure/SKILL.md +11 -40
  10. package/skills/field-service-work-rule-designer-configure/SKILL.md +95 -121
  11. package/skills/agentforce-gpt-email-generation-configure/SKILL.md +0 -312
  12. package/skills/agentforce-wizard-ui-forecasting-analytics-components-configure/SKILL.md +0 -64
  13. package/skills/agentforce-wizard-ui-sales-einstein-components-configure/SKILL.md +0 -46
  14. package/skills/automation-company-profile-info-query/SKILL.md +0 -51
  15. package/skills/automation-digital-wallet-configure/SKILL.md +0 -689
  16. package/skills/automation-flow-coordinate/SKILL.md +0 -95
  17. package/skills/automation-flow-coordinate/references/elements/action-call/action-call.md +0 -1569
  18. package/skills/automation-flow-coordinate/references/elements/action-call/conversation-messaging.md +0 -96
  19. package/skills/automation-flow-coordinate/references/elements/action-call/examples/send-sms-message.json +0 -23
  20. package/skills/automation-flow-coordinate/references/elements/action-call/examples/send-to-journey-no-fields.json +0 -31
  21. package/skills/automation-flow-coordinate/references/elements/action-call/send-to-journey.md +0 -179
  22. package/skills/automation-flow-coordinate/references/elements/exit-rule/examples/event-email-link-click.json +0 -13
  23. package/skills/automation-flow-coordinate/references/elements/exit-rule/exit-rule.md +0 -83
  24. package/skills/automation-flow-coordinate/references/elements/forward-to-bot-or-agent/examples/forward-to-bot-or-agent.json +0 -69
  25. package/skills/automation-flow-coordinate/references/elements/forward-to-bot-or-agent/forward-to-bot-or-agent.md +0 -274
  26. package/skills/automation-flow-coordinate/references/elements/start/audience-common.md +0 -192
  27. package/skills/automation-flow-coordinate/references/elements/start/automation-event-triggered.md +0 -130
  28. package/skills/automation-flow-coordinate/references/elements/start/data-cloud-data-change-triggered.md +0 -92
  29. package/skills/automation-flow-coordinate/references/elements/start/form-submission-triggered.md +0 -88
  30. package/skills/automation-flow-coordinate/references/elements/start/segment-triggered.md +0 -152
  31. package/skills/automation-flow-coordinate/references/elements/start/start.md +0 -539
  32. package/skills/automation-flow-coordinate/references/elements/subflow/subflow.md +0 -152
  33. package/skills/automation-flow-coordinate/references/elements/wait/wait.md +0 -497
  34. package/skills/automation-flow-coordinate/references/recipes/author/author.md +0 -868
  35. package/skills/automation-flow-coordinate/references/recipes/summarize/element-generic.md +0 -300
  36. package/skills/automation-flow-coordinate/references/recipes/summarize/reference-resolution.md +0 -292
  37. package/skills/automation-flow-coordinate/references/recipes/summarize/summarize.md +0 -323
  38. package/skills/automation-flow-coordinate/references/recipes/testing/examples/debug-output-template.md +0 -109
  39. package/skills/automation-flow-coordinate/references/recipes/testing/shape.md +0 -219
  40. package/skills/automation-flow-coordinate/references/recipes/testing/shared-stages/intent-analyzer.md +0 -180
  41. package/skills/automation-flow-coordinate/references/recipes/testing/workflows/debug.md +0 -175
  42. package/skills/automation-flow-coordinate/references/recipes/testing/workflows/promote.md +0 -255
  43. package/skills/automation-flow-coordinate/references/recipes/testing/workflows/save-debug.md +0 -212
  44. package/skills/automation-flow-coordinate/references/resources/variable/variable.md +0 -171
  45. package/skills/automation-flow-coordinate/references/shared/anti-patterns.md +0 -786
  46. package/skills/automation-flow-coordinate/references/shared/end/end.md +0 -267
  47. package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/eval/README.md +0 -69
  48. package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/eval/predictions.jsonl +0 -80
  49. package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/eval/prompts.jsonl +0 -116
  50. package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/flow-type-resolver.md +0 -321
  51. package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/taxonomy.json +0 -320
  52. package/skills/automation-flow-coordinate/references/shared/server-functions.md +0 -597
  53. package/skills/automotive-cloud-appraisal-management-settings-configure/SKILL.md +0 -69
  54. package/skills/automotive-cloud-dealer-essentials-configure/SKILL.md +0 -78
  55. package/skills/automotive-cloud-ground-agent-data-pipelines-configure/SKILL.md +0 -373
  56. package/skills/automotive-cloud-ground-mfg-agent-data-pipelines-configure/SKILL.md +0 -273
  57. package/skills/automotive-cloud-mfg-agent-data-pipelines-configure/SKILL.md +0 -259
  58. package/skills/automotive-cloud-mfg-sales-concierge-agent-create/SKILL.md +0 -369
  59. package/skills/automotive-cloud-mfg-sales-concierge-foundation-configure/SKILL.md +0 -542
  60. package/skills/automotive-cloud-mfg-sales-concierge-foundation-coordinate/SKILL.md +0 -159
  61. package/skills/automotive-cloud-mfg-sales-concierge-orchestrator-agent-create/SKILL.md +0 -349
  62. package/skills/automotive-cloud-mfg-sales-concierge-orchestrator-configure/SKILL.md +0 -505
  63. package/skills/automotive-cloud-sales-concierge-agent-create/SKILL.md +0 -380
  64. package/skills/automotive-cloud-sales-concierge-foundation-configure/SKILL.md +0 -567
  65. package/skills/automotive-cloud-sales-concierge-foundation-coordinate/SKILL.md +0 -159
  66. package/skills/automotive-cloud-sales-concierge-orchestrator-agent-create/SKILL.md +0 -360
  67. package/skills/automotive-cloud-sales-concierge-orchestrator-configure/SKILL.md +0 -539
  68. package/skills/automotive-cloud-sales-concierge-search-grounding-configure/SKILL.md +0 -358
  69. package/skills/commerce-b2b-merchant-quote-configure/SKILL.md +0 -602
  70. package/skills/commerce-b2b-store-configure/SKILL.md +0 -251
  71. package/skills/commerce-credit-memo-create/SKILL.md +0 -139
  72. package/skills/commerce-delivery-business-hours-configure/SKILL.md +0 -277
  73. package/skills/commerce-delivery-estimation-apply/SKILL.md +0 -155
  74. package/skills/commerce-delivery-estimation-configure/SKILL.md +0 -377
  75. package/skills/commerce-delivery-routing-specs-integrate/SKILL.md +0 -174
  76. package/skills/commerce-estimate-bopis-query/SKILL.md +0 -279
  77. package/skills/commerce-estimate-delivery-date-query/SKILL.md +0 -349
  78. package/skills/commerce-fees-apply/SKILL.md +0 -227
  79. package/skills/commerce-fulfillment-routing-analyze/SKILL.md +0 -200
  80. package/skills/commerce-guest-buyer-create/SKILL.md +0 -173
  81. package/skills/commerce-location-bopis-configure/SKILL.md +0 -378
  82. package/skills/commerce-location-business-hours-configure/SKILL.md +0 -235
  83. package/skills/commerce-location-delivery-processing-time-configure/SKILL.md +0 -309
  84. package/skills/commerce-location-graph-integrate/SKILL.md +0 -131
  85. package/skills/commerce-location-shipping-carrier-method-configure/SKILL.md +0 -755
  86. package/skills/commerce-location-temporary-operating-hours-configure/SKILL.md +0 -331
  87. package/skills/commerce-merchant-buyer-groups-configure/SKILL.md +0 -579
  88. package/skills/commerce-merchant-buyer-user-onboarding-create/SKILL.md +0 -626
  89. package/skills/commerce-merchant-catalog-configure/SKILL.md +0 -614
  90. package/skills/commerce-merchant-extension-provider-configure/SKILL.md +0 -368
  91. package/skills/commerce-merchant-insights-account-health-churn-analyze/SKILL.md +0 -118
  92. package/skills/commerce-merchant-insights-inventory-returns-analyze/SKILL.md +0 -65
  93. package/skills/commerce-merchant-insights-order-shopper-get/SKILL.md +0 -90
  94. package/skills/commerce-merchant-insights-product-performance-analyze/SKILL.md +0 -72
  95. package/skills/commerce-merchant-insights-query/SKILL.md +0 -279
  96. package/skills/commerce-merchant-storefront-guest-access-configure/SKILL.md +0 -656
  97. package/skills/commerce-order-summary-change-apply/SKILL.md +0 -375
  98. package/skills/commerce-product-details-get/SKILL.md +0 -363
  99. package/skills/commerce-product-search/SKILL.md +0 -563
  100. package/skills/commerce-react-storefront-create/SKILL.md +0 -350
  101. package/skills/commerce-return-order-summary-create/SKILL.md +0 -513
  102. package/skills/commerce-search-relevance-analyze/SKILL.md +0 -757
  103. package/skills/commerce-temporary-processing-time-override-create/SKILL.md +0 -274
  104. package/skills/communications-initial-setup-configure/SKILL.md +0 -630
  105. package/skills/communications-multisite-configure/SKILL.md +0 -571
  106. package/skills/communications-revenue-cloud-settings-configure/SKILL.md +0 -74
  107. package/skills/communications-smb-fiber-catalog-create/SKILL.md +0 -413
  108. package/skills/communications-tmf-integrate/SKILL.md +0 -1368
  109. package/skills/consumer-goods-visit-activity-act4-questions-assign/SKILL.md +0 -448
  110. package/skills/consumer-goods-visit-activity-act5-standard-create/SKILL.md +0 -269
  111. package/skills/consumer-goods-visit-activity-act6-assortment-configure/SKILL.md +0 -271
  112. package/skills/consumer-goods-visit-activity-act7-account-scoping-create/SKILL.md +0 -382
  113. package/skills/consumer-goods-visit-activity-act8-plan-attach-create/SKILL.md +0 -373
  114. package/skills/consumer-goods-visit-activity-act9-proof-validate/SKILL.md +0 -389
  115. package/skills/consumer-goods-visit-and-activity-act1-specification-create/SKILL.md +0 -338
  116. package/skills/consumer-goods-visit-and-activity-act2-template-create/SKILL.md +0 -547
  117. package/skills/consumer-goods-visit-and-activity-act3-definition-create/SKILL.md +0 -346
  118. package/skills/consumer-goods-visit-and-activity-configure/SKILL.md +0 -1110
  119. package/skills/crm-analytics-adoption-insights-configure/SKILL.md +0 -224
  120. package/skills/crm-analytics-custom-report-type-lightning-configure/SKILL.md +0 -418
  121. package/skills/crm-analytics-dashboards-configure/SKILL.md +0 -731
  122. package/skills/crm-analytics-operational-analytics-metadata-bridge-migrate/SKILL.md +0 -233
  123. package/skills/crm-analytics-report-notifications-ui-configure/SKILL.md +0 -184
  124. package/skills/crm-analytics-sharing-inheritance-coverage-analyze/SKILL.md +0 -167
  125. package/skills/crm-analytics-sonic-getting-started-configure/SKILL.md +0 -55
  126. package/skills/crm-analytics-tableau-next-app-deploy/SKILL.md +0 -1350
  127. package/skills/crm-analytics-tableau-next-c360-basemodel-validate/SKILL.md +0 -1862
  128. package/skills/crm-analytics-tableau-next-datacloud-validate/SKILL.md +0 -83
  129. package/skills/crm-analytics-tableau-next-provisioning-validate/SKILL.md +0 -81
  130. package/skills/data360-activation-create/SKILL.md +0 -104
  131. package/skills/data360-activation-external-platform-get/SKILL.md +0 -49
  132. package/skills/data360-activation-target-create/SKILL.md +0 -93
  133. package/skills/data360-activation-v2-create/SKILL.md +0 -456
  134. package/skills/data360-audience-dmo-get/SKILL.md +0 -38
  135. package/skills/data360-calculated-affinity-create/SKILL.md +0 -82
  136. package/skills/data360-calculated-insight-create/SKILL.md +0 -148
  137. package/skills/data360-catalog-metadata-create/SKILL.md +0 -183
  138. package/skills/data360-code-extension-create/SKILL.md +0 -115
  139. package/skills/data360-communication-capping-create/SKILL.md +0 -227
  140. package/skills/data360-connection-create/SKILL.md +0 -491
  141. package/skills/data360-content-lens-create/SKILL.md +0 -181
  142. package/skills/data360-data-action-create/SKILL.md +0 -126
  143. package/skills/data360-data-action-target-create/SKILL.md +0 -181
  144. package/skills/data360-data-clean-room-create/SKILL.md +0 -238
  145. package/skills/data360-data-governance-create/SKILL.md +0 -746
  146. package/skills/data360-data-graph-create/SKILL.md +0 -82
  147. package/skills/data360-data-harmonized-model-object-reference-create/SKILL.md +0 -38
  148. package/skills/data360-data-lake-object-create/SKILL.md +0 -207
  149. package/skills/data360-data-model-object-catalog-get/SKILL.md +0 -49
  150. package/skills/data360-data-model-object-create/SKILL.md +0 -128
  151. package/skills/data360-data-model-object-mapping-create/SKILL.md +0 -106
  152. package/skills/data360-data-space-create/SKILL.md +0 -161
  153. package/skills/data360-data-streams-create/SKILL.md +0 -324
  154. package/skills/data360-engagement-signal-get/SKILL.md +0 -38
  155. package/skills/data360-experiment-create/SKILL.md +0 -82
  156. package/skills/data360-goal-based-campaign-create/SKILL.md +0 -192
  157. package/skills/data360-harmonization-process-history-get/SKILL.md +0 -38
  158. package/skills/data360-harmonized-unstructured-dmo-create/SKILL.md +0 -84
  159. package/skills/data360-identity-resolution-create/SKILL.md +0 -115
  160. package/skills/data360-intelligent-document-processing-create/SKILL.md +0 -148
  161. package/skills/data360-knowledge-graph-construction-create/SKILL.md +0 -93
  162. package/skills/data360-knowledge-graph-create/SKILL.md +0 -216
  163. package/skills/data360-knowledge-space-mcp-configure/SKILL.md +0 -475
  164. package/skills/data360-machine-learning-mcp-configure/SKILL.md +0 -697
  165. package/skills/data360-multi-org-mcp-configure/SKILL.md +0 -117
  166. package/skills/data360-secondary-index-mcp-configure/SKILL.md +0 -126
  167. package/skills/data360-segment-mcp-configure/SKILL.md +0 -159
  168. package/skills/data360-semantic-calculated-dimension-mcp-configure/SKILL.md +0 -93
  169. package/skills/data360-semantic-calculated-measurement-mcp-configure/SKILL.md +0 -93
  170. package/skills/data360-semantic-data-object-mcp-configure/SKILL.md +0 -93
  171. package/skills/data360-semantic-dimension-mcp-configure/SKILL.md +0 -38
  172. package/skills/data360-semantic-engine-mcp-configure/SKILL.md +0 -62
  173. package/skills/data360-semantic-measurement-mcp-configure/SKILL.md +0 -38
  174. package/skills/data360-semantic-metric-mcp-configure/SKILL.md +0 -93
  175. package/skills/data360-semantic-model-mcp-configure/SKILL.md +0 -115
  176. package/skills/data360-standard-mapping-mcp-configure/SKILL.md +0 -38
  177. package/skills/data360-tenant-lifecycle-mcp-configure/SKILL.md +0 -73
  178. package/skills/experience-afv-query/SKILL.md +0 -234
  179. package/skills/experience-agentforce-vibes-in-react-configure/SKILL.md +0 -63
  180. package/skills/experience-apex-components-configure/SKILL.md +0 -69
  181. package/skills/experience-apex-pages-configure/SKILL.md +0 -107
  182. package/skills/experience-app-home-configure/SKILL.md +0 -76
  183. package/skills/experience-aura-lightning-out-access-configure/SKILL.md +0 -58
  184. package/skills/experience-briefcase-configure/SKILL.md +0 -287
  185. package/skills/experience-community-flexi-page-list-create/SKILL.md +0 -74
  186. package/skills/experience-community-template-definition-list-query/SKILL.md +0 -48
  187. package/skills/experience-community-theme-definition-list-query/SKILL.md +0 -38
  188. package/skills/experience-content-assets-configure/SKILL.md +0 -86
  189. package/skills/experience-custom-domain-create/SKILL.md +0 -727
  190. package/skills/experience-defer-rendering-workspace-page-setup-configure/SKILL.md +0 -41
  191. package/skills/experience-field-service-app-builder-configure/SKILL.md +0 -187
  192. package/skills/experience-flow-category-configure/SKILL.md +0 -117
  193. package/skills/experience-formula-configure/SKILL.md +0 -358
  194. package/skills/experience-global-actions-configure/SKILL.md +0 -69
  195. package/skills/experience-global-publisher-layouts-configure/SKILL.md +0 -79
  196. package/skills/experience-instrumentation-configure/SKILL.md +0 -64
  197. package/skills/experience-lex-engagement-configure/SKILL.md +0 -50
  198. package/skills/experience-lightning-bolt-open/SKILL.md +0 -12
  199. package/skills/experience-lightning-out-configure/SKILL.md +0 -129
  200. package/skills/experience-lightning-preview-configure/SKILL.md +0 -67
  201. package/skills/experience-lightning-types-query/SKILL.md +0 -98
  202. package/skills/experience-malware-scan-configure/SKILL.md +0 -71
  203. package/skills/experience-my-salesforce-configure/SKILL.md +0 -126
  204. package/skills/experience-navigation-menus-configure/SKILL.md +0 -235
  205. package/skills/experience-notification-types-configure/SKILL.md +0 -206
  206. package/skills/experience-notifications-configure/SKILL.md +0 -75
  207. package/skills/experience-record-discovery-query/SKILL.md +0 -227
  208. package/skills/experience-regenerate-previews-open/SKILL.md +0 -94
  209. package/skills/experience-sales-cloud-mab-configure/SKILL.md +0 -254
  210. package/skills/experience-schema-builder-open/SKILL.md +0 -38
  211. package/skills/experience-simple-record-home-configure/SKILL.md +0 -110
  212. package/skills/experience-static-resources-configure/SKILL.md +0 -74
  213. package/skills/field-service-setup-coordinate/SKILL.md +0 -62
  214. package/skills/field-service-work-plan-configure/SKILL.md +0 -253
  215. package/skills/fsc-actionable-segmentation-settings-configure/SKILL.md +0 -180
  216. package/skills/fsc-digital-origination-bre-decision-matrix-foundation-create/SKILL.md +0 -196
  217. package/skills/fsc-digital-origination-bre-disclosures-consent-configure/SKILL.md +0 -106
  218. package/skills/fsc-digital-origination-bre-document-upload-configure/SKILL.md +0 -96
  219. package/skills/fsc-digital-origination-bre-permissions-assign/SKILL.md +0 -111
  220. package/skills/fsc-digital-origination-bre-setup-coordinate/SKILL.md +0 -250
  221. package/skills/fsc-digital-origination-bre-stp-eligibility-configure/SKILL.md +0 -122
  222. package/skills/fsc-digital-origination-coordinate/SKILL.md +0 -653
  223. package/skills/fsc-digital-origination-demo-run/SKILL.md +0 -225
  224. package/skills/fsc-digital-origination-execution-guardrails-validate/SKILL.md +0 -112
  225. package/skills/fsc-digital-origination-initial-setup-configure/SKILL.md +0 -396
  226. package/skills/fsc-digital-origination-pricing-setup-coordinate/SKILL.md +0 -336
  227. package/skills/fsc-digital-origination-pricing-setup-data-foundation-create/SKILL.md +0 -208
  228. package/skills/fsc-digital-origination-pricing-setup-procedure-create/SKILL.md +0 -375
  229. package/skills/fsc-digital-origination-product-setup-act1-permissions-assign/SKILL.md +0 -83
  230. package/skills/fsc-digital-origination-product-setup-coordinate/SKILL.md +0 -248
  231. package/skills/fsc-digital-origination-sample-data-create/SKILL.md +0 -206
  232. package/skills/fsc-document-checklist-settings-configure/SKILL.md +0 -57
  233. package/skills/fsc-flexible-hierarchy-configure/SKILL.md +0 -148
  234. package/skills/fsc-flexible-hierarchy-foundation-configure/SKILL.md +0 -1430
  235. package/skills/fsc-flexible-hierarchy-graph-config-create/SKILL.md +0 -471
  236. package/skills/fsc-flexible-hierarchy-rule-based-graph-deploy/SKILL.md +0 -429
  237. package/skills/fsc-pricing-product-configurator-configure/SKILL.md +0 -150
  238. package/skills/fsc-pricing-salesforce-omnistudio-switch/SKILL.md +0 -123
  239. package/skills/fsc-product-act2-catalog-categories-create/SKILL.md +0 -82
  240. package/skills/fsc-product-act3-attributes-create/SKILL.md +0 -249
  241. package/skills/fsc-product-act4-classification-assignment-create/SKILL.md +0 -196
  242. package/skills/fsc-product-act5-considerations-get/SKILL.md +0 -44
  243. package/skills/fsc-stage-management-apex-flow-deploy/SKILL.md +0 -384
  244. package/skills/fsc-stage-management-compliant-data-sharing-configure/SKILL.md +0 -144
  245. package/skills/fsc-stage-management-decision-matrices-create/SKILL.md +0 -107
  246. package/skills/fsc-stage-management-definition-assignments-create/SKILL.md +0 -189
  247. package/skills/fsc-stage-management-definition-create/SKILL.md +0 -108
  248. package/skills/fsc-stage-management-permissions-assign/SKILL.md +0 -143
  249. package/skills/fsc-stage-management-transitions-configure/SKILL.md +0 -139
  250. package/skills/health-cloud-appt-booking-config-configure/SKILL.md +0 -135
  251. package/skills/health-cloud-appt-booking-url-config-configure/SKILL.md +0 -134
  252. package/skills/health-cloud-care-provider-affl-role-config-configure/SKILL.md +0 -136
  253. package/skills/health-cloud-care-provider-search-config-configure/SKILL.md +0 -134
  254. package/skills/health-cloud-care-requests-create/SKILL.md +0 -178
  255. package/skills/health-cloud-initial-configure/SKILL.md +0 -511
  256. package/skills/health-cloud-patient-appointment-scheduling-configure/SKILL.md +0 -1374
  257. package/skills/health-cloud-provider-search-settings-configure/SKILL.md +0 -924
  258. package/skills/health-cloud-referral-management-coordinate/SKILL.md +0 -513
  259. package/skills/health-cloud-timeline-configure/SKILL.md +0 -622
  260. package/skills/health-cloud-virtual-visit-config-configure/SKILL.md +0 -135
  261. package/skills/industries-account-score-settings-query/SKILL.md +0 -78
  262. package/skills/industries-actionable-list-data-sources-dmo-configure/SKILL.md +0 -177
  263. package/skills/industries-ad-product-availability-search/SKILL.md +0 -399
  264. package/skills/industries-advanced-account-forecast-settings-configure/SKILL.md +0 -628
  265. package/skills/industries-advanced-therapy-management-settings-configure/SKILL.md +0 -73
  266. package/skills/industries-atm-settings-starter-configure/SKILL.md +0 -76
  267. package/skills/industries-batch-document-ai-configure/SKILL.md +0 -196
  268. package/skills/industries-behavioral-health-app-settings-configure/SKILL.md +0 -71
  269. package/skills/industries-branch-management-settings-configure/SKILL.md +0 -89
  270. package/skills/industries-bre-decision-table-access-configure/SKILL.md +0 -367
  271. package/skills/industries-brokerage-settings-configure/SKILL.md +0 -41
  272. package/skills/industries-calendar-sync-settings-configure/SKILL.md +0 -89
  273. package/skills/industries-care-request-settings-configure/SKILL.md +0 -218
  274. package/skills/industries-case-management-intelligence-settings-configure/SKILL.md +0 -54
  275. package/skills/industries-catalog-process-attributes-products-configure/SKILL.md +0 -380
  276. package/skills/industries-catalog-process-fulfillment-configure/SKILL.md +0 -216
  277. package/skills/industries-catalog-process-intake-configure/SKILL.md +0 -219
  278. package/skills/industries-catalog-process-knowledge-configure/SKILL.md +0 -153
  279. package/skills/industries-catalog-process-preprocessor-configure/SKILL.md +0 -168
  280. package/skills/industries-cds-general-settings-configure/SKILL.md +0 -69
  281. package/skills/industries-claim-financial-settings-configure/SKILL.md +0 -169
  282. package/skills/industries-clinical-data-model-settings-configure/SKILL.md +0 -136
  283. package/skills/industries-clinical-data-model-settings-starter-configure/SKILL.md +0 -105
  284. package/skills/industries-collection-plan-settings-configure/SKILL.md +0 -132
  285. package/skills/industries-comms-service-console-settings-configure/SKILL.md +0 -159
  286. package/skills/industries-compliance-configure/SKILL.md +0 -102
  287. package/skills/industries-compliance-document-extraction-setup-configure/SKILL.md +0 -76
  288. package/skills/industries-contact-center-app-layout-configure/SKILL.md +0 -88
  289. package/skills/industries-contact-center-components-configure/SKILL.md +0 -96
  290. package/skills/industries-contact-center-path-knowledge-configure/SKILL.md +0 -95
  291. package/skills/industries-contact-center-user-access-assign/SKILL.md +0 -96
  292. package/skills/industries-course-waitlist-config-configure/SKILL.md +0 -162
  293. package/skills/industries-crisis-center-settings-configure/SKILL.md +0 -84
  294. package/skills/industries-custom-object-settings-configure/SKILL.md +0 -94
  295. package/skills/industries-data-cloud-integration-configure/SKILL.md +0 -88
  296. package/skills/industries-data-space-feature-mappings-configure/SKILL.md +0 -245
  297. package/skills/industries-decision-matrix-builder-configure/SKILL.md +0 -78
  298. package/skills/industries-decision-table-hbase-configure/SKILL.md +0 -87
  299. package/skills/industries-decision-table-version-configure/SKILL.md +0 -74
  300. package/skills/industries-decision-tables-builder-configure/SKILL.md +0 -253
  301. package/skills/industries-decision-tables-configure/SKILL.md +0 -439
  302. package/skills/industries-energy-utilities-configure/SKILL.md +0 -251
  303. package/skills/industries-eu-cloud-multisite-configure/SKILL.md +0 -1008
  304. package/skills/industries-eu-new-connections-configure/SKILL.md +0 -30
  305. package/skills/industries-explainability-msg-template-configure/SKILL.md +0 -135
  306. package/skills/industries-expression-set-message-token-configure/SKILL.md +0 -135
  307. package/skills/industries-field-service-settings-configure/SKILL.md +0 -82
  308. package/skills/industries-form-framework-settings-configure/SKILL.md +0 -30
  309. package/skills/industries-fsc-ai-settings-configure/SKILL.md +0 -57
  310. package/skills/industries-fsc-association-engine-settings-configure/SKILL.md +0 -172
  311. package/skills/industries-fsc-b2b-settings-configure/SKILL.md +0 -61
  312. package/skills/industries-fsc-insurance-settings-configure/SKILL.md +0 -58
  313. package/skills/industries-fsc-visa-fulfillment-setup-configure/SKILL.md +0 -103
  314. package/skills/industries-fsc-wealth-agent-settings-configure/SKILL.md +0 -117
  315. package/skills/industries-fsc-wealth-settings-configure/SKILL.md +0 -99
  316. package/skills/industries-fundraising-gift-entry-settings-configure/SKILL.md +0 -120
  317. package/skills/industries-fundraising-rfm-configure/SKILL.md +0 -128
  318. package/skills/industries-fundraising-settings-configure/SKILL.md +0 -145
  319. package/skills/industries-fundraising-source-code-gen-configure/SKILL.md +0 -299
  320. package/skills/industries-google-maps-api-key-settings-configure/SKILL.md +0 -87
  321. package/skills/industries-grantmaking-intelligence-settings-open/SKILL.md +0 -58
  322. package/skills/industries-hc-score-category-settings-configure/SKILL.md +0 -167
  323. package/skills/industries-health-engagement-settings-configure/SKILL.md +0 -218
  324. package/skills/industries-home-health-agentforce-setup-configure/SKILL.md +0 -131
  325. package/skills/industries-home-health-bundling-policy-create/SKILL.md +0 -50
  326. package/skills/industries-home-health-entity-labels-configure/SKILL.md +0 -29
  327. package/skills/industries-home-health-fsl-package-deploy/SKILL.md +0 -71
  328. package/skills/industries-home-health-fsl-settings-validate/SKILL.md +0 -27
  329. package/skills/industries-home-health-guided-setup-care-and-skill-get/SKILL.md +0 -80
  330. package/skills/industries-home-health-guided-setup-configure/SKILL.md +0 -836
  331. package/skills/industries-home-health-guided-setup-enable-org-fsl-get/SKILL.md +0 -55
  332. package/skills/industries-home-health-guided-setup-get-org-ready-get/SKILL.md +0 -61
  333. package/skills/industries-home-health-licenses-assign/SKILL.md +0 -27
  334. package/skills/industries-home-health-operating-hours-create/SKILL.md +0 -40
  335. package/skills/industries-home-health-scheduling-policy-create/SKILL.md +0 -41
  336. package/skills/industries-home-health-service-objective-create/SKILL.md +0 -22
  337. package/skills/industries-home-health-skills-create/SKILL.md +0 -31
  338. package/skills/industries-home-health-users-create/SKILL.md +0 -29
  339. package/skills/industries-icm-act3-toggles-configure/SKILL.md +0 -198
  340. package/skills/industries-icm-act5-genai-suggested-assessments-configure/SKILL.md +0 -361
  341. package/skills/industries-icm-act6-mcg-integration-configure/SKILL.md +0 -550
  342. package/skills/industries-integrated-care-management-configure/SKILL.md +0 -168
  343. package/skills/industries-object-enablement-configure/SKILL.md +0 -86
  344. package/skills/industries-tpm-system-processing-service-configure/SKILL.md +0 -64
  345. package/skills/integration-apex-settings-configure/SKILL.md +0 -61
  346. package/skills/marketing-e360-email-archiving-configure/SKILL.md +0 -73
  347. package/skills/platform-custom-permissions-configure/SKILL.md +0 -687
  348. package/skills/platform-label-workbench-import-create/SKILL.md +0 -67
  349. package/skills/platform-privacy-agent-settings-configure/SKILL.md +0 -752
@@ -1,1862 +0,0 @@
1
- ---
2
- name: crm-analytics-tableau-next-c360-basemodel-validate
3
- description: "Generic, app-agnostic prerequisite gate invoked by the tableau-next-app-install skill BEFORE readiness. Detects whether the caller template extends the shared C360 Semantic Model base model (sfdc_internal__C360UnifiedSemanticModel / C360SemanticModel_c360) and enforces a mandatory 3-step check — SDM presence in Data Cloud, installed C360 app in SuccessStatus, and installed-vs-current templateVersion parity — resolving the HOME/COMPANION multi-org role and auto-remediating (HOME) or refusing a shared-HOME mutation (COMPANION). Returns PROCEED or REFUSE/STOP. Not user-invocable on its own; it is a building block for any Tableau Next / App Framework install that has a C360 base-model dependency."
4
- metadata:
5
- version: "1.0"
6
- cliTools:
7
- - tool:
8
- - "jq"
9
- semver: ">=1.6"
10
- - tool:
11
- - "python3"
12
- semver: ">=3.8"
13
- - tool:
14
- - "sf"
15
- semver: ">=2.0.0"
16
- ---
17
-
18
- # tableau-next-c360-basemodel-gate
19
-
20
- ## What this does
21
-
22
- This is a **generic prerequisite gate** extracted verbatim from the `tableau-next-app-install` skill (its Step 2b) so it can be reused by any app install that depends on the shared C360 Semantic Model. The primary skill **invokes this gate in the same shell session, before its readiness (Step 3) and create (Step 4) steps.**
23
-
24
- **Contract**
25
- - **Inputs (already set by the caller in this shell):** `$ORG_ALIAS` (the org the user named), `$TEMPLATE_NAME_OR_ID` (the caller template), and `$API_BASE` / `$API_VERSION` (resolved by the primary's "Discover API version" step). The C360 template apiName is `sfdc_internal__C360UnifiedSemanticModel`.
26
- - **Behavior:** resolve HOME/COMPANION role → detect the C360 base-model dependency in the caller template's `/assets` → run the 3-step verification (SDM presence → installed C360 app `SuccessStatus` / `SuccessWithWarningsStatus` → templateVersion parity). HOME auto-remediates a missing/stale C360 inline; COMPANION refuses a shared-HOME mutation unless explicitly opted in (`TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE=1` or a TTY `y`).
27
- - **Outputs left in the shell for the caller** (the primary's Step-4 target-org guard and Step-6b update path read these): `$ORG_ROLE` (HOME / COMPANION / UNKNOWN), `$RESOLVED_ORG_FOR_C360` (= `$ORG_ALIAS` on HOME, = `$HOME_ORG_ALIAS` on COMPANION), and `$HOME_ORG_ALIAS`.
28
- - **Verdict:** **PROCEED** (all clear — the caller continues to readiness) or **REFUSE / STOP** (the caller must halt and surface the message verbatim).
29
-
30
- This gate is **self-contained**: it carries its own copies of the `update_app` and `poll_update_to_terminal` helpers (used by the 2b.5 templateVersion auto-remediation path). They are byte-identical to the same-named helpers in the primary skill's Step 6b, duplicated here so this gate runs standalone without sourcing the primary's later steps. **Define them first — run the `bash` block immediately below — then run the gate logic that follows.**
31
-
32
- ## Helper functions (define these BEFORE running the gate — used by 2b.5 auto-remediation)
33
-
34
- ```bash
35
- # ---- 6b. Update an existing app -------------------------------------------
36
- # Usage:
37
- # update_app "$APP_ID" "$TARGET_ORG_ALIAS" "$TEMPLATE_VALUES_JSON"
38
- #
39
- # $TEMPLATE_VALUES_JSON is the fully-built templateValues map from step 2
40
- # (post required-filter, post isTest inject, post placeholder resolution, post
41
- # NumberType decimal coercion). It's the SAME value that would go into the
42
- # CREATE body's `templateValues` field — the update endpoint accepts it byte
43
- # for byte.
44
- #
45
- # On success the function prints the app id, sets $UPDATED_APP_ID as a
46
- # side-effect, and returns 0. The caller then polls $API_BASE/apps/$APP_ID
47
- # with the same loop as step 5 until `applicationStatus` is terminal
48
- # (SuccessStatus / SuccessWithWarningsStatus / FailedStatus).
49
-
50
- update_app() {
51
- local app_id="$1"
52
- local target_org="$2"
53
- local template_values_json="$3"
54
-
55
- if [ -z "$app_id" ] || [ -z "$target_org" ]; then
56
- echo "update_app: app_id and target_org are required." >&2
57
- return 2
58
- fi
59
- # Default to `{}` if the caller passes an empty/blank templateValues — the
60
- # server accepts empty templateValues on update (nothing to change), same as
61
- # create. Keeps one code path.
62
- if [ -z "$template_values_json" ]; then
63
- template_values_json='{}'
64
- fi
65
-
66
- # Look up the current app record to (a) confirm it exists on this org and
67
- # (b) source the templateSourceId. Do NOT hardcode the apiName here — some
68
- # allowlisted templates live under different apiNames across releases.
69
- local current_app_json template_source_id current_status
70
- current_app_json=$(sf api request rest "$API_BASE/apps/$app_id" \
71
- --target-org "$target_org" --method GET 2>/dev/null || true)
72
-
73
- # `.templateSourceId` is the canonical field; some releases expose the same
74
- # value at `.templateApiName`. Prefer templateSourceId when both are present.
75
- template_source_id=$(echo "$current_app_json" | jq -r '
76
- .templateSourceId // .templateApiName // empty
77
- ')
78
- current_status=$(echo "$current_app_json" | jq -r '
79
- .applicationStatus // .status // empty
80
- ')
81
-
82
- if [ -z "$template_source_id" ]; then
83
- echo "update_app: could not resolve templateSourceId from GET /apps/$app_id on $target_org." >&2
84
- echo "Raw response follows:" >&2
85
- echo "$current_app_json" | jq '.' >&2 2>/dev/null || echo "$current_app_json" >&2
86
- return 1
87
- fi
88
-
89
- # Refuse the PUT if the app is currently mid-install. The server would 409
90
- # ("app is being modified"), but a clean up-front refusal keeps the message
91
- # actionable — poll to terminal first, THEN update.
92
- case "$current_status" in
93
- InProgressStatus|PendingStatus|NewStatus)
94
- echo "update_app: app $app_id on $target_org is in $current_status — refusing PUT." >&2
95
- echo "Wait for the app to reach a terminal status (Success*/Failed*) before updating." >&2
96
- return 1
97
- ;;
98
- esac
99
-
100
- # Compose the PUT body via jq so `--body @file` gets valid JSON regardless
101
- # of what shell interpolation might do to embedded quotes/backslashes.
102
- local update_body_file="${TMPDIR:-/tmp}/update_app_${app_id}.$$.json"
103
- jq -n \
104
- --arg tsid "$template_source_id" \
105
- --argjson tv "$template_values_json" \
106
- '{templateSourceId: $tsid, templateValues: $tv}' \
107
- > "$update_body_file"
108
-
109
- echo "PUT $API_BASE/apps/$app_id (target-org=$target_org, templateSourceId=$template_source_id) ..." >&2
110
-
111
- local put_response
112
- put_response=$(sf api request rest "$API_BASE/apps/$app_id" \
113
- --target-org "$target_org" --method PUT \
114
- --body "@$update_body_file")
115
- local rc=$?
116
- rm -f "$update_body_file"
117
-
118
- if [ "$rc" != "0" ]; then
119
- echo "update_app: PUT failed (rc=$rc). Raw response:" >&2
120
- echo "$put_response" >&2
121
- return 1
122
- fi
123
-
124
- echo "$put_response" | jq '.' >&2 2>/dev/null || echo "$put_response" >&2
125
-
126
- # Sanity: the response should carry the same app id. Surface a warning if not
127
- # — caller-side polling still works via the passed-in $app_id.
128
- local returned_id
129
- returned_id=$(echo "$put_response" | jq -r '.id // .app.id // empty')
130
- if [ -n "$returned_id" ] && [ "$returned_id" != "$app_id" ]; then
131
- echo "update_app: WARNING — response .id ($returned_id) != input app_id ($app_id)." >&2
132
- fi
133
-
134
- UPDATED_APP_ID="$app_id"
135
- return 0
136
- }
137
-
138
- # ---- Poll the update to terminal (reuse step 5's shape) --------------------
139
- # The update runs asynchronously — after PUT returns, poll GET /apps/{id}
140
- # until applicationStatus is terminal. Same wire rule D as step 5 (check
141
- # .applicationStatus, not .status; treat *InProgress*/*Pending*/*New* as
142
- # in-flight; treat Success*/Failed*/Cancelled as terminal).
143
- poll_update_to_terminal() {
144
- local app_id="$1"
145
- local target_org="$2"
146
- local cap_secs="${3:-1800}" # default 30 min
147
-
148
- local deadline status app_resp
149
- deadline=$(( $(date +%s) + cap_secs ))
150
- while [ "$(date +%s)" -lt "$deadline" ]; do
151
- app_resp=$(sf api request rest "$API_BASE/apps/$app_id" \
152
- --target-org "$target_org" --method GET 2>/dev/null || true)
153
- status=$(echo "$app_resp" | jq -r '.applicationStatus // .status // empty')
154
- case "$status" in
155
- SuccessStatus|SuccessWithWarningsStatus)
156
- echo "[6b] update SUCCEEDED: $app_id -> $status" >&2
157
- return 0
158
- ;;
159
- FailedStatus|CancelledStatus)
160
- echo "[6b] update TERMINAL FAILURE: $app_id -> $status" >&2
161
- echo "$app_resp" | jq '.failureReasons // .' >&2 2>/dev/null || echo "$app_resp" >&2
162
- return 1
163
- ;;
164
- InProgressStatus|PendingStatus|NewStatus|"")
165
- sleep 30
166
- ;;
167
- *)
168
- # Unknown / release-drift status — sleep and retry a few times before
169
- # treating as unknown-terminal.
170
- echo "[6b] unknown applicationStatus '$status' on $app_id — waiting." >&2
171
- sleep 30
172
- ;;
173
- esac
174
- done
175
-
176
- echo "[6b] update did not reach terminal status within ${cap_secs}s on $target_org." >&2
177
- return 1
178
- }
179
- ```
180
-
181
- ### 2b. Detect C360 Semantic Model base-model dependency (REQUIRED — do NOT skip)
182
-
183
- Some allowlisted templates ship SDMs that **extend** the shared `C360SemanticModel_c360` semantic model (a.k.a. `sfdc_internal__C360UnifiedSemanticModel`). If the C360 app isn't already installed on the org — OR is installed at a stale `templateVersion` — the extending SDMs cannot resolve their base model and the install will fail late, after `POST /apps` has already spent minutes provisioning downstream assets. Detect this dependency BEFORE readiness and refuse cleanly.
184
-
185
- **How the dependency shows up on the wire:**
186
- - `GET /app-framework/templates/{id}/assets` returns the template's declared assets. Each entry has an `assetType`.
187
- - **The response wrapper key has drifted three ways across releases**: `.assets`, `.templateAssets`, and (v68.0+, wire-verified 2026-08-26) `.templatedAssets`. Coalesce ALL three when reading the list — filtering on the first two alone silently falls through to `[]` on v68 and every downstream SemanticModel scan produces zero hits.
188
- - **Each entry's SDM content is NOT at the top of the entry — it's a stringified JSON blob under `.body`**, with quotes HTML-entity-encoded as `"` (wire-verified 2026-08-26 on `sfdc_internal__SalesInsightsTN`). You MUST html-decode then JSON-parse `.body` before `baseModels` is reachable. Reading `.baseModels` on the top-level asset entry returns null on v68.0+.
189
- - **`baseModels` is an ARRAY OF OBJECTS on v68.0+, not an array of strings.** Wire-verified 2026-08-26 shape: `"baseModels": [{"apiName": "C360SemanticModel_c360", "label": "C360 Semantic Model"}]`. Older releases emit the string form (`"baseModels": ["C360SemanticModel_c360"]`) or a bare `"baseModel": "C360SemanticModel_c360"`. Handle both — a `type == "string"` filter silently drops every object entry on v68 and produces a false-negative C360 detection.
190
- - The base-model apiName the platform uses is `C360SemanticModel_c360` (the SDM name inside the C360 app), NOT the template apiName `sfdc_internal__C360UnifiedSemanticModel`. Match on both — the field name and casing have varied across releases.
191
-
192
- > **Anti-pattern (wire-verified 2026-08-25):** the `/app-framework/apps` list endpoint honors ONLY `templateSourceId` as a server-side filter. `applicationStatus` is silently IGNORED as a query param — the server returns the unfiltered set. **Do NOT** put `applicationStatus` in the URL. Always filter client-side with `jq`.
193
-
194
- **Gate logic — 3-step verification: SDM presence → installed app SuccessStatus → templateVersion match (MANDATORY):**
195
-
196
- The 3-step gate is non-bypassable when the caller template declares a C360 base-model dependency (2b.2). In particular, 2b.5's templateVersion parity compare is a **HARD gate** — an unreadable installed version, or an unreadable current template version, refuses the install with actionable remediation. Do NOT silently proceed when either operand is missing: `SuccessStatus` on the downstream install is not proof that parity was actually observed, and templates can pin their C360 dependency by apiName only, so a stale-C360 install may fail late during base-model resolution without a clean upstream signal.
197
-
198
- The parity compare is scoped to (a) the *installed C360 app's* `templateVersion` on `$RESOLVED_ORG_FOR_C360` — read from `GET /app-framework/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel` under `.templateVersion` (with `.templateInfo.templateVersion` / `.installedTemplate.templateVersion` as release-drift fallbacks) — and (b) the *C360 template's* current `templateVersion` — read from `GET /app-framework/templates/sfdc_internal__C360UnifiedSemanticModel` under `.releaseInfo.templateVersion` (with `.templateVersion` / `.release.templateVersion` as older aliases). The **CALLER template's own version** (e.g. `sfdc_internal__ServiceInsights` v4.4) is intentionally NOT part of this compare — the C360 base-model is a shared cross-template dependency, so only its own installed-vs-current matters.
199
-
200
-
201
- ```bash
202
- # Skip this step entirely when the user is installing C360 itself — a template
203
- # doesn't depend on itself.
204
- if [ "$TEMPLATE_NAME_OR_ID" != "sfdc_internal__C360UnifiedSemanticModel" ]; then
205
-
206
- # =========================================================================
207
- # 2b HELPERS - HOME/COMPANION role resolution + inline C360 install on Home
208
- # =========================================================================
209
- # Wire signals (verified 2026-08-27 on v68.0 pc-rnd):
210
- # ROLE = SOQL against DataMultiOrgConnection
211
- # 0 rows -> HOME (standalone or Home side of no pairing)
212
- # >=1 row OrgType='HOME' -> COMPANION; the row describes the FAR END of
213
- # the connection, so OrgType=HOME means "the
214
- # other side is a Home" -> this org is the LOB.
215
- # Home id in ConnectedOrg (fallback
216
- # ConnectedParentOrg).
217
- # >=1 row OrgType='LOB' -> HOME (paired publisher); each LOB row
218
- # describes a companion consuming from this
219
- # org. This org IS the Home.
220
- # /ssot/tenant-info, /connect/tenants, PlatformTenant, DataCloudTenant, and
221
- # TnCompanionOrgAssignment are all NOT_FOUND on v68.0. Do NOT gate on them.
222
- #
223
- # INSUFFICIENT_ACCESS on DataMultiOrgConnection is a HARD REFUSE, not a
224
- # fall-back to HOME. A false-HOME classification would skip the companion
225
- # auto-install branch and mislead the operator.
226
-
227
- c360_log() {
228
- # $1 = state name, $2 = org alias in scope
229
- echo "[2b state=$1 org=${2:-<none>}]" >&2
230
- }
231
-
232
- # ---- resolve_c360_gate_orgs ---------------------------------------------
233
- # Sets ORG_ROLE in {HOME, COMPANION, UNKNOWN}, HOME_ORG_ID_15, HOME_ORG_ALIAS,
234
- # RESOLVED_ORG_FOR_C360 (= $ORG_ALIAS on HOME, = $HOME_ORG_ALIAS on COMPANION).
235
- # Returns 0 on success, 1 on unrecoverable classification failure.
236
- resolve_c360_gate_orgs() {
237
- ORG_ROLE=""
238
- HOME_ORG_ID_15=""
239
- HOME_ORG_ALIAS=""
240
- RESOLVED_ORG_FOR_C360="$ORG_ALIAS"
241
-
242
- local q_json err_name total lob_row home_row home_id_raw
243
- q_json=$(sf data query --target-org "$ORG_ALIAS" --json \
244
- --query "SELECT Id, OrgType, ConnectionStatus, ConnectedOrg, ConnectedParentOrg, TenantKey FROM DataMultiOrgConnection" \
245
- 2>&1 || true)
246
-
247
- err_name=$(echo "$q_json" | jq -r '.name // .result[0].errorCode // empty' 2>/dev/null)
248
- case "$err_name" in
249
- INSUFFICIENT_ACCESS_OR_READONLY|INSUFFICIENT_ACCESS|ACCESS_DENIED)
250
- ORG_ROLE="UNKNOWN"
251
- cat >&2 <<MSG
252
-
253
- REFUSING: cannot determine HOME/COMPANION role for $ORG_ALIAS - the running
254
- user lacks read access to DataMultiOrgConnection ($err_name).
255
-
256
- Action required - one of:
257
- 1. Grant read on DataMultiOrgConnection to the running user in $ORG_ALIAS.
258
- 2. Re-invoke with --home-org-alias <alias> to pin the COMPANION path.
259
-
260
- MSG
261
- return 1
262
- ;;
263
- INVALID_TYPE|sf:INVALID_TYPE|SOBJECT_NOT_FOUND|NoSuchEntity*)
264
- echo "NOTE: DataMultiOrgConnection not queryable on $ORG_ALIAS ($err_name) - treating as HOME (standalone)." >&2
265
- ORG_ROLE="HOME"
266
- ;;
267
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
268
- ORG_ROLE="UNKNOWN"
269
- echo "REFUSING: auth session stale while probing role on $ORG_ALIAS ($err_name). Refresh with 'sf org login web --alias $ORG_ALIAS' and retry." >&2
270
- return 1
271
- ;;
272
- "")
273
- total=$(echo "$q_json" | jq -r '.result.totalSize // ((.result.records // []) | length) // 0' 2>/dev/null)
274
- total=${total:-0}
275
- if [ "$total" = "0" ]; then
276
- ORG_ROLE="HOME"
277
- else
278
- # DataMultiOrgConnection.OrgType describes the FAR END of each
279
- # connection, not the local org:
280
- # OrgType='HOME' -> paired to a HOME -> local org is COMPANION
281
- # OrgType='LOB' -> paired to a LOB -> local org is HOME
282
- # On the COMPANION branch, the HOME's id is in ConnectedOrg
283
- # (fallback ConnectedParentOrg on releases that use the parent field).
284
- home_row=$(echo "$q_json" | jq -c '.result.records[]? | select(.OrgType == "HOME")' 2>/dev/null | head -n1)
285
- lob_row=$(echo "$q_json" | jq -c '.result.records[]? | select(.OrgType == "LOB")' 2>/dev/null | head -n1)
286
- if [ -n "$home_row" ]; then
287
- ORG_ROLE="COMPANION"
288
- home_id_raw=$(echo "$home_row" | jq -r '.ConnectedOrg // .ConnectedParentOrg // empty' 2>/dev/null)
289
- elif [ -n "$lob_row" ]; then
290
- ORG_ROLE="HOME"
291
- else
292
- echo "NOTE: DataMultiOrgConnection returned $total row(s) but no HOME/LOB OrgType - role indeterminate." >&2
293
- ORG_ROLE="UNKNOWN"
294
- fi
295
- fi
296
- ;;
297
- *)
298
- echo "REFUSING: DataMultiOrgConnection probe on $ORG_ALIAS returned $err_name - role indeterminate." >&2
299
- ORG_ROLE="UNKNOWN"
300
- return 1
301
- ;;
302
- esac
303
-
304
- if [ "$ORG_ROLE" = "HOME" ]; then
305
- RESOLVED_ORG_FOR_C360="$ORG_ALIAS"
306
- c360_log "PROBE_ROLE=HOME" "$ORG_ALIAS"
307
- return 0
308
- fi
309
-
310
- if [ "$ORG_ROLE" = "UNKNOWN" ]; then
311
- return 1
312
- fi
313
-
314
- if [ -z "$home_id_raw" ]; then
315
- cat >&2 <<MSG
316
-
317
- REFUSING: $ORG_ALIAS is classified as a COMPANION (a DataMultiOrgConnection
318
- row reports OrgType='HOME', i.e. the paired org is a HOME) but neither
319
- ConnectedOrg nor ConnectedParentOrg was populated.
320
- Re-invoke with --home-org-alias <alias>.
321
-
322
- MSG
323
- return 1
324
- fi
325
-
326
- HOME_ORG_ID_15="${home_id_raw:0:15}"
327
-
328
- local orgs home_alias
329
- orgs=$(sf org list --json 2>/dev/null || true)
330
- if [ -z "$orgs" ]; then
331
- echo "REFUSING: 'sf org list --json' returned empty body - cannot resolve HOME_ORG_ALIAS for $HOME_ORG_ID_15." >&2
332
- return 1
333
- fi
334
-
335
- home_alias=$(echo "$orgs" | jq -r --arg id15 "$HOME_ORG_ID_15" '
336
- [ (.result.nonScratchOrgs // []),
337
- (.result.scratchOrgs // []),
338
- (.result.devHubs // []),
339
- (.result.sandboxes // []),
340
- (.result.other // []) ]
341
- | add // []
342
- | map(select(((.orgId // "") | tostring | .[0:15]) == $id15))
343
- | .[0]
344
- | (.aliases // [])[0] // .alias // .username // empty
345
- ' 2>/dev/null)
346
-
347
- if [ -z "$home_alias" ] || [ "$home_alias" = "null" ]; then
348
- cat >&2 <<MSG
349
-
350
- STOPPING install of $TEMPLATE_NAME_OR_ID on companion $ORG_ALIAS:
351
- no local sf CLI auth found for the HOME org paired with this companion.
352
-
353
- WHY THIS IS BLOCKING
354
- --------------------
355
- $ORG_ALIAS is a COMPANION (LOB) org. Its C360 Semantic Model dependency
356
- lives on the paired HOME org, not here. Steps 2b.3, 2b.4 and 2b.5 must
357
- run App-Framework REST calls against the HOME:
358
- GET /app-framework/templates/sfdc_internal__C360UnifiedSemanticModel
359
- GET /app-framework/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel
360
- The companion's session token cannot be reused against the HOME - Salesforce
361
- sessions are scoped to the org that issued them. Without a HOME-scoped
362
- sf CLI alias on this machine, the 2b gate cannot verify the SDM presence
363
- or run the templateVersion parity check, and this skill will not proceed
364
- blind.
365
-
366
- HOME ORG IDENTIFIERS (from DataMultiOrgConnection on $ORG_ALIAS)
367
- ----------------------------------------------------------------
368
- HOME org id (18-char) : ${home_id_raw:-<unresolved>}
369
- HOME org id (15-char) : $HOME_ORG_ID_15
370
- Companion (this org, caller) : $ORG_ALIAS
371
- Pairing status : as reported by DataMultiOrgConnection
372
-
373
- REQUIRED - GET THE FOLLOWING FROM THE HOME ORG ADMIN
374
- ----------------------------------------------------
375
- Ask the admin of HOME org $HOME_ORG_ID_15 for these THREE things. The
376
- admin never has to share a password, security token, or 2FA code - the
377
- web login flow keeps their credentials on their side of the screen:
378
-
379
- (1) HOME org's my-domain login URL
380
- Exact form: https://<subdomain>.my.<pod>.salesforce.com
381
- Example: https://acme-home.my.pc-rnd.salesforce.com
382
- The org id alone is NOT enough - Salesforce has no public
383
- org-id -> my-domain reverse lookup, so the admin must supply this.
384
-
385
- (2) A permitted login user on the HOME
386
- Confirm the user the admin will log in as carries:
387
- - AppFrameworkManageTemplate (Manage App Templates perm)
388
- - Tableau Next enabled on the HOME
389
- - Data Cloud provisioned on the HOME
390
- Read-only 2b.5 (templates + apps GETs) works with just
391
- "View Setup and Configuration" + Tableau-Next perms, but pin
392
- AppFrameworkManageTemplate anyway in case a downstream step
393
- needs to trigger a Reconfigure.
394
-
395
- (3) The admin's ~60 seconds at this terminal
396
- They come to this laptop, run ONE command (below), complete the
397
- browser login on their side, and walk away. No secret leaves
398
- their control; the sf CLI stores a HOME-scoped refresh token
399
- keyed to the alias.
400
-
401
- ONCE (1)-(3) ARE READY - RUN THIS COMMAND (Option A)
402
- ----------------------------------------------------
403
- sf org login web \\
404
- --instance-url https://<HOME-my-domain>.my.<pod>.salesforce.com \\
405
- --alias <home-alias>
406
-
407
- Suggested alias naming (kept stable across future skill invocations):
408
- home_${HOME_ORG_ID_15}
409
- e.g.
410
- sf org login web \\
411
- --instance-url https://acme-home.my.pc-rnd.salesforce.com \\
412
- --alias home_$HOME_ORG_ID_15
413
-
414
- The browser opens, the admin logs in on the HOME, and the CLI stores a
415
- HOME-scoped refresh token under that alias. The alias is then usable by
416
- every subsequent sf api request rest --target-org <home-alias> call in
417
- this skill - no further admin involvement per run.
418
-
419
- VERIFY THE ALIAS BEFORE RE-INVOKING
420
- -----------------------------------
421
- sf org list --json | jq -r --arg id15 "$HOME_ORG_ID_15" '
422
- [ (.result.nonScratchOrgs // []),
423
- (.result.scratchOrgs // []),
424
- (.result.devHubs // []),
425
- (.result.sandboxes // []),
426
- (.result.other // []) ]
427
- | add
428
- | map(select(((.orgId // "") | tostring | .[0:15]) == \$id15))
429
- | .[0]
430
- | {alias: ((.aliases // [])[0] // .alias // .username), orgId, instanceUrl}'
431
- The row should show alias + orgId starting with $HOME_ORG_ID_15 + the
432
- HOME instance URL you supplied.
433
-
434
- WHY OPTION A AND NOT A RAW BEARER TOKEN
435
- ---------------------------------------
436
- Option B (admin pastes a HOME sf org display access token) works for a
437
- one-shot check but the token expires (~2 hours on pc-rnd) and cannot be
438
- refreshed by this skill without prompting the admin again. Option C
439
- (client_credentials via a Connected App on the HOME) is only worth the
440
- setup if this workflow becomes unattended / repeating. Option A - the
441
- web login - is the right default for interactive skill runs: no shared
442
- secret in shell history, refresh token stays on this laptop, and every
443
- future run just names the alias.
444
-
445
- This skill will now WAIT (up to 15 min, re-checking every 30 s) for the
446
- HOME alias to appear in \`sf org list --json\`. Run the login command in
447
- another terminal on this same machine; the skill picks up the alias the
448
- moment it lands and continues automatically with 2b.3 / 2b.4 / 2b.5
449
- against the HOME - no re-invoke required.
450
-
451
- If the wait times out, the skill will REFUSE and exit; re-run once the
452
- alias exists (Step 2b.0 will re-probe DataMultiOrgConnection and continue).
453
-
454
- Ctrl-C to abort and re-run later.
455
-
456
- MSG
457
- # Bounded auto-resume: re-check the sf CLI alias table every 30 s for
458
- # up to 15 min. If the HOME alias appears mid-wait, continue in this
459
- # same run (no re-invoke required). If it doesn't, refuse and exit
460
- # so a subsequent invocation picks up where this one left off.
461
- # Rationale: the STOPPING message asks the admin to run a single
462
- # 'sf org login web' in another terminal on this laptop. Round-trip
463
- # is typically 30-90 s (browser popup + click-through), so a 15-min
464
- # cap is generous but bounded - a stuck run is preferable to an
465
- # unbounded background wait.
466
- local resume_deadline resume_iter=0 resume_orgs
467
- resume_deadline=$(( $(date +%s) + 900 ))
468
- while [ "$(date +%s)" -lt "$resume_deadline" ]; do
469
- resume_iter=$(( resume_iter + 1 ))
470
- sleep 30
471
- resume_orgs=$(sf org list --json 2>/dev/null || true)
472
- if [ -z "$resume_orgs" ]; then
473
- echo "[resolve_c360_gate_orgs auto-resume iter=$resume_iter] 'sf org list --json' returned empty body - retrying." >&2
474
- continue
475
- fi
476
- home_alias=$(echo "$resume_orgs" | jq -r --arg id15 "$HOME_ORG_ID_15" '
477
- [ (.result.nonScratchOrgs // []),
478
- (.result.scratchOrgs // []),
479
- (.result.devHubs // []),
480
- (.result.sandboxes // []),
481
- (.result.other // []) ]
482
- | add // []
483
- | map(select(((.orgId // "") | tostring | .[0:15]) == $id15))
484
- | .[0]
485
- | (.aliases // [])[0] // .alias // .username // empty
486
- ' 2>/dev/null)
487
- if [ -n "$home_alias" ] && [ "$home_alias" != "null" ]; then
488
- echo "[resolve_c360_gate_orgs auto-resume iter=$resume_iter] HOME alias '$home_alias' registered for $HOME_ORG_ID_15 - continuing in this run." >&2
489
- break
490
- fi
491
- echo "[resolve_c360_gate_orgs auto-resume iter=$resume_iter] HOME alias for $HOME_ORG_ID_15 not yet present - waiting." >&2
492
- done
493
-
494
- if [ -z "$home_alias" ] || [ "$home_alias" = "null" ]; then
495
- cat >&2 <<MSG
496
-
497
- REFUSING: HOME alias for $HOME_ORG_ID_15 did not appear within 15 min
498
- on $ORG_ALIAS. Complete the 'sf org login web ... --alias ...' command
499
- from the STOPPING message above, then re-invoke this skill on $ORG_ALIAS.
500
- Step 2b.0 will re-probe DataMultiOrgConnection, find the newly-registered
501
- HOME alias, and continue with 2b.3 / 2b.4 / 2b.5 against the HOME.
502
-
503
- MSG
504
- return 1
505
- fi
506
- fi
507
-
508
- HOME_ORG_ALIAS="$home_alias"
509
- RESOLVED_ORG_FOR_C360="$HOME_ORG_ALIAS"
510
- c360_log "PROBE_ROLE=COMPANION home=$HOME_ORG_ALIAS(id $HOME_ORG_ID_15)" "$ORG_ALIAS"
511
- return 0
512
- }
513
-
514
- # ---- wait_for_c360_sdm_visible ------------------------------------------
515
- # Poll a target org's /ssot/semantic/models/C360SemanticModel_c360 endpoint
516
- # (with fall-through to the older /ssot/semantic-models/ path per the same
517
- # discipline as 2b.3's SDM presence probe) until the C360 SDM resolves. The
518
- # /apps install reaching SuccessStatus/SuccessWithWarningsStatus is NOT
519
- # sufficient to conclude the SDM is available:
520
- # HOME (self-install / standalone): SDM materialization on the publisher
521
- # is typically fast (seconds) once the install lands, but backing
522
- # Data Cloud metadata rolls out asynchronously. Callers of this skill
523
- # (2b.4/2b.5, readiness) depend on the SDM being resolvable, so we
524
- # block on visibility here rather than let a downstream step false-fail.
525
- # COMPANION (cross-org publish): the SDM must PROPAGATE from HOME to the
526
- # companion's Data Cloud via multi-org sync; observed window varies from
527
- # minutes to hours depending on pairing state and platform backlog.
528
- # POLLING DISCIPLINE — pass cap_secs=0 (or omit) for UNBOUNDED polling: the
529
- # loop runs until the SDM appears, an auth error surfaces, or the caller
530
- # kills the run. Both current callers (HOME self-install, COMPANION
531
- # propagation) pass 0 per user instruction "keep polling till the SDM is
532
- # available." A positive cap_secs is honored as a bounded probe (kept for
533
- # future callers that need one — e.g. a health check). Auth errors mid-poll
534
- # (INVALID_SESSION_ID / INVALID_AUTH_HEADER / INVALID_JWT_FORMAT) always
535
- # surface immediately with a refresh-auth refusal - a stale token would
536
- # otherwise poll forever against a broken session.
537
- # Args:
538
- # $1 = target sf org alias to probe
539
- # $2 = cap seconds (0 = unbounded; positive = bounded probe)
540
- # $3 = short role tag for log lines ("HOME self-install" / "COMPANION propagation")
541
- # $4 = publisher org (for the failure-mode message; equals $1 on HOME path,
542
- # equals $HOME_ORG_ALIAS on COMPANION path)
543
- wait_for_c360_sdm_visible() {
544
- local target="$1" cap_secs="$2" role_tag="$3" publisher="$4"
545
- local sdm_url_primary="/services/data/$API_VERSION/ssot/semantic/models/C360SemanticModel_c360"
546
- local sdm_url_alt="/services/data/$API_VERSION/ssot/semantic-models/C360SemanticModel_c360"
547
- local sdm_resp sdm_err sdm_name
548
- local unbounded=1 deadline=0
549
- if [ -n "$cap_secs" ] && [ "$cap_secs" -gt 0 ] 2>/dev/null; then
550
- unbounded=0
551
- deadline=$(( $(date +%s) + cap_secs ))
552
- fi
553
- if [ "$unbounded" -eq 1 ]; then
554
- echo "[2b state=WAIT_FOR_SDM $role_tag target=$target] polling C360 SDM visibility (unbounded - polling until SDM appears; auth expiry or Ctrl-C exits)." >&2
555
- else
556
- echo "[2b state=WAIT_FOR_SDM $role_tag target=$target] polling C360 SDM visibility (cap $((cap_secs/60))m)." >&2
557
- fi
558
- while :; do
559
- if [ "$unbounded" -eq 0 ] && [ "$(date +%s)" -ge "$deadline" ]; then
560
- break
561
- fi
562
- sdm_resp=$(sf api request rest "$sdm_url_primary" \
563
- --target-org "$target" --method GET 2>/dev/null || true)
564
- sdm_err=$(echo "$sdm_resp" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
565
- if [ -z "$sdm_resp" ] || echo "$sdm_err" | grep -qE '^(URL_NOT_FOUND|NOT_FOUND|UNSUPPORTED_API_VERSION)$'; then
566
- sdm_resp=$(sf api request rest "$sdm_url_alt" \
567
- --target-org "$target" --method GET 2>/dev/null || true)
568
- sdm_err=$(echo "$sdm_resp" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
569
- fi
570
- case "$sdm_err" in
571
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
572
- cat >&2 <<MSG
573
-
574
- REFUSING: auth session expired mid-poll on $target while waiting for
575
- C360 SDM visibility ($role_tag: $sdm_err). Refresh the org auth
576
- ('sf org login web --alias $target') and re-invoke this skill.
577
-
578
- MSG
579
- return 1
580
- ;;
581
- esac
582
- sdm_name=$(echo "$sdm_resp" | jq -r '.name // .apiName // .semanticModel.name // .semanticModel.apiName // empty' 2>/dev/null)
583
- if [ -n "$sdm_name" ]; then
584
- c360_log "WAIT_FOR_SDM visible ($role_tag)" "$target"
585
- return 0
586
- fi
587
- sleep 30
588
- done
589
- # Cap elapsed (bounded probe only — unbounded path never reaches here).
590
- # Emit a role-appropriate refusal — HOME self-install "did not
591
- # materialize" vs COMPANION "did not propagate from HOME" carry different
592
- # remediations, so the caller passes the right role_tag.
593
- if [ "$target" = "$publisher" ]; then
594
- cat >&2 <<MSG
595
-
596
- REFUSING: C360 was installed on $target (SuccessStatus) but the C360 SDM
597
- did NOT materialize in $target's Data Cloud within $((cap_secs/60)) minutes.
598
- Verify Data Cloud is fully provisioned on $target (Setup -> Data Cloud
599
- Setup), then re-invoke this skill on $target.
600
-
601
- MSG
602
- else
603
- cat >&2 <<MSG
604
-
605
- REFUSING: C360 was installed on HOME $publisher (SuccessStatus) but the
606
- C360 SDM did NOT propagate to companion $target within $((cap_secs/60))
607
- minutes. Check DataMultiOrgConnection.ConnectionStatus on both $publisher
608
- and $target, verify tenants share the same TenantKey, then re-invoke.
609
-
610
- MSG
611
- fi
612
- return 1
613
- }
614
-
615
- # ---- install_c360_on_home_and_wait_for_sync -----------------------------
616
- # Role-parametric C360 install:
617
- # ORG_ROLE=COMPANION -> install on $HOME_ORG_ALIAS, then UNBOUNDED-poll
618
- # $ORG_ALIAS for SDM propagation via
619
- # wait_for_c360_sdm_visible (30 min /apps install
620
- # cap + unbounded SDM-visibility wait).
621
- # ORG_ROLE=HOME -> install on $ORG_ALIAS itself, then UNBOUNDED-poll
622
- # $ORG_ALIAS for local SDM materialization via
623
- # wait_for_c360_sdm_visible (30 min /apps install
624
- # cap + unbounded SDM-visibility wait). SuccessStatus
625
- # on /apps is NECESSARY but not SUFFICIENT — the SDM
626
- # must be resolvable in Data Cloud before 2b.4/2b.5
627
- # continue.
628
- # NEVER shells out to a nested skill invocation. Idempotent w.r.t. an existing
629
- # install: DUPLICATE_VALUE / ITEM_ALREADY_EXISTS / APP_ALREADY_INSTALLED on
630
- # the POST short-circuits to the SDM-visibility wait step (both roles).
631
- # Caps: 30 min on /apps poll; UNBOUNDED on the SDM-visibility wait (both
632
- # roles). The user's instruction is "keep polling till the SDM is available"
633
- # — the wait terminates only on SDM visibility, auth expiry, or Ctrl-C.
634
- install_c360_on_home_and_wait_for_sync() {
635
- # Publisher (where the C360 app is created) is the resolved C360 org:
636
- # RESOLVED_ORG_FOR_C360 = $ORG_ALIAS (HOME role) -> self-install
637
- # RESOLVED_ORG_FOR_C360 = $HOME_ORG_ALIAS (COMPANION) -> home-install
638
- local home="${RESOLVED_ORG_FOR_C360:-$HOME_ORG_ALIAS}"
639
- local caller="$ORG_ALIAS"
640
- local c360="sfdc_internal__C360UnifiedSemanticModel"
641
- local body_file create_file create_resp new_app_id post_err status app_resp
642
- local install_deadline=$(( $(date +%s) + 1800 ))
643
- local sync_deadline
644
-
645
- c360_log "INSTALL_C360_HOME start" "$home"
646
-
647
- body_file=$(mktemp /tmp/c360_home_body.XXXXXX)
648
- cat > "$body_file" <<'JSON'
649
- {"templateValues": {}}
650
- JSON
651
-
652
- sf api request rest "$API_BASE/templates/$c360/readiness" \
653
- --target-org "$home" --method POST --body "@$body_file" \
654
- >/dev/null 2>&1 || true
655
-
656
- create_file=$(mktemp /tmp/c360_home_create.XXXXXX)
657
- cat > "$create_file" <<JSON
658
- {
659
- "template": {"name": "$c360"},
660
- "appLabel": "C360 Semantic Model",
661
- "templateValues": {}
662
- }
663
- JSON
664
-
665
- create_resp=$(sf api request rest "$API_BASE/apps" \
666
- --target-org "$home" --method POST --body "@$create_file" 2>/dev/null || true)
667
- rm -f "$body_file" "$create_file"
668
-
669
- post_err=$(echo "$create_resp" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
670
- new_app_id=$(echo "$create_resp" | jq -r '.id // .appId // .applicationId // empty' 2>/dev/null)
671
-
672
- if [ -n "$post_err" ]; then
673
- case "$post_err" in
674
- DUPLICATE_VALUE|ITEM_ALREADY_EXISTS|APP_ALREADY_INSTALLED)
675
- # An existing Home C360 app is present, but it may be InProgressStatus
676
- # (still installing) or FailedStatus (prior attempt broke). Blindly
677
- # entering the SDM-visibility wait in either state would time out
678
- # and refuse with a misleading "SDM did NOT materialize/propagate"
679
- # message. Look up the existing app, decide based on its
680
- # applicationStatus:
681
- # SuccessStatus / SuccessWithWarningsStatus -> SDM-visibility wait
682
- # InProgressStatus -> enter the 30-min poll
683
- # FailedStatus -> refuse immediately
684
- echo "NOTE: C360 already installed on HOME $home ($post_err) - inspecting existing app state before SDM-visibility wait." >&2
685
- local existing_resp existing_status existing_id
686
- existing_resp=$(sf api request rest \
687
- "$API_BASE/apps?templateSourceId=$c360" \
688
- --target-org "$home" --method GET 2>/dev/null || true)
689
- existing_id=$(echo "$existing_resp" | jq -r '
690
- (.apps // .items // .records // [])
691
- | map(select(
692
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
693
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
694
- ))
695
- | .[0].id // .[0].appId // .[0].applicationId // empty
696
- ' 2>/dev/null)
697
- existing_status=$(echo "$existing_resp" | jq -r '
698
- (.apps // .items // .records // [])
699
- | map(select(
700
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
701
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
702
- ))
703
- | .[0].applicationStatus // .[0].status // empty
704
- ' 2>/dev/null)
705
- case "$existing_status" in
706
- SuccessStatus|SuccessWithWarningsStatus)
707
- echo "NOTE: existing HOME C360 app on $home is $existing_status - proceeding to SDM-visibility wait." >&2
708
- new_app_id=""
709
- ;;
710
- InProgressStatus)
711
- if [ -n "$existing_id" ]; then
712
- echo "NOTE: existing HOME C360 app on $home ($existing_id) is InProgressStatus - joining the 30-min poll." >&2
713
- new_app_id="$existing_id"
714
- else
715
- echo "REFUSING: HOME C360 app on $home is InProgressStatus but its id could not be resolved from /apps. Wait for it to reach SuccessStatus and re-invoke on $caller." >&2
716
- return 1
717
- fi
718
- ;;
719
- FailedStatus)
720
- echo "REFUSING: existing HOME C360 app on $home (id: ${existing_id:-<unknown>}) is at FailedStatus. Delete or reconfigure it in Setup -> Installed Apps on $home, then re-invoke this skill on $caller." >&2
721
- return 1
722
- ;;
723
- "")
724
- echo "NOTE: POST /apps reported $post_err but no C360 app was resolvable on HOME $home. Proceeding to SDM-visibility wait - readiness on $caller will surface any real failure." >&2
725
- new_app_id=""
726
- ;;
727
- *)
728
- echo "NOTE: existing HOME C360 app on $home reports applicationStatus=$existing_status - treating as non-terminal, proceeding to SDM-visibility wait." >&2
729
- new_app_id=""
730
- ;;
731
- esac
732
- ;;
733
- *)
734
- cat >&2 <<MSG
735
-
736
- REFUSING: POST /apps for C360 on HOME $home failed with $post_err.
737
- Install C360 manually on Home (Setup -> App Manager -> New App ->
738
- "C360 Semantic Model") then re-invoke this skill on $caller.
739
-
740
- MSG
741
- return 1
742
- ;;
743
- esac
744
- elif [ -z "$new_app_id" ]; then
745
- echo "REFUSING: POST /apps for C360 on HOME $home returned no id. Raw response:" >&2
746
- echo "$create_resp" | jq . >&2 2>/dev/null || echo "$create_resp" >&2
747
- return 1
748
- fi
749
-
750
- if [ -n "$new_app_id" ]; then
751
- echo "[2b state=INSTALL_C360_HOME org=$home] C360 app id=$new_app_id - polling (cap 30m)." >&2
752
- local poll_err
753
- while [ "$(date +%s)" -lt "$install_deadline" ]; do
754
- app_resp=$(sf api request rest "$API_BASE/apps/$new_app_id" \
755
- --target-org "$home" --method GET 2>/dev/null || true)
756
- # Auth session may have gone stale mid-poll (JWT expiry, token revoked).
757
- # Surface it immediately instead of grinding through the full 30-min
758
- # deadline with an empty .applicationStatus and refusing with a
759
- # misleading "did not reach SuccessStatus" message.
760
- poll_err=$(echo "$app_resp" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
761
- case "$poll_err" in
762
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
763
- cat >&2 <<MSG
764
-
765
- REFUSING: auth session expired mid-poll on HOME $home while waiting for the
766
- C360 install (app $new_app_id) to reach SuccessStatus ($poll_err).
767
- Refresh the org auth ('sf org login web --alias $home') and re-invoke this
768
- skill on $caller.
769
-
770
- MSG
771
- return 1
772
- ;;
773
- esac
774
- # Wire rule D: applicationStatus is canonical; .status is legacy.
775
- status=$(echo "$app_resp" | jq -r '.applicationStatus // .status // empty' 2>/dev/null)
776
- case "$status" in
777
- SuccessStatus|SuccessWithWarningsStatus)
778
- c360_log "INSTALL_C360_HOME reached=$status" "$home"
779
- break
780
- ;;
781
- FailedStatus)
782
- echo "REFUSING: HOME C360 install reached FailedStatus (app $new_app_id on $home). Inspect /apps/$new_app_id and remediate before retry." >&2
783
- return 1
784
- ;;
785
- esac
786
- sleep 30
787
- done
788
- case "$status" in
789
- SuccessStatus|SuccessWithWarningsStatus) : ;;
790
- *)
791
- echo "REFUSING: HOME C360 install on $home did not reach SuccessStatus within 30 min (last: ${status:-<empty>}, app $new_app_id)." >&2
792
- return 1
793
- ;;
794
- esac
795
- fi
796
-
797
- # SDM visibility wait — role-parametric.
798
- # On HOME/standalone the caller IS the publisher, so the SDM must
799
- # materialize locally in Data Cloud after the install lands. SuccessStatus
800
- # on /apps is necessary but NOT sufficient: downstream 2b.4/2b.5 and
801
- # readiness look up the SDM directly via /ssot, so we must confirm the
802
- # SDM is resolvable there before returning. Materialization is typically
803
- # seconds after Success but Data Cloud metadata rollout is asynchronous;
804
- # per user instruction the wait is UNBOUNDED (cap_secs=0) — it polls
805
- # every 30s until the SDM appears, or until an auth error / Ctrl-C exits.
806
- #
807
- # On COMPANION the SDM must propagate cross-org from HOME to CALLER via
808
- # multi-org sync. Publish latency depends on pairing state and platform
809
- # backlog and is not reliably bounded, so per user instruction the wait
810
- # is UNBOUNDED here too (cap_secs=0). Same helper, same discipline, target
811
- # is the caller org (not the publisher).
812
- if [ "$home" = "$caller" ]; then
813
- if wait_for_c360_sdm_visible "$caller" 0 "HOME self-install" "$home"; then
814
- c360_log "SELF_INSTALL_C360 done - SDM visible on $caller" "$caller"
815
- return 0
816
- fi
817
- return 1
818
- fi
819
-
820
- if wait_for_c360_sdm_visible "$caller" 0 "COMPANION propagation" "$home"; then
821
- return 0
822
- fi
823
- return 1
824
- }
825
-
826
- # ---- 2b.1: fetch the template's declared assets --------------------------
827
- # Response shape follows AppFrameworkTemplateAssetCollectionRepresentation:
828
- # entries under `.assets[]` (older), `.templateAssets[]` (older still), or
829
- # `.templatedAssets[]` (v68.0+, wire-verified 2026-08-26). Each entry carries
830
- # `assetType` and (for SemanticModel entries) a stringified `body` containing
831
- # the SDM definition. `baseModels` lives INSIDE that body — not on the entry.
832
- ASSETS_JSON=$(sf api request rest \
833
- "$API_BASE/templates/$TEMPLATE_NAME_OR_ID/assets" \
834
- --target-org "$ORG_ALIAS" --method GET 2>/dev/null)
835
-
836
- # Empty stdout on /assets is NOT "endpoint returned nothing" — it typically
837
- # means the sf CLI hit an auth failure and wrote the error to stderr (which
838
- # is suppressed here). Refuse loudly so we do not silently bypass 2b.3-2b.5.
839
- if [ -z "$ASSETS_JSON" ]; then
840
- cat >&2 <<MSG
841
-
842
- REFUSING: /app-framework/templates/$TEMPLATE_NAME_OR_ID/assets returned an
843
- empty body on $ORG_ALIAS while probing C360 dependency. This almost always
844
- means the sf CLI session is stale or the org is unreachable.
845
-
846
- Refresh the org auth ("sf org login web --alias $ORG_ALIAS") and re-invoke
847
- this skill. Do NOT retry blindly — silently proceeding here would bypass the
848
- entire C360 dependency gate and let a doomed install kick off.
849
-
850
- MSG
851
- return 1 2>/dev/null || exit 1
852
- fi
853
-
854
- # jq-shape-tolerant errorCode probe: some releases return the error as an
855
- # ARRAY `[{errorCode:...}]`, others as an OBJECT `{errorCode:...}`. Plain
856
- # `.[0].errorCode` raises "Cannot index object with number" on the object
857
- # form, which the `//` operator does NOT catch — use the `?` operator so
858
- # the failed index yields null and the second alternative fires.
859
- ASSETS_HAS_ERROR=$(echo "$ASSETS_JSON" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
860
-
861
- # Auth errors on /assets MUST refuse (same as 2b.3/2b.4). Do not let a stale
862
- # token on the very first probe silently disable the whole gate.
863
- case "$ASSETS_HAS_ERROR" in
864
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
865
- cat >&2 <<MSG
866
-
867
- REFUSING: auth session expired while probing $TEMPLATE_NAME_OR_ID's declared
868
- assets on $ORG_ALIAS. Refresh the org auth ("sf org login web --alias
869
- $ORG_ALIAS") and re-invoke this skill.
870
-
871
- MSG
872
- return 1 2>/dev/null || exit 1
873
- ;;
874
- esac
875
-
876
- # Non-auth error on /assets — release doesn't ship /assets, or 403 on the
877
- # endpoint itself. Log a NOTE and proceed; readiness at step 3 is the last
878
- # line of defense.
879
- if [ -n "$ASSETS_HAS_ERROR" ]; then
880
- echo "NOTE: /assets endpoint returned $ASSETS_HAS_ERROR — skipping C360 dependency check." >&2
881
- C360_REQUIRED="false"
882
- else
883
- # ---- 2b.2: scan SemanticModel assets for C360 base-model references ----
884
- # Wire-verified 2026-08-26 (v68.0), three coordinated drifts require Python
885
- # rather than jq for a correct scan:
886
- # (a) Response wrapper: coalesce `.assets` / `.templateAssets` /
887
- # `.templatedAssets` — v68 uses the third.
888
- # (b) Each SemanticModel entry stores its SDM as a stringified,
889
- # HTML-entity-encoded (`&quot;`) JSON blob under `.body`. `baseModels`
890
- # is INSIDE that parsed body, not on the entry itself.
891
- # (c) `baseModels` on v68 is an array of {apiName, label} objects, not
892
- # strings. Older releases emit strings and bare `baseModel`. Extract
893
- # `.apiName`/`.name` from dict entries in addition to raw string
894
- # entries — a `type == "string"` filter drops the C360 ref silently.
895
- # Match against the SDM name AND the C360 template apiName — release drift
896
- # has emitted both.
897
- C360_HITS=$(echo "$ASSETS_JSON" | python3 -c '
898
- import sys, json, html
899
- raw = sys.stdin.read()
900
- try:
901
- data = json.loads(raw)
902
- except Exception:
903
- sys.exit(0)
904
- assets = (data.get("assets")
905
- or data.get("templateAssets")
906
- or data.get("templatedAssets")
907
- or [])
908
- targets = {"C360SemanticModel_c360", "sfdc_internal__C360UnifiedSemanticModel"}
909
-
910
- def extract_refs(v):
911
- """Yield apiName-or-string base-model refs from any baseModels/baseModel value."""
912
- if v is None:
913
- return
914
- if isinstance(v, str):
915
- yield v
916
- elif isinstance(v, dict):
917
- for k in ("apiName", "name", "templateApiName", "baseModelApiName"):
918
- val = v.get(k)
919
- if isinstance(val, str):
920
- yield val
921
- break
922
- elif isinstance(v, list):
923
- for e in v:
924
- yield from extract_refs(e)
925
-
926
- def refs_from_container(obj):
927
- """Collect base-model refs from an SDM dict (top-level AND .definition)."""
928
- out = []
929
- if not isinstance(obj, dict):
930
- return out
931
- for k in ("baseModels", "baseModel"):
932
- out.extend(extract_refs(obj.get(k)))
933
- d = obj.get("definition")
934
- if isinstance(d, dict):
935
- for k in ("baseModels", "baseModel"):
936
- out.extend(extract_refs(d.get(k)))
937
- return out
938
-
939
- hits = []
940
- for entry in assets:
941
- if not isinstance(entry, dict):
942
- continue
943
- if entry.get("assetType") != "SemanticModel":
944
- continue
945
- # Try the entry itself first (older releases stored SDM inline).
946
- refs = refs_from_container(entry)
947
- # Then parse the stringified, HTML-encoded body (v68+ shape).
948
- body = entry.get("body")
949
- if isinstance(body, str) and body.strip():
950
- try:
951
- parsed = json.loads(html.unescape(body))
952
- refs.extend(refs_from_container(parsed))
953
- except Exception:
954
- pass
955
- if any(r in targets for r in refs):
956
- name = (entry.get("templatedAssetName")
957
- or entry.get("apiName")
958
- or entry.get("name")
959
- or entry.get("fileName")
960
- or "<unnamed SemanticModel>")
961
- hits.append(name)
962
-
963
- for h in hits:
964
- print(h)
965
- ')
966
-
967
- if [ -n "$C360_HITS" ]; then
968
- C360_REQUIRED="true"
969
- echo "C360 dependency detected in $TEMPLATE_NAME_OR_ID. Extending SDMs:" >&2
970
- echo "$C360_HITS" | sed 's/^/ - /' >&2
971
- else
972
- C360_REQUIRED="false"
973
- fi
974
- fi
975
-
976
- # =========================================================================
977
- # If C360_REQUIRED, run the 3-step gate: 2b.3 → 2b.4 → 2b.5
978
- # Route-resolution runs FIRST so 2b.3/2b.4/2b.5 know which org to probe:
979
- # - HOME caller: RESOLVED_ORG_FOR_C360 = $ORG_ALIAS (existing behavior)
980
- # - COMPANION caller: RESOLVED_ORG_FOR_C360 = $HOME_ORG_ALIAS
981
- # 2b.3 (SDM presence) ALWAYS targets $ORG_ALIAS in both roles - Home->
982
- # Companion propagation IS the actionable signal we're gating on.
983
- # 2b.4 and 2b.5 target $RESOLVED_ORG_FOR_C360 (Home on companion path).
984
- # =========================================================================
985
- if [ "$C360_REQUIRED" = "true" ]; then
986
-
987
- if ! resolve_c360_gate_orgs; then
988
- # Helper already emitted an actionable refusal to stderr.
989
- return 1 2>/dev/null || exit 1
990
- fi
991
-
992
- # ---- 2b.3: SDM presence check on THIS org ------------------------------
993
- # The C360 Semantic Model must be resolvable in this org's Data Cloud
994
- # BEFORE we bother checking installed apps or versions. If the SDM isn't
995
- # visible here, the org's Data Cloud has no C360 publisher — the user has
996
- # to install C360 in the Home org that publishes to this Data Cloud.
997
- #
998
- # Note: $API_BASE ends in /app-framework — the semantic-models endpoint is
999
- # under /ssot, so we construct the URL fresh instead of reusing $API_BASE.
1000
- #
1001
- # Defense-in-depth: /ssot has historically been served at two path prefixes
1002
- # across releases: /ssot/semantic/models/{name} (current) and older nested
1003
- # forms like /ssot/semantic-models/{name}. We try the canonical path first,
1004
- # then fall through to a reasonable alternate if the first path 404s at the
1005
- # route level (URL_NOT_FOUND / NOT_FOUND at path, not on the SDM name).
1006
- SDM_URL_PRIMARY="/services/data/$API_VERSION/ssot/semantic/models/C360SemanticModel_c360"
1007
- SDM_URL_ALT="/services/data/$API_VERSION/ssot/semantic-models/C360SemanticModel_c360"
1008
-
1009
- SDM_RESP=$(sf api request rest "$SDM_URL_PRIMARY" \
1010
- --target-org "$ORG_ALIAS" --method GET 2>/dev/null || true)
1011
-
1012
- # jq shape-tolerant errorCode probe — see note on 2b.1.
1013
- SDM_ERROR=$(echo "$SDM_RESP" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
1014
-
1015
- # Alt-path retry: fires when the PRIMARY URL is unreachable at the route
1016
- # level (empty body → likely gateway 4xx/5xx; OR the primary responded with
1017
- # a route-not-found error code). Older orgs served /ssot/semantic-models/
1018
- # instead of /ssot/semantic/models/.
1019
- if [ -z "$SDM_RESP" ] || echo "$SDM_ERROR" | grep -qE '^(URL_NOT_FOUND|NOT_FOUND|UNSUPPORTED_API_VERSION)$'; then
1020
- SDM_RESP=$(sf api request rest "$SDM_URL_ALT" \
1021
- --target-org "$ORG_ALIAS" --method GET 2>/dev/null || true)
1022
- SDM_ERROR=$(echo "$SDM_RESP" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
1023
- fi
1024
-
1025
- # If the error is INVALID_SESSION_ID / INVALID_AUTH_HEADER, the sf CLI's
1026
- # cached access token is stale — nudge the user to refresh and abort this
1027
- # step, don't misinterpret it as "SDM missing."
1028
- case "$SDM_ERROR" in
1029
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
1030
- cat >&2 <<MSG
1031
-
1032
- REFUSING: auth session expired while checking the C360 SDM presence on
1033
- $ORG_ALIAS. Refresh the org auth ("sf org login web --alias $ORG_ALIAS")
1034
- and re-invoke this skill.
1035
-
1036
- MSG
1037
- return 1 2>/dev/null || exit 1
1038
- ;;
1039
- esac
1040
-
1041
- # Permission / feature-gate errors on /ssot are NOT "SDM missing" — they
1042
- # mean the caller can't reach the endpoint. Degrade to unknown with a
1043
- # targeted NOTE so the user has an actionable message, not the misleading
1044
- # "install C360 in the Home org" refusal.
1045
- if echo "$SDM_ERROR" | grep -qE '^(INSUFFICIENT_ACCESS|API_DISABLED_FOR_ORG|FUNCTIONALITY_NOT_ENABLED|ACCESS_DENIED)$'; then
1046
- echo "NOTE: /ssot/semantic/models returned $SDM_ERROR on $ORG_ALIAS — caller lacks Data Cloud read perms or Data Cloud is disabled. Skipping SDM presence check; readiness will surface any real base-model failure." >&2
1047
- SDM_PRESENT="unknown"
1048
- # Route-level path-not-found (both endpoints unreachable) — degrade cleanly.
1049
- elif echo "$SDM_ERROR" | grep -qE '^(URL_NOT_FOUND|NOT_FOUND|UNSUPPORTED_API_VERSION)$'; then
1050
- echo "NOTE: /ssot semantic-models endpoint unreachable at $API_VERSION — skipping C360 SDM presence check." >&2
1051
- SDM_PRESENT="unknown"
1052
- elif [ -z "$SDM_ERROR" ] && [ -n "$SDM_RESP" ] && [ "$(echo "$SDM_RESP" | jq -r 'type' 2>/dev/null)" = "object" ]; then
1053
- # Sanity: the response must actually look like the requested SDM. Some
1054
- # releases return `{name, apiName, ...}`, others `{semanticModel: {...}}`.
1055
- SDM_NAME_FIELD=$(echo "$SDM_RESP" | jq -r '.name // .apiName // .semanticModel.name // .semanticModel.apiName // empty' 2>/dev/null)
1056
- if [ -n "$SDM_NAME_FIELD" ]; then
1057
- SDM_PRESENT="true"
1058
- else
1059
- SDM_PRESENT="false"
1060
- fi
1061
- else
1062
- # Empty body OR any other non-classified error → treat as "SDM missing"
1063
- # from this org's perspective. This is the branch that fires the
1064
- # "publish C360 from the Home org" refusal below.
1065
- SDM_PRESENT="false"
1066
- fi
1067
-
1068
- if [ "$SDM_PRESENT" = "false" ]; then
1069
- case "$ORG_ROLE" in
1070
- COMPANION)
1071
- # AUTO path: install C360 on the resolved HOME_ORG_ALIAS, then poll
1072
- # $ORG_ALIAS for SDM propagation. Bounded ~50 min total (30 install
1073
- # + 20 sync). On success SDM_PRESENT flips true and 2b.4/2b.5 continue.
1074
- echo "[2b state=PROBE_SDM_FALSE role=COMPANION] SDM absent on $ORG_ALIAS - auto-installing on HOME $HOME_ORG_ALIAS." >&2
1075
- if install_c360_on_home_and_wait_for_sync; then
1076
- SDM_PRESENT="true"
1077
- else
1078
- # Helper already printed the exact-failure refusal.
1079
- return 1 2>/dev/null || exit 1
1080
- fi
1081
- ;;
1082
- HOME|*)
1083
- # AUTO path (standalone / paired-HOME): caller IS the publisher, so
1084
- # install C360 on THIS org. Bounded 30 min /apps poll + 10 min SDM
1085
- # visibility poll on the same org. SuccessStatus on /apps is NOT the
1086
- # same as "SDM materialized" - Data Cloud metadata rollout is
1087
- # asynchronous, so we explicitly wait for the SDM to be resolvable
1088
- # via /ssot before returning. There is no cross-org propagation
1089
- # on this branch (companion sync doesn't apply).
1090
- echo "[2b state=PROBE_SDM_FALSE role=HOME] SDM absent on $ORG_ALIAS - auto-installing C360 on the same org (standalone HOME) and waiting for SDM visibility." >&2
1091
- if install_c360_on_home_and_wait_for_sync; then
1092
- SDM_PRESENT="true"
1093
- else
1094
- # Helper already printed the exact-failure refusal.
1095
- return 1 2>/dev/null || exit 1
1096
- fi
1097
- ;;
1098
- esac
1099
- fi
1100
-
1101
- # ---- 2b.4: installed C360 app on THIS org + its templateVersion --------
1102
- # WIRE FACT (verified 2026-08-25): /app-framework/apps ignores
1103
- # applicationStatus as a query param. Only templateSourceId is a valid
1104
- # server-side filter. We MUST filter status client-side.
1105
- #
1106
- # Wrapper drift: the list body has been served under `.apps`, `.items`,
1107
- # and `.records` across releases. Coalesce all three.
1108
- #
1109
- # Target-org routing: on COMPANION path, the installed C360 app lives on
1110
- # HOME - probing the companion returns zero rows and would false-refuse.
1111
- # $RESOLVED_ORG_FOR_C360 already reflects this (= $ORG_ALIAS on HOME,
1112
- # = $HOME_ORG_ALIAS on COMPANION).
1113
- c360_log "PROBE_INSTALLED" "$RESOLVED_ORG_FOR_C360"
1114
- APPS_RESP=$(sf api request rest \
1115
- "$API_BASE/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel" \
1116
- --target-org "$RESOLVED_ORG_FOR_C360" --method GET 2>/dev/null || true)
1117
-
1118
- # Empty body → treat as "no installed C360 app" but log the wire anomaly.
1119
- if [ -z "$APPS_RESP" ]; then
1120
- echo "WARN: /apps returned empty body while checking C360 install state — treating as not-installed." >&2
1121
- APPS_RESP='{}'
1122
- fi
1123
-
1124
- APPS_ERROR=$(echo "$APPS_RESP" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
1125
- case "$APPS_ERROR" in
1126
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
1127
- cat >&2 <<MSG
1128
-
1129
- REFUSING: auth session expired while checking installed C360 app on
1130
- $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE; caller: $ORG_ALIAS). Refresh the
1131
- org auth ('sf org login web --alias $RESOLVED_ORG_FOR_C360') and re-invoke.
1132
-
1133
- MSG
1134
- return 1 2>/dev/null || exit 1
1135
- ;;
1136
- esac
1137
-
1138
- # First: count SuccessStatus C360 app entries — this is the "is C360
1139
- # installed at all on this org?" signal. Separate from "can we read its
1140
- # templateVersion?" — release drift on the version-field path is NOT the
1141
- # same failure mode as "no successful install."
1142
- INSTALLED_COUNT=$(echo "$APPS_RESP" | jq -r '
1143
- (.apps // .items // .records // [])
1144
- | map(select(
1145
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
1146
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
1147
- ))
1148
- | map(select(
1149
- ((.applicationStatus // .status // "") == "SuccessStatus") or
1150
- ((.applicationStatus // .status // "") == "SuccessWithWarningsStatus")
1151
- ))
1152
- | length
1153
- ')
1154
-
1155
- if [ "${INSTALLED_COUNT:-0}" = "0" ]; then
1156
- cat >&2 <<MSG
1157
-
1158
- REFUSING: $TEMPLATE_NAME_OR_ID extends the C360 Semantic Model, and the
1159
- C360 SDM is visible in $ORG_ALIAS's Data Cloud, but there is no
1160
- successfully-installed C360 Semantic Model app on $RESOLVED_ORG_FOR_C360
1161
- (role: $ORG_ROLE; caller: $ORG_ALIAS) - no entry with applicationStatus
1162
- in (SuccessStatus, SuccessWithWarningsStatus).
1163
-
1164
- Either the C360 app install is still in progress, or it FailedStatus'd,
1165
- or C360 has only been published from another org (not installed here).
1166
-
1167
- Action required (on $RESOLVED_ORG_FOR_C360):
1168
- Option A — Install / re-install C360 on $RESOLVED_ORG_FOR_C360:
1169
- 1. Setup -> App Manager -> New App -> select
1170
- "C360 Semantic Model" (or re-invoke this skill with template name
1171
- "C360 Semantic Model" against $RESOLVED_ORG_FOR_C360).
1172
- 2. Wait for applicationStatus in (SuccessStatus,
1173
- SuccessWithWarningsStatus) - typically 5-10 min.
1174
- 3. Re-invoke this skill for $TEMPLATE_NAME_OR_ID on $ORG_ALIAS.
1175
-
1176
- Option B — Wait for in-flight install:
1177
- 1. Setup -> Installed Apps -> "C360 Semantic Model" on
1178
- $RESOLVED_ORG_FOR_C360 - check status.
1179
- 2. Re-invoke once the app reaches SuccessStatus /
1180
- SuccessWithWarningsStatus (and, for COMPANION path, the SDM has
1181
- propagated to $ORG_ALIAS).
1182
-
1183
- MSG
1184
- return 1 2>/dev/null || exit 1
1185
- fi
1186
-
1187
- # Extract the successfully-installed C360 app's templateVersion.
1188
- # `templateVersion` has historically lived at the top level of each app
1189
- # entry; on older releases it was nested under `.templateInfo.templateVersion`
1190
- # or `.installedTemplate.templateVersion`. Coalesce all three.
1191
- INSTALLED_TEMPLATE_VERSION=$(echo "$APPS_RESP" | jq -r '
1192
- (.apps // .items // .records // [])
1193
- | map(select(
1194
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
1195
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
1196
- ))
1197
- | map(select(
1198
- ((.applicationStatus // .status // "") == "SuccessStatus") or
1199
- ((.applicationStatus // .status // "") == "SuccessWithWarningsStatus")
1200
- ))
1201
- | map(.templateVersion
1202
- // .templateInfo.templateVersion
1203
- // .installedTemplate.templateVersion
1204
- // empty)
1205
- | map(select(. != null and . != ""))
1206
- | .[0] // empty
1207
- ')
1208
-
1209
- # Also capture the installed C360 app's id. Needed by 2b.5's auto-update
1210
- # branch (PUT /apps/{id} via step 6b's update_app). Same filter as above
1211
- # so we pick the app whose version we just extracted.
1212
- INSTALLED_C360_APP_ID=$(echo "$APPS_RESP" | jq -r '
1213
- (.apps // .items // .records // [])
1214
- | map(select(
1215
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
1216
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
1217
- ))
1218
- | map(select(
1219
- ((.applicationStatus // .status // "") == "SuccessStatus") or
1220
- ((.applicationStatus // .status // "") == "SuccessWithWarningsStatus")
1221
- ))
1222
- | map(.id // .appId // .applicationId // empty)
1223
- | map(select(. != null and . != ""))
1224
- | .[0] // empty
1225
- ')
1226
-
1227
- # ---- 2b.5: MANDATORY C360 templateVersion parity check ------------------
1228
- # The user requirement (2026-08-31): whenever the CALLING template declares
1229
- # a C360 SDM base-model dependency (2b.2), this skill MUST verify that the
1230
- # installed C360 app's templateVersion is in parity with the C360 template
1231
- # version published on the org - installed >= current. There is NO soft-
1232
- # degrade / silent-skip path. If either side of the compare is unreadable,
1233
- # we refuse with actionable remediation rather than proceeding blind.
1234
- #
1235
- # This mirrors the SDM-presence gate (2b.3) in shape: mandatory,
1236
- # non-bypassable, and always emits a clear user-facing verdict.
1237
- if [ -z "$INSTALLED_TEMPLATE_VERSION" ]; then
1238
- # C360 app IS installed ($INSTALLED_COUNT >= 1) but every candidate
1239
- # templateVersion path is empty. Historically this branch soft-degraded
1240
- # to WARN + proceed; per the mandatory-parity policy it now refuses.
1241
- # Rationale: proceeding on an unreadable installed version can silently
1242
- # install $TEMPLATE_NAME_OR_ID against a stale C360, which fails late
1243
- # during base-model resolution after minutes of downstream provisioning.
1244
- cat >&2 <<MSG
1245
-
1246
- REFUSING: $TEMPLATE_NAME_OR_ID extends the C360 Semantic Model, but this
1247
- skill could NOT read the installed C360 app's templateVersion on
1248
- $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE; caller: $ORG_ALIAS).
1249
-
1250
- C360 apps in terminal-success state on $RESOLVED_ORG_FOR_C360: $INSTALLED_COUNT
1251
- Installed C360 templateVersion: <unreadable>
1252
- Fields probed: .templateVersion, .templateInfo.templateVersion,
1253
- .installedTemplate.templateVersion (all empty)
1254
-
1255
- MANDATORY C360 TEMPLATE-VERSION PARITY CHECK cannot proceed without both
1256
- versions. This is not a soft-skip - $TEMPLATE_NAME_OR_ID was built against
1257
- the current C360 SDM, and installing against an unknown-version C360 app
1258
- risks a late base-model-resolution failure.
1259
-
1260
- Action required (run on $RESOLVED_ORG_FOR_C360, then re-invoke this skill):
1261
-
1262
- 1. Inspect the raw /apps response and find where templateVersion lives:
1263
- sf api request rest \\
1264
- '$API_BASE/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel' \\
1265
- --target-org $RESOLVED_ORG_FOR_C360 --method GET | jq
1266
-
1267
- 2. If templateVersion exists under a new path, file a bug and share the
1268
- wire shape so 2b.4's coalesce chain can be extended.
1269
-
1270
- 3. Fallback: reconfigure the installed C360 app via
1271
- App Hub -> C360 App tile -> chevron -> Update. The Reconfigure re-lands
1272
- templateVersion in the canonical top-level field.
1273
-
1274
- MSG
1275
- return 1 2>/dev/null || exit 1
1276
- fi
1277
- echo "Installed C360 templateVersion on $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE): $INSTALLED_TEMPLATE_VERSION" >&2
1278
-
1279
- # The template's *current* version lives at
1280
- # GET /app-framework/templates/{apiName} -> .releaseInfo.templateVersion.
1281
- # Some older releases also expose it as top-level .templateVersion; coalesce.
1282
- # Target-org routing: on COMPANION, the template metadata that matters
1283
- # is the one on the HOME org (the version the shared C360 was installed
1284
- # against). $RESOLVED_ORG_FOR_C360 already routes correctly.
1285
- c360_log "PROBE_TEMPLATE" "$RESOLVED_ORG_FOR_C360"
1286
- C360_TEMPLATE_RESP=$(sf api request rest \
1287
- "$API_BASE/templates/sfdc_internal__C360UnifiedSemanticModel" \
1288
- --target-org "$RESOLVED_ORG_FOR_C360" --method GET 2>/dev/null || true)
1289
-
1290
- # Empty-body guard: sf CLI's `2>/dev/null || true` swallows auth-error stderr
1291
- # and returns empty stdout on stale JWT. The jq errorCode probe on empty
1292
- # input yields empty (misses the auth case), and CURRENT_TEMPLATE_VERSION
1293
- # extraction below also yields empty - which would misroute the operator
1294
- # into the "current version unreadable" refusal (whose remediation is
1295
- # inspect-wire-shape, NOT `sf org login web`). Mirror the /assets handler's
1296
- # empty-body branch and refuse with an auth-refresh remediation here.
1297
- if [ -z "$C360_TEMPLATE_RESP" ]; then
1298
- cat >&2 <<MSG
1299
-
1300
- REFUSING: GET $API_BASE/templates/sfdc_internal__C360UnifiedSemanticModel
1301
- returned an empty body on $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE; caller:
1302
- $ORG_ALIAS). sf CLI typically writes the auth error to stderr in this case;
1303
- an expired session on the /templates GET is the most common cause.
1304
-
1305
- Refresh the org auth and re-invoke:
1306
- sf org login web --alias $RESOLVED_ORG_FOR_C360
1307
-
1308
- MSG
1309
- return 1 2>/dev/null || exit 1
1310
- fi
1311
-
1312
- # Auth-error probe on the template GET - mirrors the /apps handler above.
1313
- # Without this, an expired session mid-2b.5 would leave CURRENT_TEMPLATE_VERSION
1314
- # empty and route into the "current version unreadable" refusal below, which
1315
- # is the WRONG remediation for an auth problem.
1316
- C360_TEMPLATE_ERROR=$(echo "$C360_TEMPLATE_RESP" | jq -r '.[0]?.errorCode // .errorCode // empty' 2>/dev/null)
1317
- case "$C360_TEMPLATE_ERROR" in
1318
- INVALID_SESSION_ID|INVALID_AUTH_HEADER|INVALID_JWT_FORMAT)
1319
- cat >&2 <<MSG
1320
-
1321
- REFUSING: auth session expired while fetching the current C360 template
1322
- metadata on $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE; caller: $ORG_ALIAS).
1323
- Refresh the org auth ('sf org login web --alias $RESOLVED_ORG_FOR_C360')
1324
- and re-invoke.
1325
-
1326
- MSG
1327
- return 1 2>/dev/null || exit 1
1328
- ;;
1329
- esac
1330
-
1331
- CURRENT_TEMPLATE_VERSION=$(echo "$C360_TEMPLATE_RESP" | jq -r '
1332
- .releaseInfo.templateVersion
1333
- // .templateVersion
1334
- // .release.templateVersion
1335
- // empty
1336
- ')
1337
-
1338
- if [ -z "$CURRENT_TEMPLATE_VERSION" ]; then
1339
- # Historically this branch soft-degraded to WARN + proceed. Under the
1340
- # mandatory-parity policy it now refuses - the compare cannot run
1341
- # without a right-hand side, and proceeding blind risks a late base-
1342
- # model-resolution failure identical to the installed-version-empty
1343
- # branch above.
1344
- cat >&2 <<MSG
1345
-
1346
- REFUSING: $TEMPLATE_NAME_OR_ID extends the C360 Semantic Model, but this
1347
- skill could NOT read the current C360 template version from
1348
- GET $API_BASE/templates/sfdc_internal__C360UnifiedSemanticModel on
1349
- $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE; caller: $ORG_ALIAS).
1350
-
1351
- Installed C360 templateVersion: $INSTALLED_TEMPLATE_VERSION
1352
- Current C360 templateVersion (template metadata): <unreadable>
1353
- Fields probed: .releaseInfo.templateVersion, .templateVersion,
1354
- .release.templateVersion (all empty)
1355
-
1356
- MANDATORY C360 TEMPLATE-VERSION PARITY CHECK cannot proceed without both
1357
- versions. Do NOT interpret this as "C360 is up to date" - we simply cannot
1358
- tell.
1359
-
1360
- Action required (run on $RESOLVED_ORG_FOR_C360, then re-invoke this skill):
1361
-
1362
- 1. Inspect the raw /templates response:
1363
- sf api request rest \\
1364
- '$API_BASE/templates/sfdc_internal__C360UnifiedSemanticModel' \\
1365
- --target-org $RESOLVED_ORG_FOR_C360 --method GET | jq
1366
-
1367
- 2. If templateVersion has moved to a new path, share the wire shape so
1368
- 2b.5's coalesce chain can be extended.
1369
-
1370
- 3. If the /templates endpoint itself is failing (403, 404, feature-gate),
1371
- resolve the underlying access issue before retrying.
1372
-
1373
- MSG
1374
- return 1 2>/dev/null || exit 1
1375
- fi
1376
- echo "Current C360 templateVersion on $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE): $CURRENT_TEMPLATE_VERSION" >&2
1377
-
1378
- # Portable numeric-tuple compare. templateVersion has been seen in
1379
- # multiple shapes across releases:
1380
- # "4.0", "4.1", "10.0" (2-part)
1381
- # "4.0.0", "4.1.2" (3-part)
1382
- # "4.0.0.1" (4-part, rare)
1383
- # A plain string compare fails ("10.0" < "4.0" lexicographically) and
1384
- # `sort -V` is GNU-only (not on stock macOS `sort`). We roll a pure-bash
1385
- # n-tuple compare with an awk fallback so we don't depend on GNU coreutils.
1386
- compare_versions() {
1387
- # Returns: -1 if $1 < $2
1388
- # 0 if $1 == $2
1389
- # 1 if $1 > $2
1390
- local a="$1" b="$2"
1391
- # Split on '.' into arrays, pad the shorter with zeros so "4.0" cmp "4.0.0" == 0.
1392
- local -a A B
1393
- IFS='.' read -r -a A <<< "$a"
1394
- IFS='.' read -r -a B <<< "$b"
1395
- local n=${#A[@]}
1396
- (( ${#B[@]} > n )) && n=${#B[@]}
1397
- local i ai bi
1398
- for (( i=0; i<n; i++ )); do
1399
- ai=${A[i]:-0}
1400
- bi=${B[i]:-0}
1401
- # Strip any non-digit suffix (e.g. "4.0-beta") — take the leading int only.
1402
- ai=${ai%%[!0-9]*}; bi=${bi%%[!0-9]*}
1403
- ai=${ai:-0}; bi=${bi:-0}
1404
- if (( 10#$ai < 10#$bi )); then echo -1; return; fi
1405
- if (( 10#$ai > 10#$bi )); then echo 1; return; fi
1406
- done
1407
- echo 0
1408
- }
1409
-
1410
- # awk fallback — used if the bash function above hits a shell we don't
1411
- # trust (e.g. someone runs this under `sh` with no arrays). Same semantics.
1412
- compare_versions_awk() {
1413
- awk -v a="$1" -v b="$2" '
1414
- BEGIN {
1415
- na = split(a, A, ".");
1416
- nb = split(b, B, ".");
1417
- n = (na > nb) ? na : nb;
1418
- for (i = 1; i <= n; i++) {
1419
- ai = (i <= na) ? A[i] : 0;
1420
- bi = (i <= nb) ? B[i] : 0;
1421
- # strip non-digit suffix
1422
- gsub(/[^0-9].*$/, "", ai);
1423
- gsub(/[^0-9].*$/, "", bi);
1424
- if (ai == "") ai = 0;
1425
- if (bi == "") bi = 0;
1426
- ai += 0; bi += 0;
1427
- if (ai < bi) { print -1; exit }
1428
- if (ai > bi) { print 1; exit }
1429
- }
1430
- print 0
1431
- }'
1432
- }
1433
-
1434
- # Prefer the pure-bash function; if BASH isn't set (e.g. sourced from sh),
1435
- # fall back to awk.
1436
- if [ -n "${BASH_VERSION:-}" ]; then
1437
- CMP=$(compare_versions "$INSTALLED_TEMPLATE_VERSION" "$CURRENT_TEMPLATE_VERSION")
1438
- else
1439
- CMP=$(compare_versions_awk "$INSTALLED_TEMPLATE_VERSION" "$CURRENT_TEMPLATE_VERSION")
1440
- fi
1441
-
1442
- case "$CMP" in
1443
- 0)
1444
- echo "C360 dependency satisfied — installed templateVersion matches current ($INSTALLED_TEMPLATE_VERSION)." >&2
1445
- ;;
1446
- -1)
1447
- # ORG_ROLE=HOME -> caller IS the resource owner. No shared-blast-
1448
- # radius concern. AUTO-REMEDIATE inline via
1449
- # PUT /apps/{id} (step 6b update_app + poll).
1450
- # On success, set AUTO_UPDATE_PARITY_REACHED=true
1451
- # which skips the STOP + wait-for-parity block
1452
- # and lets the install proceed to readiness.
1453
- # On failure, fall through to the STOP + wait-
1454
- # for-parity path below (UI fallback).
1455
- # ORG_ROLE=COMPANION -> $RESOLVED_ORG_FOR_C360 is a shared HOME.
1456
- # Auto-updating from a companion's context
1457
- # mutates a resource other companions depend on.
1458
- # REFUSE the auto-update by default. Caller can
1459
- # opt in explicitly via the env var
1460
- # TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE=1
1461
- # or, when running under a TTY, by answering "y"
1462
- # to a one-time confirm prompt. Opt-in runs the
1463
- # same PUT + poll code path as HOME. If opt-in
1464
- # is absent (or the TTY prompt is declined),
1465
- # STOP and drop into the wait-for-parity loop
1466
- # expecting an out-of-band Reconfigure.
1467
- local AUTO_UPDATE_PARITY_REACHED=""
1468
- local COMPANION_OPT_IN=""
1469
- if [ "$ORG_ROLE" = "COMPANION" ]; then
1470
- if [ "${TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE:-}" = "1" ]; then
1471
- COMPANION_OPT_IN="env"
1472
- echo "[2b.5 auto-update role=COMPANION] TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE=1 detected - opting in to shared-HOME auto-update on $RESOLVED_ORG_FOR_C360." >&2
1473
- elif [ -t 0 ] && [ -t 2 ]; then
1474
- # TTY prompt. Bounded by a 60 s read timeout so a stalled
1475
- # terminal never wedges the run. Default is NO on empty /
1476
- # non-y input to preserve the "refuse-by-default" invariant.
1477
- cat >&2 <<MSG
1478
-
1479
- CONFIRM SHARED-HOME AUTO-UPDATE (COMPANION $ORG_ALIAS)
1480
- ------------------------------------------------------
1481
- The installed C360 app on HOME $RESOLVED_ORG_FOR_C360 is at
1482
- templateVersion $INSTALLED_TEMPLATE_VERSION but the current C360
1483
- template ships at $CURRENT_TEMPLATE_VERSION. Continuing the install
1484
- of $TEMPLATE_NAME_OR_ID on $ORG_ALIAS requires this to be updated.
1485
-
1486
- $RESOLVED_ORG_FOR_C360 is a HOME shared by every companion paired to
1487
- it - reconfiguring the C360 app there will affect ALL of them, not
1488
- just this companion. Before proceeding, confirm that ALL companion
1489
- consumers (not just $ORG_ALIAS) are OK with the C360 update.
1490
-
1491
- Type 'y' + Enter within 60 s to proceed with the auto-update via
1492
- PUT /apps/{id} on $RESOLVED_ORG_FOR_C360. Anything else (or timeout)
1493
- declines and drops into the wait-for-parity loop below.
1494
-
1495
- MSG
1496
- local companion_reply=""
1497
- # `read -t 60` returns non-zero on timeout; guard with `|| true`
1498
- # so `set -e`-like shells don't abort the whole skill.
1499
- read -r -t 60 -p "Proceed with shared-HOME C360 update? [y/N]: " companion_reply </dev/tty 2>/dev/null || true
1500
- case "${companion_reply:-}" in
1501
- y|Y|yes|YES|Yes)
1502
- COMPANION_OPT_IN="tty"
1503
- echo "[2b.5 auto-update role=COMPANION] TTY opt-in confirmed - proceeding with shared-HOME auto-update on $RESOLVED_ORG_FOR_C360." >&2
1504
- ;;
1505
- *)
1506
- echo "[2b.5 auto-update role=COMPANION] TTY opt-in declined (reply='${companion_reply:-<timeout>}'). Falling through to wait-for-parity." >&2
1507
- ;;
1508
- esac
1509
- else
1510
- echo "[2b.5 auto-update role=COMPANION] no TTY and TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE!=1 - refusing shared-HOME auto-update by default. Falling through to wait-for-parity." >&2
1511
- fi
1512
- fi
1513
-
1514
- if [ "$ORG_ROLE" = "HOME" ] || [ -n "$COMPANION_OPT_IN" ]; then
1515
- # AU_ROLE_TAG carries the acting role + opt-in mode for logs so
1516
- # a shared-HOME COMPANION opt-in is distinguishable from a
1517
- # standard HOME self-update in the transcript.
1518
- local AU_ROLE_TAG="role=$ORG_ROLE"
1519
- [ -n "$COMPANION_OPT_IN" ] && AU_ROLE_TAG="role=COMPANION opt-in=$COMPANION_OPT_IN"
1520
- if [ -z "$INSTALLED_C360_APP_ID" ]; then
1521
- echo "[2b.5 auto-update $AU_ROLE_TAG] WARN: INSTALLED_C360_APP_ID is empty - cannot drive PUT /apps/{id}. Falling through to wait-for-parity." >&2
1522
- else
1523
- echo "[2b.5 auto-update $AU_ROLE_TAG] driving PUT /apps/$INSTALLED_C360_APP_ID on $RESOLVED_ORG_FOR_C360 to upgrade C360 templateVersion $INSTALLED_TEMPLATE_VERSION -> $CURRENT_TEMPLATE_VERSION." >&2
1524
- # C360 install accepts an empty templateValues on update — same
1525
- # shape as the auto-installer's create body. TEMPLATE_VALUES_RAW
1526
- # is the CALLING template's values, which is the wrong body for
1527
- # the C360 app's update. Pass '{}' explicitly to reuse the same
1528
- # zero-inputs shape as install_c360_on_home_and_wait_for_sync.
1529
- if update_app "$INSTALLED_C360_APP_ID" "$RESOLVED_ORG_FOR_C360" '{}' \
1530
- && poll_update_to_terminal "$INSTALLED_C360_APP_ID" "$RESOLVED_ORG_FOR_C360" 1800; then
1531
- echo "[2b.5 auto-update $AU_ROLE_TAG] PUT + poll succeeded - re-fetching versions to confirm parity." >&2
1532
- # Re-fetch the installed version. Current version is refetched
1533
- # against the same target-org for symmetry.
1534
- local reup_apps reup_tpl reup_installed reup_current reup_cmp
1535
- reup_apps=$(sf api request rest \
1536
- "$API_BASE/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel" \
1537
- --target-org "$RESOLVED_ORG_FOR_C360" --method GET 2>/dev/null || true)
1538
- reup_installed=$(echo "$reup_apps" | jq -r '
1539
- (.apps // .items // .records // [])
1540
- | map(select(
1541
- ((.applicationStatus // .status // "") | test("^Success(Status|WithWarningsStatus)$"))
1542
- and (
1543
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
1544
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
1545
- )
1546
- ))
1547
- | .[0]
1548
- | (.templateVersion // .templateInfo.templateVersion // .installedTemplate.templateVersion // empty)
1549
- ')
1550
- reup_tpl=$(sf api request rest \
1551
- "$API_BASE/templates/sfdc_internal__C360UnifiedSemanticModel" \
1552
- --target-org "$RESOLVED_ORG_FOR_C360" --method GET 2>/dev/null || true)
1553
- reup_current=$(echo "$reup_tpl" | jq -r '
1554
- .releaseInfo.templateVersion // .templateVersion // .release.templateVersion // empty
1555
- ')
1556
- if [ -n "$reup_installed" ] && [ -n "$reup_current" ]; then
1557
- if [ -n "${BASH_VERSION:-}" ]; then
1558
- reup_cmp=$(compare_versions "$reup_installed" "$reup_current")
1559
- else
1560
- reup_cmp=$(compare_versions_awk "$reup_installed" "$reup_current")
1561
- fi
1562
- case "$reup_cmp" in
1563
- 0|1)
1564
- echo "[2b.5 auto-update $AU_ROLE_TAG] parity reached after PUT: installed=$reup_installed current=$reup_current." >&2
1565
- # On COMPANION, the updated C360 SDM must PROPAGATE
1566
- # from HOME to CALLER before readiness on $ORG_ALIAS
1567
- # can bind against it. Wait unbounded (same discipline
1568
- # as the install-time SDM-visibility wait, per the
1569
- # user's "keep polling till the SDM is available"
1570
- # instruction). HOME path skips this — the SDM is
1571
- # local and readiness will re-read it directly.
1572
- if [ "$ORG_ROLE" = "COMPANION" ]; then
1573
- echo "[2b.5 auto-update $AU_ROLE_TAG] blocking on updated C360 SDM propagation to companion $ORG_ALIAS before proceeding." >&2
1574
- if wait_for_c360_sdm_visible "$ORG_ALIAS" 0 "COMPANION post-update propagation" "$RESOLVED_ORG_FOR_C360"; then
1575
- echo "[2b.5 auto-update $AU_ROLE_TAG] updated C360 SDM visible on companion $ORG_ALIAS. Proceeding to readiness." >&2
1576
- INSTALLED_TEMPLATE_VERSION="$reup_installed"
1577
- CURRENT_TEMPLATE_VERSION="$reup_current"
1578
- CMP="$reup_cmp"
1579
- AUTO_UPDATE_PARITY_REACHED="true"
1580
- else
1581
- # Auth-error refusal only — the wait is unbounded
1582
- # so no cap-elapsed path exists. Do NOT flip the
1583
- # parity flag; the "$AUTO_UPDATE_PARITY_REACHED"
1584
- # != "true" guard below fires the standard STOPPING
1585
- # + wait-for-parity path so the operator can refresh
1586
- # auth on the companion and let the resume gate
1587
- # pick up parity + visibility together.
1588
- echo "[2b.5 auto-update $AU_ROLE_TAG] SDM-visibility wait errored out (auth). Falling through to wait-for-parity." >&2
1589
- fi
1590
- else
1591
- echo "[2b.5 auto-update $AU_ROLE_TAG] HOME path — SDM is local, skipping post-update visibility wait. Proceeding to readiness." >&2
1592
- INSTALLED_TEMPLATE_VERSION="$reup_installed"
1593
- CURRENT_TEMPLATE_VERSION="$reup_current"
1594
- CMP="$reup_cmp"
1595
- AUTO_UPDATE_PARITY_REACHED="true"
1596
- fi
1597
- ;;
1598
- *)
1599
- echo "[2b.5 auto-update $AU_ROLE_TAG] PUT completed but installed=$reup_installed still < current=$reup_current. Falling through to wait-for-parity." >&2
1600
- ;;
1601
- esac
1602
- else
1603
- echo "[2b.5 auto-update $AU_ROLE_TAG] PUT completed but versions unreadable (installed='${reup_installed:-<empty>}' current='${reup_current:-<empty>}'). Falling through to wait-for-parity." >&2
1604
- fi
1605
- else
1606
- echo "[2b.5 auto-update $AU_ROLE_TAG] PUT or poll failed - falling through to manual Option A/B + wait-for-parity." >&2
1607
- fi
1608
- fi
1609
- fi
1610
-
1611
- # STOP + wait-for-parity + refusal only fire when auto-update did
1612
- # NOT reach parity (COMPANION path always, or HOME path when the
1613
- # PUT / poll / version-refetch failed).
1614
- if [ "$AUTO_UPDATE_PARITY_REACHED" != "true" ]; then
1615
- if [ "$ORG_ROLE" = "COMPANION" ]; then
1616
- cat >&2 <<MSG
1617
-
1618
- NOTE (COMPANION BLAST RADIUS): $RESOLVED_ORG_FOR_C360 is a HOME org shared
1619
- by every companion paired to it - reconfiguring or reinstalling the C360
1620
- app there will affect ALL of them, not just this companion ($ORG_ALIAS).
1621
- Coordinate with the HOME org owner and every other companion consumer
1622
- before executing Option A/B below.
1623
-
1624
- MSG
1625
- fi
1626
- cat >&2 <<MSG
1627
-
1628
- STOPPING install of $TEMPLATE_NAME_OR_ID: C360 template-version mismatch.
1629
-
1630
- Installed C360 templateVersion on $RESOLVED_ORG_FOR_C360: $INSTALLED_TEMPLATE_VERSION
1631
- Current C360 templateVersion (template metadata): $CURRENT_TEMPLATE_VERSION
1632
- Role: $ORG_ROLE Caller: $ORG_ALIAS
1633
-
1634
- $TEMPLATE_NAME_OR_ID was built against the current C360 SDM - installing
1635
- against a stale C360 would fail base-model resolution during downstream
1636
- SDM provisioning. This skill will NOT proceed until the installed C360
1637
- templateVersion on $RESOLVED_ORG_FOR_C360 matches (or exceeds) the current
1638
- template version.
1639
-
1640
- API-DRIVEN RECONFIGURE (Option 0):
1641
-
1642
- When ORG_ROLE=HOME the skill already attempted this automatically before
1643
- printing this message - the PUT /apps/{id} either failed, its poll timed
1644
- out, or the post-update version re-fetch did not confirm parity. Reasons
1645
- and remediation are captured in the "[2b.5 auto-update role=HOME]" log
1646
- lines above. Fall through to Option A / Option B.
1647
-
1648
- When ORG_ROLE=COMPANION the auto-update was skipped because
1649
- $RESOLVED_ORG_FOR_C360 is a shared HOME and reconfiguring from a
1650
- companion's context mutates a resource other companions depend on.
1651
- Two opt-in paths exist if you HAVE authorized the shared-HOME update:
1652
-
1653
- (a) Re-run this skill on $ORG_ALIAS with the env var set:
1654
- TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE=1
1655
- 2b.5 will drive PUT /apps/{id} against $RESOLVED_ORG_FOR_C360 the
1656
- same way it does on a HOME caller - no App Hub click-through.
1657
-
1658
- (b) Re-run this skill on $ORG_ALIAS under an interactive TTY. 2b.5
1659
- will prompt "Proceed with shared-HOME C360 update? [y/N]" (60 s
1660
- read timeout, default N) and, on 'y', drive the same PUT + poll.
1661
-
1662
- Otherwise coordinate with the HOME org owner (or an admin authorized on
1663
- that HOME) to run the update out-of-band via Option A / Option B, or
1664
- invoke this skill from the HOME alias directly (which auto-remediates
1665
- without an opt-in signal).
1666
-
1667
- API primitives (for reference / manual invocation on the HOME):
1668
-
1669
- update_app "\$INSTALLED_C360_APP_ID" "\$RESOLVED_ORG_FOR_C360" "{}"
1670
- poll_update_to_terminal "\$INSTALLED_C360_APP_ID" "\$RESOLVED_ORG_FOR_C360" 1800
1671
-
1672
- See step 6b for the helper implementations and wire rule I for the PUT
1673
- body shape.
1674
-
1675
- REQUIRED - USER ACTION IN THE UI (default when Option 0 is not authorized):
1676
-
1677
- Option A (preferred - Reconfigure via App Hub):
1678
- 1. Log in to $RESOLVED_ORG_FOR_C360.
1679
- 2. Setup -> App Hub (Quick Find: "App Hub").
1680
- 3. Locate the "C360 App" tile.
1681
- 4. Click the right-arrow / chevron on the tile to open the app detail.
1682
- 5. Click "Update" (or "Reconfigure" if that is the label your release
1683
- shows) and follow the wizard to upgrade to templateVersion
1684
- $CURRENT_TEMPLATE_VERSION.
1685
- 6. Wait until the app's applicationStatus returns to SuccessStatus /
1686
- SuccessWithWarningsStatus. The App Hub tile shows the status inline.
1687
-
1688
- Option B (fallback - Delete + Reinstall, only if Option A is unavailable
1689
- for your release):
1690
- 1. App Hub on $RESOLVED_ORG_FOR_C360 -> C360 App tile -> Delete.
1691
- 2. App Hub -> New App -> C360 Semantic Model -> Install.
1692
- 3. Wait for applicationStatus == SuccessStatus.
1693
-
1694
- This skill is now polling $RESOLVED_ORG_FOR_C360 every 60s for parity
1695
- (cap 60 min). It will resume the $TEMPLATE_NAME_OR_ID install
1696
- automatically as soon as the installed templateVersion is >= current.
1697
- Ctrl-C to abort and re-run later.
1698
-
1699
- MSG
1700
- # Bounded poll waiting for the user's out-of-band Reconfigure to
1701
- # land. Re-fetch both /apps and /templates each iteration - the
1702
- # current template version can drift forward mid-poll if a
1703
- # platform release rolls; parity is defined as installed >=
1704
- # current at each observation.
1705
- local parity_deadline poll_installed_ver poll_current_ver poll_cmp
1706
- local poll_apps_resp poll_tpl_resp poll_iter=0
1707
- parity_deadline=$(( $(date +%s) + 3600 ))
1708
- while [ "$(date +%s)" -lt "$parity_deadline" ]; do
1709
- poll_iter=$(( poll_iter + 1 ))
1710
- sleep 60
1711
-
1712
- poll_apps_resp=$(sf api request rest \
1713
- "$API_BASE/apps?templateSourceId=sfdc_internal__C360UnifiedSemanticModel" \
1714
- --target-org "$RESOLVED_ORG_FOR_C360" --method GET 2>/dev/null || true)
1715
- poll_installed_ver=$(echo "$poll_apps_resp" | jq -r '
1716
- (.apps // .items // .records // [])
1717
- | map(select(
1718
- ((.applicationStatus // .status // "") | test("^Success(Status|WithWarningsStatus)$"))
1719
- and (
1720
- (.templateSourceId == "sfdc_internal__C360UnifiedSemanticModel") or
1721
- (.templateApiName == "sfdc_internal__C360UnifiedSemanticModel")
1722
- )
1723
- ))
1724
- | .[0]
1725
- | (.templateVersion // .templateInfo.templateVersion // .installedTemplate.templateVersion // empty)
1726
- ' 2>/dev/null)
1727
-
1728
- poll_tpl_resp=$(sf api request rest \
1729
- "$API_BASE/templates/sfdc_internal__C360UnifiedSemanticModel" \
1730
- --target-org "$RESOLVED_ORG_FOR_C360" --method GET 2>/dev/null || true)
1731
- poll_current_ver=$(echo "$poll_tpl_resp" | jq -r '
1732
- .releaseInfo.templateVersion // .templateVersion // .release.templateVersion // empty
1733
- ' 2>/dev/null)
1734
-
1735
- if [ -z "$poll_installed_ver" ] || [ -z "$poll_current_ver" ]; then
1736
- echo "[2b.5 wait-for-parity iter=$poll_iter] versions unreadable (installed='${poll_installed_ver:-<empty>}' current='${poll_current_ver:-<empty>}') - continuing." >&2
1737
- continue
1738
- fi
1739
-
1740
- if [ -n "${BASH_VERSION:-}" ]; then
1741
- poll_cmp=$(compare_versions "$poll_installed_ver" "$poll_current_ver")
1742
- else
1743
- poll_cmp=$(compare_versions_awk "$poll_installed_ver" "$poll_current_ver")
1744
- fi
1745
-
1746
- case "$poll_cmp" in
1747
- 0|1)
1748
- cat >&2 <<MSG
1749
-
1750
- [2b.5 wait-for-parity] parity reached at iter=$poll_iter:
1751
- installed=$poll_installed_ver current=$poll_current_ver
1752
- Resuming install of $TEMPLATE_NAME_OR_ID on $ORG_ALIAS.
1753
-
1754
- MSG
1755
- INSTALLED_TEMPLATE_VERSION="$poll_installed_ver"
1756
- CURRENT_TEMPLATE_VERSION="$poll_current_ver"
1757
- CMP="$poll_cmp"
1758
- break
1759
- ;;
1760
- *)
1761
- echo "[2b.5 wait-for-parity iter=$poll_iter] still stale: installed=$poll_installed_ver current=$poll_current_ver - waiting." >&2
1762
- ;;
1763
- esac
1764
- done
1765
-
1766
- if [ "$CMP" != "0" ] && [ "$CMP" != "1" ]; then
1767
- cat >&2 <<MSG
1768
-
1769
- REFUSING: C360 templateVersion parity was NOT reached within 60 min on
1770
- $RESOLVED_ORG_FOR_C360.
1771
- Last installed: ${poll_installed_ver:-$INSTALLED_TEMPLATE_VERSION}
1772
- Last current: ${poll_current_ver:-$CURRENT_TEMPLATE_VERSION}
1773
- Complete the App Hub Reconfigure (Option A above) or Delete + Reinstall
1774
- (Option B), then re-invoke this skill for $TEMPLATE_NAME_OR_ID on
1775
- $ORG_ALIAS.
1776
-
1777
- MSG
1778
- return 1 2>/dev/null || exit 1
1779
- fi
1780
- fi # close the AUTO_UPDATE_PARITY_REACHED guard
1781
- ;;
1782
- 1)
1783
- # Installed > current. This is unusual — the installed app is AHEAD
1784
- # of what the template metadata currently advertises. Can happen if
1785
- # the template metadata is being rolled forward and this org already
1786
- # received an early publish. Warn but allow.
1787
- cat >&2 <<MSG
1788
-
1789
- WARNING: installed C360 templateVersion ($INSTALLED_TEMPLATE_VERSION) is
1790
- AHEAD of the current C360 template version ($CURRENT_TEMPLATE_VERSION)
1791
- advertised by $RESOLVED_ORG_FOR_C360 (role: $ORG_ROLE; caller: $ORG_ALIAS).
1792
- This is unusual but not a hard failure - continuing with the install. If
1793
- the downstream install fails on base-model resolution, roll the C360 app
1794
- back to $CURRENT_TEMPLATE_VERSION on $RESOLVED_ORG_FOR_C360.
1795
-
1796
- MSG
1797
- ;;
1798
- esac
1799
-
1800
- echo "[2b state=PROCEED role=$ORG_ROLE parity=OK installed=$INSTALLED_TEMPLATE_VERSION current=$CURRENT_TEMPLATE_VERSION] MANDATORY C360 template-version parity check PASSED - SDM on $ORG_ALIAS, C360 app on $RESOLVED_ORG_FOR_C360 at templateVersion $INSTALLED_TEMPLATE_VERSION (>= current template $CURRENT_TEMPLATE_VERSION) - proceeding to readiness." >&2
1801
- fi
1802
- fi
1803
- ```
1804
-
1805
- **Detection invariants:**
1806
- - Run this step BEFORE step 3 (readiness) and BEFORE step 4 (create). Readiness alone will NOT catch the C360 dependency — it validates DMOs and access expressions declared in the template's `readiness.json`, not cross-template SDM base-model resolution or version drift.
1807
- - The 3-step gate is ordered by increasing specificity: SDM present in Data Cloud (2b.3) → C360 app installed here in `SuccessStatus` (2b.4) → installed `templateVersion` matches current (2b.5). Any earlier failure short-circuits.
1808
- - Match on BOTH the C360 SDM name (`C360SemanticModel_c360`) AND the C360 template apiName (`sfdc_internal__C360UnifiedSemanticModel`) — release drift in the `baseModels` field has emitted both forms.
1809
- - Match on BOTH `baseModels` (array) AND `baseModel` (string) AND their `.definition.*` nested variants — same drift.
1810
- - **`/assets` response wrapper key drifts across three names: `.assets` / `.templateAssets` / `.templatedAssets`.** v68.0 uses the third — coalescing only the first two silently falls through to `[]` and produces a false-negative C360 detection. Wire-verified 2026-08-26 (miss on `sfdc_internal__SalesInsightsTN` before this fix).
1811
- - **Each SemanticModel entry stores its SDM as a stringified, HTML-entity-encoded JSON blob under `.body`** — NOT as a nested object on the entry itself. To read `baseModels` you MUST `html.unescape(body)` then `json.loads` first. Reading `.baseModels` on the top-level asset entry returns null on v68.0+.
1812
- - **`baseModels` on v68.0+ is an array of `{apiName, label}` objects, not strings.** Wire-verified 2026-08-26: `"baseModels": [{"apiName": "C360SemanticModel_c360", "label": "C360 Semantic Model"}]`. Extract `.apiName` (or `.name` / `.templateApiName`) from dict entries; keep string handling for older releases. A `type == "string"` filter drops every object entry silently.
1813
- - The 2b.2 scan uses Python (not jq) because it must combine three transformations that are hard to express safely in jq: response-key coalesce, `html.unescape` + `json.loads` on `.body`, and object-vs-string extraction from `baseModels`. jq shape-tolerance on nested string blobs is a footgun; Python is cheaper.
1814
- - The `/apps` list endpoint honors ONLY `templateSourceId` as a server-side filter. **Never** include `applicationStatus` in the query string — the server ignores it and returns the unfiltered set, which is a footgun if you assumed it was filtered. Always filter status with `jq` client-side, matching `SuccessStatus` OR `SuccessWithWarningsStatus` for the version-compare gate — both are terminal-success states (matches what `install_c360_on_home_and_wait_for_sync` accepts; wire-verified: `1zAVF0000000Ez32AE` legitimately lands at `SuccessWithWarningsStatus`). An `InProgressStatus` or `FailedStatus` install is not a real installed version and must not be used to seed `INSTALLED_TEMPLATE_VERSION`.
1815
- - The `/apps` response has been served under `.apps`, `.items`, and `.records` across releases — coalesce all three.
1816
- - `templateVersion` on the installed app has been top-level, nested under `.templateInfo`, and nested under `.installedTemplate` across releases — coalesce all three.
1817
- - The template's *current* version lives at `.releaseInfo.templateVersion` (top-level `.templateVersion` and `.release.templateVersion` are older aliases) — coalesce.
1818
- - Version compare MUST be numeric-tuple, not string. `"10.0"` string-compares as less than `"4.0"`, which would silently misclassify a fresh install as stale. Bash uses a pure n-tuple compare (arrays + `10#$n` decimal-force); shells without arrays fall back to an equivalent `awk` implementation. Do NOT use `sort -V` — it's GNU-only, missing from stock macOS `sort`.
1819
- - Multi-dot forms (`4.0`, `4.0.0`, `4.0.0.1`) compare by padding the shorter with zeros so `4.0` cmp `4.0.0` returns equal.
1820
- - Skip the entire gate when the user is installing C360 itself (`$TEMPLATE_NAME_OR_ID == "sfdc_internal__C360UnifiedSemanticModel"`) — a template doesn't depend on itself.
1821
- - The `/ssot/semantic/models/{name}` endpoint has an older nested variant (`/ssot/semantic-models/{name}`). We try the canonical path first and fall through to the alternate on an empty body OR when the primary responds with `URL_NOT_FOUND` / `NOT_FOUND` / `UNSUPPORTED_API_VERSION` — this covers both route-not-found and gateway-blank shapes.
1822
- - Auth errors (`INVALID_SESSION_ID`, `INVALID_AUTH_HEADER`, `INVALID_JWT_FORMAT`) mid-check are surfaced explicitly at EVERY probe (2b.1 `/assets`, 2b.3 `/ssot`, 2b.4 `/apps`) and abort the step — they must NOT be misread as "SDM missing" or "app not installed." An empty body from `/assets` refuses with the same auth-refresh remediation because `sf` typically wrote the auth error to stderr (suppressed).
1823
- - All errorCode probes use `.[0]?.errorCode // .errorCode // empty` (with the `?` operator) so both array-shape and object-shape error payloads route through the same `case` statement. Plain `.[0].errorCode` throws `Cannot index object with number` on the object shape and `//` does NOT catch it, so the auth-error branch becomes unreachable.
1824
- - Non-auth, non-route SDM errors (`INSUFFICIENT_ACCESS`, `API_DISABLED_FOR_ORG`, `FUNCTIONALITY_NOT_ENABLED`, `ACCESS_DENIED`) degrade to `SDM_PRESENT=unknown` with a targeted NOTE — NOT to the "install C360 in the Home org" refusal (whose remediation would be wrong for a permissions/feature-gate problem).
1825
- - 2b.4 splits `INSTALLED_COUNT` (any terminal-success C360 app? — matches `SuccessStatus` OR `SuccessWithWarningsStatus`, same set the auto-installer accepts) from `INSTALLED_TEMPLATE_VERSION` (can we read its version?). Count == 0 → refuse "not installed". Filtering strictly on `SuccessStatus` would produce a false refusal immediately after the auto-installer legitimately lands on `SuccessWithWarningsStatus` (wire-verified 2026-08-26 with app id `1zAVF0000000Ez32AE`).
1826
- - **MANDATORY C360 TEMPLATE-VERSION PARITY CHECK (2b.5).** Whenever `C360_REQUIRED=true` (i.e. 2b.2 detected a C360 base-model dependency in the caller template's `/assets`), the parity check is non-bypassable. Both operands MUST be readable: `INSTALLED_TEMPLATE_VERSION` from the installed C360 app on `$RESOLVED_ORG_FOR_C360` AND `CURRENT_TEMPLATE_VERSION` from `/templates/sfdc_internal__C360UnifiedSemanticModel`. Neither has a soft-degrade path — an unreadable operand on either side refuses with an actionable "how to inspect the wire and where to file a bug" remediation. The gate is scoped to the *installed C360 app's* templateVersion vs the C360 *template's* current version — the CALLER template's own version (e.g. `sfdc_internal__ServiceInsights` v4.4) is NOT part of this compare. Historical soft-degrade branches (WARN + proceed on either empty version) were removed 2026-08-31 after wire evidence showed a template can pin its C360 dep by apiName only, so `SuccessStatus` on downstream install is not proof that parity was actually observed; skipping the compare hides real drift.
1827
- - **Auth errors on the /templates GET refuse with an auth-refresh message, NOT the "current version unreadable" refusal.** 2b.5 probes `.errorCode` on the /templates response before extracting `CURRENT_TEMPLATE_VERSION` so an expired session doesn't get misclassified as "template metadata missing" — which would route the user to a wire-shape investigation when the fix is `sf org login web`.
1828
- - **HOME vs COMPANION role is the ONLY thing that determines which org 2b.4 and 2b.5 probe.** `$RESOLVED_ORG_FOR_C360` equals `$ORG_ALIAS` when `ORG_ROLE=HOME` and `$HOME_ORG_ALIAS` when `ORG_ROLE=COMPANION`. 2b.3 (SDM presence) ALWAYS targets `$ORG_ALIAS` in both roles — Home→Companion propagation IS the actionable signal we're gating on.
1829
- - **Role probe uses `DataMultiOrgConnection` SOQL (Id, OrgType, ConnectionStatus, ConnectedOrg, ConnectedParentOrg, TenantKey).** Wire-verified 2026-08-26 on v68.0: this is the only canonical signal. `/ssot/tenant-info`, `/connect/tenants`, `/connect/data-cloud/setup`, `PlatformTenant`/`DataCloudTenant`/`TnCompanionOrgAssignment` sObjects are all NOT_FOUND on v68.0 and MUST NOT be used.
1830
- - **OrgType classification** (release drift permitted): `OrgType` describes the FAR END of the connection, not the local org. 0 rows → HOME (standalone). ≥1 row `OrgType=HOME` → COMPANION (the paired org is a HOME, so this org is the LOB); Home id lives in `ConnectedOrg` (falls back to `ConnectedParentOrg`). ≥1 row `OrgType=LOB` → HOME (each LOB row is a companion consuming from this org — this org IS the publisher). Any other picklist value → UNKNOWN + refuse. Wire-verified 2026-08-27 on Companion_Org_shared_By_Chhaya_1 (1 row, OrgType=HOME → COMPANION) and Home_Org_shared_By_Chhaya_1 (2 rows, both OrgType=LOB → HOME). PRIOR VERSIONS OF THIS SKILL HAD THIS RULE INVERTED — the SOQL row semantics were misread as "OrgType describes the local org."
1831
- - **`INSUFFICIENT_ACCESS`/`ACCESS_DENIED` on the role probe MUST NOT be silently coerced to HOME** — a false-HOME classification would skip the companion auto-install branch and misroute 2b.4/2b.5. Route to `ORG_ROLE=UNKNOWN` and refuse with an actionable remediation (grant read on `DataMultiOrgConnection`, or pin `--home-org-alias`). `INVALID_TYPE`/`SOBJECT_NOT_FOUND` (feature not shipped on this release) IS safe to coerce to HOME.
1832
- - **HOME_ORG_ALIAS is resolved via `sf org list --json`** matched on the first 15 chars of `ConnectedOrg` (fallback `ConnectedParentOrg`; sf CLI emits both 15- and 18-char ids across releases; normalize before comparing). If no local sf auth exists for the resolved Home id, the skill prints a full Option-A onboarding message: (1) request the HOME my-domain URL from the HOME admin (org id alone is not enough — Salesforce has no public org-id → my-domain reverse lookup), (2) confirm the login user carries `AppFrameworkManageTemplate` + Tableau Next + Data Cloud, (3) admin runs `sf org login web --instance-url https://<HOME-my-domain>.my.<pod>.salesforce.com --alias home_<15-char-home-id>` at this terminal. Then, INSTEAD OF exiting, `resolve_c360_gate_orgs` enters a bounded auto-resume poll (30 s cadence, 15 min cap) that re-checks `sf org list --json` for the alias. As soon as the admin completes the login in another terminal, the same skill run picks up the alias and continues into 2b.3/2b.4/2b.5 — no re-invoke required. If the 15-min cap elapses, the helper refuses with a "did not appear within 15 min" message and expects a re-run. Password/2FA never leave the admin's side; the CLI stores a HOME-scoped refresh token keyed to the alias. Option B (paste a HOME sf-org-display access token) and Option C (client_credentials via a Connected App) are documented in the STOPPING message as fallbacks but Option A is the default because the token doesn't expire mid-run and no shared secret ends up in shell history. Never guess a HOME URL from the org id or `TenantKey` — always ask the admin.
1833
- - **2b.3 SDM-absent auto-install is role-parametric — HOME self-installs, COMPANION cross-org-installs on the paired HOME. BOTH roles then block on C360 SDM visibility before returning.** The single helper `install_c360_on_home_and_wait_for_sync` uses `$RESOLVED_ORG_FOR_C360` as the publisher (== `$ORG_ALIAS` when `ORG_ROLE=HOME`, == `$HOME_ORG_ALIAS` when `ORG_ROLE=COMPANION`). It inlines /readiness → POST /apps → /apps/{id} poll on the publisher (30-min cap). After the /apps install lands in `SuccessStatus`/`SuccessWithWarningsStatus`, the helper delegates to `wait_for_c360_sdm_visible` — a role-parametric SDM-visibility poll against `/ssot/semantic/models/C360SemanticModel_c360` (with fall-through to `/ssot/semantic-models/…` for release drift) that returns 0 the moment the SDM is resolvable, 1 on auth error. **Both roles poll UNBOUNDED** (cap_secs=0): the wait terminates only on SDM visibility, auth expiry (INVALID_SESSION_ID / INVALID_AUTH_HEADER / INVALID_JWT_FORMAT → refuse immediately with a refresh-auth message), or the caller killing the run (Ctrl-C). **HOME/standalone**: target = `$ORG_ALIAS`; SuccessStatus on /apps is necessary but NOT sufficient because Data Cloud metadata rollout is asynchronous, so we block on local SDM materialization before returning. **COMPANION**: target = `$ORG_ALIAS`; cross-org publish flow, SDM must propagate from HOME — latency is not reliably bounded (pairing state and platform backlog dependent), so we poll until it appears. This matches the user's instruction "keep polling till the SDM is available" for both standalone and companion. `wait_for_c360_sdm_visible` still accepts a positive `cap_secs` for future callers that need a bounded probe (kept as an optional argument), but the current install-time callers pass 0. The 2b.3 `HOME|*)` branch invokes the same helper (no separate refusal path — a standalone Home is auto-provisioned exactly like a companion's Home). It does NOT shell out to a nested skill invocation. Wire rule D applies: `applicationStatus`, not `status`.
1834
- - **Terminal Home-install states that count as success:** `SuccessStatus` AND `SuccessWithWarningsStatus`. `FailedStatus` refuses immediately with the Home-org app id. Wall-clock timeout refuses with the last observed `applicationStatus`.
1835
- - **The Home /apps POST is idempotent w.r.t. an existing install:** `DUPLICATE_VALUE`/`ITEM_ALREADY_EXISTS`/`APP_ALREADY_INSTALLED` short-circuit to the SDM-visibility wait step (`wait_for_c360_sdm_visible`, UNBOUNDED for both HOME and COMPANION at install time — polling continues until the SDM is resolvable). 2b.5 (running against Home) will still catch a stale version and refuse — the idempotency is safe.
1836
- - **Version mismatch (2b.5, `CMP=-1`) auto-remediation policy is asymmetric by role, with an explicit COMPANION opt-in.** On `ORG_ROLE=HOME` the caller IS the resource owner — no shared blast radius — so 2b.5 AUTO-DRIVES `update_app` + `poll_update_to_terminal` against `$RESOLVED_ORG_FOR_C360` (== `$ORG_ALIAS`) inline before considering any user-facing STOP, and proceeds to readiness the moment a post-update version re-fetch confirms parity (see wire rule I + step 6b + the `AUTO_UPDATE_PARITY_REACHED` flag). On the COMPANION opt-in path, after parity is confirmed on the HOME, 2b.5 additionally BLOCKS on the updated C360 SDM propagating to `$ORG_ALIAS` via an UNBOUNDED `wait_for_c360_sdm_visible` call before flipping the parity flag — otherwise readiness on the companion could race the cross-org publish and false-fail. On `ORG_ROLE=COMPANION` the resolved org is a shared HOME whose C360 app publishes to every paired companion; auto-updating from a companion's context mutates a resource other companions depend on, so 2b.5 REFUSES the auto-update by default. Two explicit opt-in signals unlock the same PUT + poll path on the COMPANION side: (1) env var `TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE=1` for unattended / scripted runs, (2) an interactive TTY prompt ("Proceed with shared-HOME C360 update? [y/N]", 60 s read timeout, default N) when stdin+stderr are both TTYs. Both preserve the "refuse by default" invariant — silence, timeout, or non-`y` reply falls through to wait-for-parity. When auto-update did NOT reach parity (COMPANION without opt-in, COMPANION opt-in declined, or any auto-update whose PUT / poll / version re-fetch failed), 2b.5 STOPS the install, prints Option A/B App Hub / Setup instructions, and enters a bounded wait-for-parity loop polling `/apps` + `/templates` every 60s (cap 60 min) until the installed `templateVersion >=` current — from an out-of-band Reconfigure (Setup → App Hub → **C360 App** tile → chevron → **Update**) landing. It resumes the install automatically once parity is reached; refuses cleanly if the cap is exceeded. The current template version is re-fetched each iteration — parity is defined by the *current* observation, so a mid-poll release roll that advances the template version does not falsely trip the resume gate.
1837
-
1838
- **Failure modes:**
1839
- - **2b.3 SDM absent — auto-install kicks in, no user-facing refusal.** On COMPANION the helper installs C360 on `$HOME_ORG_ALIAS` and then polls `$ORG_ALIAS` UNBOUNDED for propagation (5–15 min typical but not capped — the wait terminates only on SDM visibility, auth expiry, or Ctrl-C). On HOME (paired or standalone) it self-installs on `$ORG_ALIAS` and polls `$ORG_ALIAS` UNBOUNDED for local SDM materialization before returning. Only the /apps install poll is capped (30 min); the SDM-visibility wait is uncapped in both roles per the user's "keep polling till the SDM is available" instruction. If the /apps install itself fails, the skill refuses — see the `REFUSING: POST /apps for C360 on HOME ... failed` / `... reached FailedStatus` / `... did not reach SuccessStatus within 30 min` failure modes below.
1840
- - **`REFUSING: ... no successfully-installed C360 Semantic Model app`** (2b.4 fail) — SDM is visible via publish, but the resolved C360 org (HOME on companion path, self on HOME path) has no C360 app entry with `applicationStatus` in (`SuccessStatus`, `SuccessWithWarningsStatus`). Install (or wait for in-flight install) on that org.
1841
- - **`STOPPING install of ...: C360 template-version mismatch`** (2b.5 fail, installed `<` current) — install halts and the skill enters a bounded wait-for-parity loop (60s poll, cap 60 min). Remediate via **App Hub Update/Reconfigure** (Setup → App Hub → **C360 App** tile → chevron → Update, follow the wizard) or Delete + Reinstall (App Hub → **C360 App** tile → Delete → New App → C360 Semantic Model). The skill resumes automatically as soon as installed `templateVersion >=` current; if the cap elapses it refuses with the last observed versions and expects a re-run.
1842
- - **`REFUSING: ... could NOT read the installed C360 app's templateVersion`** (2b.5 mandatory-parity fail — installed version unreadable) — count of terminal-success C360 apps on `$RESOLVED_ORG_FOR_C360` is ≥1 but every candidate field (`.templateVersion`, `.templateInfo.templateVersion`, `.installedTemplate.templateVersion`) is empty. Inspect the raw `/apps` response to locate the version under a new path, file a bug so 2b.4's coalesce chain can be extended, or run App Hub → C360 App tile → Update to re-land the version in the canonical field.
1843
- - **`REFUSING: ... could NOT read the current C360 template version`** (2b.5 mandatory-parity fail — current version unreadable) — the `/templates/sfdc_internal__C360UnifiedSemanticModel` response has empty `releaseInfo.templateVersion` (and the older aliases `.templateVersion` / `.release.templateVersion`). Inspect the raw template response to find the new location, or resolve the underlying access issue (403/404/feature-gate) before retrying.
1844
- - **`WARNING: installed C360 templateVersion ... is AHEAD of current`** (2b.5 warn, installed `>` current) — allowed to proceed; the installed app is ahead of the template metadata. Roll back only if downstream base-model resolution actually fails.
1845
- - **`/assets` returns empty body** — refused with the "session may be stale" auth-refresh message. Silently proceeding here would bypass the entire C360 gate; empty stdout from `sf` almost always means an auth error written to stderr (suppressed).
1846
- - **`/assets` returns `INVALID_SESSION_ID` / `INVALID_AUTH_HEADER` / `INVALID_JWT_FORMAT`** — refused with the auth-refresh message (same handler as 2b.3/2b.4).
1847
- - **`/assets` returns HTTP 404 / `NOT_FOUND`** — endpoint not available at this API version. Log the NOTE and proceed; readiness will surface any real base-model failure.
1848
- - **`/assets` returns HTTP 403 `ACCESS_DENIED`** — caller lacks `AppFrameworkManageTemplate`. Same remediation as the other App-Framework 403s (see preconditions).
1849
- - **C360 scan silently reports "no dependency" on a template that clearly extends C360** (e.g. Sales Insights TN, whose `SalesInsights` SDM has `baseModels: [{apiName: "C360SemanticModel_c360"}]`) — the scanner was matching an outdated wire shape (top-level `.assets` + string `baseModels`). Fixed 2026-08-26: 2b.2 now coalesces `.templatedAssets`, parses `.body` (html-unescape + JSON.parse), and extracts `.apiName` from dict entries. If you see this regress again, verify the wrapper key and `baseModels` element type via a raw `sf api request rest ... /assets | head -50` and update 2b.2's Python scanner.
1850
- - **`/ssot/semantic/models` returns `URL_NOT_FOUND` / `UNSUPPORTED_API_VERSION`** — the endpoint is unreachable at the discovered `$API_VERSION`. The gate degrades to `SDM_PRESENT=unknown` (a NOTE, not a refusal) and defers to readiness.
1851
- - **`/apps` returns empty body while checking C360 install state** — logged as `WARN` and treated as not-installed (2b.4 will refuse cleanly rather than crash on empty JSON).
1852
- - **`INVALID_SESSION_ID` / auth mid-check** — refused with an explicit "refresh the org auth" message. Do NOT silently continue past an auth error, because a downstream 401 would look like "SDM missing" or "app not installed" and mislead the user.
1853
- - **`REFUSING: cannot determine HOME/COMPANION role for $ORG_ALIAS`** — `DataMultiOrgConnection` SOQL returned `INSUFFICIENT_ACCESS` / `ACCESS_DENIED`. Grant read on `DataMultiOrgConnection` to the running user in `$ORG_ALIAS`, OR re-invoke with `--home-org-alias <alias>` to pin the COMPANION path explicitly.
1854
- - **`REFUSING: $ORG_ALIAS is classified as a COMPANION ... but ... could not resolve the Home org id`** — the DataMultiOrgConnection row with `OrgType=HOME` (meaning the paired far end is a Home, i.e. this org is a companion) lacks both `ConnectedOrg` AND `ConnectedParentOrg`. Re-invoke with `--home-org-alias <alias>` or ask an admin to repair the record.
1855
- - **`STOPPING install of <template> on companion <caller>: no local sf CLI auth found for the HOME org paired with this companion`** — the resolved Home org has no matching alias in `sf org list --json`, so 2b.3 / 2b.4 / 2b.5 cannot run their HOME-side App-Framework calls YET. The STOPPING message prints the HOME org id (both 15- and 18-char forms), the three things to request from the HOME admin (my-domain URL, permitted login user with `AppFrameworkManageTemplate`, ~60 s at this terminal), the exact `sf org login web --instance-url https://<HOME-my-domain>.my.<pod>.salesforce.com --alias home_<15-char-home-id>` command to run (Option A), a suggested stable alias (`home_<15-char-home-id>`), the verify-alias `sf org list --json | jq …` recipe, and a short comparison of Option B (token paste) / Option C (client_credentials) so the admin can pick the friction level. Nothing in the skill can synthesize HOME credentials from a companion session — the message deliberately never asks for the admin's password/2FA. After printing the STOPPING message, `resolve_c360_gate_orgs()` does NOT `return 1` immediately — it enters a bounded auto-resume poll (30 s cadence, 15-min hard cap) that re-runs `sf org list --json` and re-attempts the id-15 match. As soon as the HOME admin completes the `sf org login web ... --alias ...` command in another terminal, the next 30-s tick finds the alias and 2b.0 continues INSIDE THE SAME SKILL RUN into 2b.3/2b.4/2b.5 — no user-side re-invoke required. Only if the 15-min cap elapses without the alias appearing does the helper refuse hard with a "HOME alias did not appear within 15 min" message and expect the caller to re-invoke the skill; when re-invoked, 2b.0 re-probes `DataMultiOrgConnection` and picks up the newly-registered alias automatically.
1856
- - **`REFUSING: POST /apps for C360 on HOME <home> failed with <errorCode>`** — the auto-install POST on the Home org returned a non-idempotent error. Install C360 manually on Home and re-invoke. `DUPLICATE_VALUE`/`ITEM_ALREADY_EXISTS`/`APP_ALREADY_INSTALLED` do NOT trigger this — they short-circuit to the SDM-visibility wait step instead.
1857
- - **`REFUSING: HOME C360 install reached FailedStatus (app <id> on <home>)`** — the auto-install poll observed `applicationStatus == FailedStatus`. Inspect `/apps/<id>` on Home and remediate before retry.
1858
- - **`REFUSING: HOME C360 install on <home> did not reach SuccessStatus within 30 min`** — the auto-install poll timed out. Last observed `applicationStatus` is logged. Extend the poll or investigate the Home-org install trace.
1859
- - **`REFUSING: C360 was installed on HOME <home> (SuccessStatus) but the C360 SDM did NOT propagate to companion <caller> within N minutes`** — ONLY emitted when a future caller invokes `wait_for_c360_sdm_visible` with a positive `cap_secs`. The default install-time COMPANION path polls UNBOUNDED per user instruction, so this refusal does NOT fire on the standard install flow. If you see it: pairing exists but publish flow is stalled. Check `DataMultiOrgConnection.ConnectionStatus` on both sides and verify `TenantKey` matches. Emitted by `wait_for_c360_sdm_visible` when the target is different from the publisher (COMPANION propagation branch).
1860
- - **`REFUSING: C360 was installed on <org> (SuccessStatus) but the C360 SDM did NOT materialize in <org>'s Data Cloud within N minutes`** — ONLY emitted when a future caller invokes `wait_for_c360_sdm_visible` with a positive `cap_secs`. The default install-time HOME/standalone path polls UNBOUNDED per user instruction, so this refusal does NOT fire on the standard install flow. If you see it: /apps reached Success but the SDM has not yet appeared in `/ssot/semantic/models/C360SemanticModel_c360`. Data Cloud metadata rollout is asynchronous; verify Data Cloud is fully provisioned on the org (Setup → Data Cloud Setup), then re-invoke the skill. Emitted by `wait_for_c360_sdm_visible` when the target equals the publisher (HOME self-install branch).
1861
- - **Version mismatch on COMPANION path — companion blast-radius NOTE preceding the standard `installed templateVersion ... stale` refusal** — because `$RESOLVED_ORG_FOR_C360` is a shared HOME, the reconfigure/delete+reinstall action must be coordinated with the HOME owner and every other companion consumer. By default 2b.5 does NOT auto-remediate on the COMPANION path (unlike the HOME path, which auto-drives the update inline). Explicit opt-in via `TABNEXT_ALLOW_SHARED_HOME_C360_UPDATE=1` or an interactive TTY `y` reply unlocks the same `update_app` + `poll_update_to_terminal` path — this is the caller's authorization to reconfigure the shared HOME on behalf of every paired companion.
1862
-