@salesforce/afv-skills 1.40.0 → 1.41.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 (136) hide show
  1. package/package.json +1 -1
  2. package/skills/automation-flow-generate/SKILL.md +32 -43
  3. package/skills/experience-portal-create/SKILL.md +497 -0
  4. package/skills/experience-portal-create/assets/report-template.md +30 -0
  5. package/skills/experience-portal-create/references/mcp-invocation.md +288 -0
  6. package/skills/experience-portal-create/references/post-creation-activate-publish.md +165 -0
  7. package/skills/experience-portal-create/references/templates.md +253 -0
  8. package/skills/experience-ui-bundle-features-generate/SKILL.md +5 -1
  9. package/skills/experience-ui-bundle-frontend-generate/SKILL.md +2 -0
  10. package/skills/experience-ui-bundle-frontend-generate/references/page.md +1 -0
  11. package/skills/platform-datamask-run/SKILL.md +345 -0
  12. package/skills/platform-datamask-run/references/api-surface.md +130 -0
  13. package/skills/platform-datamask-run/references/policy-authoring.md +185 -0
  14. package/skills/platform-datamask-run/references/run-and-abort.md +116 -0
  15. package/skills/platform-datamask-run/scripts/poll-job.sh +115 -0
  16. package/skills/platform-dataspace-access-configure/SKILL.md +51 -3
  17. package/skills/platform-dataspace-access-configure/scripts/inspect-dataspace-scopes.sh +56 -0
  18. package/skills/platform-lightning-type-widget-coordinate/references/build-plan-format.md +1 -0
  19. package/skills/platform-sandbox-configure/SKILL.md +17 -2
  20. package/skills/platform-trial-org-create/SKILL.md +175 -0
  21. package/skills/platform-trial-org-create/examples/create_request.json +9 -0
  22. package/skills/platform-trial-org-create/examples/error_response.json +41 -0
  23. package/skills/platform-trial-org-create/examples/success_response.json +27 -0
  24. package/skills/platform-trial-org-create/references/error_codes.md +42 -0
  25. package/skills/platform-trial-org-create/references/signup_request_fields.md +69 -0
  26. package/skills/platform-trial-org-create/scripts/create_signup_request.sh +175 -0
  27. package/skills/platform-trial-org-create/scripts/get_signup_request.sh +155 -0
  28. package/skills/platform-widget-generate/SKILL.md +47 -6
  29. package/skills/platform-widget-generate/examples/conditional.json +3 -3
  30. package/skills/platform-widget-generate/examples/list-with-foreach.json +2 -2
  31. package/skills/platform-widget-generate/examples/single-object.json +2 -2
  32. package/skills/platform-widget-generate/references/widget-bundle-layout.md +1 -1
  33. package/skills/service-agentforce-channel-configure/SKILL.md +271 -0
  34. package/skills/service-agentforce-channel-configure/references/agent-wiring.md +97 -0
  35. package/skills/service-agentforce-channel-configure/references/channel-branch-email.md +145 -0
  36. package/skills/service-agentforce-channel-configure/references/channel-branch-voice.md +69 -0
  37. package/skills/service-agentforce-channel-configure/references/channel-types.md +61 -0
  38. package/skills/service-agentforce-channel-configure/references/live-traffic-gate.md +86 -0
  39. package/skills/service-agentforce-channel-configure/references/queue-resolution.md +135 -0
  40. package/skills/service-agentforce-channel-configure/references/routing-flow.md +384 -0
  41. package/skills/service-catalog-template-deploy/SKILL.md +310 -0
  42. package/skills/service-catalog-template-deploy/references/cli-invocation.md +258 -0
  43. package/skills/service-catalog-template-deploy/scripts/activate-verify.mjs +164 -0
  44. package/skills/service-catalog-template-deploy/scripts/build-deploy-payload.mjs +94 -0
  45. package/skills/service-catalog-template-deploy/scripts/resolve-template.mjs +331 -0
  46. package/skills/service-catalog-template-search/SKILL.md +212 -0
  47. package/skills/service-catalog-template-search/references/cli-invocation.md +128 -0
  48. package/skills/service-catalog-template-search/scripts/classify-catalog.mjs +205 -0
  49. package/skills/service-concierge-portal-generate/SKILL.md +126 -0
  50. package/skills/service-concierge-portal-generate/references/portal-deploy-runbook.md +1428 -0
  51. package/skills/service-digital-engagement-channel-configure/SKILL.md +46 -6
  52. package/skills/service-digital-engagement-channel-configure/assets/messaging_channel_template.xml +2 -1
  53. package/skills/service-digital-engagement-channel-configure/examples/asa_agent_channel.xml +4 -1
  54. package/skills/service-helpagent-coordinate/README.md +8 -2
  55. package/skills/service-helpagent-coordinate/SKILL.md +126 -130
  56. package/skills/service-helpagent-coordinate/assets/help-agent-spec.md +70 -53
  57. package/skills/service-helpagent-coordinate/references/agent-script.md +571 -457
  58. package/skills/service-helpagent-coordinate/references/channel-voice.md +38 -9
  59. package/skills/service-helpagent-coordinate/references/channel-web-chat.md +173 -49
  60. package/skills/service-helpagent-coordinate/references/output-report-format.md +126 -0
  61. package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +153 -0
  62. package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +79 -0
  63. package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +35 -0
  64. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/SKILL.md +271 -0
  65. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/references/cli-invocation.md +265 -0
  66. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-enable-plan.mjs +220 -0
  67. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/classify-final-report.mjs +102 -0
  68. package/skills/service-itsm-agentic-setup-agentforce-studio-configure/scripts/record-enable-result.mjs +73 -0
  69. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/SKILL.md +206 -0
  70. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/references/cli-invocation.md +194 -0
  71. package/skills/service-itsm-agentic-setup-agentforce-studio-validate/scripts/classify-readiness.mjs +223 -0
  72. package/skills/service-itsm-agentic-setup-cmdb-configure/SKILL.md +45 -7
  73. package/skills/service-itsm-agentic-setup-cmdb-configure/references/mcp-invocation.md +45 -5
  74. package/skills/service-itsm-agentic-setup-configure/SKILL.md +116 -0
  75. package/skills/service-itsm-agentic-setup-configure/examples/output-templates.md +64 -0
  76. package/skills/service-itsm-agentic-setup-employee-agent-configure/SKILL.md +158 -0
  77. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/cli-invocation.md +361 -0
  78. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/error-taxonomy.md +44 -0
  79. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/reactivation.md +66 -0
  80. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/report-format.md +66 -0
  81. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/specialized-templates.md +148 -0
  82. package/skills/service-itsm-agentic-setup-employee-agent-configure/references/workflow-detail.md +169 -0
  83. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/build-create-body.mjs +116 -0
  84. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-agent-existence.mjs +185 -0
  85. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/classify-preflight.mjs +168 -0
  86. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/create-scratch-dir.mjs +58 -0
  87. package/skills/service-itsm-agentic-setup-employee-agent-configure/scripts/render-report.mjs +197 -0
  88. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/SKILL.md +186 -0
  89. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/action-availability.md +51 -0
  90. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/cli-invocation.md +345 -0
  91. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/error-taxonomy.md +44 -0
  92. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/reactivation.md +63 -0
  93. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/report-format.md +44 -0
  94. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/references/workflow-detail.md +149 -0
  95. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/build-create-body.mjs +110 -0
  96. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-action-availability.mjs +201 -0
  97. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-activate-result.mjs +135 -0
  98. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-agent-existence.mjs +194 -0
  99. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/classify-preflight.mjs +158 -0
  100. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/create-scratch-dir.mjs +58 -0
  101. package/skills/service-itsm-agentic-setup-fulfiller-agent-configure/scripts/render-report.mjs +191 -0
  102. package/skills/service-itsm-agentic-setup-incident-management/SKILL.md +133 -0
  103. package/skills/service-itsm-agentic-setup-incident-management/examples/output-templates.md +71 -0
  104. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +308 -0
  105. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone.json +23 -0
  106. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +193 -0
  107. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +57 -0
  108. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +394 -0
  109. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/SKILL.md +266 -0
  110. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/cli-invocation.md +106 -0
  111. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/helper-contracts.md +142 -0
  112. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/references/permset-topology.md +82 -0
  113. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-action-surface.mjs +137 -0
  114. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-assignment-state.mjs +99 -0
  115. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/classify-permset-availability.mjs +120 -0
  116. package/skills/service-itsm-agentic-setup-itsm-agentforce-permset-assign/scripts/resolve-target-user.mjs +86 -0
  117. package/skills/service-itsm-agentic-setup-uel-user-create/SKILL.md +284 -0
  118. package/skills/service-itsm-agentic-setup-uel-user-create/references/mcp-invocation.md +302 -0
  119. package/skills/service-itsm-channels-coordinate/SKILL.md +472 -0
  120. package/skills/service-itsm-incident-mgmt-configure/SKILL.md +212 -0
  121. package/skills/service-itsm-incident-mgmt-configure/references/mcp-invocation.md +225 -0
  122. package/skills/service-itsm-incident-priority-configure/SKILL.md +53 -12
  123. package/skills/service-itsm-swarming-configure/SKILL.md +212 -0
  124. package/skills/service-itsm-teams-configure/SKILL.md +395 -0
  125. package/skills/service-itsm-teams-configure/references/azure-credential-population.md +213 -0
  126. package/skills/service-itsm-teams-configure/references/gotchas.md +23 -0
  127. package/skills/service-itsm-teams-coordinate/SKILL.md +175 -0
  128. package/skills/service-itsm-teams-coordinate/examples/output-templates.md +85 -0
  129. package/skills/service-itsm-teams-debug/SKILL.md +144 -0
  130. package/skills/service-itsm-teams-debug/references/configuration-checklists.md +277 -0
  131. package/skills/service-itsm-teams-debug/references/report-generation.md +95 -0
  132. package/skills/service-itsm-teams-employee-agent-configure/SKILL.md +139 -0
  133. package/skills/service-itsm-teams-employee-agent-configure/assets/Teams_AgentForce.EmbeddedServiceConfig-meta.xml +43 -0
  134. package/skills/service-itsm-teams-employee-agent-configure/references/teams-embedded-employee-agent.md +480 -0
  135. package/skills/service-itsm-teams-itdesk-configure/SKILL.md +232 -0
  136. package/skills/service-itsm-teams-itservice-configure/SKILL.md +391 -0
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic verify-phase classifier for the ITSM Intelligence permset-assign flow.
3
+ //
4
+ // Reads the `/services/data/v67.0/actions/custom/generatePromptResponse`
5
+ // response capture and decides whether the ITSM Intelligence prompt-template
6
+ // invocable actions (Fulfiller-only — `svc_itsm_intelligence__*`) are
7
+ // surfaced for the running user. Replaces the prose "grep the response for
8
+ // svc_itsm_intelligence__" step in Phase 4 (A9 — deterministic decisions
9
+ // belong in scripts). Employee agent actions (`svc_emp_intelligence__*`)
10
+ // are out of scope for this skill.
11
+ //
12
+ // Rules:
13
+ // - Accepts both response shapes for `actions`:
14
+ // (a) legacy `{ actions: [ { name, ... }, ... ] }`
15
+ // (b) newer `{ actions: { <fullyQualifiedName>: {...}, ... } }`
16
+ // Either wrapped by `data.result` (CLI envelope) or at top level.
17
+ // - If `expectedActions` (CSV of specific action API names) is provided,
18
+ // partition each into `present` / `missing` by exact name match on the
19
+ // extracted names. `verdict`:
20
+ // * SURFACED — every expected action is present.
21
+ // * PARTIAL — at least one is present but not all.
22
+ // * MISSING — none are present.
23
+ // - If `expectedActions` is omitted, look for AT LEAST ONE action name
24
+ // starting with `svc_itsm_intelligence__`.
25
+ // * SURFACED — one or more found; `present` lists them, `missing` is [].
26
+ // * MISSING — none found; `missing` is [] (no explicit expectation set).
27
+ // - Envelope missing/failed / unexpected shape ⇒ CANNOT-CONFIRM.
28
+ //
29
+ // Usage:
30
+ // node classify-action-surface.mjs <generate-prompt-response.json> [expectedActions-csv]
31
+ //
32
+ // Emits a single JSON object to stdout:
33
+ // { present: [name, ...],
34
+ // missing: [name, ...],
35
+ // totalItsmActionsSeen: <int>,
36
+ // verdict: "SURFACED" | "PARTIAL" | "MISSING" | "CANNOT-CONFIRM",
37
+ // reasons: [...] }
38
+ // Exit is always 0 on parseable bodies; verdict carries the decision.
39
+
40
+ import { readFileSync } from 'node:fs';
41
+
42
+ const [responsePath, expectedCsv] = process.argv.slice(2);
43
+ if (!responsePath) {
44
+ process.stderr.write('usage: node classify-action-surface.mjs <generate-prompt-response.json> [expectedActions-csv]\n');
45
+ process.exit(2);
46
+ }
47
+
48
+ function emit(verdict, extras, reasons) {
49
+ process.stdout.write(JSON.stringify({
50
+ present: extras.present ?? [],
51
+ missing: extras.missing ?? [],
52
+ totalItsmActionsSeen: extras.totalItsmActionsSeen ?? 0,
53
+ verdict,
54
+ reasons,
55
+ }, null, 2) + '\n');
56
+ process.exit(0);
57
+ }
58
+
59
+ let text;
60
+ try {
61
+ text = readFileSync(responsePath, 'utf8').trim();
62
+ } catch (e) {
63
+ emit('CANNOT-CONFIRM', {}, [`unreadable file at ${responsePath}: ${e?.message ?? e}`]);
64
+ }
65
+ if (!text) emit('CANNOT-CONFIRM', {}, [`empty file: ${responsePath}`]);
66
+
67
+ let data;
68
+ try {
69
+ data = JSON.parse(text);
70
+ } catch (e) {
71
+ emit('CANNOT-CONFIRM', {}, [`invalid JSON at ${responsePath}: ${e?.message ?? e}`]);
72
+ }
73
+ if (!data || typeof data !== 'object') {
74
+ emit('CANNOT-CONFIRM', {}, [`non-object JSON at ${responsePath}`]);
75
+ }
76
+ if (typeof data.status === 'number' && data.status !== 0) {
77
+ emit('CANNOT-CONFIRM', {}, [`sf api request at ${responsePath} failed with status ${data.status}`]);
78
+ }
79
+
80
+ // The generatePromptResponse endpoint has two observed response shapes; accept
81
+ // both so a valid surface never mis-classifies as CANNOT-CONFIRM:
82
+ // (a) legacy `{ actions: [ { name, ... }, ... ] }`
83
+ // (b) newer `{ actions: { <fullyQualifiedName>: {...}, ... } }`
84
+ // Both forms may also be wrapped by the CLI envelope under `data.result`.
85
+ function extractNames(actionsField) {
86
+ if (Array.isArray(actionsField)) {
87
+ return actionsField
88
+ .map((a) => (a && typeof a.name === 'string' ? a.name : null))
89
+ .filter((n) => n !== null);
90
+ }
91
+ if (actionsField && typeof actionsField === 'object') {
92
+ return Object.keys(actionsField);
93
+ }
94
+ return null;
95
+ }
96
+ const names = extractNames(data.actions) ?? extractNames(data.result?.actions);
97
+ if (names === null) {
98
+ emit('CANNOT-CONFIRM', {}, [
99
+ `response body has no \`actions\` array or object — expected the /actions/custom/generatePromptResponse envelope. Confirm the CLI wrote the full body to ${responsePath}.`,
100
+ ]);
101
+ }
102
+
103
+ const itsmNames = names.filter((n) => n.startsWith('svc_itsm_intelligence__'));
104
+
105
+ const expected = (expectedCsv ?? '')
106
+ .split(',')
107
+ .map((s) => s.trim())
108
+ .filter(Boolean);
109
+
110
+ if (expected.length > 0) {
111
+ const present = expected.filter((e) => names.includes(e));
112
+ const missing = expected.filter((e) => !names.includes(e));
113
+ let verdict;
114
+ if (missing.length === 0) verdict = 'SURFACED';
115
+ else if (present.length === 0) verdict = 'MISSING';
116
+ else verdict = 'PARTIAL';
117
+ const reasons = [
118
+ `Checked ${expected.length} expected action name(s): ${present.length} present, ${missing.length} missing.`,
119
+ ];
120
+ if (missing.length > 0) {
121
+ reasons.push(`Missing: ${missing.join(', ')}. A permset write can succeed while the target action set is still ungated by the assigned permset (cache, wrong permset). Do not report success.`);
122
+ }
123
+ if (itsmNames.length > 0 && (present.length !== itsmNames.length || missing.length > 0)) {
124
+ reasons.push(`Response contains ${itsmNames.length} svc_itsm_intelligence__ action(s) overall: ${itsmNames.slice(0, 20).join(', ')}${itsmNames.length > 20 ? ', …' : ''}.`);
125
+ }
126
+ emit(verdict, { present, missing, totalItsmActionsSeen: itsmNames.length }, reasons);
127
+ }
128
+
129
+ // No explicit expectation — surface if ANY ITSM Intelligence action is present.
130
+ if (itsmNames.length === 0) {
131
+ emit('MISSING', { present: [], missing: [], totalItsmActionsSeen: 0 }, [
132
+ 'No `svc_itsm_intelligence__` invocable actions were returned for this user. Assignment may have succeeded but the actions are not surfaced — try a session refresh, or the assigned persona permset does not gate the target actions.',
133
+ ]);
134
+ }
135
+ emit('SURFACED', { present: itsmNames, missing: [], totalItsmActionsSeen: itsmNames.length }, [
136
+ `Found ${itsmNames.length} ITSM Intelligence invocable action(s) surfaced for this user.`,
137
+ ]);
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic idempotency classifier for the ITSM Intelligence permset-assign flow.
3
+ //
4
+ // Reads the `sf data query --json` captures:
5
+ // - PermissionSetAssignment (permset ↔ user) for the target user + permset
6
+ // - PermissionSetLicenseAssign (PSL ↔ user) for the target user + PSL
7
+ // — ONLY when the selected permset actually has a backing license.
8
+ // Pass the sentinel `NO-PSL` in place of the PSLA path when
9
+ // `classify-permset-availability.mjs` reported `needsPsl:false`
10
+ // (standalone permset — no PSL query, no PSL POST in Phase 2d).
11
+ // and decides whether the Phase 2d write should be skipped.
12
+ //
13
+ // Rules (A9):
14
+ // - needsPsl AND both rows present ⇒ needsWrite=false (already fully assigned).
15
+ // - needsPsl AND either row missing ⇒ needsWrite=true (assign the missing
16
+ // piece; the workflow's Phase 2d handles PSL before permset regardless of
17
+ // which one is currently missing to preserve the write-order invariant).
18
+ // - !needsPsl (pslaPath === "NO-PSL"):
19
+ // * permsetAssigned ⇒ needsWrite=false (nothing to write).
20
+ // * !permsetAssigned ⇒ needsWrite=true (assign the permset only —
21
+ // Phase 2d MUST skip the PSL POST).
22
+ // `licenseAssigned` is reported as null in this branch (not applicable).
23
+ // - Query envelope missing/failed ⇒ CANNOT-CONFIRM; surface it and stop.
24
+ //
25
+ // Usage:
26
+ // node classify-assignment-state.mjs <psa-existing.json> <psla-existing.json | NO-PSL>
27
+ //
28
+ // Emits a single JSON object to stdout:
29
+ // { permsetAssigned, licenseAssigned, needsWrite,
30
+ // verdict: "NEEDS-WRITE" | "ALREADY-ASSIGNED" | "CANNOT-CONFIRM",
31
+ // reasons: [...] }
32
+ // Exit is always 0 on parseable bodies; verdict carries the decision.
33
+
34
+ import { readFileSync } from 'node:fs';
35
+
36
+ function readEnvelope(path) {
37
+ try {
38
+ const text = readFileSync(path, 'utf8').trim();
39
+ if (!text) return { ok: false, reason: `empty file: ${path}` };
40
+ const data = JSON.parse(text);
41
+ if (!data || typeof data !== 'object') return { ok: false, reason: `non-object JSON at ${path}` };
42
+ if (typeof data.status === 'number' && data.status !== 0) {
43
+ return { ok: false, reason: `sf data query at ${path} failed with status ${data.status}` };
44
+ }
45
+ const records = Array.isArray(data.result?.records) ? data.result.records : null;
46
+ if (!records) return { ok: false, reason: `missing result.records[] at ${path}` };
47
+ return { ok: true, records };
48
+ } catch (e) {
49
+ return { ok: false, reason: `unreadable/invalid JSON at ${path}: ${e?.message ?? e}` };
50
+ }
51
+ }
52
+
53
+ const argv = process.argv.slice(2).filter((a) => !!a);
54
+ if (argv.length < 2) {
55
+ process.stderr.write('usage: node classify-assignment-state.mjs <psa-existing.json> <psla-existing.json | NO-PSL>\n');
56
+ process.exit(2);
57
+ }
58
+ const [psaPath, pslaArg] = argv;
59
+ const needsPsl = pslaArg !== 'NO-PSL';
60
+
61
+ const psa = readEnvelope(psaPath);
62
+ const psla = needsPsl ? readEnvelope(pslaArg) : { ok: true, records: [], skipped: true };
63
+
64
+ const readErrors = [psa, psla].filter((r) => !r.ok).map((r) => r.reason);
65
+ if (readErrors.length) {
66
+ process.stdout.write(JSON.stringify({
67
+ permsetAssigned: null, licenseAssigned: null, needsWrite: null,
68
+ verdict: 'CANNOT-CONFIRM',
69
+ reasons: readErrors,
70
+ }, null, 2) + '\n');
71
+ process.exit(0);
72
+ }
73
+
74
+ const permsetAssigned = psa.records.length > 0;
75
+ const licenseAssigned = needsPsl ? psla.records.length > 0 : null;
76
+ const needsWrite = needsPsl
77
+ ? !(permsetAssigned && licenseAssigned)
78
+ : !permsetAssigned;
79
+
80
+ const verdict = needsWrite ? 'NEEDS-WRITE' : 'ALREADY-ASSIGNED';
81
+ const reasons = [
82
+ `PermissionSetAssignment: ${permsetAssigned ? 'present' : 'missing'} (${psa.records.length} row${psa.records.length === 1 ? '' : 's'}).`,
83
+ needsPsl
84
+ ? `PermissionSetLicenseAssign: ${licenseAssigned ? 'present' : 'missing'} (${psla.records.length} row${psla.records.length === 1 ? '' : 's'}).`
85
+ : 'PermissionSetLicenseAssign: not applicable — the selected permission set has no backing LicenseId (standalone-permset flow; PSL SOQL + POST are skipped).',
86
+ ];
87
+ reasons.push(
88
+ needsWrite
89
+ ? (needsPsl
90
+ ? 'At least one assignment is missing — proceed to Phase 2c confirm-to-write, then Phase 2d (PSL first, permset second).'
91
+ : 'Permission set assignment is missing — proceed to Phase 2c confirm-to-write, then Phase 2d permset assign ONLY (skip the PSL POST).')
92
+ : (needsPsl
93
+ ? 'Both the permission set and its backing license are already assigned to this user — skip Phase 2d, go straight to Phase 4 verify.'
94
+ : 'The permission set is already assigned to this user (no backing license required) — skip Phase 2d, go straight to Phase 4 verify.')
95
+ );
96
+
97
+ process.stdout.write(JSON.stringify({
98
+ permsetAssigned, licenseAssigned, needsWrite, verdict, reasons,
99
+ }, null, 2) + '\n');
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic branch classifier for the Fulfiller persona permset-assign flow.
3
+ //
4
+ // Reads ONE input:
5
+ // - PermissionSet query capture (from `sf data query --json`) for the four
6
+ // Core-shipped Fulfiller persona permsets:
7
+ // * IncidentFulfiller
8
+ // * ProblemFulfillerPermSet
9
+ // * ChangeRequestFulfillerPermSet
10
+ // * ReleaseManagerPermSet
11
+ // (namespace `force`, not a managed-package namespace)
12
+ //
13
+ // Rules (A9):
14
+ // - `verdict:"ASSIGN"` — ≥1 of the four personas is present. Caller
15
+ // asks the user which persona to assign; do NOT
16
+ // auto-select.
17
+ // - `verdict:"HAND-OFF"` — none of the four present. The ITSM AddOn(s)
18
+ // are not provisioned; permset-assign is a
19
+ // no-op. Hand off to the studio validate skill
20
+ // so the AddOn(s) can be enabled first.
21
+ // - `verdict:"CANNOT-CONFIRM"` — query envelope missing / unparseable.
22
+ //
23
+ // Usage:
24
+ // node classify-permset-availability.mjs <permsets.json>
25
+ //
26
+ // Emits a single JSON object to stdout:
27
+ // { personasFound: [Name, ...],
28
+ // personasMissing: [Name, ...],
29
+ // candidates: [{ Id, Name, Label, LicenseId, needsPsl }, ...],
30
+ // verdict: "ASSIGN" | "HAND-OFF" | "CANNOT-CONFIRM",
31
+ // reasons: [...] }
32
+ // `candidates` includes only rows for the four known persona Names; unrelated
33
+ // rows in the query result are filtered out. `needsPsl` is derived per-row
34
+ // from `LicenseId !== null`. Exit is always 0 on parseable bodies; verdict
35
+ // carries the branch decision. Exit 2 on missing argv.
36
+
37
+ import { readFileSync } from 'node:fs';
38
+
39
+ const KNOWN_PERSONAS = [
40
+ 'IncidentFulfiller',
41
+ 'ProblemFulfillerPermSet',
42
+ 'ChangeRequestFulfillerPermSet',
43
+ 'ReleaseManagerPermSet',
44
+ ];
45
+
46
+ function readEnvelope(path) {
47
+ try {
48
+ const text = readFileSync(path, 'utf8').trim();
49
+ if (!text) return { ok: false, reason: `empty file: ${path}` };
50
+ const data = JSON.parse(text);
51
+ if (!data || typeof data !== 'object') return { ok: false, reason: `non-object JSON at ${path}` };
52
+ if (typeof data.status === 'number' && data.status !== 0) {
53
+ return { ok: false, reason: `sf data query at ${path} failed with status ${data.status}` };
54
+ }
55
+ const records = Array.isArray(data.result?.records) ? data.result.records : null;
56
+ if (!records) return { ok: false, reason: `missing result.records[] at ${path}` };
57
+ return { ok: true, records };
58
+ } catch (e) {
59
+ return { ok: false, reason: `unreadable/invalid JSON at ${path}: ${e?.message ?? e}` };
60
+ }
61
+ }
62
+
63
+ const [psPath] = process.argv.slice(2);
64
+ if (!psPath) {
65
+ process.stderr.write('usage: node classify-permset-availability.mjs <permsets.json>\n');
66
+ process.exit(2);
67
+ }
68
+
69
+ const ps = readEnvelope(psPath);
70
+ if (!ps.ok) {
71
+ process.stdout.write(JSON.stringify({
72
+ personasFound: [],
73
+ personasMissing: [...KNOWN_PERSONAS],
74
+ candidates: [],
75
+ verdict: 'CANNOT-CONFIRM',
76
+ reasons: [ps.reason],
77
+ }, null, 2) + '\n');
78
+ process.exit(0);
79
+ }
80
+
81
+ // Filter to only the five known persona Names — drop any unrelated rows.
82
+ const knownRows = ps.records.filter((r) => KNOWN_PERSONAS.includes(r?.Name));
83
+ const foundNames = knownRows.map((r) => r.Name);
84
+ const personasFound = KNOWN_PERSONAS.filter((n) => foundNames.includes(n));
85
+ const personasMissing = KNOWN_PERSONAS.filter((n) => !foundNames.includes(n));
86
+
87
+ const candidates = knownRows.map((r) => ({
88
+ Id: r.Id ?? null,
89
+ Name: r.Name ?? null,
90
+ Label: r.Label ?? null,
91
+ LicenseId: r.LicenseId ?? null,
92
+ needsPsl: (r.LicenseId ?? null) !== null,
93
+ }));
94
+
95
+ const reasons = [];
96
+ let verdict;
97
+ if (personasFound.length === 0) {
98
+ verdict = 'HAND-OFF';
99
+ reasons.push('None of the four Core-shipped Fulfiller persona permsets (IncidentFulfiller, ProblemFulfillerPermSet, ChangeRequestFulfillerPermSet, ReleaseManagerPermSet) is present on this org — the ITSM AddOn(s) are not provisioned.');
100
+ reasons.push('Permset-assign cannot fix this; hand off to service-itsm-agentic-setup-agentforce-studio-validate.');
101
+ } else {
102
+ verdict = 'ASSIGN';
103
+ reasons.push(`Found ${personasFound.length} of 4 Fulfiller persona permset(s) on this org: ${personasFound.join(', ')}.`);
104
+ if (personasMissing.length > 0) {
105
+ reasons.push(`Not provisioned (AddOn(s) missing for these personas): ${personasMissing.join(', ')}.`);
106
+ }
107
+ const standalone = candidates.filter((c) => !c.needsPsl).map((c) => c.Name);
108
+ if (standalone.length > 0) {
109
+ reasons.push(`Standalone-permset flow (no backing LicenseId — Phase 2 skips PSL SOQL/POST if selected): ${standalone.join(', ')}.`);
110
+ }
111
+ reasons.push('Caller MUST ask the user which persona to assign before Phase 2 — do not auto-select. A Fulfiller commonly needs only one persona (e.g. only Incident).');
112
+ }
113
+
114
+ process.stdout.write(JSON.stringify({
115
+ personasFound,
116
+ personasMissing,
117
+ candidates,
118
+ verdict,
119
+ reasons,
120
+ }, null, 2) + '\n');
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env node
2
+ // Deterministic target-user resolver for the ITSM Intelligence permset-assign flow.
3
+ //
4
+ // Reads the `GET /services/data/v67.0/` capture (or an equivalent versions-list
5
+ // response the CLI writes when hitting the API root), and extracts the running
6
+ // user's Id from the `identity` URL. The URL shape is:
7
+ // https://<mydomain>.my.salesforce.com/id/<orgId 00D…>/<userId 005…>
8
+ // so we take the LAST path segment and require it to start with `005` and be
9
+ // 15 or 18 chars (case-preserving).
10
+ //
11
+ // This exists so no prose step ever tries to parse the identity URL by hand
12
+ // (A9 — deterministic decisions belong in scripts).
13
+ //
14
+ // Usage:
15
+ // node resolve-target-user.mjs <api-root.json>
16
+ //
17
+ // Emits a single JSON object to stdout:
18
+ // { userId: "005…" | null,
19
+ // identity: "<full URL>" | null,
20
+ // verdict: "RESOLVED" | "CANNOT-CONFIRM",
21
+ // reasons: [...] }
22
+ // Exit is always 0 on parseable bodies; verdict carries the decision.
23
+
24
+ import { readFileSync } from 'node:fs';
25
+
26
+ const [rootPath] = process.argv.slice(2);
27
+ if (!rootPath) {
28
+ process.stderr.write('usage: node resolve-target-user.mjs <api-root.json>\n');
29
+ process.exit(2);
30
+ }
31
+
32
+ function fail(reasons) {
33
+ process.stdout.write(JSON.stringify({
34
+ userId: null, identity: null, verdict: 'CANNOT-CONFIRM', reasons,
35
+ }, null, 2) + '\n');
36
+ process.exit(0);
37
+ }
38
+
39
+ let text;
40
+ try {
41
+ text = readFileSync(rootPath, 'utf8').trim();
42
+ } catch (e) {
43
+ fail([`unreadable file at ${rootPath}: ${e?.message ?? e}`]);
44
+ }
45
+ if (!text) fail([`empty file: ${rootPath}`]);
46
+
47
+ let data;
48
+ try {
49
+ data = JSON.parse(text);
50
+ } catch (e) {
51
+ fail([`invalid JSON at ${rootPath}: ${e?.message ?? e}`]);
52
+ }
53
+ if (!data || typeof data !== 'object') fail([`non-object JSON at ${rootPath}`]);
54
+ if (typeof data.status === 'number' && data.status !== 0) {
55
+ fail([`sf api request at ${rootPath} failed with status ${data.status}`]);
56
+ }
57
+
58
+ // `sf api request rest` returns the raw body directly; the API root response
59
+ // contains `identity`, `rest`, and version fields at the top level.
60
+ const identity = typeof data.identity === 'string'
61
+ ? data.identity
62
+ : (typeof data.result?.identity === 'string' ? data.result.identity : null);
63
+
64
+ if (!identity) {
65
+ fail(['response body has no `identity` field — cannot resolve the running user without it. Confirm the org is reachable and re-run `sf api request rest "/services/data/v67.0/"`.']);
66
+ }
67
+
68
+ const segments = identity.split('/').filter(Boolean);
69
+ const last = segments[segments.length - 1] ?? '';
70
+
71
+ // Salesforce user Ids: 15-char case-sensitive or 18-char case-insensitive, prefix `005`.
72
+ if (!/^005[A-Za-z0-9]{12}([A-Za-z0-9]{3})?$/.test(last)) {
73
+ fail([
74
+ `identity URL trailing segment "${last}" does not match the expected user-Id shape (005 + 12 or 15 alphanum chars).`,
75
+ `full identity URL: ${identity}`,
76
+ ]);
77
+ }
78
+
79
+ process.stdout.write(JSON.stringify({
80
+ userId: last,
81
+ identity,
82
+ verdict: 'RESOLVED',
83
+ reasons: [
84
+ `Extracted running-user Id "${last}" from identity URL trailing path segment.`,
85
+ ],
86
+ }, null, 2) + '\n');