@salesforce/afv-skills 1.44.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +72 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/references/inputs-and-namespace.md +38 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/references/procedure.md +158 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/detect-namespace.js +86 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/render-apex.js +64 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/resolve-id-by-name.js +47 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/sf-rest.js +171 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/soql-escape.js +26 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex +51 -0
- package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex +21 -0
- package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +74 -0
- package/skills/consumer-goods-tpe-dashboard-configure/references/phases-1-6.md +112 -0
- package/skills/consumer-goods-tpe-dashboard-configure/references/phases-7-12.md +157 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/find-failure-reason.js +132 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/poll-status.js +116 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/render-apex.js +64 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/run-data-transform.js +121 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/schedule-business-period-export.apex +27 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/sf-rest.js +171 -0
- package/skills/consumer-goods-tpe-dashboard-configure/scripts/soql-escape.js +25 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +141 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/payload-shapes.md +447 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/procedure.md +263 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/clone-tpe-dashboards.js +537 -0
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/sf-rest.js +195 -0
- package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +157 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/detect-namespace.js +86 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/download-static-resource.js +151 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/extract-crm-field-permissions.js +115 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/sf-rest.js +109 -0
- package/skills/consumer-goods-tpe-datakit-deploy/scripts/update-field-permissions.js +433 -0
- package/skills/service-catalog-template-coordinate/SKILL.md +263 -0
- package/skills/service-catalog-template-coordinate/examples/output-templates.md +44 -0
- package/skills/service-catalog-template-coordinate/references/mcp-invocation.md +183 -0
- package/skills/service-catalog-template-coordinate/references/operations.md +230 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +243 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/cli-invocation.md +205 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/helper-contracts.md +236 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/permset-topology.md +132 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-activated-agents.mjs +106 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-agent-access-state.mjs +113 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-assignment-state.mjs +99 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-platform-permset-availability.mjs +155 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/gate-unified-catalog-tiers.mjs +100 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/rank-candidate-users.mjs +95 -0
- package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/resolve-target-user.mjs +86 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +44 -23
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +33 -9
- package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -18
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +3 -1
- package/skills/service-itsm-agentic-setup-configure/SKILL.md +20 -12
- package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +73 -5
- package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -7
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +45 -33
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +8 -6
- package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +10 -10
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +114 -56
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +33 -17
- package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +9 -3
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -7
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +43 -32
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +6 -4
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +10 -10
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +106 -55
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +27 -13
- package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +9 -3
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +159 -161
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +51 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +1 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +120 -0
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +28 -5
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +19 -1
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +350 -30
- package/skills/service-itsm-channels-coordinate/SKILL.md +80 -213
- package/skills/service-itsm-slack-itservice-configure/SKILL.md +363 -0
- package/skills/service-itsm-slack-itservice-configure/references/connect-agentforce-to-slack.md +159 -0
- package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +88 -0
- package/skills/service-itsm-slack-itservice-configure/references/manage-user-access.md +117 -0
- package/skills/service-itsm-slack-itservice-configure/references/record-visibility.md +78 -0
- package/skills/service-itsm-slack-itservice-configure/references/site-membership-verification.md +126 -0
- package/skills/service-itsm-slack-itservice-configure/scripts/classify-user-access.mjs +167 -0
- package/skills/service-itsm-teams-configure/SKILL.md +50 -47
- package/skills/service-itsm-teams-configure/references/azure-credential-population.md +42 -28
- package/skills/service-itsm-teams-configure/references/gotchas.md +1 -2
- package/skills/service-itsm-teams-coordinate/SKILL.md +22 -18
- package/skills/service-itsm-teams-coordinate/examples/output-templates.md +12 -9
- package/skills/service-itsm-teams-itdesk-configure/SKILL.md +60 -44
- package/skills/service-itsm-teams-itservice-configure/SKILL.md +56 -70
- package/skills/service-catalog-template-deploy/SKILL.md +0 -310
- package/skills/service-catalog-template-deploy/references/cli-invocation.md +0 -258
- package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +0 -164
- package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +0 -94
- package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +0 -331
- package/skills/service-catalog-template-search/SKILL.md +0 -212
- package/skills/service-catalog-template-search/references/cli-invocation.md +0 -128
- package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +0 -205
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
# CLI Invocation Reference — Deploy Unified Catalog Template
|
|
2
|
-
|
|
3
|
-
Every operation runs through the **Salesforce CLI**. The Connect API routes are plain REST
|
|
4
|
-
(`sf api request rest`); the access self-heal and activation use `sf org assign …` and
|
|
5
|
-
`sf data …`. This skill runs a preflight probe, a read (resolve), a write (deploy), an activation, and
|
|
6
|
-
a read (verify).
|
|
7
|
-
|
|
8
|
-
- `--method GET|POST` — the verb.
|
|
9
|
-
- `--body '<json>'` (`-b`) — the POST payload. Accepts an inline JSON string, `@file.json`, or `""`
|
|
10
|
-
for an empty body. GET takes no `--body`.
|
|
11
|
-
- `--target-org <alias>` (`-o`) — pick the org; omit to use the default-org config.
|
|
12
|
-
- `-i` / `--include` — prepend the HTTP status line + headers (use it to read a `403`/`404`/`500`).
|
|
13
|
-
- **No `--json` flag exists** on `sf api request rest`. Do not pass one.
|
|
14
|
-
- Path is pinned to **`v67.0`** (the version the shapes below match); the routes do not exist below
|
|
15
|
-
`v65.0`. Use a higher version only if the org's own version exceeds v67.0.
|
|
16
|
-
|
|
17
|
-
## Response shape — RAW body, no wrapper
|
|
18
|
-
|
|
19
|
-
`sf api request rest` prints the **raw Connect response body** — there is **no** `{status_code, body}`
|
|
20
|
-
envelope around it. Read the fields **top-level**:
|
|
21
|
-
|
|
22
|
-
- GET templates → top-level `serviceProcessTemplateOutputRepresentation` (array)
|
|
23
|
-
- POST deploy → top-level `{ deploymentResult: string, status: "SUCCESS"|"FAILURE", templateId: string }`
|
|
24
|
-
|
|
25
|
-
**The deploy is synchronous** — there is no `jobId` and no poll endpoint for the single-template
|
|
26
|
-
route. Verify by re-reading (below). (Only the out-of-scope **bulk** endpoint returns a `batchJobId`.)
|
|
27
|
-
|
|
28
|
-
To read the HTTP status explicitly, add `-i`. A `403` + `FUNCTIONALITY_NOT_ENABLED [ServiceAutomationFamily]`
|
|
29
|
-
means the org lacks Unified Catalog; a `404` + `NOT_FOUND` means the route is below its minimum API
|
|
30
|
-
version (this skill targets **v67.0**; the routes do not exist below v65.0).
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Step 0 — Preflight access check (behavior-based, self-healing)
|
|
35
|
-
|
|
36
|
-
Unified Catalog access is **per-user**. The Step 1 GET is *also* the access probe — don't pre-check
|
|
37
|
-
with SOQL. Run it with `-i` to read the status:
|
|
38
|
-
|
|
39
|
-
- **`HTTP 200`** → access present (via `UnifiedCatalogAdmin` **or any persona the user already holds**
|
|
40
|
-
— `UnifiedCatalogAgent`, `UnifiedCatalogCommunityUser`, or a custom equivalent). Accept it; assign
|
|
41
|
-
nothing. Continue to name resolution.
|
|
42
|
-
- **`403` + `FUNCTIONALITY_NOT_ENABLED [ServiceAutomationFamily]`** → self-heal, in order, then
|
|
43
|
-
re-probe **once**:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
sf org assign permsetlicense --name UnifiedCatalogAdminPsl # layer 2: entitlement
|
|
47
|
-
sf org assign permset --name UnifiedCatalogAdmin # layer 3: the SET flips 403→200
|
|
48
|
-
# then re-run the Step 1 GET -i once
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
- Now `200` → continue.
|
|
52
|
-
- Still `403` → the org lacks the Unified Catalog **license** (layer 1), which a user assignment
|
|
53
|
-
cannot fix. **Report and stop.** Never loop the heal.
|
|
54
|
-
|
|
55
|
-
**The re-probe GET is the arbiter — not the assign command's exit code.** If the user was already
|
|
56
|
-
assigned, `sf org assign permset` prints a **`Duplicate PermissionSetAssignment`** failure and exits
|
|
57
|
-
non-zero. That is **benign** — the assignment already exists. Do not abort on it; judge success only
|
|
58
|
-
by the re-probe returning `200`. (`sf org assign permsetlicense` on an existing assignment is a clean
|
|
59
|
-
no-op.)
|
|
60
|
-
|
|
61
|
-
Why both assignments: the permission-set **license** (`permsetlicense`) is the entitlement and is
|
|
62
|
-
**necessary but not sufficient** — the permission **set** (`permset`) is what actually flips the route
|
|
63
|
-
from `403` to `200`. Assigning only the PSL still `403`s.
|
|
64
|
-
|
|
65
|
-
There is **no** "Designer" permission set to assign — `UnifiedCatalogAdmin` is the verified-sufficient
|
|
66
|
-
heal target. (A `PermissionsUnifiedCatalogDesignGAPerm` user-permission bit exists, but no dedicated
|
|
67
|
-
permission set carries it.) The three UC personas and their gating permission:
|
|
68
|
-
|
|
69
|
-
| Permission set | Carries |
|
|
70
|
-
|----------------|---------|
|
|
71
|
-
| `UnifiedCatalogAdmin` | `PermissionsUnifiedCatalogAdminPerm` — **verified to unlock the route** |
|
|
72
|
-
| `UnifiedCatalogAgent` | `PermissionsUnifiedCatalogAgentPerm` |
|
|
73
|
-
| `UnifiedCatalogCommunityUser` | `PermissionsUnifiedCatalogRuntimePerm` + `PermissionsAccessToServiceProcess` |
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Step 1 — Re-fetch the catalog and resolve the name
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
sf api request rest \
|
|
81
|
-
'/services/data/v67.0/connect/service-automation/service-process/get-all-templates' \
|
|
82
|
-
--method GET -i
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
`resolve-template.mjs` reads the top-level `serviceProcessTemplateOutputRepresentation` and matches the
|
|
86
|
-
user's named template case-insensitively against each `name`, returning an `action`:
|
|
87
|
-
|
|
88
|
-
- **exactly one exact-name match** → `DEPLOY`; the script returns `resolved.id` and
|
|
89
|
-
`templateDependencyMetadata`, proceed
|
|
90
|
-
- **two or more matches** → `STOP_AMBIGUOUS`; list matches, ask for the exact name — **never pick the
|
|
91
|
-
first**. This also covers a **category term** (e.g. "access") that is not itself a template name but
|
|
92
|
-
appears in ≥2 template names: the request is ambiguous, so `matchCount` is the candidate count
|
|
93
|
-
- **zero matches, fewer than two near-matches** → `STOP_NOT_FOUND`; report requested name + list
|
|
94
|
-
available names, never deploy a near-match
|
|
95
|
-
|
|
96
|
-
An exact single match short-circuits before the substring fallback, so naming an exact template (even
|
|
97
|
-
one whose words appear in other names) always deploys that one. Never reuse an `id` carried over from
|
|
98
|
-
the search skill — re-resolve from this live fetch every run.
|
|
99
|
-
|
|
100
|
-
Each element (`ServiceProcessTemplateOutputRepresentation`) carries `id`, `name`, `description`,
|
|
101
|
-
`type`, and `templateDependencyMetadata[]`.
|
|
102
|
-
|
|
103
|
-
- **`id`** is a **name-style string** (e.g. `itsmserviceprocess_RequestNewLaptop`), **not** an 18-char
|
|
104
|
-
Salesforce Id. Use it verbatim in the deploy path.
|
|
105
|
-
- **`type`** is a category such as `"Service"` — **not** `Intake`/`Fulfillment` (those are *dependency*
|
|
106
|
-
`templateType` values, below).
|
|
107
|
-
|
|
108
|
-
Each dependency (`DependencyDetails`) — **enum values come back in SCREAMING_SNAKE_CASE and must be
|
|
109
|
-
echoed verbatim**:
|
|
110
|
-
|
|
111
|
-
| Field | Live value (verbatim) |
|
|
112
|
-
|-------|-----------------------|
|
|
113
|
-
| `templateApiName` | API name of the dependency flow, e.g. `sfdc_internal__ItServiceRequestNewLaptopIntake` |
|
|
114
|
-
| `templateType` | `INTAKE` \| `FULFILLMENT` |
|
|
115
|
-
| `templateDependencyType` | `FLOW` |
|
|
116
|
-
| `dependencyDeploymentMedium` | `APP_FRAMEWORK` |
|
|
117
|
-
| `requiresDeploymentInput` | boolean — if any is `true`, collect input before deploying |
|
|
118
|
-
|
|
119
|
-
Verbatim example (live `templateDependencyMetadata` for "Request New Laptop"):
|
|
120
|
-
|
|
121
|
-
```json
|
|
122
|
-
[
|
|
123
|
-
{
|
|
124
|
-
"dependencyDeploymentMedium": "APP_FRAMEWORK",
|
|
125
|
-
"requiresDeploymentInput": false,
|
|
126
|
-
"templateApiName": "sfdc_internal__ItServiceRequestNewLaptopIntake",
|
|
127
|
-
"templateDependencyType": "FLOW",
|
|
128
|
-
"templateType": "INTAKE"
|
|
129
|
-
}
|
|
130
|
-
]
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## Step 2 — Build the deploy body
|
|
136
|
-
|
|
137
|
-
**This transformation runs in `scripts/build-deploy-payload.mjs`, not by hand** — iterating
|
|
138
|
-
dependencies, picking a fallback key, and passing enums through verbatim is a fixed algorithm
|
|
139
|
-
(authoring standard A9), so the skill invokes the script rather than constructing the body in prose.
|
|
140
|
-
The shape it produces is documented below for reference.
|
|
141
|
-
|
|
142
|
-
Build `flowTemplates[]` from `templateDependencyMetadata` — one element per dependency. **Echo every
|
|
143
|
-
enum verbatim** from the fetched metadata (SCREAMING_SNAKE_CASE); only fall back to an alternate source
|
|
144
|
-
key when the primary is absent, and **never normalize casing or hardcode a literal**:
|
|
145
|
-
|
|
146
|
-
```jsonc
|
|
147
|
-
// for each dep in templateDependencyMetadata:
|
|
148
|
-
{
|
|
149
|
-
"templateType": dep.templateType, // verbatim: "INTAKE" | "FULFILLMENT"
|
|
150
|
-
"templateApiName": dep.templateApiName ?? dep.dependencyApiName, // first non-empty
|
|
151
|
-
"templateDependencyType": dep.templateDependencyType ?? dep.dependencyType, // verbatim: "FLOW"
|
|
152
|
-
"dependencyDeploymentMedium": dep.dependencyDeploymentMedium, // verbatim: "APP_FRAMEWORK" — NOT hardcoded
|
|
153
|
-
"templateVariables": {} // {} unless deployment inputs collected
|
|
154
|
-
}
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
Do **not** write `"AppFramework"`/`"Intake"`/`"Flow"` (title-case) or hardcode
|
|
158
|
-
`dependencyDeploymentMedium`. The live API rejects mismatched enum casing. The verified-working body
|
|
159
|
-
for "Request New Laptop" was exactly:
|
|
160
|
-
|
|
161
|
-
```json
|
|
162
|
-
{"flowTemplates":[{"templateType":"INTAKE","templateApiName":"sfdc_internal__ItServiceRequestNewLaptopIntake","templateDependencyType":"FLOW","dependencyDeploymentMedium":"APP_FRAMEWORK","templateVariables":{}}]}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
Full deploy call (send only the optional keys the user supplied; omit the rest entirely). Pass the
|
|
166
|
-
body inline or, for anything non-trivial, from a file with `--body @deploy-body.json`:
|
|
167
|
-
|
|
168
|
-
```bash
|
|
169
|
-
sf api request rest \
|
|
170
|
-
'/services/data/v67.0/connect/service-automation/template/deploy/<templateId>' \
|
|
171
|
-
--method POST \
|
|
172
|
-
--body '{
|
|
173
|
-
"flowTemplates": [ /* built above */ ],
|
|
174
|
-
"description": "<desc>",
|
|
175
|
-
"isActive": true,
|
|
176
|
-
"deploymentMode": "Async",
|
|
177
|
-
"catalog": "<catalog>",
|
|
178
|
-
"category": "<category>"
|
|
179
|
-
}'
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
Only `flowTemplates` is always present (even if `[]`). `description` / `isActive` / `deploymentMode`
|
|
183
|
-
(`Async` | `CrossOrg` | `Sync`) / `catalog` / `category` appear **only if the user supplied them**.
|
|
184
|
-
Read the top-level `status` from the response.
|
|
185
|
-
|
|
186
|
-
> **Activation caveat (live-verified):** the deploy POST lands the resulting `Product2` with
|
|
187
|
-
> **`IsActive=false`** regardless — passing `isActive: true` in the body is **not** a verified
|
|
188
|
-
> activation path. The reliable, live-proven way to activate is the post-deploy DML in Step 3. Treat
|
|
189
|
-
> the body `isActive` as an unverified optional passthrough; rely on Step 3 for activation.
|
|
190
|
-
|
|
191
|
-
### `serviceProcessName` drift — read this
|
|
192
|
-
|
|
193
|
-
The **67.0 OAS marks `serviceProcessName` as a required body field**, but the **tested ITXM v66
|
|
194
|
-
client deliberately omits it**, with a regression test asserting the minimal body is exactly
|
|
195
|
-
`{ "flowTemplates": [] }` and the comment: *"serviceProcessName was removed (Salesforce API rejects
|
|
196
|
-
it on the deploy endpoint)."* These two authoritative sources disagree.
|
|
197
|
-
|
|
198
|
-
**Resolution:** build the body **without** `serviceProcessName` first (matching the tested client). If
|
|
199
|
-
the org's schema requires it and rejects the body, retry once **with** `serviceProcessName` set to the
|
|
200
|
-
Service Process name. Either way, keep `serviceProcessName` for the return/display value — it is not
|
|
201
|
-
necessarily sent to the API.
|
|
202
|
-
|
|
203
|
-
---
|
|
204
|
-
|
|
205
|
-
## Step 3 — Activate, then verify by re-reading
|
|
206
|
-
|
|
207
|
-
A deployed Service Process must end up **active**, and the POST leaves `Product2.IsActive=false`. The
|
|
208
|
-
`serviceProcessName` is **user-supplied**, so it must never be interpolated into a Bash command or a
|
|
209
|
-
SOQL literal — a name carrying shell metacharacters or a stray quote could execute during command
|
|
210
|
-
construction or alter the query and touch an unintended `Product2`. The resolve→activate→verify
|
|
211
|
-
sequence therefore runs in `scripts/activate-verify.mjs` (Agent Safety + authoring standard A9), which
|
|
212
|
-
takes the name as an **argument** (no shell interpolation), escapes it for the SOQL literal, and
|
|
213
|
-
invokes `sf` via an argument array (`shell:false`):
|
|
214
|
-
|
|
215
|
-
```bash
|
|
216
|
-
# pass the name as an argument — the script never builds a shell/SOQL string from it.
|
|
217
|
-
# add --no-activate only if the user explicitly asked to leave the Service Process inactive.
|
|
218
|
-
node "<skill_dir>/scripts/activate-verify.mjs" "<serviceProcessName>" --target-org <alias>
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
Internally the script runs the equivalent of: resolve the deployed `Product2` by name (`WHERE
|
|
222
|
-
Name='…' AND UsedFor='ServiceProcess' ORDER BY CreatedDate DESC LIMIT 1`, name SOQL-escaped),
|
|
223
|
-
conditionally `sf data update record … -v "IsActive=true"` when it is not already active, then re-read
|
|
224
|
-
to confirm. It prints `{ found, id, isActive, activated, verified }` and exits 0. If an `sf` query
|
|
225
|
-
itself fails (auth/transport/malformed — not a genuine absence), it instead prints
|
|
226
|
-
`{ error: true, detail, found, id, isActive: null, activated, verified: false }` and exits **non-zero**:
|
|
227
|
-
treat that as a **check failure**, not a verified not-found — report the `detail` and do not claim the
|
|
228
|
-
deploy did or did not land.
|
|
229
|
-
|
|
230
|
-
The deployed surface for a Service Process is the `Product2` (with `UsedFor='ServiceProcess'`) plus its
|
|
231
|
-
intake/fulfillment Flow(s) — the Flows land active on their own; only the `Product2` needs activating.
|
|
232
|
-
There is no separate `ServiceProcess`/`CatalogItem` sobject to publish. Report success only when the
|
|
233
|
-
script's `verified` is `true` (the re-read confirms the Service Process exists **and `IsActive=true`**,
|
|
234
|
-
unless `--no-activate`).
|
|
235
|
-
|
|
236
|
-
> Add `--target-org <alias>` to the script (or any `sf data …` / `sf org …` call) to target a specific
|
|
237
|
-
> org; omit to use the default.
|
|
238
|
-
|
|
239
|
-
---
|
|
240
|
-
|
|
241
|
-
## Gotchas
|
|
242
|
-
|
|
243
|
-
| Issue | Resolution |
|
|
244
|
-
|-------|------------|
|
|
245
|
-
| Enum casing | Live API returns `INTAKE`/`FULFILLMENT`/`FLOW`/`APP_FRAMEWORK` (SCREAMING_SNAKE). Echo verbatim; never title-case or hardcode `dependencyDeploymentMedium`. |
|
|
246
|
-
| Template `id` shape | Name-style string (`itsmserviceprocess_RequestNewLaptop`), not an 18-char Id. Use verbatim in the deploy path. |
|
|
247
|
-
| Template-level `type` | A category like `"Service"` — not `Intake`/`Fulfillment` (those are *dependency* `templateType` values). |
|
|
248
|
-
| `serviceProcessName` required vs rejected | Build without it first; retry once with it if the org rejects the body (see drift note). |
|
|
249
|
-
| Expecting a `{status_code, body}` wrapper | There is none — `sf api request rest` prints the **raw** body. Read `status`/`serviceProcessTemplateOutputRepresentation` top-level; use `-i` for the HTTP status. |
|
|
250
|
-
| Name → 0 templates | Stop; list available names; never deploy a near-match. |
|
|
251
|
-
| Name → 2+ templates | Stop; list matches; ask for the exact name; never pick the first. |
|
|
252
|
-
| POST `status: FAILURE` | Surface `deploymentResult` verbatim; stop; do not retry blindly. |
|
|
253
|
-
| `403` / `FUNCTIONALITY_NOT_ENABLED` on the GET probe | Per-user gap. Self-heal (Step 0): assign `UnifiedCatalogAdminPsl` **and** `UnifiedCatalogAdmin`, re-probe once. Still `403` = missing org license → report and stop. The permission **set** flips it, not the license alone. |
|
|
254
|
-
| Deployed `Product2` is `IsActive=false` | Expected — the POST does not activate. Activate in Step 3 via `sf data update record`. Passing body `isActive:true` is unverified; DML is the reliable path. |
|
|
255
|
-
| `404` / `NOT_FOUND` on GET or POST | The path is below the route's minimum API version — this skill targets **v67.0** (the routes do not exist below v65.0). Fix the version; do not fabricate. |
|
|
256
|
-
| Tempted to poll | Single-template deploy is synchronous — no job id; verify by re-read. |
|
|
257
|
-
| `requiresDeploymentInput: true` | Collect values in Phase 3; place them in `templateVariables`. |
|
|
258
|
-
| `INVALID_LOGIN` / auth error | The org's sf CLI auth has expired — re-authenticate with `sf org login web`. |
|
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Injection-safe activate-and-verify helper (authoring standards: Agent Safety + A9).
|
|
3
|
-
//
|
|
4
|
-
// SECURITY (the BLOCKER this script exists to fix): the Service Process name is
|
|
5
|
-
// USER-SUPPLIED. It must never be interpolated into a double-quoted Bash command
|
|
6
|
-
// or a single-quoted SOQL literal — a name containing shell metacharacters or a
|
|
7
|
-
// stray quote could execute during command construction or alter the query and
|
|
8
|
-
// touch an unintended Product2. This script:
|
|
9
|
-
// 1. accepts the name as an argv ARGUMENT (no shell interpolation ever), and
|
|
10
|
-
// 2. invokes `sf` via execFileSync with an ARGUMENT ARRAY (shell:false), and
|
|
11
|
-
// 3. escapes the name for the SOQL string literal before embedding it.
|
|
12
|
-
//
|
|
13
|
-
// A9: the resolve-Id → conditional-activate → re-read sequence is a fixed
|
|
14
|
-
// multi-command operation with query parsing and conditional DML; encoding it
|
|
15
|
-
// here keeps it deterministic instead of relying on model interpretation.
|
|
16
|
-
//
|
|
17
|
-
// Usage:
|
|
18
|
-
// node activate-verify.mjs <serviceProcessName> [--target-org <alias>] [--no-activate]
|
|
19
|
-
//
|
|
20
|
-
// --no-activate : verify only (use when the user asked to leave it inactive).
|
|
21
|
-
//
|
|
22
|
-
// Prints a JSON result: { found, id, isActive, activated, verified }.
|
|
23
|
-
// Exit 0 when the operation completed (found or honestly not-found); exit 2 on
|
|
24
|
-
// bad args; exit 1 only when an sf call errors unexpectedly.
|
|
25
|
-
|
|
26
|
-
import { execFileSync } from 'node:child_process';
|
|
27
|
-
|
|
28
|
-
const argv = process.argv.slice(2);
|
|
29
|
-
const noActivate = argv.includes('--no-activate');
|
|
30
|
-
const orgIdx = argv.indexOf('--target-org');
|
|
31
|
-
const targetOrg = orgIdx !== -1 ? argv[orgIdx + 1] : null;
|
|
32
|
-
// Positional name = first non-flag token that is NOT the --target-org value.
|
|
33
|
-
// Excluding the alias slot stops a flags-first call
|
|
34
|
-
// (`--target-org myOrg "My Template"`) from mistaking the alias for the name.
|
|
35
|
-
// Guard on orgIdx !== -1: when --target-org is absent, orgIdx is -1 and a bare
|
|
36
|
-
// `orgIdx + 1` would be 0 and wrongly exclude a name sitting at argv[0]. Setting
|
|
37
|
-
// the exclude slot to -1 in that case means it matches no real index.
|
|
38
|
-
const aliasIdx = orgIdx === -1 ? -1 : orgIdx + 1;
|
|
39
|
-
const name = argv.find((a, i) => !a.startsWith('--') && i !== aliasIdx);
|
|
40
|
-
|
|
41
|
-
if (!name) {
|
|
42
|
-
process.stderr.write('usage: node activate-verify.mjs <serviceProcessName> [--target-org <alias>] [--no-activate]\n');
|
|
43
|
-
process.exit(2);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Escape a value for embedding inside a single-quoted SOQL string literal.
|
|
47
|
-
// SOQL uses backslash escaping: backslash and single-quote must be escaped.
|
|
48
|
-
// (Reject any residual control chars defensively.)
|
|
49
|
-
function soqlLiteral(value) {
|
|
50
|
-
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/[\n\r\t]/g, ' ');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Run `sf` with an argument ARRAY — no shell, so nothing in `name` can be
|
|
54
|
-
// interpreted as a shell token. Returns parsed --json stdout.
|
|
55
|
-
function sf(args) {
|
|
56
|
-
const full = targetOrg ? [...args, '--target-org', targetOrg] : args;
|
|
57
|
-
try {
|
|
58
|
-
const out = execFileSync('sf', full, { encoding: 'utf8', maxBuffer: 20 * 1024 * 1024 });
|
|
59
|
-
return { ok: true, json: JSON.parse(out) };
|
|
60
|
-
} catch (err) {
|
|
61
|
-
// sf exits non-zero on query/DML errors; stdout may still hold --json.
|
|
62
|
-
const stdout = err.stdout ? String(err.stdout) : '';
|
|
63
|
-
try {
|
|
64
|
-
return { ok: false, json: JSON.parse(stdout) };
|
|
65
|
-
} catch {
|
|
66
|
-
return { ok: false, json: null, raw: stdout || String(err.message) };
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const literal = soqlLiteral(name);
|
|
72
|
-
const findQuery =
|
|
73
|
-
`SELECT Id, Name, IsActive FROM Product2 ` +
|
|
74
|
-
`WHERE Name='${literal}' AND UsedFor='ServiceProcess' ` +
|
|
75
|
-
`ORDER BY CreatedDate DESC LIMIT 1`;
|
|
76
|
-
|
|
77
|
-
// ---- 1. resolve the deployed Product2 by name (no shell; name is a query arg)
|
|
78
|
-
const found = sf(['data', 'query', '--query', findQuery, '--json']);
|
|
79
|
-
|
|
80
|
-
// A failed CLI query (auth/transport/malformed) must NOT be reported as a
|
|
81
|
-
// verified absence — `found:false` would be indistinguishable from a real
|
|
82
|
-
// not-found and could let a caller conclude the deploy silently vanished.
|
|
83
|
-
// Surface the error and exit non-zero so the caller treats it as a check failure.
|
|
84
|
-
if (!found.ok) {
|
|
85
|
-
const detail =
|
|
86
|
-
found.json?.message ||
|
|
87
|
-
found.json?.[0]?.message ||
|
|
88
|
-
found.raw ||
|
|
89
|
-
'sf data query failed';
|
|
90
|
-
process.stderr.write(`activate-verify: initial Product2 lookup failed — ${detail}\n`);
|
|
91
|
-
process.stdout.write(
|
|
92
|
-
JSON.stringify({
|
|
93
|
-
error: true,
|
|
94
|
-
found: null,
|
|
95
|
-
id: null,
|
|
96
|
-
isActive: null,
|
|
97
|
-
activated: false,
|
|
98
|
-
verified: false,
|
|
99
|
-
detail: String(detail),
|
|
100
|
-
}) + '\n',
|
|
101
|
-
);
|
|
102
|
-
process.exit(1);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const rec = found.json?.result?.records?.[0];
|
|
106
|
-
|
|
107
|
-
if (!rec) {
|
|
108
|
-
process.stdout.write(
|
|
109
|
-
JSON.stringify({ found: false, id: null, isActive: null, activated: false, verified: false }) + '\n',
|
|
110
|
-
);
|
|
111
|
-
process.exit(0);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
let activated = false;
|
|
115
|
-
|
|
116
|
-
// ---- 2. activate (conditional) — only if not already active and allowed ----
|
|
117
|
-
if (!noActivate && rec.IsActive !== true) {
|
|
118
|
-
const upd = sf(['data', 'update', 'record', '--sobject', 'Product2', '--record-id', rec.Id, '--values', 'IsActive=true', '--json']);
|
|
119
|
-
activated = upd.ok;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ---- 3. verify by re-reading -----------------------------------------------
|
|
123
|
-
const verifyRes = sf(['data', 'query', '--query', findQuery, '--json']);
|
|
124
|
-
|
|
125
|
-
// Symmetric to the initial-lookup guard above: a failed verification re-read
|
|
126
|
-
// (auth/transport/malformed) must NOT collapse to verified:false — that is
|
|
127
|
-
// indistinguishable from a genuine "record absent after activate" and could
|
|
128
|
-
// mask a deploy that actually landed. Surface the check error and exit non-zero.
|
|
129
|
-
// `activated` still reflects what the DML step reported.
|
|
130
|
-
if (!verifyRes.ok) {
|
|
131
|
-
const detail =
|
|
132
|
-
verifyRes.json?.message ||
|
|
133
|
-
verifyRes.json?.[0]?.message ||
|
|
134
|
-
verifyRes.raw ||
|
|
135
|
-
'sf data query failed';
|
|
136
|
-
process.stderr.write(`activate-verify: verification re-read failed — ${detail}\n`);
|
|
137
|
-
process.stdout.write(
|
|
138
|
-
JSON.stringify({
|
|
139
|
-
error: true,
|
|
140
|
-
found: true,
|
|
141
|
-
id: rec.Id,
|
|
142
|
-
isActive: null,
|
|
143
|
-
activated,
|
|
144
|
-
verified: false,
|
|
145
|
-
detail: String(detail),
|
|
146
|
-
}) + '\n',
|
|
147
|
-
);
|
|
148
|
-
process.exit(1);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const verifyRec = verifyRes.json?.result?.records?.[0];
|
|
152
|
-
const isActive = verifyRec?.IsActive === true;
|
|
153
|
-
const verified = Boolean(verifyRec) && (noActivate || isActive);
|
|
154
|
-
|
|
155
|
-
process.stdout.write(
|
|
156
|
-
JSON.stringify({
|
|
157
|
-
found: true,
|
|
158
|
-
id: verifyRec?.Id ?? rec.Id,
|
|
159
|
-
isActive,
|
|
160
|
-
activated,
|
|
161
|
-
verified,
|
|
162
|
-
}) + '\n',
|
|
163
|
-
);
|
|
164
|
-
process.exit(0);
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Deterministic deploy-body builder (authoring standard A9).
|
|
3
|
-
//
|
|
4
|
-
// Transforming templateDependencyMetadata into the deploy POST body's
|
|
5
|
-
// flowTemplates[] is a FIXED transformation — iterate dependencies, pick the
|
|
6
|
-
// primary key with a defined fallback, and pass enum values through VERBATIM
|
|
7
|
-
// (SCREAMING_SNAKE_CASE). Doing it in prose invites the model to normalize
|
|
8
|
-
// casing or hardcode a literal, which the live API rejects. This script
|
|
9
|
-
// guarantees a consistent body and never mutates enum casing.
|
|
10
|
-
//
|
|
11
|
-
// Usage:
|
|
12
|
-
// node build-deploy-payload.mjs <resolved.json> [optional-fields.json]
|
|
13
|
-
//
|
|
14
|
-
// <resolved.json> is a FILE PATH to either:
|
|
15
|
-
// - the object emitted by resolve-template.mjs (its .resolved.templateDependencyMetadata is used), OR
|
|
16
|
-
// - a bare templateDependencyMetadata array, OR
|
|
17
|
-
// - a single template object carrying templateDependencyMetadata.
|
|
18
|
-
// [optional-fields.json] is a FILE PATH to a JSON object of user-supplied
|
|
19
|
-
// optional keys to merge in (description / isActive / deploymentMode / catalog
|
|
20
|
-
// / category / serviceProcessName). Only keys the user actually supplied should
|
|
21
|
-
// be present; absent keys are omitted from the body entirely.
|
|
22
|
-
//
|
|
23
|
-
// Prints the deploy POST body to stdout. Exit 0 on success; exit 2 on bad args.
|
|
24
|
-
|
|
25
|
-
import { readFileSync } from 'node:fs';
|
|
26
|
-
|
|
27
|
-
const [resolvedPath, optionalPath] = process.argv.slice(2);
|
|
28
|
-
if (!resolvedPath) {
|
|
29
|
-
process.stderr.write('usage: node build-deploy-payload.mjs <resolved.json> [optional-fields.json]\n');
|
|
30
|
-
process.exit(2);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readJson(path) {
|
|
34
|
-
try {
|
|
35
|
-
return JSON.parse(readFileSync(path, 'utf8'));
|
|
36
|
-
} catch (err) {
|
|
37
|
-
process.stderr.write(`build-deploy-payload: cannot read/parse ${path}: ${err.message}\n`);
|
|
38
|
-
process.exit(2);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const input = readJson(resolvedPath);
|
|
43
|
-
|
|
44
|
-
// Accept any of the three shapes described above.
|
|
45
|
-
let deps;
|
|
46
|
-
if (Array.isArray(input)) {
|
|
47
|
-
deps = input;
|
|
48
|
-
} else if (Array.isArray(input?.resolved?.templateDependencyMetadata)) {
|
|
49
|
-
deps = input.resolved.templateDependencyMetadata;
|
|
50
|
-
} else if (Array.isArray(input?.templateDependencyMetadata)) {
|
|
51
|
-
deps = input.templateDependencyMetadata;
|
|
52
|
-
} else {
|
|
53
|
-
// No recognized deploy-ready shape: not a bare templateDependencyMetadata
|
|
54
|
-
// array, not a resolve-template.mjs DEPLOY output (its `.resolved` is null on a
|
|
55
|
-
// STOP_AMBIGUOUS / STOP_NOT_FOUND / SELF_HEAL), and not a single template
|
|
56
|
-
// object. Fail loud rather than emit a valid-but-empty {"flowTemplates":[]}
|
|
57
|
-
// that is indistinguishable from a dependency-free template — piping a stop /
|
|
58
|
-
// null resolution into the builder must produce an error signal, never a body.
|
|
59
|
-
process.stderr.write(
|
|
60
|
-
'build-deploy-payload: input is not a deploy-ready template (no ' +
|
|
61
|
-
'templateDependencyMetadata found; a STOP / null resolution has ' +
|
|
62
|
-
'resolved:null and cannot build a deploy body). Refusing to emit an empty body.\n',
|
|
63
|
-
);
|
|
64
|
-
process.exit(2);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// The ONLY constructed field is templateVariables (defaults to {}); every enum
|
|
68
|
-
// is echoed verbatim, with a defined fallback key when the primary is absent.
|
|
69
|
-
const flowTemplates = deps.map((dep) => {
|
|
70
|
-
const el = {
|
|
71
|
-
templateType: dep.templateType, // verbatim: "INTAKE" | "FULFILLMENT"
|
|
72
|
-
templateApiName: dep.templateApiName ?? dep.dependencyApiName, // first non-empty
|
|
73
|
-
templateDependencyType: dep.templateDependencyType ?? dep.dependencyType, // verbatim: "FLOW"
|
|
74
|
-
dependencyDeploymentMedium: dep.dependencyDeploymentMedium, // verbatim: "APP_FRAMEWORK"
|
|
75
|
-
templateVariables:
|
|
76
|
-
dep.templateVariables && typeof dep.templateVariables === 'object' ? dep.templateVariables : {},
|
|
77
|
-
};
|
|
78
|
-
return el;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
const body = { flowTemplates };
|
|
82
|
-
|
|
83
|
-
// Merge user-supplied optional fields — but only keys that are actually present
|
|
84
|
-
// (never send empty strings/nulls; omit the key entirely).
|
|
85
|
-
if (optionalPath) {
|
|
86
|
-
const optional = readJson(optionalPath);
|
|
87
|
-
for (const key of ['description', 'isActive', 'deploymentMode', 'catalog', 'category', 'serviceProcessName']) {
|
|
88
|
-
if (optional[key] !== undefined && optional[key] !== null && optional[key] !== '') {
|
|
89
|
-
body[key] = optional[key];
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
process.stdout.write(JSON.stringify(body) + '\n');
|