@salesforce/afv-skills 1.30.0 → 1.31.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/data360-code-extension-generate/SKILL.md +15 -2
- package/skills/dx-pkg-post-install-configure/SKILL.md +184 -0
- package/skills/experience-ui-bundle-app-coordinate/SKILL.md +198 -34
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh +26 -0
- package/skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh +15 -0
- package/skills/experience-ui-bundle-metadata-generate/SKILL.md +19 -8
- package/skills/experience-ui-bundle-metadata-generate/references/csp-metadata-format.md +2 -2
- package/skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh +30 -0
- package/skills/platform-custom-field-generate/SKILL.md +12 -11
- package/skills/platform-custom-field-generate/references/advanced-picklists.md +17 -17
- package/skills/platform-custom-report-type-generate/SKILL.md +259 -0
- package/skills/platform-custom-report-type-generate/examples/AccountProjectsWithTasks.reportType-meta.xml +51 -0
- package/skills/platform-custom-report-type-generate/examples/AccountsWithIndustry.reportType-meta.xml +27 -0
- package/skills/platform-custom-report-type-generate/examples/AccountsWithProjects.reportType-meta.xml +44 -0
- package/skills/platform-custom-report-type-generate/references/category-values.md +32 -0
- package/skills/platform-custom-report-type-generate/references/errors-and-troubleshooting.md +24 -0
- package/skills/platform-dataspace-access-configure/SKILL.md +318 -0
- package/skills/platform-docs-get/SKILL.md +4 -0
- package/skills/platform-docs-get/scripts/extract_salesforce_doc.py +78 -13
- package/skills/platform-encryption-configure/SKILL.md +122 -0
- package/skills/platform-encryption-configure/assets/EncryptionKey.settings-meta.xml +17 -0
- package/skills/platform-encryption-configure/assets/PlatformEncryption.settings-meta.xml +15 -0
- package/skills/platform-encryption-configure/assets/encrypted-field.field-meta.xml +22 -0
- package/skills/platform-encryption-configure/examples/cache-only-keys.settings-meta.xml +11 -0
- package/skills/platform-encryption-configure/references/encryption-schemes.md +34 -0
- package/skills/platform-encryption-configure/references/key-models.md +34 -0
- package/skills/platform-encryption-configure/references/tenant-secret-lifecycle.md +23 -0
- package/skills/platform-encryption-configure/scripts/validate-encryption-metadata.sh +54 -0
- package/skills/platform-metadata-api-context-get/SKILL.md +13 -10
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ReportType xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<fullName>AccountProjectsWithTasks</fullName>
|
|
4
|
+
<baseObject>Account</baseObject>
|
|
5
|
+
<category>other</category>
|
|
6
|
+
<deployed>true</deployed>
|
|
7
|
+
<label>Accounts with Projects with Tasks</label>
|
|
8
|
+
<description>Reports on accounts that have projects, and those projects that have tasks — useful for active-engagement tracking. Demonstrates a nested inner join chain and how each level extends the dotted relationship path in <table> (Account → Account.Projects__r → Account.Projects__r.Tasks__r).</description>
|
|
9
|
+
<join>
|
|
10
|
+
<outerJoin>false</outerJoin>
|
|
11
|
+
<relationship>Projects__r</relationship>
|
|
12
|
+
<join>
|
|
13
|
+
<outerJoin>false</outerJoin>
|
|
14
|
+
<relationship>Tasks__r</relationship>
|
|
15
|
+
</join>
|
|
16
|
+
</join>
|
|
17
|
+
<sections>
|
|
18
|
+
<masterLabel>Account Fields</masterLabel>
|
|
19
|
+
<columns>
|
|
20
|
+
<checkedByDefault>true</checkedByDefault>
|
|
21
|
+
<field>Name</field>
|
|
22
|
+
<table>Account</table>
|
|
23
|
+
</columns>
|
|
24
|
+
</sections>
|
|
25
|
+
<sections>
|
|
26
|
+
<masterLabel>Project Fields</masterLabel>
|
|
27
|
+
<columns>
|
|
28
|
+
<checkedByDefault>true</checkedByDefault>
|
|
29
|
+
<field>Name</field>
|
|
30
|
+
<table>Account.Projects__r</table>
|
|
31
|
+
</columns>
|
|
32
|
+
<columns>
|
|
33
|
+
<checkedByDefault>true</checkedByDefault>
|
|
34
|
+
<field>Status__c</field>
|
|
35
|
+
<table>Account.Projects__r</table>
|
|
36
|
+
</columns>
|
|
37
|
+
</sections>
|
|
38
|
+
<sections>
|
|
39
|
+
<masterLabel>Task Fields</masterLabel>
|
|
40
|
+
<columns>
|
|
41
|
+
<checkedByDefault>true</checkedByDefault>
|
|
42
|
+
<field>Name</field>
|
|
43
|
+
<table>Account.Projects__r.Tasks__r</table>
|
|
44
|
+
</columns>
|
|
45
|
+
<columns>
|
|
46
|
+
<checkedByDefault>false</checkedByDefault>
|
|
47
|
+
<field>Due_Date__c</field>
|
|
48
|
+
<table>Account.Projects__r.Tasks__r</table>
|
|
49
|
+
</columns>
|
|
50
|
+
</sections>
|
|
51
|
+
</ReportType>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ReportType xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<fullName>AccountsWithIndustry</fullName>
|
|
4
|
+
<baseObject>Account</baseObject>
|
|
5
|
+
<category>accounts</category>
|
|
6
|
+
<deployed>true</deployed>
|
|
7
|
+
<label>Accounts with Industry Detail</label>
|
|
8
|
+
<description>Report framework for reviewing accounts with industry and revenue fields surfaced.</description>
|
|
9
|
+
<sections>
|
|
10
|
+
<masterLabel>Account Fields</masterLabel>
|
|
11
|
+
<columns>
|
|
12
|
+
<checkedByDefault>true</checkedByDefault>
|
|
13
|
+
<field>Name</field>
|
|
14
|
+
<table>Account</table>
|
|
15
|
+
</columns>
|
|
16
|
+
<columns>
|
|
17
|
+
<checkedByDefault>true</checkedByDefault>
|
|
18
|
+
<field>Industry</field>
|
|
19
|
+
<table>Account</table>
|
|
20
|
+
</columns>
|
|
21
|
+
<columns>
|
|
22
|
+
<checkedByDefault>false</checkedByDefault>
|
|
23
|
+
<field>AnnualRevenue</field>
|
|
24
|
+
<table>Account</table>
|
|
25
|
+
</columns>
|
|
26
|
+
</sections>
|
|
27
|
+
</ReportType>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<ReportType xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
3
|
+
<fullName>AccountsWithProjects</fullName>
|
|
4
|
+
<baseObject>Account</baseObject>
|
|
5
|
+
<category>accounts</category>
|
|
6
|
+
<deployed>true</deployed>
|
|
7
|
+
<label>Accounts with or without Projects</label>
|
|
8
|
+
<description>Shows every account; related project fields appear when projects exist. Demonstrates outer join — primary records included regardless of related-record presence — and the dotted relationship path used in <table>Account.Projects__r</table> for joined-object fields.</description>
|
|
9
|
+
<join>
|
|
10
|
+
<outerJoin>true</outerJoin>
|
|
11
|
+
<relationship>Projects__r</relationship>
|
|
12
|
+
</join>
|
|
13
|
+
<sections>
|
|
14
|
+
<masterLabel>Account Fields</masterLabel>
|
|
15
|
+
<columns>
|
|
16
|
+
<checkedByDefault>true</checkedByDefault>
|
|
17
|
+
<field>Name</field>
|
|
18
|
+
<table>Account</table>
|
|
19
|
+
</columns>
|
|
20
|
+
<columns>
|
|
21
|
+
<checkedByDefault>true</checkedByDefault>
|
|
22
|
+
<field>Industry</field>
|
|
23
|
+
<table>Account</table>
|
|
24
|
+
</columns>
|
|
25
|
+
</sections>
|
|
26
|
+
<sections>
|
|
27
|
+
<masterLabel>Project Fields</masterLabel>
|
|
28
|
+
<columns>
|
|
29
|
+
<checkedByDefault>true</checkedByDefault>
|
|
30
|
+
<field>Name</field>
|
|
31
|
+
<table>Account.Projects__r</table>
|
|
32
|
+
</columns>
|
|
33
|
+
<columns>
|
|
34
|
+
<checkedByDefault>true</checkedByDefault>
|
|
35
|
+
<field>Status__c</field>
|
|
36
|
+
<table>Account.Projects__r</table>
|
|
37
|
+
</columns>
|
|
38
|
+
<columns>
|
|
39
|
+
<checkedByDefault>false</checkedByDefault>
|
|
40
|
+
<field>CreatedDate</field>
|
|
41
|
+
<table>Account.Projects__r</table>
|
|
42
|
+
</columns>
|
|
43
|
+
</sections>
|
|
44
|
+
</ReportType>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# `<category>` Values — `ReportTypeCategory` Enumeration
|
|
2
|
+
|
|
3
|
+
The `category` value determines where the CRT appears in the report builder's "Create New Report Type" wizard. Use one of these Salesforce-defined values from the Metadata API `ReportTypeCategory` enum.
|
|
4
|
+
|
|
5
|
+
| Category value | Typical use |
|
|
6
|
+
|----------------|-------------|
|
|
7
|
+
| `accounts` | Accounts & Contacts |
|
|
8
|
+
| `opportunities` | Opportunities |
|
|
9
|
+
| `forecasts` | Forecasts |
|
|
10
|
+
| `cases` | Customer Support Reports |
|
|
11
|
+
| `leads` | Leads |
|
|
12
|
+
| `campaigns` | Campaigns |
|
|
13
|
+
| `activities` | Activities |
|
|
14
|
+
| `busop` | Business operations |
|
|
15
|
+
| `products` | Price Books, Products and Assets |
|
|
16
|
+
| `admin` | Administrative Reports |
|
|
17
|
+
| `territory` | Territory management |
|
|
18
|
+
| `territory2` | Territory management (Enterprise Territory Management) — API 31.0+ |
|
|
19
|
+
| `usage_entitlement` | Usage entitlements |
|
|
20
|
+
| `wdc` | Work.com / Calibration — API 29.0+ |
|
|
21
|
+
| `calibration` | Calibration — API 29.0+ |
|
|
22
|
+
| `other` | Other Reports (default for custom-object-based CRTs without a natural home) |
|
|
23
|
+
| `content` | Content |
|
|
24
|
+
| `quotes` | Quotes |
|
|
25
|
+
| `individual` | Individual (privacy) — API 45.0+ |
|
|
26
|
+
| `employee` | Employee — API 46.0+ |
|
|
27
|
+
| `data_cloud` | Data Cloud — API 55.0+ |
|
|
28
|
+
| `commerce` | Commerce — API 60.0+ |
|
|
29
|
+
| `flow` | Flow — API 60.0+ |
|
|
30
|
+
| `semantic_model` | Semantic model — API 60.0+ |
|
|
31
|
+
|
|
32
|
+
**When in doubt:** Use `other` for custom-object-based CRTs.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Common Authoring Errors
|
|
2
|
+
|
|
3
|
+
Read this when a generated `.reportType-meta.xml` is rejected during deployment or fields don't appear in the report builder. Use these to validate the authored XML before handing it off; deployment itself is outside this skill's scope.
|
|
4
|
+
|
|
5
|
+
| Error | Cause | Fix |
|
|
6
|
+
|-------|-------|-----|
|
|
7
|
+
| `Invalid object name 'X'` on `<baseObject>` | Primary object doesn't exist or isn't deployed | Deploy the custom object before the CRT |
|
|
8
|
+
| `Invalid relationship name 'X'` on `<join>` | Used the field API name instead of the child relationship name, or forgot `__r` | Use the child relationship name (e.g. `Projects__r` for a custom relationship) |
|
|
9
|
+
| `Invalid field 'X' for object 'Y'` | Field doesn't exist on `<table>`, used label instead of API name, or field not yet deployed | Verify field API name; deploy dependent fields first |
|
|
10
|
+
| `Invalid category value 'X'` | Typo or non-existent category | Use a valid `ReportTypeCategory` value (see `category-values.md`); use `other` for general-purpose custom-object CRTs |
|
|
11
|
+
| Inner join after outer join | A nested `<join>` has `<outerJoin>false</outerJoin>` following an earlier outer join | Switch the nested join to `<outerJoin>true</outerJoin>`, or restructure so inner joins come first |
|
|
12
|
+
| Fields from joined object not visible in report builder | `<table>` in `<sections>` for the joined object doesn't use the dotted relationship path | Change `<table>` to the full path (e.g. `Account.Projects__r` not `Project__c`) |
|
|
13
|
+
| `Cannot change base object` on update | Attempted to change `<baseObject>` after initial deploy | Create a new CRT with the new primary object; retire the old one |
|
|
14
|
+
| File not found / fullName mismatch | File name doesn't match `<fullName>` | Rename file so `<fullName>.reportType-meta.xml` matches |
|
|
15
|
+
|
|
16
|
+
## Handling Conflicting Join Requirements
|
|
17
|
+
|
|
18
|
+
When a user wants both "include primary records without children" AND "exclude children that lack their own children" — those constraints are inexpressible in a single CRT join chain (Rule 7 forbids inner-after-outer). Three workarounds:
|
|
19
|
+
|
|
20
|
+
1. **All-outer + report-level filter** — keep all joins outer and filter at report time (e.g. `Case ID ≠ null`). Pragmatic, but every report author must remember the filter.
|
|
21
|
+
2. **Flip the base object** — base on the deepest required object. Lose primary-without-children but gain the inner-join guarantee. Lookup fields on the new base let you traverse back up for context.
|
|
22
|
+
3. **Two CRTs** — one all-outer for "all primary records," one inner-chained for "only primary records with full children." Users pick the right one.
|
|
23
|
+
|
|
24
|
+
Surface this trade-off to the user rather than picking silently.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: platform-dataspace-access-configure
|
|
3
|
+
description: "Use this skill to configure Salesforce Data Cloud DataSpace access for permission sets. Grants dataspace-level access via MDAPI PermissionSet XML with dataspaceScopes elements, and optionally grants object-level access to specific DMO, DLO, or CIO objects via the Object Access Grants Connect API. TRIGGER when: user needs to create or update a permission set that includes DataSpace access, grant a permission set access to a specific dataspace, configure dataAccessLevel or objectAccessLevel for a dataspace scope, add RBAC object access grants for Data Cloud objects, or list or remove object access grants for a permission set and DataSpace pair. DO NOT TRIGGER when: the task is a generic permission set without any dataspace access (use platform-permission-set-generate), the request is about data ingestion or streams (use data360-prepare), or the work involves creating dataspaces themselves rather than granting access to them."
|
|
4
|
+
metadata:
|
|
5
|
+
version: "1.0"
|
|
6
|
+
minApiVersion: "67.0"
|
|
7
|
+
cliTools:
|
|
8
|
+
- tool: ["sf"]
|
|
9
|
+
semver: ">=2.0.0"
|
|
10
|
+
- tool: ["jq"]
|
|
11
|
+
semver: ">=1.6"
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# platform-dataspace-access-configure
|
|
15
|
+
|
|
16
|
+
Configure DataSpace access in Salesforce Data Cloud using a two-layer model:
|
|
17
|
+
|
|
18
|
+
1. **DataSpace-level access** — grant a `PermissionSet` access to a DataSpace by embedding a `<dataspaceScopes>` element in the permission set XML and deploying via MDAPI.
|
|
19
|
+
2. **Object-level access (optional)** — grant that permission set access to specific DMO / DLO / CIO objects within the DataSpace using the Object Access Grants Connect API.
|
|
20
|
+
|
|
21
|
+
The MDAPI layer is required to establish the PermissionSet → DataSpace linkage. The Connect API layer is optional and only needed when access should be scoped to specific objects rather than governed entirely by data governance policies.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Decide the Case First
|
|
26
|
+
|
|
27
|
+
Pick exactly one case from the table below before writing any files. Each case has a different output shape.
|
|
28
|
+
|
|
29
|
+
| Case | User intent | Permission set state | Files to emit |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| **A. Create new permset with DS access** | "create a permission set called X with dataspace scope Y" | does NOT exist yet | `permissionsets/<Name>.permissionset-meta.xml` **and** `package.xml` |
|
|
32
|
+
| **B. Add DS access to existing permset** | "grant existing permission set X access to dataspace Y" | already deployed (may contain other permissions) | patched `permissionsets/<Name>.permissionset-meta.xml` **and** `package.xml` — see Case B workflow below |
|
|
33
|
+
| **C. Object-level grant only** | "grant permset X access to object Z (in dataspace Y)" — permset + scope already configured | already deployed with `dataspaceScopes` | `api-request.json` (Connect API body). NO permission set XML, NO `package.xml` |
|
|
34
|
+
|
|
35
|
+
Only emit the files listed for the case you picked. Emitting Case A/B files for a Case C prompt (or vice versa) is a correctness failure — extra files change the deployment shape.
|
|
36
|
+
|
|
37
|
+
> **Case B — critical:** PermissionSet MDAPI deploy is a **full metadata replace**. Every `<objectPermissions>`, `<fieldPermissions>`, `<userPermissions>`, `<tabSettings>`, `<applicationVisibilities>`, `<recordTypeVisibilities>`, `<customPermissions>`, `<pageAccesses>`, `<classAccesses>`, `<customMetadataTypeAccesses>`, `<customSettingAccesses>`, `<externalDataSourceAccesses>` element you omit from the redeploy is **deleted from the org**. Before adding `<dataspaceScopes>` to an existing permset, retrieve the current XML and patch it — do not hand-author from scratch.
|
|
38
|
+
|
|
39
|
+
### Case B workflow
|
|
40
|
+
|
|
41
|
+
1. Retrieve the existing permission set:
|
|
42
|
+
```bash
|
|
43
|
+
sf project retrieve start --metadata PermissionSet:<Name> --target-org <alias>
|
|
44
|
+
```
|
|
45
|
+
2. Open the retrieved `permissionsets/<Name>.permissionset-meta.xml`. Keep every element already there.
|
|
46
|
+
3. Insert the `<dataspaceScopes>` block for the target DataSpace (element order in the file does not matter for MDAPI). If the file already has a `<dataspaceScopes>` block **for this same DataSpace**, replace only that block. Leave every `<dataspaceScopes>` block for other DataSpaces untouched — one block per DataSpace, and removing a block revokes that DataSpace grant.
|
|
47
|
+
4. Write `package.xml` listing the permset in `<members>`.
|
|
48
|
+
5. Redeploy with `sf project deploy start`.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## When This Skill Owns the Task
|
|
53
|
+
|
|
54
|
+
Trigger this skill when the user wants to:
|
|
55
|
+
- Create a permission set that grants access to a Data Cloud DataSpace
|
|
56
|
+
- Add or modify `dataspaceScopes` on an existing permission set
|
|
57
|
+
- Grant a permission set access to specific DMO / DLO / CIO objects in a DataSpace
|
|
58
|
+
- Configure `dataAccessLevel` and `objectAccessLevel` for a DataSpace scope
|
|
59
|
+
- List or remove object access grants for a permission set + DataSpace pair
|
|
60
|
+
|
|
61
|
+
Delegate elsewhere when:
|
|
62
|
+
- The permission set has no DataSpace access at all → `platform-permission-set-generate`
|
|
63
|
+
- The task is creating the DataSpace itself → `data360-orchestrate`
|
|
64
|
+
- The task is ingesting data or configuring streams → `data360-prepare`
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Layer 1 — DataSpace-Level Access (MDAPI)
|
|
69
|
+
|
|
70
|
+
Embed a `<dataspaceScopes>` element inside the `PermissionSet` XML. Deploy with MDAPI.
|
|
71
|
+
|
|
72
|
+
```xml
|
|
73
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
74
|
+
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
75
|
+
<label>Data Cloud Analyst</label>
|
|
76
|
+
<description>Data cloud analyst access to the default dataspace</description>
|
|
77
|
+
<hasActivationRequired>false</hasActivationRequired>
|
|
78
|
+
<dataspaceScopes>
|
|
79
|
+
<dataspaceScope>default</dataspaceScope>
|
|
80
|
+
<dataAccessLevel>ALL</dataAccessLevel>
|
|
81
|
+
<objectAccessLevel>BY_POLICY</objectAccessLevel>
|
|
82
|
+
</dataspaceScopes>
|
|
83
|
+
</PermissionSet>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Element Rules
|
|
87
|
+
|
|
88
|
+
| Element | Required | Valid Values | Purpose |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| `<dataspaceScopes>` | yes | parent element (plural) | Container for a single dataspace scope grant |
|
|
91
|
+
| `<dataspaceScope>` | yes | DataSpace API name (e.g. `default`) | Which DataSpace this grant is for |
|
|
92
|
+
| `<dataAccessLevel>` | yes | `NONE`, `CONTROLLED_BY_PARENT`, `ALL` | Row-level data access within the DataSpace |
|
|
93
|
+
| `<objectAccessLevel>` | yes | `BY_POLICY`, `ALL_IN_DATASPACE` | Object-level access. `BY_POLICY` defers to data governance policies. `ALL_IN_DATASPACE` is only allowed when `dataAccessLevel` is `CONTROLLED_BY_PARENT` |
|
|
94
|
+
|
|
95
|
+
### Common Mistakes
|
|
96
|
+
|
|
97
|
+
- **Wrong parent name** — using `<dataspaceScopeAccess>` instead of `<dataspaceScopes>`. Deployment fails silently or with cryptic errors.
|
|
98
|
+
- **Wrong child name** — using `<dataspaceScopeName>` instead of `<dataspaceScope>`.
|
|
99
|
+
- **Wrong enum values** — `ViewAllRows` / `Read` / `OWNER` / `EDIT` are not valid. Use `NONE`, `CONTROLLED_BY_PARENT`, or `ALL` for `dataAccessLevel`; use `BY_POLICY` or `ALL_IN_DATASPACE` for `objectAccessLevel`. See Element Rules table for allowed combinations. Deployment error `-379999659` means invalid enum.
|
|
100
|
+
- **Multiple scopes in one element** — `<dataspaceScopes>` grants access to exactly one DataSpace. To grant access to multiple, add multiple `<dataspaceScopes>` blocks.
|
|
101
|
+
|
|
102
|
+
### Package Layout (Case A and Case B)
|
|
103
|
+
|
|
104
|
+
A deployable bundle for Layer 1 always contains **both** files:
|
|
105
|
+
|
|
106
|
+
```text
|
|
107
|
+
<output-root>/
|
|
108
|
+
package.xml
|
|
109
|
+
permissionsets/<Name>.permissionset-meta.xml
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`package.xml` (required — list every permission set being deployed in `<members>`):
|
|
113
|
+
|
|
114
|
+
```xml
|
|
115
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
116
|
+
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
117
|
+
<types>
|
|
118
|
+
<members>Data_Cloud_Analyst</members>
|
|
119
|
+
<name>PermissionSet</name>
|
|
120
|
+
</types>
|
|
121
|
+
<version>67.0</version>
|
|
122
|
+
</Package>
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Deploy:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
sf project deploy start --source-dir force-app/main/default/permissionsets/ --target-org <alias>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Layer 2 — Object-Level Access (Connect API) — Case C
|
|
134
|
+
|
|
135
|
+
Only needed when `objectAccessLevel` is not `BY_POLICY`, or when governance policies do not cover the target objects. Grants are runtime — **no MDAPI deploy, no `package.xml`, no permission set XML**. The only artifact for a Case C task is a single `api-request.json` describing the Connect API call.
|
|
136
|
+
|
|
137
|
+
### Resolve the API version first
|
|
138
|
+
|
|
139
|
+
Every Connect API `endpoint` in this layer contains an `/services/data/v<apiVersion>/…` segment. **Do not hardcode `v67.0`.** Resolve the target org's actual API version before writing the envelope so the request matches the org's supported surface:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
sf org display --target-org <alias> --json | jq -r '.result.apiVersion'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- Substitute the returned value (e.g. `67.0`, `68.0`) into the `endpoint` as `v<apiVersion>`.
|
|
146
|
+
- If the org can't be queried (offline authoring, no alias yet), fall back to the `minApiVersion` from this skill's frontmatter (`67.0`) — the endpoint was introduced there and any newer version accepts the same body.
|
|
147
|
+
- If the user explicitly specifies a version in the prompt, use that verbatim.
|
|
148
|
+
|
|
149
|
+
In the templates below, `{apiVersion}` is a placeholder. Replace it with the resolved API version (e.g., `67.0`, `68.0`) before emitting `api-request.json`.
|
|
150
|
+
|
|
151
|
+
### `api-request.json` — canonical shape
|
|
152
|
+
|
|
153
|
+
Emit the request as a self-describing envelope with `method`, `endpoint`, `headers`, `body`, and `expectedResponse`. Do NOT emit only the body — reviewers and downstream tooling read the envelope.
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"method": "POST",
|
|
158
|
+
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants",
|
|
159
|
+
"headers": {
|
|
160
|
+
"Content-Type": "application/json"
|
|
161
|
+
},
|
|
162
|
+
"body": {
|
|
163
|
+
"permissionSetName": "Data_Cloud_Analyst",
|
|
164
|
+
"dataSpaceName": "default",
|
|
165
|
+
"objectApiName": "Account__dlm"
|
|
166
|
+
},
|
|
167
|
+
"expectedResponse": {
|
|
168
|
+
"status": 201,
|
|
169
|
+
"body": {
|
|
170
|
+
"permissionSetName": "Data_Cloud_Analyst",
|
|
171
|
+
"dataSpaceName": "default",
|
|
172
|
+
"objectApiName": "Account__dlm"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Bulk Grant
|
|
179
|
+
|
|
180
|
+
Same `api-request.json` envelope shape. `endpoint` gains the `/actions/bulk-create` suffix, `body.objectApiName` is replaced by the list-valued `body.objectApiNames`, and `expectedResponse` omits the `body` field because bulk responses return per-object status entries rather than the flat request payload (see Gotchas below).
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"method": "POST",
|
|
185
|
+
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants/actions/bulk-create",
|
|
186
|
+
"headers": {
|
|
187
|
+
"Content-Type": "application/json"
|
|
188
|
+
},
|
|
189
|
+
"body": {
|
|
190
|
+
"permissionSetName": "Data_Cloud_Analyst",
|
|
191
|
+
"dataSpaceName": "default",
|
|
192
|
+
"objectApiNames": ["Account__dlm", "Contact__dlm", "Opportunity__dlm"]
|
|
193
|
+
},
|
|
194
|
+
"expectedResponse": {
|
|
195
|
+
"status": 201
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### List Grants
|
|
201
|
+
|
|
202
|
+
Same envelope shape with `method: "GET"`, query parameters on the `endpoint`, and no `body`.
|
|
203
|
+
|
|
204
|
+
```json
|
|
205
|
+
{
|
|
206
|
+
"method": "GET",
|
|
207
|
+
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants?permissionSetName=Data_Cloud_Analyst&dataSpaceName=default",
|
|
208
|
+
"headers": {
|
|
209
|
+
"Accept": "application/json"
|
|
210
|
+
},
|
|
211
|
+
"expectedResponse": {
|
|
212
|
+
"status": 200
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Revoke Grant
|
|
218
|
+
|
|
219
|
+
Same envelope shape with `method: "DELETE"`, the object API name as a path segment, and `expectedResponse.status: 204` (No Content).
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"method": "DELETE",
|
|
224
|
+
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants/Account__dlm?permissionSetName=Data_Cloud_Analyst&dataSpaceName=default",
|
|
225
|
+
"headers": {
|
|
226
|
+
"Accept": "application/json"
|
|
227
|
+
},
|
|
228
|
+
"expectedResponse": {
|
|
229
|
+
"status": 204
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Object Types
|
|
235
|
+
|
|
236
|
+
- **DMO** (Data Model Object) — unified profile objects, suffix `__dlm`
|
|
237
|
+
- **DLO** (Data Lake Object) — raw ingested data, suffix `__dll`
|
|
238
|
+
- **CIO** (Calculated Insight Object) — computed metrics, suffix `__cio`
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Combined Setup — Case A + Case C from a Cold Start
|
|
243
|
+
|
|
244
|
+
Use this section ONLY when the user is starting from nothing and asks for both the permset+scope AND per-object grants in a single request. If the user's prompt is only about the Connect API grant (Case C) — for example "grant Account__dlm access; the permset and dataspace scope already exist" — SKIP this section entirely and emit only `api-request.json` from Layer 2.
|
|
245
|
+
|
|
246
|
+
The commands below are operator-facing `sf` CLI invocations (a runnable cold-start walkthrough), NOT the artifact you emit. For a normal Case C task the artifact is a single `api-request.json` envelope as documented in Layer 2 above.
|
|
247
|
+
|
|
248
|
+
**Goal:** Grant `Data_Cloud_Analyst` permission set access to `Account__dlm` and `Contact__dlm` in the `default` DataSpace.
|
|
249
|
+
|
|
250
|
+
**Step 1 — Deploy PermissionSet with DataSpace scope (MDAPI):**
|
|
251
|
+
|
|
252
|
+
```xml
|
|
253
|
+
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
|
254
|
+
<label>Data Cloud Analyst</label>
|
|
255
|
+
<description>Data cloud analyst access to the default dataspace</description>
|
|
256
|
+
<hasActivationRequired>false</hasActivationRequired>
|
|
257
|
+
<dataspaceScopes>
|
|
258
|
+
<dataspaceScope>default</dataspaceScope>
|
|
259
|
+
<dataAccessLevel>ALL</dataAccessLevel>
|
|
260
|
+
<objectAccessLevel>BY_POLICY</objectAccessLevel>
|
|
261
|
+
</dataspaceScopes>
|
|
262
|
+
</PermissionSet>
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
sf project deploy start --source-dir permissionsets/ --target-org <alias>
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Step 2 — Grant object access (Connect API):**
|
|
270
|
+
*(Required here because the `default` DataSpace has no governance policies covering `Account__dlm` and `Contact__dlm`. Skip Step 2 when `BY_POLICY` policies already govern the target objects — Layer 1 alone is sufficient.)*
|
|
271
|
+
|
|
272
|
+
Resolve the org's API version first (see [Resolve the API version first](#resolve-the-api-version-first) above), then substitute it into the `--path` value:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
API_VERSION=$(sf org display --target-org <alias> --json | jq -r '.result.apiVersion')
|
|
276
|
+
|
|
277
|
+
sf org api rest --target-org <alias> \
|
|
278
|
+
--method POST \
|
|
279
|
+
--path "/services/data/v${API_VERSION}/ssot/data-governance/object-access-grants/actions/bulk-create" \
|
|
280
|
+
--body '{
|
|
281
|
+
"permissionSetName": "Data_Cloud_Analyst",
|
|
282
|
+
"dataSpaceName": "default",
|
|
283
|
+
"objectApiNames": ["Account__dlm", "Contact__dlm"]
|
|
284
|
+
}'
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**Step 3 — Verify:**
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
sf org api rest --target-org <alias> \
|
|
291
|
+
--path "/services/data/v${API_VERSION}/ssot/data-governance/object-access-grants?permissionSetName=Data_Cloud_Analyst&dataSpaceName=default"
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Rules and Constraints
|
|
297
|
+
|
|
298
|
+
| Rule | Reason |
|
|
299
|
+
|---|---|
|
|
300
|
+
| Use `<dataspaceScopes>` (plural) as parent, `<dataspaceScope>` (singular) as child | XML schema requirement; other names deploy-fail |
|
|
301
|
+
| `dataAccessLevel` values: `NONE`, `CONTROLLED_BY_PARENT`, `ALL` only | Other values (e.g. `OWNER`, `ViewAllRows`) are rejected |
|
|
302
|
+
| `objectAccessLevel` values: `BY_POLICY`, `ALL_IN_DATASPACE` only | Other values (e.g. `READ`, `EDIT`, `Read`) are rejected. `ALL_IN_DATASPACE` requires `dataAccessLevel=CONTROLLED_BY_PARENT` |
|
|
303
|
+
| Prefer `BY_POLICY` when data governance policies exist | Delegates row/column filtering to central policy — no per-object grants needed |
|
|
304
|
+
| One `<dataspaceScopes>` block per DataSpace | Repeat the block for multiple DataSpaces on the same permission set |
|
|
305
|
+
| Org must have Data Cloud provisioned to deploy `<dataspaceScopes>` | On non-Data-Cloud orgs, the element is ignored or rejected |
|
|
306
|
+
| Do not query `DataspaceScope` / `DataspaceScopeAccess` via SOQL | Not queryable; use MDAPI retrieve to inspect existing grants |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Gotchas
|
|
311
|
+
|
|
312
|
+
| Issue | Resolution |
|
|
313
|
+
|---|---|
|
|
314
|
+
| Deployment fails with error `-379999659` | Check enum values — `dataAccessLevel` must be `NONE`/`CONTROLLED_BY_PARENT`/`ALL`; `objectAccessLevel` must be `BY_POLICY`/`ALL_IN_DATASPACE` |
|
|
315
|
+
| Permission set deploys but users still can't query DataSpace data | Layer 2 not applied — objects need explicit grants if `objectAccessLevel != BY_POLICY` |
|
|
316
|
+
| Bulk-create returns `AlreadyExists` for some objects | Idempotent — safe to retry; response shows per-object status |
|
|
317
|
+
| Connect API returns 404 for object grants endpoint | Org lacks Data Cloud provisioning, or the resolved API version is below the minimum. The endpoint was introduced in `v67.0` — re-run `sf org display --json` to confirm the org's `apiVersion` field is `67.0` or later, and substitute that value into the `endpoint` path |
|
|
318
|
+
| Retrieved PermissionSet XML shows different element names than deployed | Metadata API sometimes echoes legacy names on retrieve — always author with current names |
|
|
@@ -3,6 +3,10 @@ name: platform-docs-get
|
|
|
3
3
|
description: "Official Salesforce documentation retrieval skill. Use when you need authoritative Salesforce docs from developer.salesforce.com, help.salesforce.com, architect.salesforce.com, admin.salesforce.com, or lightningdesignsystem.com, especially when pages are JS-heavy, shell-rendered, or hard to extract with naive fetching. Use to ground answers in official Salesforce sources instead of third-party blogs or summaries. TRIGGER when: user asks for official Salesforce documentation, Apex or API reference, LWC docs, Agentforce docs, setup or help articles, or any doc from a Salesforce-owned domain. DO NOT TRIGGER when: user is asking for a code change, deployment task, or anything not requiring documentation retrieval — use the appropriate sf-* skill instead."
|
|
4
4
|
metadata:
|
|
5
5
|
version: "1.1"
|
|
6
|
+
relatedSkills: ["platform-soql-query"]
|
|
7
|
+
cliTools:
|
|
8
|
+
- tool: ["python3"]
|
|
9
|
+
semver: ">=3.8.0"
|
|
6
10
|
---
|
|
7
11
|
|
|
8
12
|
# platform-docs-get
|
|
@@ -153,27 +153,53 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
|
|
153
153
|
http_status = response.status if response else None
|
|
154
154
|
page.wait_for_timeout(1500)
|
|
155
155
|
try:
|
|
156
|
-
page.
|
|
157
|
-
|
|
156
|
+
page.wait_for_load_state("networkidle", timeout=min(timeout_ms, 15000))
|
|
157
|
+
except PlaywrightTimeoutError:
|
|
158
|
+
pass
|
|
159
|
+
|
|
160
|
+
# Wait for content: check both light DOM and shadow DOM content
|
|
161
|
+
is_developer = "developer.salesforce.com" in host
|
|
162
|
+
wait_js = r"""
|
|
158
163
|
() => {
|
|
164
|
+
// Light DOM check (works for most official sites)
|
|
159
165
|
const el = document.querySelector('main, article, [role="main"]');
|
|
160
|
-
const
|
|
161
|
-
|
|
166
|
+
const lightText = (el?.innerText || el?.textContent || '').trim();
|
|
167
|
+
if (lightText.length > 200) return true;
|
|
168
|
+
|
|
169
|
+
// Shadow DOM check (needed for developer.salesforce.com)
|
|
170
|
+
function findDeepText(root, depth) {
|
|
171
|
+
if (depth > 5 || !root) return '';
|
|
172
|
+
let best = '';
|
|
173
|
+
const els = root.querySelectorAll ? root.querySelectorAll('*') : [];
|
|
174
|
+
for (const child of els) {
|
|
175
|
+
if (child.shadowRoot) {
|
|
176
|
+
// Check direct children of shadow root for text
|
|
177
|
+
for (const sc of child.shadowRoot.children) {
|
|
178
|
+
const t = (sc.innerText || '').trim();
|
|
179
|
+
if (t.length > best.length) best = t;
|
|
180
|
+
}
|
|
181
|
+
const deeper = findDeepText(child.shadowRoot, depth + 1);
|
|
182
|
+
if (deeper.length > best.length) best = deeper;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return best;
|
|
186
|
+
}
|
|
187
|
+
const shadowText = findDeepText(document, 0);
|
|
188
|
+
return shadowText.length > 200;
|
|
162
189
|
}
|
|
163
|
-
"""
|
|
164
|
-
timeout=min(timeout_ms, 15000),
|
|
165
|
-
)
|
|
166
|
-
except PlaywrightTimeoutError:
|
|
167
|
-
pass
|
|
190
|
+
"""
|
|
168
191
|
try:
|
|
169
|
-
page.
|
|
192
|
+
page.wait_for_function(
|
|
193
|
+
wait_js,
|
|
194
|
+
timeout=min(timeout_ms, 20000 if is_developer else 15000),
|
|
195
|
+
)
|
|
170
196
|
except PlaywrightTimeoutError:
|
|
171
197
|
pass
|
|
172
198
|
page.wait_for_timeout(500)
|
|
173
199
|
|
|
174
200
|
payload = page.evaluate(
|
|
175
201
|
r"""
|
|
176
|
-
() => {
|
|
202
|
+
(host) => {
|
|
177
203
|
function normalize(text) {
|
|
178
204
|
return String(text || '')
|
|
179
205
|
.replace(/\u00a0/g, ' ')
|
|
@@ -233,6 +259,14 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
|
|
233
259
|
return Array.from(urls);
|
|
234
260
|
}
|
|
235
261
|
|
|
262
|
+
// --- Noise removal: strip cookie/consent overlays before scoring ---
|
|
263
|
+
document.querySelectorAll(
|
|
264
|
+
'#onetrust-banner-sdk, #onetrust-consent-sdk, .onetrust-consent-sdk, ' +
|
|
265
|
+
'[class*="onetrust-pc"], #onetrust-pc-sdk, ' +
|
|
266
|
+
'[id*="cookie-consent"], [class*="cookie-banner"], ' +
|
|
267
|
+
'.evidon-consent-button, #evidon-banner'
|
|
268
|
+
).forEach(function(el) { el.remove(); });
|
|
269
|
+
|
|
236
270
|
const title = document.title || normalize(document.querySelector('title')?.innerText || 'Untitled');
|
|
237
271
|
const childLinks = new Set();
|
|
238
272
|
for (const root of allRoots()) {
|
|
@@ -258,12 +292,42 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
|
|
258
292
|
{ selector: 'main .content, article .content', strategy: 'nested-content', base: 140 },
|
|
259
293
|
];
|
|
260
294
|
|
|
295
|
+
// --- Host-aware selector overrides for developer.salesforce.com ---
|
|
296
|
+
if (host.includes('developer.salesforce.com')) {
|
|
297
|
+
selectorConfigs.unshift(
|
|
298
|
+
{ selector: 'doc-content', strategy: 'dev-doc-content', base: 295 },
|
|
299
|
+
{ selector: '#maincontent', strategy: 'dev-maincontent', base: 290 },
|
|
300
|
+
{ selector: '.doc-body', strategy: 'dev-doc-body', base: 285 },
|
|
301
|
+
{ selector: '#topic-content', strategy: 'dev-topic-content', base: 285 },
|
|
302
|
+
{ selector: '.topicContent', strategy: 'dev-topic', base: 280 },
|
|
303
|
+
{ selector: '[class*="docContent"]', strategy: 'dev-doc-content-class', base: 275 },
|
|
304
|
+
{ selector: 'doc-xml-content .content', strategy: 'dev-xml-content', base: 270 },
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Extract text from an element, including shadow root content
|
|
309
|
+
function extractText(el) {
|
|
310
|
+
let text = (el.innerText || el.textContent || '').trim();
|
|
311
|
+
// If element is a shadow host with no slotted light DOM text,
|
|
312
|
+
// gather text from its shadow root's children
|
|
313
|
+
if (text.length < 200 && el.shadowRoot) {
|
|
314
|
+
let shadowText = '';
|
|
315
|
+
for (const child of el.shadowRoot.children) {
|
|
316
|
+
shadowText += (child.innerText || child.textContent || '') + '\n';
|
|
317
|
+
}
|
|
318
|
+
if (shadowText.trim().length > text.length) {
|
|
319
|
+
text = shadowText.trim();
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return text;
|
|
323
|
+
}
|
|
324
|
+
|
|
261
325
|
const candidates = [];
|
|
262
326
|
for (const cfg of selectorConfigs) {
|
|
263
327
|
const nodes = deepQueryAll(cfg.selector);
|
|
264
328
|
for (const node of nodes) {
|
|
265
329
|
if (!isVisible(node)) continue;
|
|
266
|
-
const text = normalize(node
|
|
330
|
+
const text = normalize(extractText(node));
|
|
267
331
|
if (text.length < 200) continue;
|
|
268
332
|
let score = cfg.base + Math.min(text.length, 5000) / 30;
|
|
269
333
|
const lowered = text.toLowerCase();
|
|
@@ -306,7 +370,8 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
|
|
306
370
|
candidateCount: candidates.length,
|
|
307
371
|
};
|
|
308
372
|
}
|
|
309
|
-
"""
|
|
373
|
+
""",
|
|
374
|
+
host,
|
|
310
375
|
)
|
|
311
376
|
|
|
312
377
|
text = normalize_text(payload.get("text", ""))
|