Enable the CMDB Feature (Service Cloud ITSM)
Takes an org from "CMDB off" to "CMDB feature enabled" by walking the first three layers of the
CMDB prerequisite stack in order. Every call runs through the Salesforce-hosted Headless-360 MCP
server (server key headless-360) via its four meta-tools (discover, describe,
dispatch_readonly, dispatch). The org is derived from the OAuth JWT bound to the current MCP
session — the skill never handles an org id, alias, or credentials — so this works identically
against production and sandbox with no per-user MCP install.
This skill covers Layers 0–2. User access (Layer 3) and content bundles (Layer 4) are separate
skills — see the end of this file.
The gate this skill lifts
Every CMDB Connect API checks:
orgHasCMDBEnabled = orgHasCMDBPermission (org perm ITSrvcsCnfgMgmnt) && OrgPreferences.CMDBEnabled
Until orgHasCMDBEnabled is true, CMDB APIs return 403 FUNCTIONALITY_NOT_ENABLED. CMDBEnabled
is NOT a directly settable preference — it is flipped as a side effect of enabling the feature in
Layer 2. This skill's job is to make that gate return true.
Necessary but not always sufficient for a given user. Lifting this org gate does not by itself
let a specific user read CMDB data. Some CMDB reads (e.g. bundleListView) also enforce
user-level CMDB access and return the same 403 FUNCTIONALITY_NOT_ENABLED ("not enabled for
this user") when the running user holds no CMDB permission sets — even though the feature is
correctly ENABLED. That is Layer 3 (service-itsm-agentic-setup-cmdb-access-assign), not a failure
of this skill. Confirm this skill's success via the feature status == ENABLED, never via a CMDB
data read.
Scope
- In scope: verifying the CMDB org permission (Layer 0), triggering + polling ITOM tenant
provisioning (Layer 1), pre-checking + enabling + verifying the CMDB feature (Layer 2).
- Out of scope: permission-set assignment (Layer 3 —
service-itsm-agentic-setup-cmdb-access-assign),
bundle installation (Layer 4 — service-itsm-agentic-setup-cmdb-bundle-deploy), CMDB record CRUD,
Discovery / Service Graph Connector, identification rules.
Mechanism
All operations dispatch through headless-360 MCP tools. Reads go through
mcp__headless-360__dispatch_readonly, writes through mcp__headless-360__dispatch — both take raw
HTTP: {"url": "<path>", "method": "GET|POST", "body"?: {...}, "queryParams"?: {...}} — not
{operation_id, arguments}. See references/mcp-invocation.md for the exact url / method /
body of every call. The four tools:
mcp__headless-360__discover — semantic search over the indexed operation catalog. The Setup/Connect
routes this skill uses are not always ranked first (or indexed), so a miss does not mean the
route is absent — dispatch the exact path directly (see references/mcp-invocation.md).
mcp__headless-360__describe — pull the full input schema and canonical route before any POST.
mcp__headless-360__dispatch_readonly — the dispatcher for every read (GET).
mcp__headless-360__dispatch — the dispatcher for every write (POST/PATCH).
The skill never handles credentials — the org is bound to the current OAuth session. If a dispatch*
call returns an auth error, tell the user to re-authenticate the headless-360 MCP connection (and
confirm the session points at the intended org), then stop.
Clarifying questions
Ask only what you cannot infer from conversation:
- Which org? Confirm the target org and state plainly that this org will be modified
(tenant provisioning and feature enable are writes). For production, get explicit confirmation.
Do not re-ask for anything the user already provided; pre-populate and note "(from conversation)".
Workflow
All steps are sequential and gated — do not advance past a failed layer. Always read before you
write: run the read-only check before every mutation.
Layer 0 — Verify the CMDB org SKU (read-only, hard gate)
CMDB requires the org permission ITSrvcsCnfgMgmnt, granted only by edition / license / org
template. No API can set it. The most reliable, universally-available way to verify the org
carries the CMDB SKU is to probe for the CMDB permission-set license — it exists only in orgs
provisioned with that license:
dispatch_readonly({ "url": "/services/data/v63.0/query", "method": "GET", "queryParams": { "q": "SELECT Id FROM PermissionSetLicense WHERE DeveloperName = 'ItSrvcCnfgItmReadPsl'" } })
→ totalSize == 1 (licensed) | totalSize == 0 (not licensed)
- totalSize == 1 → org is CMDB-licensed; proceed to Layer 1.
- totalSize == 0 → STOP. Tell the user in plain language (no developer names or API references
in the message they see):
This org isn't licensed for CMDB. CMDB availability is determined by the org's edition or
license and can't be turned on through setup — it has to be included when the org is
provisioned. Please have the org set up with CMDB (or use one that already has it), then run
this again.
The core Connect API GET /services/data/v63.0/setup/org/permissions/ITSrvcsCnfgMgmnt
({"isPermissionEnabled": true|false}) is an alternative, but it 404s on some org types
(including orgfarm test orgs), so prefer the PSL probe above. See
references/mcp-invocation.md for the details. If no probe resolves, report that the org perm could
not be verified and ask the user to confirm the org has CMDB licensed before continuing.
Layer 1 — Provision the ITOM tenant
CMDB runs on an ITOM tenant that must reach status PROVISIONED (asynchronous).
Check current status (read):
dispatch_readonly({ "url": "/services/data/v67.0/connect/tenantProvisioningStatus", "method": "GET" })
Branch on status:
PROVISIONED → already done; skip to Layer 2 (no trigger, no poll).
UNPROVISIONED → no job has run; go to step 2 and trigger it. Do not wait or poll on
this state — waiting never starts provisioning and just burns the budget.
PROVISIONING_IN_PROGRESS → a job is already running; skip the trigger and go straight to
the poll in step 3.
FAILED → treat as the FAILED case in step 3 (surface the reason; do not retry via API).
Trigger provisioning (write) — only when step 1 showed UNPROVISIONED. Confirm with the
user first:
dispatch({ "url": "/services/data/v67.0/connect/tenantProvisioningStatus", "method": "POST" })
After the trigger returns, tell the user provisioning has started and typically takes 2+
minutes, so there is nothing to check yet.
Poll the GET until status == PROVISIONED. This is async and reliably takes 2+ minutes
(measured completion clusters right around ~2 min 40 s), so an immediate poll is a guaranteed
no-op. Anchor all timing on the response's triggeredAt, not on when this run started — the
job may have been triggered by an earlier run (the PROVISIONING_IN_PROGRESS entry from step 1).
Parse triggeredAt as a UTC epoch and compute elapsed = max(0, now − triggeredAt) — clamp to
≥ 0 so a clock skew (or a server-vs-agent timezone mismatch) can't yield a negative elapsed
(waits forever) or a false timeout. If triggeredAt is missing or unparseable (the trigger
POST response may not have populated it yet, or an in-progress row from an earlier run may omit
it), fall back to anchoring on this run's start — treat elapsed = 0 and wait the full ~2-min
floor, so a branch always fires deterministically. Then:
elapsed < ~2 min → wait until ~2 min after triggeredAt before the first check (skips the
guaranteed-useless early polls), then poll every 30 seconds.
~2 min ≤ elapsed < 10 min → poll immediately (the initial wait has already passed — do not
wait a fresh 2 min), then every 30 seconds.
elapsed ≥ 10 min → do not start a fresh wait; treat it as the timeout case below (report
the last-seen status and let the user decide).
The overall budget is 10 minutes from triggeredAt (≈16 checks after the ~2-min floor). The
~2-min floor is a floor, not an extra delay — it brackets the typical ~2:40 completion within a
poll or two; do not stretch it longer. Do not poll during the initial wait. Exit the loop as soon
as:
status == PROVISIONED → success, advance to Layer 2.
status == FAILED → stop immediately. Do NOT retry via the API — surface the failure to the
user in plain language with three things:
- The failure reason, decoded to human-readable text. Read it from the response body (the
FAILED payload carries a detail field such as
error / failureReason / message —
unescape any HTML entities like </> and strip markup). If the response carries no
detail, say the tenant provisioning failed without a returned reason.
- A link to the org's tenant provisioning Setup page, built from the target org's instance
URL:
<org instance URL>/lightning/setup/CMDBProvisionalSettings/home.
- Ask the user to open that page and try provisioning manually, then re-run this skill once
the tenant shows
PROVISIONED. Only if the manual retry also fails does it need Salesforce
support.
- the 10-minute window elapses → stop, report the last-seen status, and let the user decide
whether to keep waiting (re-run) or investigate. Never spin past the 10-minute bound.
Polling in the background (runtime-permitting). Provisioning is a multi-minute wait, so the
user should not have to sit idle. If — and only if — the executing runtime supports backgrounded
work (e.g. an agent runtime that can spawn a detached sub-agent or a scheduled wake-up), delegate
the wait-then-poll loop to a background task so the user can keep working, and report the outcome
(PROVISIONED / FAILED / timed-out) when it finishes. If the runtime is a single-threaded turn
(the ADK / Agentforce / headless-360 production path is single-threaded — a poll loop blocks the
conversation there), poll inline instead. Either way: (a) tell the user up front it takes a few
minutes, and (b) give a resume path — if they step away and the turn ends, they can re-run this
skill and Layer 1 picks up from the current status (an already PROVISIONED tenant skips straight
to Layer 2). Never require a background primitive the runtime may not have.
Layer 2 — Enable the CMDB feature (this lifts the 403 gate)
The feature api name is service-cloud-itsm-cmdb-integration.
- Pre-check status (read):
dispatch_readonly({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/status", "method": "GET" })
status == ENABLED → already done; skip to verification.
status == NOT_ENABLED with enableBlockedReasons: [] → clear to enable.
enableBlockedReasons non-empty → STOP and relay each reason to the user in plain language
(these are prerequisites the org still needs — do not attempt the enable).
- Confirm with the user, then enable (write):
dispatch({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/enable", "method": "POST", "body": {} })
→ {"success": true}
- Verify (read) — do NOT trust the POST response alone:
dispatch_readonly({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/status", "method": "GET" })
→ expect status == ENABLED
status == ENABLED is the definitive — and only — confirmation this skill needs. Layer 2
succeeds or fails on this check alone; it does not perform any CMDB data read to confirm the
gate. A CMDB data read (e.g. bundleListView) also depends on the running user's own CMDB
access, so it cannot cleanly confirm the org-level enable — see the note under "The gate this
skill lifts". Once the feature shows ENABLED, Layer 2 is done.
Rules / Constraints
| Constraint |
Rationale |
| Verify Layer 0 before anything else |
If ITSrvcsCnfgMgmnt is off, no later step can succeed — fail fast with a clear message |
Never try to set ITSrvcsCnfgMgmnt via API |
There is no setter; it is license/edition/template only |
Never set CMDBEnabled directly (e.g. via updateDefaultOrgPrefs) |
It is not in any settable-pref allowlist; the server rejects it with 500. It flips only as a side effect of the Layer 2 feature enable |
| Read before every write; verify after every write |
Tenant + feature are async/stateful; the POST response can lag the real state |
| Confirm the target org and each write with the user |
These are real, hard-to-reverse changes on a live org |
| Do not advance past a failed or blocked layer |
Later layers depend on earlier ones and will 403 |
Anchor poll timing on the response's triggeredAt (parse as UTC epoch; elapsed = max(0, now − triggeredAt)), not on this run's start; ~2-min floor before the first check, then every 30s, 10-min total budget from triggeredAt. If triggeredAt is missing/unparseable, fall back to this run's start (elapsed = 0) |
Provisioning reliably takes 2+ min (measured ~2:40), so earlier polls are guaranteed no-ops. A PROVISIONING_IN_PROGRESS entry may have been triggered by an earlier run — if already past the ~2-min floor, poll immediately; if already past 10 min, report a timeout rather than waiting a fresh 2 min. Clamp elapsed to ≥ 0 so clock skew / timezone mismatch can't wait forever or false-timeout; the fallback keeps a branch firing when the timestamp is absent. Never spin past the 10-min bound |
| Background the poll loop only where the runtime supports it; else poll inline — never require a background primitive |
The user shouldn't sit idle for a multi-minute wait, but the production headless-360/ADK path is a single-threaded turn; a skill that mandates a background poller breaks there. Always give a re-run resume path |
On FAILED, never retry via API — decode the reason, give the Setup URL, ask for a manual retry |
The API trigger has already failed; the user can retry from the CMDB provisioning Setup page, which surfaces the real error and any manual remediation |
| Never expose internal jargon to the user |
Keep record IDs, org IDs, HTTP status codes (403/500/…), API error codes (FUNCTIONALITY_NOT_ENABLED, …), endpoint names (bundleListView, tenantProvisioningStatus), developer names (ITSrvcsCnfgMgmnt, CMDBEnabled), and tooling internals (dispatch, headless-360) out of user-facing output. Translate to plain language; use human-readable names and statuses |
Verification checklist
Output expectations
CMDB Feature Enable — Complete (via service-itsm-agentic-setup-cmdb-configure)
Target org: <org>
CMDB license .................. Present
ITOM tenant ................... Provisioned
CMDB feature .................. Enabled
CMDB is now enabled on this org. Next steps:
• Assign user access → service-itsm-agentic-setup-cmdb-access-assign
• Install base bundle → service-itsm-agentic-setup-cmdb-bundle-deploy
Keep internal jargon out of user-facing output (no record IDs, HTTP status codes, error codes,
endpoint or developer names). If any step fails, stop and tell the user — in plain language — which
part of setup didn't succeed and what it means for them, then point to the relevant fix. Translate
any raw error (e.g. a 403 or FUNCTIONALITY_NOT_ENABLED) into what it means ("CMDB isn't enabled
yet"), rather than echoing the code.
Common failures (surface these in plain language)
| Symptom |
Likely cause |
What to tell the user |
Layer 0 returns false |
Org lacks the CMDB SKU |
License/edition prerequisite — no API can grant it; provision the org with CMDB |
403 FUNCTIONALITY_NOT_ENABLED on CMDB reads while feature status != ENABLED |
Feature not yet enabled (Layer 2 incomplete) |
Finish Layer 2; the gate lifts only after the feature is ENABLED |
403 FUNCTIONALITY_NOT_ENABLED on bundleListView while feature status == ENABLED |
Feature IS enabled; the running user lacks CMDB permission sets (bundleListView also enforces user-level access) |
Not a Layer 2 failure — this is Layer 3; run service-itsm-agentic-setup-cmdb-access-assign to grant the user CMDB access |
Feature enable blocked (enableBlockedReasons non-empty) |
Missing dependency the org still needs |
Relay each reason; resolve those first, then retry |
Tenant stuck UNPROVISIONED / PROVISIONING_IN_PROGRESS / long-running |
Provisioning is async |
It typically takes ~2–3 min; keep polling within the 10-min budget or retry the trigger |
Tenant FAILED |
Provisioning job failed Salesforce-side |
Share the decoded failure reason + the org's /lightning/setup/CMDBProvisionalSettings/home link and ask the user to retry provisioning manually there; escalate to Salesforce support only if the manual retry also fails |
dispatch* auth error |
headless-360 MCP session not authenticated / token expired |
Re-authenticate the headless-360 MCP connection and confirm the session points at the intended org |
Reference file index
| File |
When to read |
references/mcp-invocation.md |
Exact dispatch* url/method/body for every Layer 0–2 call, response envelopes, and error table |
1---2name: service-itsm-agentic-setup-cmdb-configure3description: Enable the CMDB (Configuration Management Database) feature in Service Cloud ITSM against a production or sandbox org: verify the CMDB org SKU, provision the ITOM tenant, and enable the service-cloud-itsm-cmdb-integration feature that lifts the CMDB access gate. Use when the user asks to enable CMDB, turn on the Configuration Management Database, provision the ITOM tenant, enable the CMDB feature, or fix a CMDB 403 FUNCTIONALITY_NOT_ENABLED error. Triggers on: enable CMDB feature, provision ITOM tenant, turn on CMDB, CMDB not enabled, CMDB 403 error, service-cloud-itsm-cmdb-integration. DO NOT TRIGGER when: the user only wants to assign CMDB permission sets to users, only install a CMDB content bundle, or work with CMDB records directly.4---5
6# Enable the CMDB Feature (Service Cloud ITSM)
7
8Takes an org from "CMDB off" to "CMDB feature enabled" by walking the first three layers of the
9CMDB prerequisite stack in order. Every call runs through the **Salesforce-hosted Headless-360 MCP
10server** (server key `headless-360`) via its four meta-tools (`discover`, `describe`,
11`dispatch_readonly`, `dispatch`). The org is derived from the OAuth JWT bound to the current MCP
12session — the skill never handles an org id, alias, or credentials — so this works identically
13against **production** and sandbox with no per-user MCP install.
14
15This skill covers **Layers 0–2**. User access (Layer 3) and content bundles (Layer 4) are separate
16skills — see the end of this file.
17
18## The gate this skill lifts
19
20Every CMDB Connect API checks:
21
22```text
23orgHasCMDBEnabled = orgHasCMDBPermission (org perm ITSrvcsCnfgMgmnt) && OrgPreferences.CMDBEnabled
24```
25
26Until `orgHasCMDBEnabled` is true, CMDB APIs return `403 FUNCTIONALITY_NOT_ENABLED`. `CMDBEnabled`
27is NOT a directly settable preference — it is flipped as a side effect of enabling the feature in
28Layer 2. This skill's job is to make that gate return true.
29
30> **Necessary but not always sufficient for a given user.** Lifting this org gate does not by itself
31> let a *specific* user read CMDB data. Some CMDB reads (e.g. `bundleListView`) also enforce
32> **user-level** CMDB access and return the same `403 FUNCTIONALITY_NOT_ENABLED` ("not enabled for
33> this user") when the running user holds no CMDB permission sets — even though the feature is
34> correctly ENABLED. That is Layer 3 (`service-itsm-agentic-setup-cmdb-access-assign`), not a failure
35> of this skill. Confirm this skill's success via the feature `status == ENABLED`, never via a CMDB
36> data read.
37
38## Scope
39
40- **In scope**: verifying the CMDB org permission (Layer 0), triggering + polling ITOM tenant
41 provisioning (Layer 1), pre-checking + enabling + verifying the CMDB feature (Layer 2).
42- **Out of scope**: permission-set assignment (Layer 3 — `service-itsm-agentic-setup-cmdb-access-assign`),
43 bundle installation (Layer 4 — `service-itsm-agentic-setup-cmdb-bundle-deploy`), CMDB record CRUD,
44 Discovery / Service Graph Connector, identification rules.
45
46## Mechanism
47
48All operations dispatch through **headless-360** MCP tools. Reads go through
49`mcp__headless-360__dispatch_readonly`, writes through `mcp__headless-360__dispatch` — both take raw
50HTTP: `{"url": "<path>", "method": "GET|POST", "body"?: {...}, "queryParams"?: {...}}` — **not**
51`{operation_id, arguments}`. See `references/mcp-invocation.md` for the exact `url` / `method` /
52`body` of every call. The four tools:
53
54- `mcp__headless-360__discover` — semantic search over the indexed operation catalog. The Setup/Connect
55 routes this skill uses are not always ranked first (or indexed), so a miss does **not** mean the
56 route is absent — dispatch the exact path directly (see `references/mcp-invocation.md`).
57- `mcp__headless-360__describe` — pull the full input schema and canonical route before any POST.
58- `mcp__headless-360__dispatch_readonly` — the dispatcher for every read (GET).
59- `mcp__headless-360__dispatch` — the dispatcher for every write (POST/PATCH).
60
61The skill never handles credentials — the org is bound to the current OAuth session. If a `dispatch*`
62call returns an auth error, tell the user to re-authenticate the headless-360 MCP connection (and
63confirm the session points at the intended org), then stop.
64
65---
66
67## Clarifying questions
68
69Ask only what you cannot infer from conversation:
70
71- **Which org?** Confirm the target org and state plainly that **this org will be modified**
72 (tenant provisioning and feature enable are writes). For production, get explicit confirmation.
73
74Do not re-ask for anything the user already provided; pre-populate and note "(from conversation)".
75
76---
77
78## Workflow
79
80All steps are sequential and gated — **do not advance past a failed layer.** Always read before you
81write: run the read-only check before every mutation.
82
83### Layer 0 — Verify the CMDB org SKU (read-only, hard gate)
84
85CMDB requires the org permission `ITSrvcsCnfgMgmnt`, granted only by edition / license / org
86template. **No API can set it.** The most reliable, universally-available way to verify the org
87carries the CMDB SKU is to probe for the CMDB permission-set license — it exists **only** in orgs
88provisioned with that license:
89
90```text
91dispatch_readonly({ "url": "/services/data/v63.0/query", "method": "GET", "queryParams": { "q": "SELECT Id FROM PermissionSetLicense WHERE DeveloperName = 'ItSrvcCnfgItmReadPsl'" } })
92→ totalSize == 1 (licensed) | totalSize == 0 (not licensed)
93```
94
95- **totalSize == 1** → org is CMDB-licensed; proceed to Layer 1.
96- **totalSize == 0** → STOP. Tell the user in plain language (no developer names or API references
97 in the message they see):
98 > This org isn't licensed for CMDB. CMDB availability is determined by the org's edition or
99 > license and can't be turned on through setup — it has to be included when the org is
100 > provisioned. Please have the org set up with CMDB (or use one that already has it), then run
101 > this again.
102
103The core Connect API `GET /services/data/v63.0/setup/org/permissions/ITSrvcsCnfgMgmnt`
104(`{"isPermissionEnabled": true|false}`) is an alternative, but it **404s on some org types
105(including orgfarm test orgs)**, so prefer the PSL probe above. See
106`references/mcp-invocation.md` for the details. If no probe resolves, report that the org perm could
107not be verified and ask the user to confirm the org has CMDB licensed before continuing.
108
109### Layer 1 — Provision the ITOM tenant
110
111CMDB runs on an ITOM tenant that must reach status `PROVISIONED` (asynchronous).
112
1131. **Check current status** (read):
114 ```text
115 dispatch_readonly({ "url": "/services/data/v67.0/connect/tenantProvisioningStatus", "method": "GET" })
116 ```
117 Branch on `status`:
118 - `PROVISIONED` → already done; skip to Layer 2 (no trigger, no poll).
119 - `UNPROVISIONED` → no job has run; go to step 2 and **trigger** it. Do **not** wait or poll on
120 this state — waiting never starts provisioning and just burns the budget.
121 - `PROVISIONING_IN_PROGRESS` → a job is already running; **skip the trigger** and go straight to
122 the poll in step 3.
123 - `FAILED` → treat as the FAILED case in step 3 (surface the reason; do not retry via API).
1242. **Trigger provisioning** (write) — only when step 1 showed `UNPROVISIONED`. Confirm with the
125 user first:
126 ```text
127 dispatch({ "url": "/services/data/v67.0/connect/tenantProvisioningStatus", "method": "POST" })
128 ```
129 After the trigger returns, tell the user provisioning has started and **typically takes 2+
130 minutes**, so there is nothing to check yet.
1313. **Poll** the GET until `status == PROVISIONED`. This is async and reliably takes **2+ minutes**
132 (measured completion clusters right around **~2 min 40 s**), so an immediate poll is a guaranteed
133 no-op. **Anchor all timing on the response's `triggeredAt`, not on when this run started** — the
134 job may have been triggered by an earlier run (the `PROVISIONING_IN_PROGRESS` entry from step 1).
135 Parse `triggeredAt` as a UTC epoch and compute `elapsed = max(0, now − triggeredAt)` — clamp to
136 `≥ 0` so a clock skew (or a server-vs-agent timezone mismatch) can't yield a negative `elapsed`
137 (waits forever) or a false timeout. **If `triggeredAt` is missing or unparseable** (the trigger
138 POST response may not have populated it yet, or an in-progress row from an earlier run may omit
139 it), fall back to anchoring on this run's start — treat `elapsed = 0` and wait the full ~2-min
140 floor, so a branch always fires deterministically. Then:
141 - `elapsed < ~2 min` → wait until ~2 min after `triggeredAt` before the first check (skips the
142 guaranteed-useless early polls), then poll every 30 seconds.
143 - `~2 min ≤ elapsed < 10 min` → **poll immediately** (the initial wait has already passed — do not
144 wait a fresh 2 min), then every 30 seconds.
145 - `elapsed ≥ 10 min` → **do not start a fresh wait**; treat it as the timeout case below (report
146 the last-seen status and let the user decide).
147
148 The overall budget is **10 minutes from `triggeredAt`** (≈16 checks after the ~2-min floor). The
149 ~2-min floor is a floor, not an extra delay — it brackets the typical ~2:40 completion within a
150 poll or two; do not stretch it longer. Do not poll during the initial wait. Exit the loop as soon
151 as:
152 - `status == PROVISIONED` → success, advance to Layer 2.
153 - `status == FAILED` → stop immediately. Do NOT retry via the API — surface the failure to the
154 user in plain language with three things:
155 1. **The failure reason, decoded to human-readable text.** Read it from the response body (the
156 FAILED payload carries a detail field such as `error` / `failureReason` / `message` —
157 unescape any HTML entities like `<`/`>` and strip markup). If the response carries no
158 detail, say the tenant provisioning failed without a returned reason.
159 2. **A link to the org's tenant provisioning Setup page**, built from the target org's instance
160 URL: `<org instance URL>/lightning/setup/CMDBProvisionalSettings/home`.
161 3. **Ask the user to open that page and try provisioning manually**, then re-run this skill once
162 the tenant shows `PROVISIONED`. Only if the manual retry also fails does it need Salesforce
163 support.
164 - the 10-minute window elapses → stop, report the last-seen status, and let the user decide
165 whether to keep waiting (re-run) or investigate. Never spin past the 10-minute bound.
166
167 **Polling in the background (runtime-permitting).** Provisioning is a multi-minute wait, so the
168 user should not have to sit idle. **If — and only if — the executing runtime supports backgrounded
169 work** (e.g. an agent runtime that can spawn a detached sub-agent or a scheduled wake-up), delegate
170 the wait-then-poll loop to a background task so the user can keep working, and report the outcome
171 (`PROVISIONED` / `FAILED` / timed-out) when it finishes. **If the runtime is a single-threaded turn**
172 (the ADK / Agentforce / headless-360 production path is single-threaded — a poll loop blocks the
173 conversation there), poll **inline** instead. Either way: (a) tell the user up front it takes a few
174 minutes, and (b) give a resume path — if they step away and the turn ends, they can re-run this
175 skill and Layer 1 picks up from the current status (an already `PROVISIONED` tenant skips straight
176 to Layer 2). Never *require* a background primitive the runtime may not have.
177
178### Layer 2 — Enable the CMDB feature (this lifts the 403 gate)
179
180The feature api name is `service-cloud-itsm-cmdb-integration`.
181
1821. **Pre-check status** (read):
183 ```text
184 dispatch_readonly({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/status", "method": "GET" })
185 ```
186 - `status == ENABLED` → already done; skip to verification.
187 - `status == NOT_ENABLED` with `enableBlockedReasons: []` → clear to enable.
188 - `enableBlockedReasons` non-empty → STOP and relay each reason to the user in plain language
189 (these are prerequisites the org still needs — do not attempt the enable).
1902. **Confirm with the user**, then **enable** (write):
191 ```text
192 dispatch({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/enable", "method": "POST", "body": {} })
193 → {"success": true}
194 ```
1953. **Verify** (read) — do NOT trust the POST response alone:
196 ```text
197 dispatch_readonly({ "url": "/services/data/v67.0/connect/setup/discovery/feature/service-cloud-itsm-cmdb-integration/status", "method": "GET" })
198 → expect status == ENABLED
199 ```
200 **`status == ENABLED` is the definitive — and only — confirmation this skill needs.** Layer 2
201 succeeds or fails on this check alone; it does **not** perform any CMDB data read to confirm the
202 gate. A CMDB data read (e.g. `bundleListView`) also depends on the *running user's* own CMDB
203 access, so it cannot cleanly confirm the org-level enable — see the note under "The gate this
204 skill lifts". Once the feature shows `ENABLED`, Layer 2 is done.
205
206---
207
208## Rules / Constraints
209
210| Constraint | Rationale |
211|-----------|-----------|
212| Verify Layer 0 before anything else | If `ITSrvcsCnfgMgmnt` is off, no later step can succeed — fail fast with a clear message |
213| Never try to set `ITSrvcsCnfgMgmnt` via API | There is no setter; it is license/edition/template only |
214| Never set `CMDBEnabled` directly (e.g. via `updateDefaultOrgPrefs`) | It is not in any settable-pref allowlist; the server rejects it with 500. It flips only as a side effect of the Layer 2 feature enable |
215| Read before every write; verify after every write | Tenant + feature are async/stateful; the POST response can lag the real state |
216| Confirm the target org and each write with the user | These are real, hard-to-reverse changes on a live org |
217| Do not advance past a failed or blocked layer | Later layers depend on earlier ones and will 403 |
218| Anchor poll timing on the response's `triggeredAt` (parse as UTC epoch; elapsed = max(0, now − triggeredAt)), not on this run's start; ~2-min floor before the first check, then every 30s, 10-min total budget from `triggeredAt`. If `triggeredAt` is missing/unparseable, fall back to this run's start (elapsed = 0) | Provisioning reliably takes 2+ min (measured ~2:40), so earlier polls are guaranteed no-ops. A `PROVISIONING_IN_PROGRESS` entry may have been triggered by an earlier run — if already past the ~2-min floor, poll immediately; if already past 10 min, report a timeout rather than waiting a fresh 2 min. Clamp elapsed to ≥ 0 so clock skew / timezone mismatch can't wait forever or false-timeout; the fallback keeps a branch firing when the timestamp is absent. Never spin past the 10-min bound |
219| Background the poll loop only where the runtime supports it; else poll inline — never require a background primitive | The user shouldn't sit idle for a multi-minute wait, but the production headless-360/ADK path is a single-threaded turn; a skill that mandates a background poller breaks there. Always give a re-run resume path |
220| On `FAILED`, never retry via API — decode the reason, give the Setup URL, ask for a manual retry | The API trigger has already failed; the user can retry from the CMDB provisioning Setup page, which surfaces the real error and any manual remediation |
221| Never expose internal jargon to the user | Keep record IDs, org IDs, HTTP status codes (403/500/…), API error codes (`FUNCTIONALITY_NOT_ENABLED`, …), endpoint names (`bundleListView`, `tenantProvisioningStatus`), developer names (`ITSrvcsCnfgMgmnt`, `CMDBEnabled`), and tooling internals (`dispatch`, `headless-360`) out of user-facing output. Translate to plain language; use human-readable names and statuses |
222
223---
224
225## Verification checklist
226
227- [ ] Layer 0: `ITSrvcsCnfgMgmnt` confirmed `true` (or stopped with a clear license message)?
228- [ ] Layer 1: tenant `status == PROVISIONED`?
229- [ ] Layer 2: pre-check showed `enableBlockedReasons: []` before enabling?
230- [ ] Layer 2: enable returned `success: true`?
231- [ ] Layer 2: verification GET shows `status == ENABLED`? **(this is the sole success criterion — no CMDB data read is used to confirm)**
232- [ ] Confirmed the target org and each write with the user first?
233
234---
235
236## Output expectations
237
238```text
239CMDB Feature Enable — Complete (via service-itsm-agentic-setup-cmdb-configure)
240
241Target org: <org>
242
243 CMDB license .................. Present
244 ITOM tenant ................... Provisioned
245 CMDB feature .................. Enabled
246
247CMDB is now enabled on this org. Next steps:
248 • Assign user access → service-itsm-agentic-setup-cmdb-access-assign
249 • Install base bundle → service-itsm-agentic-setup-cmdb-bundle-deploy
250```
251
252Keep internal jargon out of user-facing output (no record IDs, HTTP status codes, error codes,
253endpoint or developer names). If any step fails, stop and tell the user — in plain language — which
254part of setup didn't succeed and what it means for them, then point to the relevant fix. Translate
255any raw error (e.g. a 403 or `FUNCTIONALITY_NOT_ENABLED`) into what it means ("CMDB isn't enabled
256yet"), rather than echoing the code.
257
258---
259
260## Common failures (surface these in plain language)
261
262| Symptom | Likely cause | What to tell the user |
263|---------|--------------|-----------------------|
264| Layer 0 returns `false` | Org lacks the CMDB SKU | License/edition prerequisite — no API can grant it; provision the org with CMDB |
265| `403 FUNCTIONALITY_NOT_ENABLED` on CMDB reads **while feature `status != ENABLED`** | Feature not yet enabled (Layer 2 incomplete) | Finish Layer 2; the gate lifts only after the feature is ENABLED |
266| `403 FUNCTIONALITY_NOT_ENABLED` on `bundleListView` **while feature `status == ENABLED`** | Feature IS enabled; the running user lacks CMDB permission sets (`bundleListView` also enforces user-level access) | Not a Layer 2 failure — this is Layer 3; run `service-itsm-agentic-setup-cmdb-access-assign` to grant the user CMDB access |
267| Feature enable blocked (`enableBlockedReasons` non-empty) | Missing dependency the org still needs | Relay each reason; resolve those first, then retry |
268| Tenant stuck `UNPROVISIONED` / `PROVISIONING_IN_PROGRESS` / long-running | Provisioning is async | It typically takes ~2–3 min; keep polling within the 10-min budget or retry the trigger |
269| Tenant `FAILED` | Provisioning job failed Salesforce-side | Share the decoded failure reason + the org's `/lightning/setup/CMDBProvisionalSettings/home` link and ask the user to retry provisioning manually there; escalate to Salesforce support only if the manual retry also fails |
270| `dispatch*` auth error | headless-360 MCP session not authenticated / token expired | Re-authenticate the headless-360 MCP connection and confirm the session points at the intended org |
271
272---
273
274## Reference file index
275
276| File | When to read |
277|------|--------------|
278| `references/mcp-invocation.md` | Exact `dispatch*` url/method/body for every Layer 0–2 call, response envelopes, and error table |