@salesforce/afv-skills 1.47.0 → 1.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/education-cloud-domain-configure/SKILL.md +234 -0
- package/skills/education-cloud-domain-configure/references/dependent-features.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/academic-operations.md +386 -0
- package/skills/education-cloud-domain-configure/references/domain-features/alumni-relations.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/cross-domain-dependencies.md +18 -0
- package/skills/education-cloud-domain-configure/references/domain-features/industriessettings-toggle-reference.md +43 -0
- package/skills/education-cloud-domain-configure/references/domain-features/mentoring.md +198 -0
- package/skills/education-cloud-domain-configure/references/domain-features/recruitment-admissions.md +315 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-management.md +155 -0
- package/skills/education-cloud-domain-configure/references/domain-features/student-success.md +249 -0
- package/skills/education-cloud-domain-configure/references/domain-features.md +27 -0
- package/skills/education-cloud-domain-configure/references/gotchas-extended.md +23 -0
- package/skills/education-cloud-domain-configure/references/toggle-write-path.md +67 -0
- package/skills/education-cloud-domain-configure/scripts/check_license_provisioned.py +62 -0
- package/skills/education-cloud-domain-configure/scripts/check_org_edition.py +44 -0
- package/skills/education-cloud-domain-configure/scripts/check_permission_assigned.py +39 -0
- package/skills/education-cloud-multi-campus-configure/SKILL.md +2 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +4 -3
- package/skills/service-agentforce-channel-configure/SKILL.md +33 -28
- package/skills/service-agentforce-human-escalation-configure/SKILL.md +205 -0
- package/skills/service-agentforce-human-escalation-configure/assets/escalation-thresholds.instructions.md +37 -0
- package/skills/service-agentforce-human-escalation-configure/assets/force-app/main/default/flows/Human_Escalation_Outbound_Flow.flow-meta.xml +173 -0
- package/skills/service-agentforce-human-escalation-configure/assets/package.xml +8 -0
- package/skills/service-agentforce-human-escalation-configure/assets/sfdx-project.json +5 -0
- package/skills/service-agentforce-human-escalation-configure/references/classifier-contracts.md +103 -0
- package/skills/service-agentforce-human-escalation-configure/references/escalation-mechanism.md +63 -0
- package/skills/service-agentforce-human-escalation-configure/references/failure-policy.md +44 -0
- package/skills/service-agentforce-human-escalation-configure/references/nga-escalation.md +57 -0
- package/skills/service-agentforce-human-escalation-configure/references/runtime-verification.md +72 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-agent-active.mjs +49 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/classify-nga-escalation.mjs +166 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/extract-outbound-route.mjs +67 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/patch-escalation-surfaces.mjs +131 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/__init__.py +0 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/_bootstrap.py +249 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/tests/test_escalation_contracts.py +899 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-and-configure.sh +705 -0
- package/skills/service-agentforce-human-escalation-configure/scripts/verify-escalation-config.mjs +89 -0
- package/skills/service-de-channel-routing-configure/SKILL.md +13 -5
- package/skills/service-de-channel-routing-configure/scripts/tests/test_skill_contract.py +25 -0
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/SKILL.md +23 -10
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/examples/output-templates.md +63 -48
- package/skills/service-itsm-agentic-setup-agentforce-coordinate/scripts/verify-child-verdict.mjs +6 -2
- package/skills/service-omni-agent-users-create/SKILL.md +96 -0
- package/skills/service-omni-agent-users-create/assets/create-users.apex.template +80 -0
- package/skills/service-omni-agent-users-create/references/apex-patterns.md +135 -0
- package/skills/service-omni-agent-users-create/references/apex-template-notes.md +40 -0
- package/skills/service-omni-agent-users-create/scripts/detect-and-create.sh +206 -0
- package/skills/service-omni-agent-users-create/scripts/detect-existing.sh +122 -0
- package/skills/service-omni-agent-users-create/scripts/run-create.sh +298 -0
- package/skills/service-omni-agent-users-create/scripts/tests/test_user_create_security.py +171 -0
- package/skills/service-omni-agent-work-sharing-configure/SKILL.md +78 -0
- package/skills/service-omni-agent-work-sharing-configure/references/api-notes.md +31 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/configure-and-report.sh +186 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-agent-work-sharing-configure/scripts/tests/test_agent_work_sharing_contracts.py +93 -0
- package/skills/service-omni-base-settings-configure/SKILL.md +77 -0
- package/skills/service-omni-base-settings-configure/assets/force-app/main/default/settings/OmniChannel.settings-meta.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/package.xml +8 -0
- package/skills/service-omni-base-settings-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-base-settings-configure/references/api-notes.md +121 -0
- package/skills/service-omni-base-settings-configure/scripts/configure-and-report.sh +164 -0
- package/skills/service-omni-base-settings-configure/scripts/detect-existing.sh +69 -0
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +168 -0
- package/skills/service-omni-channel-setup-coordinate/examples/output-templates.md +161 -0
- package/skills/service-omni-channel-setup-coordinate/references/gap-catalog.md +99 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +1542 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/__init__.py +0 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/_bootstrap.py +223 -0
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +804 -0
- package/skills/service-omni-command-center-analyze/SKILL.md +84 -0
- package/skills/service-omni-command-center-analyze/references/api-notes.md +54 -0
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +198 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/_bootstrap.py +115 -0
- package/skills/service-omni-command-center-analyze/scripts/tests/test_command_center_analyze_contracts.py +165 -0
- package/skills/service-omni-permission-set-assign/SKILL.md +88 -0
- package/skills/service-omni-permission-set-assign/assets/package.xml +8 -0
- package/skills/service-omni-permission-set-assign/references/api-notes.md +143 -0
- package/skills/service-omni-permission-set-assign/scripts/verify-and-assign.sh +397 -0
- package/skills/service-omni-presence-status-deploy/SKILL.md +87 -0
- package/skills/service-omni-presence-status-deploy/assets/package.xml +12 -0
- package/skills/service-omni-presence-status-deploy/references/api-notes.md +115 -0
- package/skills/service-omni-presence-status-deploy/scripts/deploy-and-report.sh +422 -0
- package/skills/service-omni-presence-user-config-deploy/SKILL.md +80 -0
- package/skills/service-omni-presence-user-config-deploy/references/api-notes.md +60 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/deploy-and-report.sh +261 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/_bootstrap.py +106 -0
- package/skills/service-omni-presence-user-config-deploy/scripts/tests/test_presence_user_config_contracts.py +142 -0
- package/skills/service-omni-queue-deploy/SKILL.md +91 -0
- package/skills/service-omni-queue-deploy/references/api-notes.md +140 -0
- package/skills/service-omni-queue-deploy/scripts/verify-and-align.sh +470 -0
- package/skills/service-omni-queue-members-assign/SKILL.md +93 -0
- package/skills/service-omni-queue-members-assign/references/api-notes.md +144 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/_bootstrap.py +67 -0
- package/skills/service-omni-queue-members-assign/scripts/tests/test_queue_members_contracts.py +73 -0
- package/skills/service-omni-queue-members-assign/scripts/verify-and-bind.sh +362 -0
- package/skills/service-omni-queue-routing-config-deploy/SKILL.md +101 -0
- package/skills/service-omni-queue-routing-config-deploy/references/api-notes.md +127 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/_bootstrap.py +90 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/tests/test_queue_routing_config_contracts.py +70 -0
- package/skills/service-omni-queue-routing-config-deploy/scripts/upsert-and-report.sh +538 -0
- package/skills/service-omni-routing-flow-deploy/SKILL.md +111 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Case_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_Cases.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.SkillsBased.flow-meta.xml +94 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCall_Trigger.flow-meta.xml +88 -0
- package/skills/service-omni-routing-flow-deploy/assets/force-app/main/default/flows/Omni_Route_VoiceCalls.flow-meta.xml +144 -0
- package/skills/service-omni-routing-flow-deploy/assets/package.xml +11 -0
- package/skills/service-omni-routing-flow-deploy/assets/sfdx-project.json +11 -0
- package/skills/service-omni-routing-flow-deploy/references/api-notes.md +265 -0
- package/skills/service-omni-routing-flow-deploy/scripts/deploy-and-report.sh +684 -0
- package/skills/service-omni-service-channel-configure/SKILL.md +83 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Cases.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/Incidents.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/MessagingSessions.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/force-app/main/default/serviceChannels/VoiceCalls.serviceChannel-meta.xml +9 -0
- package/skills/service-omni-service-channel-configure/assets/package.xml +11 -0
- package/skills/service-omni-service-channel-configure/assets/sfdx-project.json +12 -0
- package/skills/service-omni-service-channel-configure/references/api-notes.md +84 -0
- package/skills/service-omni-service-channel-configure/scripts/deploy-and-report.sh +383 -0
- package/skills/service-omni-sidebar-configure/SKILL.md +77 -0
- package/skills/service-omni-sidebar-configure/references/api-notes.md +51 -0
- package/skills/service-omni-sidebar-configure/scripts/enable-and-report.sh +315 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/_bootstrap.py +142 -0
- package/skills/service-omni-sidebar-configure/scripts/tests/test_sidebar_contracts.py +139 -0
- package/skills/service-omni-skills-based-routing-configure/SKILL.md +81 -0
- package/skills/service-omni-skills-based-routing-configure/references/api-notes.md +50 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/configure-and-report.sh +270 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/_bootstrap.py +120 -0
- package/skills/service-omni-skills-based-routing-configure/scripts/tests/test_sbr_contracts.py +129 -0
- package/skills/service-omni-supervisor-config-deploy/SKILL.md +97 -0
- package/skills/service-omni-supervisor-config-deploy/assets/force-app/main/default/omniSupervisorConfigs/Omni_Supervisor.omniSupervisorConfig-meta.xml +9 -0
- package/skills/service-omni-supervisor-config-deploy/assets/package.xml +8 -0
- package/skills/service-omni-supervisor-config-deploy/assets/sfdx-project.json +12 -0
- package/skills/service-omni-supervisor-config-deploy/references/api-notes.md +205 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/deploy-and-report.sh +586 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/_bootstrap.py +153 -0
- package/skills/service-omni-supervisor-config-deploy/scripts/tests/test_supervisor_config_contracts.py +116 -0
- package/skills/service-omni-supervisor-permset-assign/SKILL.md +88 -0
- package/skills/service-omni-supervisor-permset-assign/references/api-notes.md +48 -0
- package/skills/service-omni-supervisor-permset-assign/scripts/verify-and-assign.sh +316 -0
- package/skills/service-omni-supervisor-surface-deploy/SKILL.md +85 -0
- package/skills/service-omni-supervisor-surface-deploy/references/api-notes.md +47 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/deploy-and-report.sh +216 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/_bootstrap.py +95 -0
- package/skills/service-omni-supervisor-surface-deploy/scripts/tests/test_supervisor_surface_contracts.py +154 -0
- package/skills/service-omni-supervisor-users-create/SKILL.md +92 -0
- package/skills/service-omni-supervisor-users-create/assets/create-supervisors.apex.template +72 -0
- package/skills/service-omni-supervisor-users-create/references/apex-patterns.md +73 -0
- package/skills/service-omni-supervisor-users-create/references/apex-template-notes.md +41 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-and-create.sh +197 -0
- package/skills/service-omni-supervisor-users-create/scripts/detect-existing.sh +112 -0
- package/skills/service-omni-supervisor-users-create/scripts/run-create.sh +296 -0
- package/skills/service-omni-work-skill-routing-configure/SKILL.md +80 -0
- package/skills/service-omni-work-skill-routing-configure/references/api-notes.md +67 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/configure-and-report.sh +262 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/_bootstrap.py +121 -0
- package/skills/service-omni-work-skill-routing-configure/scripts/tests/test_work_skill_routing_contracts.py +161 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# service-omni-supervisor-users-create — Apex patterns
|
|
2
|
+
|
|
3
|
+
Load this reference **only when the Apex template needs modification or debugging**. Under normal operation `run-create.sh` handles everything.
|
|
4
|
+
|
|
5
|
+
## Why Anonymous Apex over REST User POST
|
|
6
|
+
|
|
7
|
+
Two forcing constraints:
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
1. **`System.setPassword()` is Apex-only.** REST POST creates the user but requires an email-based password reset flow. That's a UX blocker for bootstrap scenarios.
|
|
11
|
+
2. **In-transaction re-check for concurrency.** If two operators run this skill simultaneously against the same org, REST POSTs race and one hits `DUPLICATE_USERNAME`. Apex re-queries inside the transaction and skips already-existing indexes atomically.
|
|
12
|
+
|
|
13
|
+
## Username pattern
|
|
14
|
+
|
|
15
|
+
`supervisor{i}.<orgsuffix>@example.com`
|
|
16
|
+
|
|
17
|
+
- `i` is 1..N (max 5 for supervisors)
|
|
18
|
+
- `orgsuffix` is deterministic — the last 8 chars of `Organization.Id`, lowercased
|
|
19
|
+
- `@example.com` is a reserved TLD, safe to use per RFC 2606
|
|
20
|
+
|
|
21
|
+
Deterministic pattern ⇒ any operator, any subsequent run, any skill can query for existing supervisors without shared state.
|
|
22
|
+
|
|
23
|
+
## Alias construction
|
|
24
|
+
|
|
25
|
+
Salesforce Alias field: **max 8 chars, alphanumeric**.
|
|
26
|
+
|
|
27
|
+
Format: `omsu{i}{last-3-of-suffix}`
|
|
28
|
+
|
|
29
|
+
- `omsu` prefix identifies these as Omni Supervisor agent users
|
|
30
|
+
- `i` is 1 char (1..9 max — 5 supervisors max ensures we never hit 10+)
|
|
31
|
+
- 3 trailing suffix chars give per-org uniqueness
|
|
32
|
+
- Total: 8 chars exactly
|
|
33
|
+
|
|
34
|
+
## Password pattern
|
|
35
|
+
|
|
36
|
+
`SuprV1<10-char-random-hex>` — 16 chars total.
|
|
37
|
+
|
|
38
|
+
Guaranteed to satisfy Salesforce's default password policy:
|
|
39
|
+
- ≥1 uppercase letter (`S`, `V`)
|
|
40
|
+
- ≥1 lowercase letter (from random)
|
|
41
|
+
- ≥1 digit (`1`)
|
|
42
|
+
- ≥1 special char (none required by default)
|
|
43
|
+
- ≥8 chars total (16)
|
|
44
|
+
|
|
45
|
+
Random source: `Crypto.generateAesKey(128)` → hex encode → first 10 chars.
|
|
46
|
+
|
|
47
|
+
## Standard User profile
|
|
48
|
+
|
|
49
|
+
**Why Standard User instead of a Supervisor-specific profile?**
|
|
50
|
+
|
|
51
|
+
1. **All standard editions ship it.** Custom profiles differ per org edition.
|
|
52
|
+
2. **Supervisor perms live in a PermissionSet** (`Omni_Supervisor`, assigned by `service-omni-supervisor-permset-assign`), not in the profile.
|
|
53
|
+
3. **Consistent with agent skill.** Same profile, different permset. Simpler mental model for reviewers.
|
|
54
|
+
|
|
55
|
+
## Debug log parsing
|
|
56
|
+
|
|
57
|
+
Each successful user creation emits exactly one line to the Apex debug log:
|
|
58
|
+
|
|
59
|
+
```text
|
|
60
|
+
SUPERVISOR_CREATED|<userId>|<username>|<password>|<email>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`run-create.sh` greps this pattern with a strict regex anchored on the User Id shape (`005` + 15 alphanumerics = 18 chars) to filter out source-code echoes and comment lines that might appear in the log.
|
|
64
|
+
|
|
65
|
+
## Failure modes surfaced to the skill-report
|
|
66
|
+
|
|
67
|
+
| Symptom | Cause | User-friendly translation |
|
|
68
|
+
|---------|-------|--------------------------|
|
|
69
|
+
| `LICENSE_LIMIT_EXCEEDED` | Org is out of user license slots | "Org has reached its user license limit. Free up a license slot or request a larger CDO." |
|
|
70
|
+
| `DUPLICATE_USERNAME` | Global collision (rare — deterministic suffix should prevent) | "Retry the skill; if it persists, escalate." |
|
|
71
|
+
| `INVALID_EMAIL_ADDRESS` | Should never happen with @example.com | "Bug in the skill template — escalate." |
|
|
72
|
+
| `INSUFFICIENT_ACCESS` | SF CLI user is not admin | "Ensure the authenticated user is System Administrator." |
|
|
73
|
+
| `INVALID_SESSION_ID` | Session expired | "Re-authenticate: sf org login web -a <alias>" |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# `create-supervisors.apex.template` notes
|
|
2
|
+
|
|
3
|
+
Anonymous Apex template for creating supervisor demo users. `run-create.sh` resolves the
|
|
4
|
+
tokens before execution and submits the rendered Apex.
|
|
5
|
+
|
|
6
|
+
## Tokens
|
|
7
|
+
|
|
8
|
+
| token | meaning |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| `__COUNT__` | integer 1..5 — number of supervisor slots |
|
|
11
|
+
| `__PROFILE_ID__` | 18-char Profile Id (typically Standard User) |
|
|
12
|
+
| `__SUFFIX__` | 8-char lowercase hex string derived from `Organization.Id` |
|
|
13
|
+
|
|
14
|
+
## Idempotency
|
|
15
|
+
|
|
16
|
+
The block re-queries existing users inside the transaction and inserts only the truly
|
|
17
|
+
missing indexes. Two concurrent runs can both pass the pre-query before either commits;
|
|
18
|
+
Salesforce's `DUPLICATE_USERNAME` constraint is the authoritative deduplicator, and
|
|
19
|
+
`run-create.sh` treats `DUPLICATE_USERNAME` as "another agent won the race" and retries a
|
|
20
|
+
re-detect.
|
|
21
|
+
|
|
22
|
+
## Security note (password / TraceFlag)
|
|
23
|
+
|
|
24
|
+
This block only creates the `User` rows and never emits a password via `System.debug`. The
|
|
25
|
+
caller (`run-create.sh`) sets each password with a SEPARATE Anonymous Apex
|
|
26
|
+
`System.setPassword(id, pw)` submission per newly-created user. That plaintext lives in the
|
|
27
|
+
submitted Apex source, which appears in the inline `executeAnonymous` debug log and — ONLY
|
|
28
|
+
when a debug-log TraceFlag is active for the running user — in a queryable `ApexLog` row.
|
|
29
|
+
`run-create.sh` fails closed BEFORE the first `System.setPassword`: if it cannot prove no
|
|
30
|
+
active TraceFlag exists, it sets no password (the user is left `reset_required`) rather than
|
|
31
|
+
risk a queryable password log.
|
|
32
|
+
|
|
33
|
+
## Output contract
|
|
34
|
+
|
|
35
|
+
For every user created this run, `System.debug` emits one line:
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
SUPERVISOR_CREATED|<userId>|<username>|<email>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
which `run-create.sh` greps from the log to build the skill's JSON return value.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# detect-and-create.sh - canonical entry point: chains detect-existing.sh → run-create.sh (plan|run), idempotent, production-guarded. Args, JSON status contract, exit codes: SKILL.md.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
if [ $# -lt 2 ]; then
|
|
7
|
+
echo '{"status":"blocked","blocking_issue":"Missing required args","usage":"bash detect-and-create.sh <plan|run> <org-alias> [count=3] [profile-name=\"Standard User\"]"}' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
MODE="$1"
|
|
12
|
+
ORG="$2"
|
|
13
|
+
COUNT="${3:-3}"
|
|
14
|
+
PROFILE_NAME="${4:-Standard User}"
|
|
15
|
+
|
|
16
|
+
case "$MODE" in
|
|
17
|
+
plan|run) ;;
|
|
18
|
+
*)
|
|
19
|
+
echo "{\"status\":\"blocked\",\"blocking_issue\":\"Invalid mode '$MODE'. Expected 'plan' or 'run'.\"}" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
;;
|
|
22
|
+
esac
|
|
23
|
+
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
25
|
+
DETECT_SCRIPT="$SCRIPT_DIR/detect-existing.sh"
|
|
26
|
+
CREATE_SCRIPT="$SCRIPT_DIR/run-create.sh"
|
|
27
|
+
|
|
28
|
+
if [ ! -f "$DETECT_SCRIPT" ]; then
|
|
29
|
+
echo "{\"status\":\"blocked\",\"blocking_issue\":\"Sibling script not found: $DETECT_SCRIPT\"}" >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
if [ "$MODE" = "run" ] && [ ! -f "$CREATE_SCRIPT" ]; then
|
|
33
|
+
echo "{\"status\":\"blocked\",\"blocking_issue\":\"Sibling script not found: $CREATE_SCRIPT\"}" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Step 1: detect
|
|
38
|
+
DETECT_EXIT=0
|
|
39
|
+
DETECT_JSON=$(bash "$DETECT_SCRIPT" "$ORG" "$COUNT" "$PROFILE_NAME" 2>&1) || DETECT_EXIT=$?
|
|
40
|
+
|
|
41
|
+
if [ "$DETECT_EXIT" -ne 0 ]; then
|
|
42
|
+
jq -n --arg raw "$DETECT_JSON" --argjson exit "$DETECT_EXIT" \
|
|
43
|
+
'{status:"blocked",blocking_issue:("detect-existing.sh exited " + ($exit|tostring)),detect_raw:$raw,create:null}'
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
if ! echo "$DETECT_JSON" | jq -e . >/dev/null 2>&1; then
|
|
48
|
+
jq -n --arg raw "$DETECT_JSON" \
|
|
49
|
+
'{status:"blocked",blocking_issue:"detect-existing.sh produced non-JSON output",detect_raw:$raw,create:null}'
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
MISSING_COUNT=$(echo "$DETECT_JSON" | jq -r '.missing_count // 0')
|
|
54
|
+
SAFE_TO_WRITE=$(echo "$DETECT_JSON" | jq -r '.safe_to_write // false')
|
|
55
|
+
PROFILE_ID=$(echo "$DETECT_JSON" | jq -r '.profile_id // ""')
|
|
56
|
+
ORG_SUFFIX=$(echo "$DETECT_JSON" | jq -r '.org_suffix // ""')
|
|
57
|
+
EXISTING_COUNT=$(echo "$DETECT_JSON" | jq -r '.existing_users | length')
|
|
58
|
+
# A deactivated user in a slot is neither reusable nor recreatable (globally-unique username);
|
|
59
|
+
# surface it as a manual action so a dead slot never passes as clean.
|
|
60
|
+
INACTIVE_COUNT=$(echo "$DETECT_JSON" | jq -r '.inactive_count // 0')
|
|
61
|
+
INACTIVE_ACTION=""
|
|
62
|
+
if [ "$INACTIVE_COUNT" -gt 0 ]; then
|
|
63
|
+
INACTIVE_ACTION="$INACTIVE_COUNT supervisor slot(s) are occupied by DEACTIVATED users (see inactive_users). Their usernames are globally unique so they cannot be recreated - reactivate them in Setup → Users, or free the usernames, before those slots can be used."
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Step 2: early returns. Nothing missing => "reused" (or action_needed if a dead slot is present).
|
|
67
|
+
if [ "$MISSING_COUNT" = "0" ]; then
|
|
68
|
+
if [ "$INACTIVE_COUNT" -gt 0 ]; then
|
|
69
|
+
jq -n \
|
|
70
|
+
--argjson detect "$DETECT_JSON" \
|
|
71
|
+
--argjson reused_count "$EXISTING_COUNT" \
|
|
72
|
+
--arg action "$INACTIVE_ACTION" \
|
|
73
|
+
'{
|
|
74
|
+
status: "action_needed",
|
|
75
|
+
action_required: $action,
|
|
76
|
+
detect: $detect,
|
|
77
|
+
create: null,
|
|
78
|
+
created_count: 0,
|
|
79
|
+
reused_count: $reused_count,
|
|
80
|
+
total_present_after: $reused_count,
|
|
81
|
+
safe_to_write: $detect.safe_to_write
|
|
82
|
+
}'
|
|
83
|
+
exit 0
|
|
84
|
+
fi
|
|
85
|
+
jq -n \
|
|
86
|
+
--argjson detect "$DETECT_JSON" \
|
|
87
|
+
--argjson reused_count "$EXISTING_COUNT" \
|
|
88
|
+
'{
|
|
89
|
+
status: "reused",
|
|
90
|
+
detect: $detect,
|
|
91
|
+
create: null,
|
|
92
|
+
created_count: 0,
|
|
93
|
+
reused_count: $reused_count,
|
|
94
|
+
total_present_after: $reused_count,
|
|
95
|
+
safe_to_write: $detect.safe_to_write
|
|
96
|
+
}'
|
|
97
|
+
exit 0
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# --plan mode: never create. Report action_needed with the missing indexes (and any dead slots).
|
|
101
|
+
if [ "$MODE" = "plan" ]; then
|
|
102
|
+
jq -n \
|
|
103
|
+
--argjson detect "$DETECT_JSON" \
|
|
104
|
+
--argjson reused_count "$EXISTING_COUNT" \
|
|
105
|
+
--argjson missing_count "$MISSING_COUNT" \
|
|
106
|
+
--arg inactive_action "$INACTIVE_ACTION" \
|
|
107
|
+
'{
|
|
108
|
+
status: "action_needed",
|
|
109
|
+
action_required: (
|
|
110
|
+
"Missing " + ($missing_count|tostring) + " user(s) at indexes " + ($detect.missing_indexes | map(tostring) | join(",")) + ". Re-run in --run mode to create."
|
|
111
|
+
+ (if $inactive_action == "" then "" else " ALSO: " + $inactive_action end)
|
|
112
|
+
),
|
|
113
|
+
detect: $detect,
|
|
114
|
+
create: null,
|
|
115
|
+
created_count: 0,
|
|
116
|
+
reused_count: $reused_count,
|
|
117
|
+
total_present_after: $reused_count,
|
|
118
|
+
safe_to_write: $detect.safe_to_write
|
|
119
|
+
}'
|
|
120
|
+
exit 0
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
# Step 3: --run mode with missing users - safe_to_write guard, then create.
|
|
124
|
+
if [ "$SAFE_TO_WRITE" != "true" ]; then
|
|
125
|
+
jq -n --argjson detect "$DETECT_JSON" \
|
|
126
|
+
'{status:"blocked",blocking_issue:"safe_to_write=false - refusing to create users on a production customer org.",detect:$detect,create:null,created_count:0,reused_count:0,total_present_after:0,safe_to_write:false}'
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [ -z "$PROFILE_ID" ] || [ -z "$ORG_SUFFIX" ]; then
|
|
131
|
+
jq -n --argjson detect "$DETECT_JSON" \
|
|
132
|
+
'{status:"blocked",blocking_issue:"detect-existing.sh did not resolve profile_id or org_suffix - cannot invoke create.",detect:$detect,create:null,created_count:0,reused_count:0,total_present_after:0,safe_to_write:$detect.safe_to_write}'
|
|
133
|
+
exit 1
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
CREATE_EXIT=0
|
|
137
|
+
CREATE_JSON=$(bash "$CREATE_SCRIPT" "$ORG" "$COUNT" "$PROFILE_ID" "$ORG_SUFFIX" 2>&1) || CREATE_EXIT=$?
|
|
138
|
+
|
|
139
|
+
if [ "$CREATE_EXIT" -ne 0 ]; then
|
|
140
|
+
jq -n --argjson detect "$DETECT_JSON" --arg raw "$CREATE_JSON" --argjson exit "$CREATE_EXIT" \
|
|
141
|
+
'{status:"blocked",blocking_issue:("run-create.sh exited " + ($exit|tostring)),detect:$detect,create:null,create_raw:$raw,created_count:0,reused_count:0,total_present_after:0,safe_to_write:$detect.safe_to_write}'
|
|
142
|
+
exit 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
if ! echo "$CREATE_JSON" | jq -e . >/dev/null 2>&1; then
|
|
146
|
+
jq -n --argjson detect "$DETECT_JSON" --arg raw "$CREATE_JSON" \
|
|
147
|
+
'{status:"blocked",blocking_issue:"run-create.sh produced non-JSON output",detect:$detect,create:null,create_raw:$raw,created_count:0,reused_count:0,total_present_after:0,safe_to_write:$detect.safe_to_write}'
|
|
148
|
+
exit 1
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
CREATED_COUNT=$(echo "$CREATE_JSON" | jq -r '.created_count // 0')
|
|
152
|
+
ROLLED_BACK_COUNT=$(echo "$CREATE_JSON" | jq -r '.rolled_back_users | length')
|
|
153
|
+
# A created user without a working password (setPassword failed, non-destructive: stays active +
|
|
154
|
+
# reset_required) is not login-ready; any pending reset forces "partial" with a manual action.
|
|
155
|
+
PW_RESET_COUNT=$(echo "$CREATE_JSON" | jq -r '(.users_needing_password_reset // []) | length')
|
|
156
|
+
|
|
157
|
+
# Classify: created (all landed with a password) / partial (some landed, rollbacks, or pending
|
|
158
|
+
# password resets) / blocked (nothing landed).
|
|
159
|
+
if [ "$CREATED_COUNT" = "0" ]; then
|
|
160
|
+
STATUS="blocked"
|
|
161
|
+
elif [ "$ROLLED_BACK_COUNT" -gt 0 ] || [ "$CREATED_COUNT" -lt "$MISSING_COUNT" ] || [ "$PW_RESET_COUNT" -gt 0 ] || [ "$INACTIVE_COUNT" -gt 0 ]; then
|
|
162
|
+
STATUS="partial"
|
|
163
|
+
else
|
|
164
|
+
STATUS="created"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
jq -n \
|
|
168
|
+
--argjson detect "$DETECT_JSON" \
|
|
169
|
+
--argjson create "$CREATE_JSON" \
|
|
170
|
+
--argjson created_count "$CREATED_COUNT" \
|
|
171
|
+
--argjson reused_count "$EXISTING_COUNT" \
|
|
172
|
+
--argjson pw_reset_count "$PW_RESET_COUNT" \
|
|
173
|
+
--arg inactive_action "$INACTIVE_ACTION" \
|
|
174
|
+
--arg status "$STATUS" \
|
|
175
|
+
'{
|
|
176
|
+
status: $status,
|
|
177
|
+
detect: $detect,
|
|
178
|
+
create: $create,
|
|
179
|
+
created_count: $created_count,
|
|
180
|
+
reused_count: $reused_count,
|
|
181
|
+
total_present_after: ($created_count + $reused_count),
|
|
182
|
+
users_needing_password_reset: ($create.users_needing_password_reset // []),
|
|
183
|
+
action_required: (
|
|
184
|
+
[
|
|
185
|
+
(if $pw_reset_count > 0 then
|
|
186
|
+
($pw_reset_count | tostring) + " supervisor user(s) were created but could NOT have a password set automatically and cannot log in yet. Manually reset their passwords (Setup → Users → Reset Password, or `sf org open` → user record) before use. See users_needing_password_reset and CREDENTIALS.json."
|
|
187
|
+
else empty end),
|
|
188
|
+
(if $inactive_action == "" then empty else $inactive_action end)
|
|
189
|
+
] | if length == 0 then null else join(" ") end
|
|
190
|
+
),
|
|
191
|
+
safe_to_write: $detect.safe_to_write
|
|
192
|
+
}'
|
|
193
|
+
|
|
194
|
+
case "$STATUS" in
|
|
195
|
+
created|partial) exit 0 ;;
|
|
196
|
+
blocked) exit 1 ;;
|
|
197
|
+
esac
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# detect-existing.sh - read-only probe for existing supervisor users (org/profile/existing/missing indexes); deterministic, no side effects. Args + JSON output: SKILL.md.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
if [ $# -lt 1 ]; then
|
|
7
|
+
echo '{"error":"Missing required argument: org-alias","usage":"bash detect-existing.sh <org-alias> [count=1] [profile-name=\"Standard User\"]"}' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
ORG="$1"
|
|
12
|
+
COUNT="${2:-1}"
|
|
13
|
+
PROFILE_NAME="${3:-Standard User}"
|
|
14
|
+
|
|
15
|
+
if ! [[ "$COUNT" =~ ^[1-5]$ ]]; then
|
|
16
|
+
echo "{\"error\":\"Count must be an integer in range 1..5, got: $COUNT\"}" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
if ! sf org display --target-org "$ORG" --json >/dev/null 2>&1; then
|
|
21
|
+
echo "{\"error\":\"Org alias '$ORG' is not authenticated. Run: sf org login web -a $ORG -r <my-domain-url>\"}" >&2
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
ORG_JSON=$(sf data query --target-org "$ORG" --query "SELECT Id, IsSandbox, TrialExpirationDate, OrganizationType FROM Organization LIMIT 1" --json 2>/dev/null || true)
|
|
26
|
+
ORG_ID=$(echo "$ORG_JSON" | jq -r '.result.records[0].Id // empty')
|
|
27
|
+
IS_SANDBOX=$(echo "$ORG_JSON" | jq -r '.result.records[0].IsSandbox // false')
|
|
28
|
+
TRIAL_EXP=$(echo "$ORG_JSON" | jq -r '.result.records[0].TrialExpirationDate // "null"')
|
|
29
|
+
ORG_TYPE=$(echo "$ORG_JSON" | jq -r '.result.records[0].OrganizationType // "Unknown"')
|
|
30
|
+
|
|
31
|
+
if [ -z "$ORG_ID" ]; then
|
|
32
|
+
echo '{"error":"Failed to query Organization.Id - check org auth and API permissions."}' >&2
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
SAFE_TO_WRITE=false
|
|
37
|
+
if [ "$IS_SANDBOX" = "true" ] || [ "$TRIAL_EXP" != "null" ] || [ "$ORG_TYPE" = "Developer Edition" ] || [ "$ORG_TYPE" = "Base Edition" ]; then
|
|
38
|
+
SAFE_TO_WRITE=true
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
SUFFIX=$(echo -n "${ORG_ID:10:8}" | tr '[:upper:]' '[:lower:]')
|
|
42
|
+
|
|
43
|
+
# Profile.Name is a display name; escape ' for the SOQL literal (apostrophe safety + no injection).
|
|
44
|
+
PROFILE_NAME_SOQL=${PROFILE_NAME//\'/\\\'}
|
|
45
|
+
PROFILE_JSON=$(sf data query --target-org "$ORG" --query "SELECT Id, Name FROM Profile WHERE Name = '$PROFILE_NAME_SOQL' LIMIT 1" --json 2>/dev/null || true)
|
|
46
|
+
PROFILE_ID=$(echo "$PROFILE_JSON" | jq -r '.result.records[0].Id // empty')
|
|
47
|
+
|
|
48
|
+
if [ -z "$PROFILE_ID" ]; then
|
|
49
|
+
echo "{\"error\":\"Profile '$PROFILE_NAME' not found on org '$ORG'. Check spelling - profile names are case-sensitive on some orgs and may be localized.\"}" >&2
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
USER_JSON=$(sf data query --target-org "$ORG" \
|
|
54
|
+
--query "SELECT Id, Username, IsActive FROM User WHERE Username LIKE 'supervisor%.${SUFFIX}@example.com'" \
|
|
55
|
+
--json 2>/dev/null || true)
|
|
56
|
+
|
|
57
|
+
EXPECTED_USERNAMES=$(seq 1 "$COUNT" | awk -v s="$SUFFIX" '{ printf "\"supervisor%s.%s@example.com\"\n", $1, s }' | jq -s '.')
|
|
58
|
+
|
|
59
|
+
FULL_JSON=$(jq -n \
|
|
60
|
+
--arg org_id "$ORG_ID" \
|
|
61
|
+
--arg org_suffix "$SUFFIX" \
|
|
62
|
+
--argjson is_sandbox "$IS_SANDBOX" \
|
|
63
|
+
--arg trial_expiration_date "$TRIAL_EXP" \
|
|
64
|
+
--arg organization_type "$ORG_TYPE" \
|
|
65
|
+
--argjson safe_to_write "$SAFE_TO_WRITE" \
|
|
66
|
+
--arg profile_id "$PROFILE_ID" \
|
|
67
|
+
--arg profile_name "$PROFILE_NAME" \
|
|
68
|
+
--argjson count "$COUNT" \
|
|
69
|
+
--argjson expected "$EXPECTED_USERNAMES" \
|
|
70
|
+
--argjson user_records "$(echo "$USER_JSON" | jq '.result.records // []')" \
|
|
71
|
+
'
|
|
72
|
+
($user_records | map({ (.Username | ascii_downcase): {id: .Id, is_active: .IsActive} }) | add // {}) as $by_uname
|
|
73
|
+
|
|
|
74
|
+
[range(1; $count + 1) as $i
|
|
75
|
+
| ($expected[$i - 1] | ascii_downcase) as $uname
|
|
76
|
+
| if $by_uname[$uname] then
|
|
77
|
+
{index: $i, id: $by_uname[$uname].id, username: $uname, is_active: $by_uname[$uname].is_active}
|
|
78
|
+
else
|
|
79
|
+
null
|
|
80
|
+
end
|
|
81
|
+
] as $slots
|
|
82
|
+
|
|
|
83
|
+
# occupied slots (any user present, active or not)
|
|
84
|
+
($slots | map(select(. != null))) as $occupied
|
|
85
|
+
|
|
|
86
|
+
# existing_users: only active occupants are reusable; inactive users are surfaced separately
|
|
87
|
+
# (globally-unique username, not recreatable).
|
|
88
|
+
($occupied | map(select(.is_active == true))) as $existing
|
|
89
|
+
|
|
|
90
|
+
($occupied | map(select(.is_active != true))) as $inactive
|
|
91
|
+
|
|
|
92
|
+
([range(1; $count + 1) as $i | if $slots[$i - 1] == null then $i else null end] | map(select(. != null))) as $missing
|
|
93
|
+
|
|
|
94
|
+
{
|
|
95
|
+
org_id: $org_id,
|
|
96
|
+
org_suffix: $org_suffix,
|
|
97
|
+
is_sandbox: $is_sandbox,
|
|
98
|
+
trial_expiration_date: (if $trial_expiration_date == "null" then null else $trial_expiration_date end),
|
|
99
|
+
organization_type: $organization_type,
|
|
100
|
+
safe_to_write: $safe_to_write,
|
|
101
|
+
profile_id: $profile_id,
|
|
102
|
+
profile_name: $profile_name,
|
|
103
|
+
requested_count: $count,
|
|
104
|
+
existing_users: $existing,
|
|
105
|
+
inactive_users: $inactive,
|
|
106
|
+
inactive_count: ($inactive | length),
|
|
107
|
+
missing_indexes: $missing,
|
|
108
|
+
missing_count: ($missing | length)
|
|
109
|
+
}
|
|
110
|
+
')
|
|
111
|
+
|
|
112
|
+
echo "$FULL_JSON"
|