@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.
Files changed (97) hide show
  1. package/package.json +1 -1
  2. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +72 -0
  3. package/skills/consumer-goods-rtr-datacloud-export-configure/references/inputs-and-namespace.md +38 -0
  4. package/skills/consumer-goods-rtr-datacloud-export-configure/references/procedure.md +158 -0
  5. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/detect-namespace.js +86 -0
  6. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/render-apex.js +64 -0
  7. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/resolve-id-by-name.js +47 -0
  8. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/sf-rest.js +171 -0
  9. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/soql-escape.js +26 -0
  10. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-report-config.apex +51 -0
  11. package/skills/consumer-goods-rtr-datacloud-export-configure/scripts/upsert-system-setting.apex +21 -0
  12. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +74 -0
  13. package/skills/consumer-goods-tpe-dashboard-configure/references/phases-1-6.md +112 -0
  14. package/skills/consumer-goods-tpe-dashboard-configure/references/phases-7-12.md +157 -0
  15. package/skills/consumer-goods-tpe-dashboard-configure/scripts/find-failure-reason.js +132 -0
  16. package/skills/consumer-goods-tpe-dashboard-configure/scripts/poll-status.js +116 -0
  17. package/skills/consumer-goods-tpe-dashboard-configure/scripts/render-apex.js +64 -0
  18. package/skills/consumer-goods-tpe-dashboard-configure/scripts/run-data-transform.js +121 -0
  19. package/skills/consumer-goods-tpe-dashboard-configure/scripts/schedule-business-period-export.apex +27 -0
  20. package/skills/consumer-goods-tpe-dashboard-configure/scripts/sf-rest.js +171 -0
  21. package/skills/consumer-goods-tpe-dashboard-configure/scripts/soql-escape.js +25 -0
  22. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +141 -0
  23. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/payload-shapes.md +447 -0
  24. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/references/procedure.md +263 -0
  25. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/clone-tpe-dashboards.js +537 -0
  26. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/scripts/sf-rest.js +195 -0
  27. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +157 -0
  28. package/skills/consumer-goods-tpe-datakit-deploy/scripts/detect-namespace.js +86 -0
  29. package/skills/consumer-goods-tpe-datakit-deploy/scripts/download-static-resource.js +151 -0
  30. package/skills/consumer-goods-tpe-datakit-deploy/scripts/extract-crm-field-permissions.js +115 -0
  31. package/skills/consumer-goods-tpe-datakit-deploy/scripts/sf-rest.js +109 -0
  32. package/skills/consumer-goods-tpe-datakit-deploy/scripts/update-field-permissions.js +433 -0
  33. package/skills/service-catalog-template-coordinate/SKILL.md +263 -0
  34. package/skills/service-catalog-template-coordinate/examples/output-templates.md +44 -0
  35. package/skills/service-catalog-template-coordinate/references/mcp-invocation.md +183 -0
  36. package/skills/service-catalog-template-coordinate/references/operations.md +230 -0
  37. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/SKILL.md +243 -0
  38. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/cli-invocation.md +205 -0
  39. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/helper-contracts.md +236 -0
  40. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/references/permset-topology.md +132 -0
  41. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-activated-agents.mjs +106 -0
  42. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-agent-access-state.mjs +113 -0
  43. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-assignment-state.mjs +99 -0
  44. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/classify-platform-permset-availability.mjs +155 -0
  45. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/gate-unified-catalog-tiers.mjs +100 -0
  46. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/rank-candidate-users.mjs +95 -0
  47. package/skills/service-itsm-agentic-setup-agent-runtime-access-assign/scripts/resolve-target-user.mjs +86 -0
  48. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +44 -23
  49. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +33 -9
  50. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +17 -18
  51. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +3 -1
  52. package/skills/service-itsm-agentic-setup-configure/SKILL.md +20 -12
  53. package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +73 -5
  54. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +8 -7
  55. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +45 -33
  56. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +8 -6
  57. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +10 -10
  58. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +114 -56
  59. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +33 -17
  60. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +9 -3
  61. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +8 -7
  62. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +43 -32
  63. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +6 -4
  64. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +10 -10
  65. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +106 -55
  66. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +27 -13
  67. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +9 -3
  68. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +159 -161
  69. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +51 -0
  70. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +1 -1
  71. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +120 -0
  72. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +28 -5
  73. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +19 -1
  74. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +350 -30
  75. package/skills/service-itsm-channels-coordinate/SKILL.md +80 -213
  76. package/skills/service-itsm-slack-itservice-configure/SKILL.md +363 -0
  77. package/skills/service-itsm-slack-itservice-configure/references/connect-agentforce-to-slack.md +159 -0
  78. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +88 -0
  79. package/skills/service-itsm-slack-itservice-configure/references/manage-user-access.md +117 -0
  80. package/skills/service-itsm-slack-itservice-configure/references/record-visibility.md +78 -0
  81. package/skills/service-itsm-slack-itservice-configure/references/site-membership-verification.md +126 -0
  82. package/skills/service-itsm-slack-itservice-configure/scripts/classify-user-access.mjs +167 -0
  83. package/skills/service-itsm-teams-configure/SKILL.md +50 -47
  84. package/skills/service-itsm-teams-configure/references/azure-credential-population.md +42 -28
  85. package/skills/service-itsm-teams-configure/references/gotchas.md +1 -2
  86. package/skills/service-itsm-teams-coordinate/SKILL.md +22 -18
  87. package/skills/service-itsm-teams-coordinate/examples/output-templates.md +12 -9
  88. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +60 -44
  89. package/skills/service-itsm-teams-itservice-configure/SKILL.md +56 -70
  90. package/skills/service-catalog-template-deploy/SKILL.md +0 -310
  91. package/skills/service-catalog-template-deploy/references/cli-invocation.md +0 -258
  92. package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +0 -164
  93. package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +0 -94
  94. package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +0 -331
  95. package/skills/service-catalog-template-search/SKILL.md +0 -212
  96. package/skills/service-catalog-template-search/references/cli-invocation.md +0 -128
  97. package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +0 -205
@@ -0,0 +1,205 @@
1
+ # CLI Invocation — service-itsm-agentic-setup-agent-runtime-access-assign
2
+
3
+ Every read and write uses the **Salesforce CLI (`sf`)** against the CLI's stored
4
+ session for `--target-org <alias>`. **Never extract the access token** from
5
+ `sf org display` — hand-building a raw HTTP request bypasses the CLI session and
6
+ leaks a bearer token into shell context.
7
+
8
+ ## `--json` rule
9
+
10
+ - `sf data query` **takes** `--json` → `{status, result:{records:[...]}}` envelope. That is what the classifier scripts expect.
11
+ - `sf api request rest` does **not** — its raw stdout body is already JSON. Passing `--json` errors on some endpoints.
12
+ - `sf org assign permset` **takes** `--json` → `result:{successes:[...], failures:[...]}`.
13
+
14
+ ## Phase 1 — discovery reads
15
+
16
+ Platform feature permsets (see `permset-topology.md` for the fixed six Names):
17
+
18
+ ```bash
19
+ sf data query \
20
+ -q "SELECT Id, Name, Label, LicenseId FROM PermissionSet WHERE Name IN ('EinsteinGPTPromptTemplateUser','EinsteinGPTPromptTemplateManager','GenieUserEnhancedSecurity','GenieAdmin','UnifiedCatalogCommunityUser','UnifiedCatalogAdmin')" \
21
+ --target-org <alias> --json
22
+ ```
23
+
24
+ Activated agents (an agent is activated iff the `BotVersions` child returns ≥1 row):
25
+
26
+ ```bash
27
+ sf data query \
28
+ -q "SELECT Id, DeveloperName, MasterLabel, (SELECT Status FROM BotVersions WHERE Status='Active') FROM BotDefinition WHERE Type='InternalCopilot'" \
29
+ --target-org <alias> --json
30
+ ```
31
+
32
+ ## Phase 2 — identity read
33
+
34
+ ```bash
35
+ sf api request rest "/services/data/v67.0/" --method GET --target-org <alias>
36
+ ```
37
+
38
+ `identity` is a URL ending in `/<orgId>/<userId>`; take the trailing `005…`
39
+ segment via `scripts/resolve-target-user.mjs`. Do NOT call `USER_ID()` (Apex-only)
40
+ or `/chatter/users/me` (403 when Chatter is off).
41
+
42
+ The resolver returns only `{ userId, identity, verdict }` — it carries no name. To
43
+ label the running-user option **"Me — <name>"** (fall back to just **"Me"** if this
44
+ returns nothing), read the name explicitly. Then query active org users with the
45
+ fields the ranker needs (`Profile.Name`, `Profile.UserLicense.Name`) and hand them
46
+ to `rank-candidate-users.mjs`, which filters out service/integration/bot/agent
47
+ accounts and returns up to five real candidates ranked for the `<audience>`
48
+ (`fulfiller` ⇒ standard-license users first; `employee` ⇒ Unified Employee users
49
+ first; `any` ⇒ input order):
50
+
51
+ The running-user query also selects `Profile.Name` + `Profile.UserLicense.Name` —
52
+ the Unified Catalog gate (Phase 3) needs the target user's license/profile:
53
+
54
+ ```bash
55
+ sf data query -q "SELECT Name, Profile.Name, Profile.UserLicense.Name FROM User WHERE Id = '<userId>'" \
56
+ --target-org <alias> --json > /tmp/itsm-running-user.json 2>/dev/null || true
57
+
58
+ sf data query -q "SELECT Id, Name, Username, Profile.Name, Profile.UserLicense.Name FROM User WHERE IsActive = true ORDER BY LastLoginDate DESC NULLS LAST LIMIT 25" \
59
+ --target-org <alias> --json > /tmp/itsm-candidate-users.json 2>/dev/null || true
60
+
61
+ node "<skill_dir>/scripts/rank-candidate-users.mjs" /tmp/itsm-candidate-users.json <audience> <userId>
62
+ ```
63
+
64
+ The `AskUserQuestion` picker takes at most four options, so offer **"Me — <name>"**
65
+ (recommended) plus the **top three** ranked candidates; its built-in **"Other"**
66
+ free-text option covers any username not listed. Determine `<audience>` from the
67
+ agent this grant is for (named in the request/handoff, or the Phase-1 activated
68
+ set) — a Fulfiller agent is run by IT staff (standard license), an Employee agent
69
+ by end employees (Unified Employee).
70
+
71
+ Named user (or any chosen/typed option) — also select the profile + license so the
72
+ Unified Catalog gate can run for this user. **Capture each selected user to its own
73
+ file**, keyed by username — never a single shared path: the target-user picker is a
74
+ multi-select, so a fixed file would clobber earlier users and mis-gate them against
75
+ the last user's license/profile:
76
+
77
+ ```bash
78
+ # Run once per selected user; each writes a distinct, username-keyed file.
79
+ sf data query -q "SELECT Id, Username, Name, IsActive, Profile.Name, Profile.UserLicense.Name FROM User WHERE Username = '<username>'" \
80
+ --target-org <alias> --json > "/tmp/itsm-target-user-<username>.json"
81
+ ```
82
+
83
+ ## Phase 3 — Unified Catalog per-user tier gate
84
+
85
+ The Unified Catalog PSLs are license-**shape** gated: the Community User tier can
86
+ be held only by a **Unified Employee** user and the Admin tier only by a **System
87
+ Administrator**. Before asking the Unified Catalog tier, run the gate **once per
88
+ selected target user** against **that user's own capture**
89
+ (`/tmp/itsm-running-user.json` for the running user,
90
+ `/tmp/itsm-target-user-<username>.json` for each named user) plus the Phase-1
91
+ availability capture, and **retain each user's `offer[]`/`omit` with that user's
92
+ assignment plan** — never reuse one user's result for another. Offer only the tiers
93
+ in `offer[]`, and on `omit` skip Unified Catalog for that user as "not applicable for
94
+ this user's license/profile — skipped":
95
+
96
+ ```bash
97
+ node "<skill_dir>/scripts/classify-platform-permset-availability.mjs" /tmp/itsm-platform-permsets.json > /tmp/itsm-availability.json
98
+ # Running user:
99
+ node "<skill_dir>/scripts/gate-unified-catalog-tiers.mjs" /tmp/itsm-availability.json /tmp/itsm-running-user.json
100
+ # Each named user — against that user's own capture:
101
+ node "<skill_dir>/scripts/gate-unified-catalog-tiers.mjs" /tmp/itsm-availability.json "/tmp/itsm-target-user-<username>.json"
102
+ ```
103
+
104
+ ## Phase 4 — idempotency reads
105
+
106
+ Agent Access permset + its existing `BotDefinition` grants (second query only
107
+ when the permset exists — else pass `NO-PERMSET` to the classifier):
108
+
109
+ ```bash
110
+ sf data query -q "SELECT Id, Name, Label FROM PermissionSet WHERE Name='Agent_Access'" \
111
+ --target-org <alias> --json > /tmp/agent-access.json
112
+
113
+ sf data query -q "SELECT SetupEntityId FROM SetupEntityAccess WHERE ParentId='<permsetId>' AND SetupEntityType='BotDefinition'" \
114
+ --target-org <alias> --json > /tmp/sea.json
115
+ ```
116
+
117
+ Per user + permset (feature tier or `Agent_Access`). PSLA query only when the
118
+ selected tier's `needsPsl:true`:
119
+
120
+ ```bash
121
+ sf data query -q "SELECT Id FROM PermissionSetAssignment WHERE AssigneeId='<userId>' AND PermissionSetId='<permsetId>'" \
122
+ --target-org <alias> --json > /tmp/psa.json
123
+
124
+ sf data query -q "SELECT Id FROM PermissionSetLicenseAssign WHERE AssigneeId='<userId>' AND PermissionSetLicenseId='<licenseId>'" \
125
+ --target-org <alias> --json > /tmp/psla.json
126
+ ```
127
+
128
+ ## Phase 6 — writes
129
+
130
+ **Create `Agent_Access`** (only if `needsCreate`). `Name` is the API name;
131
+ `Label` is the display label:
132
+
133
+ ```bash
134
+ sf api request rest "/services/data/v67.0/sobjects/PermissionSet" \
135
+ --method POST \
136
+ --body '{"Name":"Agent_Access","Label":"Agent Access"}' \
137
+ --target-org <alias>
138
+ ```
139
+
140
+ Response: `{"id":"0PS...","success":true,"errors":[]}`. Capture `id` — it is the
141
+ `ParentId` for the grants.
142
+
143
+ **Grant each chosen agent** (`SetupEntityId` = the `BotDefinition` Id). Send
144
+ `ParentId` + `SetupEntityId` ONLY — `SetupEntityType` is not creatable:
145
+
146
+ ```bash
147
+ sf api request rest "/services/data/v67.0/sobjects/SetupEntityAccess" \
148
+ --method POST \
149
+ --body '{"ParentId":"<permsetId>","SetupEntityId":"<botDefinitionId>"}' \
150
+ --target-org <alias>
151
+ ```
152
+
153
+ `DUPLICATE_VALUE` here ⇒ that agent is already granted; treat as success.
154
+
155
+ **PSL first, then permset** for a license-gated feature tier (`needsPsl:true`).
156
+ Use the selected tier's own `LicenseId` as `PermissionSetLicenseId`:
157
+
158
+ ```bash
159
+ sf api request rest "/services/data/v67.0/sobjects/PermissionSetLicenseAssign" \
160
+ --method POST \
161
+ --body '{"AssigneeId":"<userId>","PermissionSetLicenseId":"<licenseId>"}' \
162
+ --target-org <alias>
163
+
164
+ # Running user (the default target): OMIT --on-behalf-of; the command assigns to
165
+ # the authenticated user.
166
+ sf org assign permset --name "<permsetName>" --target-org <alias> --json
167
+ # A NAMED target user: pass their resolved <username> — NOT a 005 Id.
168
+ sf org assign permset --name "<permsetName>" --on-behalf-of "<username>" \
169
+ --target-org <alias> --json
170
+ ```
171
+
172
+ > **`--on-behalf-of` resolves by `Username`/alias, not a `005…` user Id.** Passing
173
+ > a user Id fails to match. For the running user (the default target) omit the flag
174
+ > entirely; for a named user pass the `<username>` resolved by the
175
+ > `WHERE Username=` query. Do **not** substitute a `$USERNAME` shell variable — in
176
+ > zsh it is a read-only builtin bound to the OS user and will silently mis-target.
177
+
178
+ **`Agent_Access` assignment** per user (standalone — no PSL):
179
+
180
+ ```bash
181
+ # Running user (default): OMIT --on-behalf-of.
182
+ sf org assign permset --name "Agent_Access" --target-org <alias> --json
183
+ # Named user: --on-behalf-of "<username>" (Username, never a 005 Id).
184
+ sf org assign permset --name "Agent_Access" --on-behalf-of "<username>" \
185
+ --target-org <alias> --json
186
+ ```
187
+
188
+ `sf org assign permset` response: `result.successes[]` / `result.failures[]`; a
189
+ `failure.message` matching `Duplicate id`/`already has` is idempotent success.
190
+
191
+ ## Phase 7 — verify read-backs
192
+
193
+ Re-run the Phase-4 `PermissionSetAssignment` / `PermissionSetLicenseAssign`
194
+ queries for each target user, and the `SetupEntityAccess` query for
195
+ `Agent_Access`, and confirm each intended row is now present.
196
+
197
+ ## Common error responses (surface verbatim)
198
+
199
+ | Status / errorCode | Meaning | Handling |
200
+ |---|---|---|
201
+ | `400 DUPLICATE_VALUE` on any POST | Row already exists | Idempotent success; not a failure |
202
+ | `400 INSUFFICIENT_ACCESS` on a PSL POST | Seat exhausted | STOP that write; tell the user no seats are available |
203
+ | `400`/`404` on `SetupEntityAccess` POST with `SetupEntityType` in body | Field not creatable | Remove `SetupEntityType`; send `ParentId`+`SetupEntityId` only |
204
+ | `401 Unauthorized` | CLI session expired | `sf org login web --alias <alias>` and retry |
205
+ | `404 Not Found` on `sf org assign permset` | Permset name absent on this org | Re-run Phase 1 discovery; org state may have changed |
@@ -0,0 +1,236 @@
1
+ # Helper Script Contracts — service-itsm-agentic-setup-agent-runtime-access-assign
2
+
3
+ All seven scripts are deterministic, side-effect-free JSON classifiers (A9). Each
4
+ reads one or more `sf data query --json` / `sf api request rest` captures and
5
+ emits a single JSON object to stdout. Exit is `0` on any parseable body — the
6
+ verdict is carried in the payload. Exit `2` is a usage error (missing args); the
7
+ scripts never exit non-zero on a workflow-blocking condition — that is the
8
+ caller's job based on the emitted `verdict`.
9
+
10
+ ## `scripts/classify-platform-permset-availability.mjs`
11
+
12
+ Which platform features are provisioned, and each tier's `present` + `needsPsl`.
13
+
14
+ ### Input
15
+
16
+ 1. `permsets.json` — `sf data query --json` capture of `PermissionSet` filtered
17
+ to the six known feature-tier `Name` values (see `permset-topology.md`).
18
+
19
+ ### Output
20
+
21
+ ```json
22
+ {
23
+ "features": [
24
+ { "feature": "Prompt Templates",
25
+ "tiers": {
26
+ "use": { "tier":"use", "name":"EinsteinGPTPromptTemplateUser", "displayLabel":"Prompt Template User", "present":true, "Id":"0PS…", "LicenseId":"0PL…", "needsPsl":true },
27
+ "admin": { "tier":"admin", "name":"EinsteinGPTPromptTemplateManager", "displayLabel":"Prompt Template Manager", "present":true, "Id":"0PS…", "LicenseId":"0PL…", "needsPsl":true }
28
+ },
29
+ "anyPresent": true }
30
+ ],
31
+ "provisionedFeatures": ["Prompt Templates","Data Cloud"],
32
+ "absentFeatures": ["Unified Catalog"],
33
+ "verdict": "ASSIGNABLE",
34
+ "reasons": ["..."]
35
+ }
36
+ ```
37
+
38
+ - `displayLabel` prefers the org's own `Label`; falls back to a canonical label only when the row is absent.
39
+ - `needsPsl` is per row (`LicenseId !== null`). Read it from the SELECTED tier.
40
+
41
+ | verdict | Meaning | Caller action |
42
+ |---|---|---|
43
+ | `ASSIGNABLE` | ≥1 feature provisioned | Ask a tier per provisioned feature; skip + report absent features |
44
+ | `NONE-PROVISIONED` | no feature permset present | Report it; still continue to the Agent Access concern |
45
+ | `CANNOT-CONFIRM` | query envelope unparseable | Surface `reasons[]` verbatim and stop |
46
+
47
+ **Never auto-select a tier.** Present use/agent vs admin and let the user pick.
48
+
49
+ ## `scripts/gate-unified-catalog-tiers.mjs`
50
+
51
+ Per **target user**, which **Unified Catalog** tiers are eligible to offer — or
52
+ whether to omit Unified Catalog for that user. The Unified Catalog PSLs are
53
+ license-**shape** gated (not seat-gated): the Community User tier can be held only
54
+ by a **Unified Employee**-licensed user, and the Admin tier is meaningful only for
55
+ a **System Administrator**. Offering a tier the user's license/profile can never
56
+ hold is a hard write-time failure, so this gate runs *before* the tier prompt.
57
+
58
+ ### Input (two positional args)
59
+
60
+ 1. `availability.json` — the `classify-platform-permset-availability.mjs` output
61
+ capture (carries the Unified Catalog `use`/`admin` tier objects with
62
+ `present`/`Id`/`LicenseId`/`needsPsl`).
63
+ 2. `user.json` — a `sf data query --json` capture of the **one** target user,
64
+ selecting `Profile.Name` and `Profile.UserLicense.Name`.
65
+
66
+ ### Output
67
+
68
+ ```json
69
+ {
70
+ "feature": "Unified Catalog",
71
+ "provisioned": true,
72
+ "user": { "profile":"System Administrator", "license":"Salesforce", "unifiedEmployee":false, "sysAdmin":true },
73
+ "offer": [ { "tier":"admin", "name":"UnifiedCatalogAdmin", "Id":"0PS…", "LicenseId":"0PL…", "needsPsl":true } ],
74
+ "omit": false,
75
+ "note": "..."
76
+ }
77
+ ```
78
+
79
+ - `offer[]` is the subset of **present** UC tiers this user is eligible for: `use`
80
+ (`UnifiedCatalogCommunityUser`) only when `unifiedEmployee`; `admin`
81
+ (`UnifiedCatalogAdmin`) only when `sysAdmin`. Feed those tier objects straight to
82
+ the tier prompt / write path.
83
+ - `omit:true` (empty `offer[]`) ⇒ do **not** present Unified Catalog for this user;
84
+ report it "not applicable for this user's license/profile — skipped" (never a
85
+ failed write). Run the gate **once per target user**, each against **that user's
86
+ own capture file** (the target-user picker is a multi-select — capture each user to
87
+ a distinct `/tmp/itsm-target-user-<username>.json`, never a shared path that a later
88
+ lookup would clobber), and retain each user's result with that user's assignment
89
+ plan — different users can differ.
90
+ - Fails **closed**: an unreadable availability or user capture ⇒ `omit:true` (better
91
+ to skip than to offer a tier that will fail); the reason is in `note`. Always exits
92
+ `0` on parseable bodies; exit `2` only on missing argv.
93
+
94
+ ## `scripts/resolve-target-user.mjs`
95
+
96
+ Extracts the running user's `005…` Id from the API-root `identity` URL.
97
+
98
+ ### Input
99
+
100
+ 1. `api-root.json` — capture of `sf api request rest "/services/data/v67.0/" --method GET`.
101
+
102
+ ### Output
103
+
104
+ `{ userId, identity, verdict: "RESOLVED" | "CANNOT-CONFIRM", reasons }`. The
105
+ trailing path segment must match `^005[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$` (15 or
106
+ 18 chars). Any other shape ⇒ `CANNOT-CONFIRM` (surface verbatim; do NOT fall back
107
+ to prose parsing).
108
+
109
+ ## `scripts/rank-candidate-users.mjs`
110
+
111
+ Up to five real, non-service candidate users to OFFER as selectable options when
112
+ the caller did not name a target user. A presentation aid, never a gating decision.
113
+
114
+ ### Input (three positional args)
115
+
116
+ 1. `users.json` — `sf data query --json` capture selecting active users with
117
+ `Id, Name, Username, Profile.Name, Profile.UserLicense.Name`.
118
+ 2. `audience` — `fulfiller` | `employee` | `any` (anything else ⇒ `any`). A
119
+ Fulfiller agent is run by IT staff on a **standard** license; an Employee agent
120
+ by end employees on a **Unified Employee** license — the matching cohort is
121
+ surfaced first.
122
+ 3. `runningUserId` (optional) — the "Me" user, excluded from candidates (it is
123
+ offered separately as the recommended option).
124
+
125
+ ### Output
126
+
127
+ ```json
128
+ {
129
+ "candidates": [ { "Id":"005…", "Name":"Ada Lovelace", "Username":"ada@example.com", "profile":"Standard User", "cohort":"standard" } ],
130
+ "audience": "fulfiller",
131
+ "total": 3,
132
+ "note": "Offering 3 candidate(s), standard cohort first (audience: fulfiller)."
133
+ }
134
+ ```
135
+
136
+ - Service/integration/bot/agent accounts (matched on profile, license, username,
137
+ or display name) are filtered out; `candidates[]` is capped at five.
138
+ - `cohort` is `unified-employee` when the user's Unified Employee **license**
139
+ (`Profile.UserLicense.Name`) matches — or, defensively, their `Profile.Name` — else
140
+ `standard`. The license is the authoritative signal, so a custom profile carrying
141
+ the license still ranks as Unified Employee. The audience-matching cohort is ranked
142
+ first; input order (the query orders by most-recent login) is preserved within a cohort.
143
+ - Empty `candidates[]` (no non-service humans, or an unreadable capture) ⇒ offer
144
+ **"Me"** plus the free-text **"Other"** option only. Always exits `0` on a
145
+ parseable body; exit `2` only on missing argv.
146
+ - The `AskUserQuestion` picker takes ≤4 options, so offer **"Me"** plus the **top
147
+ three** candidates; the rest stay reachable via **"Other"**.
148
+
149
+ ## `scripts/classify-activated-agents.mjs`
150
+
151
+ The activated-agent candidate list for the `Agent_Access` grants.
152
+
153
+ ### Input
154
+
155
+ 1. `bot-definitions.json` — capture of the `BotDefinition` + `(SELECT Status FROM BotVersions WHERE Status='Active')` query.
156
+
157
+ ### Output
158
+
159
+ ```json
160
+ {
161
+ "agents": [ { "Id":"0Xx…", "DeveloperName":"IT_Service_Fulfiller_Agent", "MasterLabel":"IT Service Fulfiller Agent", "active": true } ],
162
+ "activatedAgents": [ { "Id":"0Xx…", "DeveloperName":"…", "MasterLabel":"…" } ],
163
+ "inactiveAgents": [],
164
+ "verdict": "AGENTS-FOUND" | "NONE-ACTIVE" | "CANNOT-CONFIRM",
165
+ "reasons": ["..."]
166
+ }
167
+ ```
168
+
169
+ An agent is `active` iff its `BotVersions` child subquery returns ≥1 row. Only
170
+ `activatedAgents[]` are offered for the multi-select.
171
+
172
+ | verdict | Meaning | Caller action |
173
+ |---|---|---|
174
+ | `AGENTS-FOUND` | ≥1 activated agent | Present `activatedAgents[]` for multi-select |
175
+ | `NONE-ACTIVE` | no activated agent (none exist, or none active) | Report it; if features are also `NONE-PROVISIONED`, stop |
176
+ | `CANNOT-CONFIRM` | query envelope unparseable | Surface `reasons[]` verbatim and stop |
177
+
178
+ ## `scripts/classify-agent-access-state.mjs`
179
+
180
+ Whether `Agent_Access` must be created and which chosen agents still need a grant.
181
+
182
+ ### Input (three positional args)
183
+
184
+ 1. `agent-access-permset.json` — `PermissionSet WHERE Name='Agent_Access'` capture.
185
+ 2. `sea-existing.json` **OR** the sentinel `NO-PERMSET`:
186
+ - permset present ⇒ pass the `SetupEntityAccess WHERE ParentId=<permsetId> AND SetupEntityType='BotDefinition'` capture.
187
+ - permset absent ⇒ pass `NO-PERMSET` (no parent to query yet).
188
+ 3. `chosenAgentIds-csv` — comma-separated `BotDefinition` Ids the user selected.
189
+
190
+ ### Output
191
+
192
+ ```json
193
+ {
194
+ "permsetExists": false, "permsetId": null,
195
+ "chosenAgentIds": ["0Xx…","0Xx…"],
196
+ "grantedAgentIds": [], "missingAgentIds": ["0Xx…","0Xx…"],
197
+ "needsCreate": true, "needsGrants": true,
198
+ "verdict": "NEEDS-WORK" | "ALREADY-COMPLETE" | "CANNOT-CONFIRM",
199
+ "reasons": ["..."]
200
+ }
201
+ ```
202
+
203
+ | verdict | Meaning | Caller action |
204
+ |---|---|---|
205
+ | `NEEDS-WORK` | permset missing and/or ≥1 chosen agent ungranted | Confirm-to-write, then create (if `needsCreate`) + grant each `missingAgentIds` |
206
+ | `ALREADY-COMPLETE` | permset exists and grants every chosen agent | No `SetupEntityAccess` write; still assign the permset to the user(s) if needed |
207
+ | `CANNOT-CONFIRM` | envelope unparseable, or a contradictory `NO-PERMSET`/empty-chosen input | Surface `reasons[]` verbatim and stop |
208
+
209
+ ## `scripts/classify-assignment-state.mjs`
210
+
211
+ Per user + permset idempotency (feature tier OR `Agent_Access`).
212
+
213
+ ### Input (two positional args)
214
+
215
+ 1. `psa-existing.json` — `PermissionSetAssignment` capture for the target user + permset Id.
216
+ 2. `psla-existing.json` **OR** the sentinel `NO-PSL`:
217
+ - selected tier `needsPsl:true` ⇒ pass the `PermissionSetLicenseAssign` capture.
218
+ - standalone (`Agent_Access`, or any `needsPsl:false` tier) ⇒ pass `NO-PSL`; `licenseAssigned` is reported `null`.
219
+
220
+ ### Output
221
+
222
+ `{ permsetAssigned, licenseAssigned, needsWrite, verdict, reasons }`.
223
+
224
+ | verdict | Meaning | Caller action |
225
+ |---|---|---|
226
+ | `NEEDS-WRITE` | permset (or, when `needsPsl:true`, its license) missing | Confirm-to-write, then assign (PSL first if `needsPsl:true`; permset only if `false`) |
227
+ | `ALREADY-ASSIGNED` | all applicable rows present | Skip the write; go to verify |
228
+ | `CANNOT-CONFIRM` | a query envelope was unparseable | Surface `reasons[]` verbatim and stop |
229
+
230
+ ## Why deterministic classifiers?
231
+
232
+ Every decision that gates a write or a success report lives in a script, not in
233
+ prose. Model interpretation of `records[].length === 0` is a documented source of
234
+ false-positive skips ("empty, so nothing exists — proceed") when the real cause
235
+ was an auth failure that returned an empty envelope. The scripts distinguish a
236
+ real empty result from a failed read and carry the difference in the verdict.
@@ -0,0 +1,132 @@
1
+ # Platform Feature Permset Topology + Agent Access Mechanism
2
+
3
+ This skill grants a **user** two kinds of access after an ITSM agent is
4
+ activated:
5
+
6
+ 1. the **runtime action-execution permissions** the agent's actions call, and
7
+ 2. **access to the activated agents themselves** via a custom `Agent_Access`
8
+ permission set.
9
+
10
+ ## 1. Platform feature permsets (runtime action execution)
11
+
12
+ Verified live against real orgs. Each feature has a lighter **use/agent** tier
13
+ (enough to *use* the feature at runtime) and a full **admin** tier. All shipped
14
+ tiers observed are **license-gated** (`LicenseId != null` → `needsPsl:true`), but
15
+ the write path never assumes this — it reads `needsPsl` per row.
16
+
17
+ | Feature | use/agent tier (PermissionSet.Name) | admin tier (PermissionSet.Name) | Notes |
18
+ |---|---|---|---|
19
+ | Prompt Templates | `EinsteinGPTPromptTemplateUser` ("Prompt Template User") | `EinsteinGPTPromptTemplateManager` ("Prompt Template Manager") | both tiers share one PSL (`EinsteinGPTPromptTemplatesPsl`) |
20
+ | Data Cloud | `GenieUserEnhancedSecurity` ("Data Cloud User") | `GenieAdmin` (ships as "Data Cloud Architect" or "Data Cloud Admin") | both tiers share one PSL (`GenieDataPlatformStarterPsl`) |
21
+ | Unified Catalog | `UnifiedCatalogCommunityUser` ("Unified Catalog Community User") | `UnifiedCatalogAdmin` ("Unified Catalog Admin") | tiers are backed by per-tier PSLs (`UnifiedCatalogCommunityUserPsl` / `UnifiedCatalogAdminPsl`). **`UnifiedCatalogAgent` is NOT the human tier** — its shipped Description reads "Enables the *agent* to run service process", i.e. it is the agent's own bot-runtime identity permset, not what a human user assigns to exercise the agent |
22
+
23
+ ### Key facts
24
+
25
+ - **No single org has all three features.** On a Prompt-Template + Data-Cloud
26
+ org, the two Unified Catalog rows simply do not exist — the classifier reports
27
+ Unified Catalog `absent` and the skill skips it. This is expected, not a
28
+ failure. Detect-if-present / assign-what-is-there / report-the-rest is the
29
+ whole point of the availability classifier.
30
+ - **Display label comes from the org.** `GenieAdmin` renders as "Data Cloud
31
+ Architect" on some orgs and "Data Cloud Admin" on others — the classifier
32
+ prefers the row's own `Label` and falls back to the canonical label only when
33
+ the row is absent. Never hard-code the label in user-facing text.
34
+ - **`needsPsl` is per row, from `LicenseId`.** The PSL POST uses the selected
35
+ tier's own `LicenseId` as `PermissionSetLicenseId` — PSL *names* above are for
36
+ human orientation only and are never queried by name in the write path.
37
+ - **Unified Catalog user tier = `UnifiedCatalogCommunityUser`, not `UnifiedCatalogAgent`.**
38
+ The three shipped standard permsets and their productized `Description` are:
39
+ `UnifiedCatalogAgent` = "Enables the **agent** to run service process" (the
40
+ agent's own bot-runtime identity — a machine tier, not a human one);
41
+ `UnifiedCatalogCommunityUser` = "Enables the **Community User** to **run**
42
+ unified catalog service process" (the human RUN tier — what a user, including a
43
+ Unified Employee user, assigns to exercise the agent's Unified Catalog actions);
44
+ `UnifiedCatalogAdmin` = "Enables the user to **setup** unified catalog" (the
45
+ admin/config tier). There is no plain `UnifiedCatalogUser`. So the human tiers
46
+ are `UnifiedCatalogCommunityUser` (use) + `UnifiedCatalogAdmin` (admin);
47
+ `UnifiedCatalogAgent` is deliberately excluded (it belongs to the agent's own
48
+ user, provisioned elsewhere, not to the human being granted access here).
49
+ - `DataCloudAISpecialist` also exists but is not one of this skill's tiers.
50
+ - **Unified Catalog tiers are license-SHAPE gated per target user.** Unlike Prompt
51
+ Templates and Data Cloud (offered to any user), the two Unified Catalog PSLs can
52
+ only be held by specific cohorts: `UnifiedCatalogCommunityUser` (the Community
53
+ User / use tier) requires a **Unified Employee** license, and `UnifiedCatalogAdmin`
54
+ (the Admin tier) is meaningful only for a **System Administrator**. Offering a UC
55
+ tier to a user whose license/profile can't hold it is a hard write-time failure
56
+ (a license-shape limit, not a seat shortage — retrying won't help). So
57
+ `scripts/gate-unified-catalog-tiers.mjs` runs per target user and offers the
58
+ Community User tier only to a Unified Employee user, the Admin tier only to a
59
+ System Administrator, and **omits Unified Catalog entirely** for any other user
60
+ (reported "not applicable for this user's license/profile — skipped", never a
61
+ failed write). This gate is Unified-Catalog-specific; the other two features are
62
+ not cohort-gated.
63
+
64
+ ### Fixed-lookup discovery query (Phase 1)
65
+
66
+ ```sql
67
+ SELECT Id, Name, Label, LicenseId
68
+ FROM PermissionSet
69
+ WHERE Name IN (
70
+ 'EinsteinGPTPromptTemplateUser',
71
+ 'EinsteinGPTPromptTemplateManager',
72
+ 'GenieUserEnhancedSecurity',
73
+ 'GenieAdmin',
74
+ 'UnifiedCatalogCommunityUser',
75
+ 'UnifiedCatalogAdmin'
76
+ )
77
+ ```
78
+
79
+ ## 2. Agent access via `Agent_Access` + `SetupEntityAccess`
80
+
81
+ An Agentforce / NGA agent is a **`BotDefinition`** record with
82
+ `Type = 'InternalCopilot'`. A permission set grants access to a specific agent
83
+ through a **`SetupEntityAccess`** row — exactly like granting Apex-class access:
84
+
85
+ ```text
86
+ SetupEntityAccess {
87
+ ParentId: <permission set Id>, // the Agent_Access permset
88
+ SetupEntityId: <BotDefinition Id> // the agent (0Xx… key prefix)
89
+ }
90
+ ```
91
+
92
+ ### Load-bearing details (verified via `sf sobject describe`)
93
+
94
+ - **`PermissionSet` is createable over the standard data API** (`createable:true`)
95
+ with just `Name` + `Label`. Create `Agent_Access` with a POST to
96
+ `/sobjects/PermissionSet` — no Tooling API, no Metadata XML, no deploy.
97
+ - **`SetupEntityAccess` is createable** with `ParentId` + `SetupEntityId`
98
+ (both required, both `createable:true`).
99
+ - **`SetupEntityType` is NOT createable** (`createable:false`, picklist). It is
100
+ derived from the `SetupEntityId` key prefix (`0Xx` → `BotDefinition`). Sending
101
+ it in the POST body errors — omit it. It IS filterable on the read, so the
102
+ idempotency query uses `WHERE ... AND SetupEntityType='BotDefinition'`.
103
+ - `SetupEntityAccess` is **not updateable** — grants are add/remove only, so the
104
+ idempotent path is "read existing grants, add the missing ones".
105
+
106
+ ### Activated-agent discovery query (Phase 1)
107
+
108
+ Only agents with an **active** version should be offered — a draft/inactive
109
+ agent has nothing to exercise yet:
110
+
111
+ ```sql
112
+ SELECT Id, DeveloperName, MasterLabel,
113
+ (SELECT Status FROM BotVersions WHERE Status='Active')
114
+ FROM BotDefinition
115
+ WHERE Type='InternalCopilot'
116
+ ```
117
+
118
+ An agent is activated iff its `BotVersions` child subquery returns ≥1 row.
119
+
120
+ ## Codesearch / verification note
121
+
122
+ The feature-permset matrix and the `SetupEntityAccess`/`BotDefinition` mechanism
123
+ were verified live. The Unified Catalog **user** tier was corrected from
124
+ `UnifiedCatalogAgent` to `UnifiedCatalogCommunityUser` after the shipped
125
+ `PermissionSet.Description` fields showed `UnifiedCatalogAgent` is the agent's own
126
+ bot-runtime permset ("Enables the agent to run service process"), while
127
+ `UnifiedCatalogCommunityUser` is the human run tier ("Enables the Community User
128
+ to run unified catalog service process"). If the shipped permset Names, their
129
+ `Description`, or the `SetupEntityAccess` create-field set drift on a future
130
+ release, re-verify with `sf sobject describe --sobject SetupEntityAccess` and
131
+ `SELECT Name, Label, Description, LicenseId FROM PermissionSet WHERE Name LIKE 'UnifiedCatalog%'`
132
+ before changing the fixed lists in `scripts/`.