@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.
- package/package.json +1 -1
- package/skills/field-service-data-capture-form-deployer-configure/SKILL.md +2 -23
- package/skills/field-service-data-capture-form-designer-configure/SKILL.md +3 -13
- package/skills/field-service-mobile-branding-configure/SKILL.md +3 -17
- package/skills/field-service-objective-designer-configure/SKILL.md +379 -72
- package/skills/field-service-prework-brief-deployer-configure/SKILL.md +537 -10
- package/skills/field-service-scheduling-policy-designer-query/SKILL.md +1093 -65
- package/skills/field-service-sobject-create-configure/SKILL.md +213 -13
- package/skills/field-service-voice-to-form-configure/SKILL.md +11 -40
- package/skills/field-service-work-rule-designer-configure/SKILL.md +95 -121
- package/skills/agentforce-gpt-email-generation-configure/SKILL.md +0 -312
- package/skills/agentforce-wizard-ui-forecasting-analytics-components-configure/SKILL.md +0 -64
- package/skills/agentforce-wizard-ui-sales-einstein-components-configure/SKILL.md +0 -46
- package/skills/automation-company-profile-info-query/SKILL.md +0 -51
- package/skills/automation-digital-wallet-configure/SKILL.md +0 -689
- package/skills/automation-flow-coordinate/SKILL.md +0 -95
- package/skills/automation-flow-coordinate/references/elements/action-call/action-call.md +0 -1569
- package/skills/automation-flow-coordinate/references/elements/action-call/conversation-messaging.md +0 -96
- package/skills/automation-flow-coordinate/references/elements/action-call/examples/send-sms-message.json +0 -23
- package/skills/automation-flow-coordinate/references/elements/action-call/examples/send-to-journey-no-fields.json +0 -31
- package/skills/automation-flow-coordinate/references/elements/action-call/send-to-journey.md +0 -179
- package/skills/automation-flow-coordinate/references/elements/exit-rule/examples/event-email-link-click.json +0 -13
- package/skills/automation-flow-coordinate/references/elements/exit-rule/exit-rule.md +0 -83
- package/skills/automation-flow-coordinate/references/elements/forward-to-bot-or-agent/examples/forward-to-bot-or-agent.json +0 -69
- package/skills/automation-flow-coordinate/references/elements/forward-to-bot-or-agent/forward-to-bot-or-agent.md +0 -274
- package/skills/automation-flow-coordinate/references/elements/start/audience-common.md +0 -192
- package/skills/automation-flow-coordinate/references/elements/start/automation-event-triggered.md +0 -130
- package/skills/automation-flow-coordinate/references/elements/start/data-cloud-data-change-triggered.md +0 -92
- package/skills/automation-flow-coordinate/references/elements/start/form-submission-triggered.md +0 -88
- package/skills/automation-flow-coordinate/references/elements/start/segment-triggered.md +0 -152
- package/skills/automation-flow-coordinate/references/elements/start/start.md +0 -539
- package/skills/automation-flow-coordinate/references/elements/subflow/subflow.md +0 -152
- package/skills/automation-flow-coordinate/references/elements/wait/wait.md +0 -497
- package/skills/automation-flow-coordinate/references/recipes/author/author.md +0 -868
- package/skills/automation-flow-coordinate/references/recipes/summarize/element-generic.md +0 -300
- package/skills/automation-flow-coordinate/references/recipes/summarize/reference-resolution.md +0 -292
- package/skills/automation-flow-coordinate/references/recipes/summarize/summarize.md +0 -323
- package/skills/automation-flow-coordinate/references/recipes/testing/examples/debug-output-template.md +0 -109
- package/skills/automation-flow-coordinate/references/recipes/testing/shape.md +0 -219
- package/skills/automation-flow-coordinate/references/recipes/testing/shared-stages/intent-analyzer.md +0 -180
- package/skills/automation-flow-coordinate/references/recipes/testing/workflows/debug.md +0 -175
- package/skills/automation-flow-coordinate/references/recipes/testing/workflows/promote.md +0 -255
- package/skills/automation-flow-coordinate/references/recipes/testing/workflows/save-debug.md +0 -212
- package/skills/automation-flow-coordinate/references/resources/variable/variable.md +0 -171
- package/skills/automation-flow-coordinate/references/shared/anti-patterns.md +0 -786
- package/skills/automation-flow-coordinate/references/shared/end/end.md +0 -267
- package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/eval/README.md +0 -69
- package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/eval/predictions.jsonl +0 -80
- package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/eval/prompts.jsonl +0 -116
- package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/flow-type-resolver.md +0 -321
- package/skills/automation-flow-coordinate/references/shared/flow-type-resolver/taxonomy.json +0 -320
- package/skills/automation-flow-coordinate/references/shared/server-functions.md +0 -597
- package/skills/automotive-cloud-appraisal-management-settings-configure/SKILL.md +0 -69
- package/skills/automotive-cloud-dealer-essentials-configure/SKILL.md +0 -78
- package/skills/automotive-cloud-ground-agent-data-pipelines-configure/SKILL.md +0 -373
- package/skills/automotive-cloud-ground-mfg-agent-data-pipelines-configure/SKILL.md +0 -273
- package/skills/automotive-cloud-mfg-agent-data-pipelines-configure/SKILL.md +0 -259
- package/skills/automotive-cloud-mfg-sales-concierge-agent-create/SKILL.md +0 -369
- package/skills/automotive-cloud-mfg-sales-concierge-foundation-configure/SKILL.md +0 -542
- package/skills/automotive-cloud-mfg-sales-concierge-foundation-coordinate/SKILL.md +0 -159
- package/skills/automotive-cloud-mfg-sales-concierge-orchestrator-agent-create/SKILL.md +0 -349
- package/skills/automotive-cloud-mfg-sales-concierge-orchestrator-configure/SKILL.md +0 -505
- package/skills/automotive-cloud-sales-concierge-agent-create/SKILL.md +0 -380
- package/skills/automotive-cloud-sales-concierge-foundation-configure/SKILL.md +0 -567
- package/skills/automotive-cloud-sales-concierge-foundation-coordinate/SKILL.md +0 -159
- package/skills/automotive-cloud-sales-concierge-orchestrator-agent-create/SKILL.md +0 -360
- package/skills/automotive-cloud-sales-concierge-orchestrator-configure/SKILL.md +0 -539
- package/skills/automotive-cloud-sales-concierge-search-grounding-configure/SKILL.md +0 -358
- package/skills/commerce-b2b-merchant-quote-configure/SKILL.md +0 -602
- package/skills/commerce-b2b-store-configure/SKILL.md +0 -251
- package/skills/commerce-credit-memo-create/SKILL.md +0 -139
- package/skills/commerce-delivery-business-hours-configure/SKILL.md +0 -277
- package/skills/commerce-delivery-estimation-apply/SKILL.md +0 -155
- package/skills/commerce-delivery-estimation-configure/SKILL.md +0 -377
- package/skills/commerce-delivery-routing-specs-integrate/SKILL.md +0 -174
- package/skills/commerce-estimate-bopis-query/SKILL.md +0 -279
- package/skills/commerce-estimate-delivery-date-query/SKILL.md +0 -349
- package/skills/commerce-fees-apply/SKILL.md +0 -227
- package/skills/commerce-fulfillment-routing-analyze/SKILL.md +0 -200
- package/skills/commerce-guest-buyer-create/SKILL.md +0 -173
- package/skills/commerce-location-bopis-configure/SKILL.md +0 -378
- package/skills/commerce-location-business-hours-configure/SKILL.md +0 -235
- package/skills/commerce-location-delivery-processing-time-configure/SKILL.md +0 -309
- package/skills/commerce-location-graph-integrate/SKILL.md +0 -131
- package/skills/commerce-location-shipping-carrier-method-configure/SKILL.md +0 -755
- package/skills/commerce-location-temporary-operating-hours-configure/SKILL.md +0 -331
- package/skills/commerce-merchant-buyer-groups-configure/SKILL.md +0 -579
- package/skills/commerce-merchant-buyer-user-onboarding-create/SKILL.md +0 -626
- package/skills/commerce-merchant-catalog-configure/SKILL.md +0 -614
- package/skills/commerce-merchant-extension-provider-configure/SKILL.md +0 -368
- package/skills/commerce-merchant-insights-account-health-churn-analyze/SKILL.md +0 -118
- package/skills/commerce-merchant-insights-inventory-returns-analyze/SKILL.md +0 -65
- package/skills/commerce-merchant-insights-order-shopper-get/SKILL.md +0 -90
- package/skills/commerce-merchant-insights-product-performance-analyze/SKILL.md +0 -72
- package/skills/commerce-merchant-insights-query/SKILL.md +0 -279
- package/skills/commerce-merchant-storefront-guest-access-configure/SKILL.md +0 -656
- package/skills/commerce-order-summary-change-apply/SKILL.md +0 -375
- package/skills/commerce-product-details-get/SKILL.md +0 -363
- package/skills/commerce-product-search/SKILL.md +0 -563
- package/skills/commerce-react-storefront-create/SKILL.md +0 -350
- package/skills/commerce-return-order-summary-create/SKILL.md +0 -513
- package/skills/commerce-search-relevance-analyze/SKILL.md +0 -757
- package/skills/commerce-temporary-processing-time-override-create/SKILL.md +0 -274
- package/skills/communications-initial-setup-configure/SKILL.md +0 -630
- package/skills/communications-multisite-configure/SKILL.md +0 -571
- package/skills/communications-revenue-cloud-settings-configure/SKILL.md +0 -74
- package/skills/communications-smb-fiber-catalog-create/SKILL.md +0 -413
- package/skills/communications-tmf-integrate/SKILL.md +0 -1368
- package/skills/consumer-goods-visit-activity-act4-questions-assign/SKILL.md +0 -448
- package/skills/consumer-goods-visit-activity-act5-standard-create/SKILL.md +0 -269
- package/skills/consumer-goods-visit-activity-act6-assortment-configure/SKILL.md +0 -271
- package/skills/consumer-goods-visit-activity-act7-account-scoping-create/SKILL.md +0 -382
- package/skills/consumer-goods-visit-activity-act8-plan-attach-create/SKILL.md +0 -373
- package/skills/consumer-goods-visit-activity-act9-proof-validate/SKILL.md +0 -389
- package/skills/consumer-goods-visit-and-activity-act1-specification-create/SKILL.md +0 -338
- package/skills/consumer-goods-visit-and-activity-act2-template-create/SKILL.md +0 -547
- package/skills/consumer-goods-visit-and-activity-act3-definition-create/SKILL.md +0 -346
- package/skills/consumer-goods-visit-and-activity-configure/SKILL.md +0 -1110
- package/skills/crm-analytics-adoption-insights-configure/SKILL.md +0 -224
- package/skills/crm-analytics-custom-report-type-lightning-configure/SKILL.md +0 -418
- package/skills/crm-analytics-dashboards-configure/SKILL.md +0 -731
- package/skills/crm-analytics-operational-analytics-metadata-bridge-migrate/SKILL.md +0 -233
- package/skills/crm-analytics-report-notifications-ui-configure/SKILL.md +0 -184
- package/skills/crm-analytics-sharing-inheritance-coverage-analyze/SKILL.md +0 -167
- package/skills/crm-analytics-sonic-getting-started-configure/SKILL.md +0 -55
- package/skills/crm-analytics-tableau-next-app-deploy/SKILL.md +0 -1350
- package/skills/crm-analytics-tableau-next-c360-basemodel-validate/SKILL.md +0 -1862
- package/skills/crm-analytics-tableau-next-datacloud-validate/SKILL.md +0 -83
- package/skills/crm-analytics-tableau-next-provisioning-validate/SKILL.md +0 -81
- package/skills/data360-activation-create/SKILL.md +0 -104
- package/skills/data360-activation-external-platform-get/SKILL.md +0 -49
- package/skills/data360-activation-target-create/SKILL.md +0 -93
- package/skills/data360-activation-v2-create/SKILL.md +0 -456
- package/skills/data360-audience-dmo-get/SKILL.md +0 -38
- package/skills/data360-calculated-affinity-create/SKILL.md +0 -82
- package/skills/data360-calculated-insight-create/SKILL.md +0 -148
- package/skills/data360-catalog-metadata-create/SKILL.md +0 -183
- package/skills/data360-code-extension-create/SKILL.md +0 -115
- package/skills/data360-communication-capping-create/SKILL.md +0 -227
- package/skills/data360-connection-create/SKILL.md +0 -491
- package/skills/data360-content-lens-create/SKILL.md +0 -181
- package/skills/data360-data-action-create/SKILL.md +0 -126
- package/skills/data360-data-action-target-create/SKILL.md +0 -181
- package/skills/data360-data-clean-room-create/SKILL.md +0 -238
- package/skills/data360-data-governance-create/SKILL.md +0 -746
- package/skills/data360-data-graph-create/SKILL.md +0 -82
- package/skills/data360-data-harmonized-model-object-reference-create/SKILL.md +0 -38
- package/skills/data360-data-lake-object-create/SKILL.md +0 -207
- package/skills/data360-data-model-object-catalog-get/SKILL.md +0 -49
- package/skills/data360-data-model-object-create/SKILL.md +0 -128
- package/skills/data360-data-model-object-mapping-create/SKILL.md +0 -106
- package/skills/data360-data-space-create/SKILL.md +0 -161
- package/skills/data360-data-streams-create/SKILL.md +0 -324
- package/skills/data360-engagement-signal-get/SKILL.md +0 -38
- package/skills/data360-experiment-create/SKILL.md +0 -82
- package/skills/data360-goal-based-campaign-create/SKILL.md +0 -192
- package/skills/data360-harmonization-process-history-get/SKILL.md +0 -38
- package/skills/data360-harmonized-unstructured-dmo-create/SKILL.md +0 -84
- package/skills/data360-identity-resolution-create/SKILL.md +0 -115
- package/skills/data360-intelligent-document-processing-create/SKILL.md +0 -148
- package/skills/data360-knowledge-graph-construction-create/SKILL.md +0 -93
- package/skills/data360-knowledge-graph-create/SKILL.md +0 -216
- package/skills/data360-knowledge-space-mcp-configure/SKILL.md +0 -475
- package/skills/data360-machine-learning-mcp-configure/SKILL.md +0 -697
- package/skills/data360-multi-org-mcp-configure/SKILL.md +0 -117
- package/skills/data360-secondary-index-mcp-configure/SKILL.md +0 -126
- package/skills/data360-segment-mcp-configure/SKILL.md +0 -159
- package/skills/data360-semantic-calculated-dimension-mcp-configure/SKILL.md +0 -93
- package/skills/data360-semantic-calculated-measurement-mcp-configure/SKILL.md +0 -93
- package/skills/data360-semantic-data-object-mcp-configure/SKILL.md +0 -93
- package/skills/data360-semantic-dimension-mcp-configure/SKILL.md +0 -38
- package/skills/data360-semantic-engine-mcp-configure/SKILL.md +0 -62
- package/skills/data360-semantic-measurement-mcp-configure/SKILL.md +0 -38
- package/skills/data360-semantic-metric-mcp-configure/SKILL.md +0 -93
- package/skills/data360-semantic-model-mcp-configure/SKILL.md +0 -115
- package/skills/data360-standard-mapping-mcp-configure/SKILL.md +0 -38
- package/skills/data360-tenant-lifecycle-mcp-configure/SKILL.md +0 -73
- package/skills/experience-afv-query/SKILL.md +0 -234
- package/skills/experience-agentforce-vibes-in-react-configure/SKILL.md +0 -63
- package/skills/experience-apex-components-configure/SKILL.md +0 -69
- package/skills/experience-apex-pages-configure/SKILL.md +0 -107
- package/skills/experience-app-home-configure/SKILL.md +0 -76
- package/skills/experience-aura-lightning-out-access-configure/SKILL.md +0 -58
- package/skills/experience-briefcase-configure/SKILL.md +0 -287
- package/skills/experience-community-flexi-page-list-create/SKILL.md +0 -74
- package/skills/experience-community-template-definition-list-query/SKILL.md +0 -48
- package/skills/experience-community-theme-definition-list-query/SKILL.md +0 -38
- package/skills/experience-content-assets-configure/SKILL.md +0 -86
- package/skills/experience-custom-domain-create/SKILL.md +0 -727
- package/skills/experience-defer-rendering-workspace-page-setup-configure/SKILL.md +0 -41
- package/skills/experience-field-service-app-builder-configure/SKILL.md +0 -187
- package/skills/experience-flow-category-configure/SKILL.md +0 -117
- package/skills/experience-formula-configure/SKILL.md +0 -358
- package/skills/experience-global-actions-configure/SKILL.md +0 -69
- package/skills/experience-global-publisher-layouts-configure/SKILL.md +0 -79
- package/skills/experience-instrumentation-configure/SKILL.md +0 -64
- package/skills/experience-lex-engagement-configure/SKILL.md +0 -50
- package/skills/experience-lightning-bolt-open/SKILL.md +0 -12
- package/skills/experience-lightning-out-configure/SKILL.md +0 -129
- package/skills/experience-lightning-preview-configure/SKILL.md +0 -67
- package/skills/experience-lightning-types-query/SKILL.md +0 -98
- package/skills/experience-malware-scan-configure/SKILL.md +0 -71
- package/skills/experience-my-salesforce-configure/SKILL.md +0 -126
- package/skills/experience-navigation-menus-configure/SKILL.md +0 -235
- package/skills/experience-notification-types-configure/SKILL.md +0 -206
- package/skills/experience-notifications-configure/SKILL.md +0 -75
- package/skills/experience-record-discovery-query/SKILL.md +0 -227
- package/skills/experience-regenerate-previews-open/SKILL.md +0 -94
- package/skills/experience-sales-cloud-mab-configure/SKILL.md +0 -254
- package/skills/experience-schema-builder-open/SKILL.md +0 -38
- package/skills/experience-simple-record-home-configure/SKILL.md +0 -110
- package/skills/experience-static-resources-configure/SKILL.md +0 -74
- package/skills/field-service-setup-coordinate/SKILL.md +0 -62
- package/skills/field-service-work-plan-configure/SKILL.md +0 -253
- package/skills/fsc-actionable-segmentation-settings-configure/SKILL.md +0 -180
- package/skills/fsc-digital-origination-bre-decision-matrix-foundation-create/SKILL.md +0 -196
- package/skills/fsc-digital-origination-bre-disclosures-consent-configure/SKILL.md +0 -106
- package/skills/fsc-digital-origination-bre-document-upload-configure/SKILL.md +0 -96
- package/skills/fsc-digital-origination-bre-permissions-assign/SKILL.md +0 -111
- package/skills/fsc-digital-origination-bre-setup-coordinate/SKILL.md +0 -250
- package/skills/fsc-digital-origination-bre-stp-eligibility-configure/SKILL.md +0 -122
- package/skills/fsc-digital-origination-coordinate/SKILL.md +0 -653
- package/skills/fsc-digital-origination-demo-run/SKILL.md +0 -225
- package/skills/fsc-digital-origination-execution-guardrails-validate/SKILL.md +0 -112
- package/skills/fsc-digital-origination-initial-setup-configure/SKILL.md +0 -396
- package/skills/fsc-digital-origination-pricing-setup-coordinate/SKILL.md +0 -336
- package/skills/fsc-digital-origination-pricing-setup-data-foundation-create/SKILL.md +0 -208
- package/skills/fsc-digital-origination-pricing-setup-procedure-create/SKILL.md +0 -375
- package/skills/fsc-digital-origination-product-setup-act1-permissions-assign/SKILL.md +0 -83
- package/skills/fsc-digital-origination-product-setup-coordinate/SKILL.md +0 -248
- package/skills/fsc-digital-origination-sample-data-create/SKILL.md +0 -206
- package/skills/fsc-document-checklist-settings-configure/SKILL.md +0 -57
- package/skills/fsc-flexible-hierarchy-configure/SKILL.md +0 -148
- package/skills/fsc-flexible-hierarchy-foundation-configure/SKILL.md +0 -1430
- package/skills/fsc-flexible-hierarchy-graph-config-create/SKILL.md +0 -471
- package/skills/fsc-flexible-hierarchy-rule-based-graph-deploy/SKILL.md +0 -429
- package/skills/fsc-pricing-product-configurator-configure/SKILL.md +0 -150
- package/skills/fsc-pricing-salesforce-omnistudio-switch/SKILL.md +0 -123
- package/skills/fsc-product-act2-catalog-categories-create/SKILL.md +0 -82
- package/skills/fsc-product-act3-attributes-create/SKILL.md +0 -249
- package/skills/fsc-product-act4-classification-assignment-create/SKILL.md +0 -196
- package/skills/fsc-product-act5-considerations-get/SKILL.md +0 -44
- package/skills/fsc-stage-management-apex-flow-deploy/SKILL.md +0 -384
- package/skills/fsc-stage-management-compliant-data-sharing-configure/SKILL.md +0 -144
- package/skills/fsc-stage-management-decision-matrices-create/SKILL.md +0 -107
- package/skills/fsc-stage-management-definition-assignments-create/SKILL.md +0 -189
- package/skills/fsc-stage-management-definition-create/SKILL.md +0 -108
- package/skills/fsc-stage-management-permissions-assign/SKILL.md +0 -143
- package/skills/fsc-stage-management-transitions-configure/SKILL.md +0 -139
- package/skills/health-cloud-appt-booking-config-configure/SKILL.md +0 -135
- package/skills/health-cloud-appt-booking-url-config-configure/SKILL.md +0 -134
- package/skills/health-cloud-care-provider-affl-role-config-configure/SKILL.md +0 -136
- package/skills/health-cloud-care-provider-search-config-configure/SKILL.md +0 -134
- package/skills/health-cloud-care-requests-create/SKILL.md +0 -178
- package/skills/health-cloud-initial-configure/SKILL.md +0 -511
- package/skills/health-cloud-patient-appointment-scheduling-configure/SKILL.md +0 -1374
- package/skills/health-cloud-provider-search-settings-configure/SKILL.md +0 -924
- package/skills/health-cloud-referral-management-coordinate/SKILL.md +0 -513
- package/skills/health-cloud-timeline-configure/SKILL.md +0 -622
- package/skills/health-cloud-virtual-visit-config-configure/SKILL.md +0 -135
- package/skills/industries-account-score-settings-query/SKILL.md +0 -78
- package/skills/industries-actionable-list-data-sources-dmo-configure/SKILL.md +0 -177
- package/skills/industries-ad-product-availability-search/SKILL.md +0 -399
- package/skills/industries-advanced-account-forecast-settings-configure/SKILL.md +0 -628
- package/skills/industries-advanced-therapy-management-settings-configure/SKILL.md +0 -73
- package/skills/industries-atm-settings-starter-configure/SKILL.md +0 -76
- package/skills/industries-batch-document-ai-configure/SKILL.md +0 -196
- package/skills/industries-behavioral-health-app-settings-configure/SKILL.md +0 -71
- package/skills/industries-branch-management-settings-configure/SKILL.md +0 -89
- package/skills/industries-bre-decision-table-access-configure/SKILL.md +0 -367
- package/skills/industries-brokerage-settings-configure/SKILL.md +0 -41
- package/skills/industries-calendar-sync-settings-configure/SKILL.md +0 -89
- package/skills/industries-care-request-settings-configure/SKILL.md +0 -218
- package/skills/industries-case-management-intelligence-settings-configure/SKILL.md +0 -54
- package/skills/industries-catalog-process-attributes-products-configure/SKILL.md +0 -380
- package/skills/industries-catalog-process-fulfillment-configure/SKILL.md +0 -216
- package/skills/industries-catalog-process-intake-configure/SKILL.md +0 -219
- package/skills/industries-catalog-process-knowledge-configure/SKILL.md +0 -153
- package/skills/industries-catalog-process-preprocessor-configure/SKILL.md +0 -168
- package/skills/industries-cds-general-settings-configure/SKILL.md +0 -69
- package/skills/industries-claim-financial-settings-configure/SKILL.md +0 -169
- package/skills/industries-clinical-data-model-settings-configure/SKILL.md +0 -136
- package/skills/industries-clinical-data-model-settings-starter-configure/SKILL.md +0 -105
- package/skills/industries-collection-plan-settings-configure/SKILL.md +0 -132
- package/skills/industries-comms-service-console-settings-configure/SKILL.md +0 -159
- package/skills/industries-compliance-configure/SKILL.md +0 -102
- package/skills/industries-compliance-document-extraction-setup-configure/SKILL.md +0 -76
- package/skills/industries-contact-center-app-layout-configure/SKILL.md +0 -88
- package/skills/industries-contact-center-components-configure/SKILL.md +0 -96
- package/skills/industries-contact-center-path-knowledge-configure/SKILL.md +0 -95
- package/skills/industries-contact-center-user-access-assign/SKILL.md +0 -96
- package/skills/industries-course-waitlist-config-configure/SKILL.md +0 -162
- package/skills/industries-crisis-center-settings-configure/SKILL.md +0 -84
- package/skills/industries-custom-object-settings-configure/SKILL.md +0 -94
- package/skills/industries-data-cloud-integration-configure/SKILL.md +0 -88
- package/skills/industries-data-space-feature-mappings-configure/SKILL.md +0 -245
- package/skills/industries-decision-matrix-builder-configure/SKILL.md +0 -78
- package/skills/industries-decision-table-hbase-configure/SKILL.md +0 -87
- package/skills/industries-decision-table-version-configure/SKILL.md +0 -74
- package/skills/industries-decision-tables-builder-configure/SKILL.md +0 -253
- package/skills/industries-decision-tables-configure/SKILL.md +0 -439
- package/skills/industries-energy-utilities-configure/SKILL.md +0 -251
- package/skills/industries-eu-cloud-multisite-configure/SKILL.md +0 -1008
- package/skills/industries-eu-new-connections-configure/SKILL.md +0 -30
- package/skills/industries-explainability-msg-template-configure/SKILL.md +0 -135
- package/skills/industries-expression-set-message-token-configure/SKILL.md +0 -135
- package/skills/industries-field-service-settings-configure/SKILL.md +0 -82
- package/skills/industries-form-framework-settings-configure/SKILL.md +0 -30
- package/skills/industries-fsc-ai-settings-configure/SKILL.md +0 -57
- package/skills/industries-fsc-association-engine-settings-configure/SKILL.md +0 -172
- package/skills/industries-fsc-b2b-settings-configure/SKILL.md +0 -61
- package/skills/industries-fsc-insurance-settings-configure/SKILL.md +0 -58
- package/skills/industries-fsc-visa-fulfillment-setup-configure/SKILL.md +0 -103
- package/skills/industries-fsc-wealth-agent-settings-configure/SKILL.md +0 -117
- package/skills/industries-fsc-wealth-settings-configure/SKILL.md +0 -99
- package/skills/industries-fundraising-gift-entry-settings-configure/SKILL.md +0 -120
- package/skills/industries-fundraising-rfm-configure/SKILL.md +0 -128
- package/skills/industries-fundraising-settings-configure/SKILL.md +0 -145
- package/skills/industries-fundraising-source-code-gen-configure/SKILL.md +0 -299
- package/skills/industries-google-maps-api-key-settings-configure/SKILL.md +0 -87
- package/skills/industries-grantmaking-intelligence-settings-open/SKILL.md +0 -58
- package/skills/industries-hc-score-category-settings-configure/SKILL.md +0 -167
- package/skills/industries-health-engagement-settings-configure/SKILL.md +0 -218
- package/skills/industries-home-health-agentforce-setup-configure/SKILL.md +0 -131
- package/skills/industries-home-health-bundling-policy-create/SKILL.md +0 -50
- package/skills/industries-home-health-entity-labels-configure/SKILL.md +0 -29
- package/skills/industries-home-health-fsl-package-deploy/SKILL.md +0 -71
- package/skills/industries-home-health-fsl-settings-validate/SKILL.md +0 -27
- package/skills/industries-home-health-guided-setup-care-and-skill-get/SKILL.md +0 -80
- package/skills/industries-home-health-guided-setup-configure/SKILL.md +0 -836
- package/skills/industries-home-health-guided-setup-enable-org-fsl-get/SKILL.md +0 -55
- package/skills/industries-home-health-guided-setup-get-org-ready-get/SKILL.md +0 -61
- package/skills/industries-home-health-licenses-assign/SKILL.md +0 -27
- package/skills/industries-home-health-operating-hours-create/SKILL.md +0 -40
- package/skills/industries-home-health-scheduling-policy-create/SKILL.md +0 -41
- package/skills/industries-home-health-service-objective-create/SKILL.md +0 -22
- package/skills/industries-home-health-skills-create/SKILL.md +0 -31
- package/skills/industries-home-health-users-create/SKILL.md +0 -29
- package/skills/industries-icm-act3-toggles-configure/SKILL.md +0 -198
- package/skills/industries-icm-act5-genai-suggested-assessments-configure/SKILL.md +0 -361
- package/skills/industries-icm-act6-mcg-integration-configure/SKILL.md +0 -550
- package/skills/industries-integrated-care-management-configure/SKILL.md +0 -168
- package/skills/industries-object-enablement-configure/SKILL.md +0 -86
- package/skills/industries-tpm-system-processing-service-configure/SKILL.md +0 -64
- package/skills/integration-apex-settings-configure/SKILL.md +0 -61
- package/skills/marketing-e360-email-archiving-configure/SKILL.md +0 -73
- package/skills/platform-custom-permissions-configure/SKILL.md +0 -687
- package/skills/platform-label-workbench-import-create/SKILL.md +0 -67
- 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 (`"`) 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
|
-
|