mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 03:09:50 +08:00
Compare commits
4 Commits
696c0786d3
...
ab67cdb76d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab67cdb76d | ||
|
|
75727bbb28 | ||
|
|
a0b7442d8f | ||
|
|
ae560c9719 |
@ -1,3 +1,12 @@
|
||||
# [1.31.0](https://github.com/forcedotcom/sf-skills/compare/1.30.0...1.31.0) (2026-07-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Release 4 new skills + 6 skill updates (weekly batch 2026-07-16) @W-23461225@ ([ae560c9](https://github.com/forcedotcom/sf-skills/commit/ae560c9719cd051d58b72b82b7965fd899df241b))
|
||||
|
||||
|
||||
|
||||
# [1.30.0](https://github.com/forcedotcom/sf-skills/compare/1.29.0...1.30.0) (2026-07-10)
|
||||
|
||||
|
||||
|
||||
20
package-lock.json
generated
20
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@salesforce/afv-skills",
|
||||
"version": "1.13.0",
|
||||
"version": "1.31.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@salesforce/afv-skills",
|
||||
"version": "1.13.0",
|
||||
"version": "1.31.0",
|
||||
"license": "CC-BY-NC-4.0",
|
||||
"devDependencies": {
|
||||
"@salesforce/ui-bundle-template-app-react-sample-b2e": "^10.2.2",
|
||||
@ -1921,17 +1921,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
|
||||
"integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
"hasown": "^2.0.4",
|
||||
"mime-types": "^2.1.35"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
@ -2091,9 +2091,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@salesforce/afv-skills",
|
||||
"version": "1.30.0",
|
||||
"version": "1.31.0",
|
||||
"description": "Salesforce skills for Agentforce Vibes",
|
||||
"license": "CC-BY-NC-4.0",
|
||||
"files": [
|
||||
|
||||
@ -3,6 +3,19 @@ name: data360-code-extension-generate
|
||||
description: "Develop and deploy Data Cloud Code Extensions using SF CLI plugin. Use this skill when creating custom Python transformations for Data Cloud, deploying code extensions, or testing data transformations. Supports init, run, scan, and deploy operations."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
relatedSkills:
|
||||
- "data360-schema-get"
|
||||
cliTools:
|
||||
- tool: ["sf"]
|
||||
semver: ">=2.0.0"
|
||||
- tool: ["pip"]
|
||||
semver: ">=21.0.0"
|
||||
- tool: ["python3"]
|
||||
semver: ">=3.11.0"
|
||||
- tool: ["docker"]
|
||||
semver: ">=20.0.0"
|
||||
- tool: ["git"]
|
||||
semver: ">=2.0.0"
|
||||
---
|
||||
|
||||
# data360-code-extension-generate Skill
|
||||
@ -30,7 +43,7 @@ Before executing any code extension commands, verify prerequisites:
|
||||
```
|
||||
If not installed:
|
||||
```bash
|
||||
sf plugins install @salesforce/plugin-data-codeextension
|
||||
sf plugins install @salesforce/plugin-data-code-extension
|
||||
```
|
||||
|
||||
2. **Python 3.11**
|
||||
@ -254,7 +267,7 @@ sf data-code-extension script deploy --target-org <org_alias> --name <name> --pa
|
||||
|
||||
| Error | Solution |
|
||||
|-------|----------|
|
||||
| `command data-code-extension not found` | `sf plugins install @salesforce/plugin-data-codeextension` |
|
||||
| `command data-code-extension not found` | `sf plugins install @salesforce/plugin-data-code-extension` |
|
||||
| `datacustomcode CLI not found` | `pip install salesforce-data-customcode` |
|
||||
| `Python version mismatch` | Use pyenv: `pyenv install 3.11.0 && pyenv local 3.11.0` |
|
||||
| `Cannot connect to Docker daemon` | Start Docker Desktop |
|
||||
|
||||
184
skills/dx-pkg-post-install-configure/SKILL.md
Normal file
184
skills/dx-pkg-post-install-configure/SKILL.md
Normal file
@ -0,0 +1,184 @@
|
||||
---
|
||||
name: dx-pkg-post-install-configure
|
||||
description: "Use this skill to automate managed package post-install configuration. Package-agnostic — works with any managed package (LMA, FMA, work.com, Certinia, etc.). TRIGGER when: user installs a managed package and needs post-install configuration, mentions LMA/FMA/work.com post-install setup, asks to configure permission sets/FLS/page layouts for an installed package, says 'post-install', 'package setup', 'configure LMA', 'set up FMA', 'post-install steps'. DO NOT TRIGGER for: standalone permission set assignment (use dx-org-permission-set-assign), generating permission set metadata XML (use platform-permission-set-generate), package installation, or org switching."
|
||||
metadata:
|
||||
version: "2.2"
|
||||
minApiVersion: "67.0"
|
||||
cliTools:
|
||||
- tool: ["sf"]
|
||||
semver: ">=2.0.0"
|
||||
---
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
Use when automating post-install configuration for any Salesforce managed package. This skill reads the package's post-install documentation, discovers available execution methods, and automates the configuration steps — including permission sets, object/field permissions, page layouts, Visualforce page access, and tab settings.
|
||||
|
||||
## Input
|
||||
|
||||
- **Required:** Package name (e.g., `LMA`, `FMA`, `work.com`)
|
||||
- **Optional:** Path to post-install doc (PDF, markdown, URL)
|
||||
|
||||
If no doc is provided, ask the user to supply it.
|
||||
|
||||
## Workflow
|
||||
|
||||
Execute phases in order. Each phase must pass before proceeding.
|
||||
|
||||
---
|
||||
|
||||
### Phase 1: Discover Available Execution Methods
|
||||
|
||||
**Priority order:**
|
||||
1. Org-native platform MCP servers (highest — direct org access via Headless 360)
|
||||
2. Claude Code external MCP servers (sf-sobject-all, sf-sobject-all-sb, etc.)
|
||||
3. sf CLI fallback (always available if authenticated)
|
||||
|
||||
#### Step 1A: Resolve org API version
|
||||
|
||||
Discover the org's current API version dynamically — never hardcode a version number:
|
||||
|
||||
```bash
|
||||
sf org display --target-org <alias> --json
|
||||
```
|
||||
|
||||
From the JSON response, read `result.apiVersion` (e.g., `"67.0"`). Store this value and use it as `v<apiVersion>` in all subsequent REST paths. If the command fails, fall back to the `minApiVersion` declared in this skill's metadata (`67.0`).
|
||||
|
||||
#### Step 1B: Check for org-native platform MCP servers
|
||||
|
||||
Query the Tooling API for MCP server availability:
|
||||
|
||||
```bash
|
||||
sf api request rest "/services/data/v<apiVersion>/tooling/query?q=SELECT+Id,DeveloperName,MasterLabel+FROM+McpServerAccess" --target-org <alias>
|
||||
```
|
||||
|
||||
#### Step 1C: Determine execution method
|
||||
|
||||
Check which Claude Code MCP tools are available and authenticated.
|
||||
|
||||
**MCP tool prefixes by org type:**
|
||||
|
||||
| Org Type | Tool Prefix |
|
||||
|---|---|
|
||||
| Production | `mcp__sf-sobject-all__` |
|
||||
| Sandbox | `mcp__sf-sobject-all-sb__` |
|
||||
| Falcon Test (pc-rnd) | `mcp__sf-sobject-all-falcon__` |
|
||||
|
||||
If MCP needs auth, call the authenticate tool. If auth fails, fall back to sf CLI.
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Verify Authentication & Org Identity
|
||||
|
||||
1. Run a lightweight test query (`SELECT Id, Name, IsSandbox FROM Organization`)
|
||||
2. If MCP auth fails, automatically fall back to sf CLI
|
||||
3. Display org info and ask user to confirm before proceeding
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Verify Package Installation
|
||||
|
||||
1. Determine the package namespace (ask user if unknown)
|
||||
2. Check via Tooling API (`InstalledSubscriberPackage`) — do NOT use `PackageLicense`
|
||||
3. If package not found, stop and inform user
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Read and Parse Post-Install Document
|
||||
|
||||
Read the provided document and extract discrete configuration steps.
|
||||
|
||||
**Supported formats:** PDF, markdown, URL (via WebFetch), pasted text.
|
||||
|
||||
**Parsing approach:**
|
||||
1. Extract each numbered/bulleted step from the document
|
||||
2. Present the extracted steps to the user for validation before proceeding
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: Classify Steps & Interactive Plan Review
|
||||
|
||||
For each step extracted from the doc, classify as Automated or Manual.
|
||||
|
||||
#### Automation capabilities reference
|
||||
|
||||
**Via MCP (sobject-all) or sf CLI CRUD:**
|
||||
- Record CRUD on any standard or custom object (PermissionSet, ObjectPermissions,
|
||||
FieldPermissions, SetupEntityAccess, PermissionSetTabSetting, PermissionSetAssignment, etc.)
|
||||
|
||||
**Via Metadata API retrieve/deploy (sf CLI):**
|
||||
- Page layout modifications (add related lists, fields, sections)
|
||||
- Profile settings
|
||||
- Custom metadata type records
|
||||
|
||||
**Via sf CLI Tooling API:**
|
||||
- Tooling queries (InstalledSubscriberPackage, ApexPage, ApexClass, etc.)
|
||||
- Any REST-accessible Tooling operation
|
||||
|
||||
**Manual (no API path — requires Setup UI):**
|
||||
- System permissions not exposed via REST
|
||||
- Connected app OAuth configuration
|
||||
- Environment Hub linkage
|
||||
|
||||
#### Interactive approval
|
||||
|
||||
Present the classified plan and let the user choose:
|
||||
- **"Approve all"** — Execute all steps as planned
|
||||
- **"Let me choose"** — Select which steps to approve/skip
|
||||
- **"I have questions"** — Discuss specific steps before deciding
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Execute Approved Steps
|
||||
|
||||
For each approved step, use the resolved execution method.
|
||||
|
||||
#### Execution method reference
|
||||
|
||||
| Operation | Via MCP | Via sf CLI |
|
||||
|---|---|---|
|
||||
| SOQL query | `soqlQuery` tool | `sf data query --query "<SOQL>" --target-org <alias> --json` |
|
||||
| Create record | `createSobjectRecord` tool | `sf data create record --sobject <Object> --values "..." --target-org <alias> --json` |
|
||||
| Update record | `updateSobjectRecord` tool | `sf data update record --sobject <Object> --record-id <id> --values "..." --target-org <alias> --json` |
|
||||
| Describe object | `getObjectSchema` tool | `sf api request rest "/services/data/v<apiVersion>/sobjects/<Object>/describe" --target-org <alias>` |
|
||||
| Page layout | N/A | Metadata API retrieve/deploy |
|
||||
|
||||
#### Page layout modifications via Metadata API
|
||||
|
||||
Use `sf project retrieve start` → edit the layout XML → `sf project deploy start`.
|
||||
|
||||
#### Execution rules
|
||||
|
||||
- **Idempotency:** Before creating any record, query to check if it already exists. Skip if so.
|
||||
- **Report after each step:** Show success count, skipped items, and reasons.
|
||||
- **Automatic fallback:** If MCP fails mid-execution, retry via sf CLI.
|
||||
- **On failure:** Report error, ask user to retry/skip/stop.
|
||||
|
||||
---
|
||||
|
||||
### Phase 7: Guide Manual Steps (if any)
|
||||
|
||||
If any steps could not be automated, present each with Setup navigation instructions.
|
||||
Wait for user confirmation before proceeding to the next.
|
||||
|
||||
---
|
||||
|
||||
### Phase 8: Summary
|
||||
|
||||
Display final summary with step-by-step status, method used, and any skipped items.
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
- **Auth failure mid-execution:** Stop, ask user to re-auth, offer to resume
|
||||
- **Duplicate record errors:** Treat as "already configured", skip and continue
|
||||
- **Permission errors:** Report which permission is missing, suggest resolution
|
||||
- **Unknown step type:** Ask user to clarify, offer to mark as manual
|
||||
|
||||
## Notes
|
||||
|
||||
- **Priority: org-native MCP > Claude Code MCP > sf CLI > manual**
|
||||
- sf CLI is always a valid fallback for all CRUD and Tooling API operations
|
||||
- Page layout modifications are automated via Metadata API retrieve/deploy
|
||||
- Always verify org identity before making changes
|
||||
- All actions respect the authenticated user's permissions
|
||||
@ -3,7 +3,14 @@ name: experience-ui-bundle-app-coordinate
|
||||
description: "MUST activate when the user wants to build, create, or generate a React application, React app, web application, single-page application (SPA), or frontend application — even if no project files exist yet. MUST also activate when the project contains a uiBundles/*/src/ directory or sfdx-project.json and the prompt says create, build, construct, or generate a new app, site, or page from scratch — even if the prompt also describes visual styling. MUST also activate when the task spans more than one ui-bundle skill. Use this skill when building a complete app end-to-end. Do NOT use for Lightning Experience apps with custom objects (use platform-lightning-app-coordinate). Do NOT use for single-concern edits to an existing page (use experience-ui-bundle-frontend-generate)."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
relatedSkills: experience-ui-bundle-metadata-generate, experience-ui-bundle-features-generate, experience-ui-bundle-salesforce-data-access, experience-ui-bundle-frontend-generate, experience-ui-bundle-agentforce-client-generate, experience-ui-bundle-file-upload-generate, experience-ui-bundle-deploy, experience-ui-bundle-site-generate, experience-ui-bundle-custom-app-generate
|
||||
relatedSkills: experience-ui-bundle-project-generate, experience-ui-bundle-metadata-generate, experience-ui-bundle-features-generate, experience-ui-bundle-salesforce-data-access, experience-ui-bundle-frontend-generate, experience-ui-bundle-agentforce-client-generate, experience-ui-bundle-file-upload-generate, experience-ui-bundle-deploy, experience-ui-bundle-site-generate, experience-ui-bundle-custom-app-generate
|
||||
cliTools:
|
||||
- tool: ["sf"]
|
||||
semver: ">=2.0.0"
|
||||
- tool: ["npm"]
|
||||
semver: ">=7.0.0"
|
||||
- tool: ["npx"]
|
||||
semver: ">=7.0.0"
|
||||
---
|
||||
|
||||
# Building a UI Bundle App
|
||||
@ -12,6 +19,8 @@ metadata:
|
||||
|
||||
Build a complete, deployable Salesforce React UI bundle application from a natural language description by orchestrating specialized UI bundle skills in correct dependency order. Each skill **MUST** be explicitly loaded before executing its phase.
|
||||
|
||||
**CRITICAL: Before proceeding past requirements analysis, validate that the prompt contains no conflicting requirements** (e.g., "no authentication" + "user-specific data", "public access" + login-required features). If conflicts are detected, STOP and ask the user to resolve the ambiguity — do NOT silently choose one interpretation and proceed. See STEP 1 action #8 for the full conflict checklist.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
**Use when:**
|
||||
@ -38,23 +47,75 @@ Build a complete, deployable Salesforce React UI bundle application from a natur
|
||||
|
||||
---
|
||||
|
||||
## Prompt Classification Keywords
|
||||
|
||||
This skill makes two decisions directly from the raw prompt text. Use these tables as the single
|
||||
source for both — do not restate or re-derive the lists elsewhere in this file.
|
||||
|
||||
**1. Phase 2 (Features) is required if the prompt mentions ANY of:**
|
||||
|
||||
| Category | Keywords | Notes |
|
||||
|----------|----------|-------|
|
||||
| Data features | search, filter, sort, pagination, table, grid, list | |
|
||||
| Navigation | navigation, nav, menu, routing | |
|
||||
| Authentication | authentication, auth, login, logout, user session, user login | |
|
||||
| Integrations | upload, file | |
|
||||
| UI | shadcn, components, forms, buttons, cards | |
|
||||
| Chat (Phase 5 only) | chat | Phase 5 only — unless combined with a Phase 2 keyword (e.g., "chat with authentication"), then Phase 2 runs first for auth prerequisites |
|
||||
|
||||
Negating one category (e.g. "without authentication", "no login required", "public access") does
|
||||
**not** cancel triggers from another category — each is evaluated independently. Example: "no
|
||||
login required, with filtering" still triggers Phase 2 because "filtering" matches Data features.
|
||||
Skip Phase 2 only when the prompt matches none of the keywords above.
|
||||
|
||||
**2. Hosting target — extract from prompt keywords:**
|
||||
|
||||
| Hosting target | Keywords |
|
||||
|-----------------|----------|
|
||||
| Experience Site | "Experience Site", "Community", "external users", "public users", "guest users" |
|
||||
| Custom Application | "Custom Application", "internal users", "Lightning app" |
|
||||
|
||||
If the prompt matches neither list, or matches both, ask the user to clarify before proceeding — do not guess.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph & Build Order
|
||||
|
||||
### Phase 0: Template Offer & Bootstrap (Prerequisites)
|
||||
|
||||
```text
|
||||
Offer prebuilt starter template (experience-ui-bundle-project-generate)
|
||||
v
|
||||
If template chosen: scaffold via sf template generate project -- Phase 1 skipped
|
||||
v
|
||||
If declined: run scripts/check-sfdx-project.sh
|
||||
v
|
||||
If missing: create sfdx-project.json
|
||||
v
|
||||
Verify project directory initialized
|
||||
```
|
||||
|
||||
Offers a faster, less error-prone starting point before building from scratch. If no template is used, ensures an SFDX project exists before attempting to generate a UI bundle — without this, `sf template generate ui-bundle` will fail with a hard error. Always check first — do not assume the project structure exists.
|
||||
|
||||
**Action:** Load `experience-ui-bundle-project-generate` and offer the two starter templates. If declined, run `scripts/check-sfdx-project.sh` and report any errors it returns. If the script reports an error, create the missing `sfdx-project.json` before proceeding.
|
||||
|
||||
### Phase 1: Scaffolding (Foundation)
|
||||
|
||||
```text
|
||||
UI Bundle scaffold (sf template generate ui-bundle)
|
||||
Determine hosting target (Experience Site / Custom Application)
|
||||
v
|
||||
UI Bundle scaffold (sf template generate ui-bundle --template reactbasic)
|
||||
v
|
||||
Install dependencies (npm install)
|
||||
v
|
||||
Bundle metadata (uibundle-meta.xml, ui-bundle.json)
|
||||
Bundle metadata (uibundle-meta.xml with <target>, ui-bundle.json)
|
||||
v
|
||||
CSP Trusted Sites (if external domains needed)
|
||||
```
|
||||
|
||||
Creates the UI bundle directory structure, meta XML, and optional routing/headers config. All subsequent phases require the scaffold to exist.
|
||||
Creates the UI bundle directory structure, meta XML (including hosting target), and optional routing/headers config. **CRITICAL**: Hosting target must be determined FIRST because the metadata skill requires `<target>` in Phase 1. All subsequent phases require the scaffold to exist.
|
||||
|
||||
### Phase 2: Features (Optional)
|
||||
### Phase 2: Features (Required if prompt mentions feature keywords — see "Prompt Classification Keywords" above)
|
||||
|
||||
```text
|
||||
Search project code (src/) for existing implementations
|
||||
@ -68,7 +129,9 @@ Resolve conflicts (two-pass: --on-conflict error, then --conflict-resolution)
|
||||
Integrate __example__ files into target files, then delete them
|
||||
```
|
||||
|
||||
Installs pre-built, tested feature packages. Skip if the app requires no pre-built features. Always check for an existing feature before building from scratch. Features provide the foundation that UI components build on top of.
|
||||
Installs pre-built, tested feature packages. See "Prompt Classification Keywords" above for the full trigger keyword list and negative-phrasing handling — these features provide the foundation that UI components build on top of.
|
||||
|
||||
Only skip this phase if the app is truly a minimal "hello world" with no interactive features (no trigger keywords present at all).
|
||||
|
||||
### Phase 3: Data Access (Backend Wiring)
|
||||
|
||||
@ -123,16 +186,16 @@ Post-deploy configuration (permissions, profiles, named credentials, connected a
|
||||
Import data (if data plan exists)
|
||||
v
|
||||
Fetch GraphQL schema and run codegen
|
||||
*(Re-fetches schema from the deployed org -- required because the remote schema may differ from the local one used in Phase 3)*
|
||||
*(Re-fetches schema from the deployed org -- required because the remote schema may differ from the local one used in Phase 3. Guard against an empty or stale result -- Salesforce Edge caching can briefly serve the pre-deploy schema; re-fetch/retry before trusting it as empty and before running codegen)*
|
||||
v
|
||||
Final UI bundle build (rebuilds with the deployed schema)
|
||||
```
|
||||
|
||||
Follows the canonical 7-step deployment sequence. Must deploy metadata before fetching schema. Must assign permissions before schema fetch.
|
||||
|
||||
### Phase 7: Hosting Target
|
||||
### Phase 7: Hosting Target Infrastructure
|
||||
|
||||
Choose **one** of the following based on the app's audience:
|
||||
Deploy the hosting target infrastructure determined in Phase 1. Choose **one** of the following based on the app's audience:
|
||||
|
||||
#### Phase 7a: Experience Site (External)
|
||||
|
||||
@ -144,7 +207,7 @@ Generate site metadata (Network, CustomSite, DigitalExperience)
|
||||
Deploy site infrastructure
|
||||
```
|
||||
|
||||
Creates the Digital Experience site that hosts the UI bundle. Use when the user wants a public-facing or authenticated site URL for external users.
|
||||
Creates the Digital Experience site that hosts the UI bundle. Use when the user wants a public-facing or authenticated site URL for external users. **Note**: The `<target>ExperienceSite</target>` was already set in meta XML during Phase 1.
|
||||
|
||||
#### Phase 7b: Custom Application (Internal)
|
||||
|
||||
@ -153,17 +216,30 @@ Resolve app properties (appName, appNamespace, appLabel)
|
||||
v
|
||||
Generate CustomApplication metadata (applications/*.app-meta.xml)
|
||||
v
|
||||
Add <target>CustomApplication</target> to .uibundle-meta.xml
|
||||
v
|
||||
Deploy custom application
|
||||
```
|
||||
|
||||
Creates a Custom Application entry in the Lightning App Launcher. Use when the app is for internal users accessing it within Lightning Experience.
|
||||
Creates a Custom Application entry in the Lightning App Launcher. Use when the app is for internal users accessing it within Lightning Experience. **Note**: The `<target>CustomApplication</target>` was already set in meta XML during Phase 1.
|
||||
|
||||
---
|
||||
|
||||
## Execution Workflow
|
||||
|
||||
### STEP 0: Offer a Prebuilt Template (before scaffolding from scratch)
|
||||
|
||||
**Before** analyzing requirements or scaffolding, check whether a prebuilt starter template fits —
|
||||
it is faster and less error-prone than building from scratch.
|
||||
|
||||
- **Load skill: Invoke `experience-ui-bundle-project-generate`.** It offers two minimal React starter
|
||||
projects (internal / employee-facing and external / customer-facing) and, if the user picks one,
|
||||
generates it into the project directory with `sf template generate project`.
|
||||
- **If the user chooses a template:** the scaffolding phase (Phase 1) is effectively done. Skip
|
||||
straight to populating/customizing the project — continue at the phase that matches what they
|
||||
want to change (typically Phase 4 UI, or Phase 3 data access), then Phase 6 deployment.
|
||||
- **If the user declines** (wants to start from scratch, or none fit): proceed normally to STEP 1.
|
||||
|
||||
Do not skip this step silently — always offer the choice at the start of a from-scratch app build.
|
||||
|
||||
### STEP 1: Requirements Analysis & Planning
|
||||
|
||||
**Actions:**
|
||||
@ -173,8 +249,12 @@ Creates a Custom Application entry in the Lightning App Launcher. Use when the a
|
||||
3. Extract pages and navigation structure
|
||||
4. Identify data entities and Salesforce objects needed
|
||||
5. Detect feature requirements (authentication, search, file upload, chat)
|
||||
6. Determine if an Experience Site is needed
|
||||
6. Determine hosting target (Experience Site OR Custom Application) — see "Prompt Classification Keywords" above; if ambiguous, ask user to clarify before proceeding
|
||||
7. Identify external domains for CSP registration
|
||||
8. **Check for conflicting requirements** — STOP and ask user if any of the following conflicts are detected:
|
||||
- "No authentication" OR "public/guest access" AND "user-specific data" OR "show current user's data" OR "My [Entity]" view
|
||||
- "External users" AND "Custom Application" (Custom Apps are internal-only)
|
||||
- "Public access" AND features requiring login (file upload, user profile, personalization)
|
||||
|
||||
> **The plan MUST contain an explicit grounding step before any query authoring.** Do not list
|
||||
> guessed object/field names as settled facts and defer verification to codegen. The data-access
|
||||
@ -183,6 +263,18 @@ Creates a Custom Application entry in the Lightning App Launcher. Use when the a
|
||||
> authors queries first and codegens later is the failure mode that produces guessed fields and
|
||||
> hand-stubbed types — do not emit it.
|
||||
|
||||
**Before proceeding to Output (Build Plan), validate:**
|
||||
|
||||
- [ ] **No conflicting requirements detected** — if conflicts exist (see action #8 above), STOP and report:
|
||||
```text
|
||||
ERROR: Conflicting requirements detected:
|
||||
- [describe the specific conflict, e.g., "The prompt requires 'no authentication' while also requiring a 'My Cases' view scoped to the current user's identity"]
|
||||
|
||||
RESOLUTION NEEDED: Please clarify:
|
||||
- [specific question, e.g., "Should the app require login (removing the 'no authentication' requirement), or should all data be public (removing the user-scoped view)?"]
|
||||
```
|
||||
**Do NOT proceed to build plan generation or any phase execution until this conflict is resolved.**
|
||||
|
||||
**Output: Build Plan**
|
||||
|
||||
```text
|
||||
@ -190,6 +282,7 @@ UI Bundle App Build Plan: [App Name]
|
||||
|
||||
SCAFFOLDING:
|
||||
- App name: [PascalCase name]
|
||||
- Hosting target: [Experience Site / Custom Application] **REQUIRED**
|
||||
- Routing: [SPA rewrites, trailing slash config]
|
||||
- External domains: [domains needing CSP registration]
|
||||
|
||||
@ -214,10 +307,10 @@ INTEGRATIONS (if applicable):
|
||||
|
||||
DEPLOYMENT:
|
||||
- Target org: [org alias if known]
|
||||
- Hosting target: [Experience Site / Custom Application / none]
|
||||
|
||||
SKILL LOAD ORDER:
|
||||
1. experience-ui-bundle-metadata-generate
|
||||
0. experience-ui-bundle-project-generate (offer a prebuilt template first; if chosen, Phase 1 scaffolding is skipped; if declined, run the Bootstrap check for an existing SFDX project -- no skill load required)
|
||||
1. experience-ui-bundle-metadata-generate (determines hosting target FIRST)
|
||||
2. experience-ui-bundle-features-generate (if features needed)
|
||||
3. experience-ui-bundle-salesforce-data-access (if data access needed)
|
||||
4. experience-ui-bundle-frontend-generate
|
||||
@ -239,33 +332,45 @@ Execute each phase sequentially. Complete all steps within a phase before moving
|
||||
| **3. Verify** | Run lint and build from the UI bundle directory | Catch errors before moving to the next phase |
|
||||
| **4. Checkpoint** | Confirm phase completion before proceeding | Ensures dependencies are satisfied for the next phase |
|
||||
|
||||
**Do NOT skip step 1 (loading the skill).** Even if you remember the skill's content, skills evolve. Always load the current version.
|
||||
**CRITICAL: Do NOT skip step 1 (loading the skill).** Even if you remember the skill's content, skills evolve. Always load the current version. **Skipping or reordering phases produces broken, non-deployable apps.** Phase dependencies are strict and cannot be violated.
|
||||
|
||||
---
|
||||
|
||||
**Phase 1 -- Scaffolding**
|
||||
**Phase 0 -- Template Offer & Bootstrap** (always, before scaffolding)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-project-generate`
|
||||
- 2. Execute: Offer the two starter templates; if the user picks one, generate it into the project dir with `sf template generate project`
|
||||
- 3. Decision: If a template was used, the project is scaffolded (including a valid SFDX project structure) — skip Phase 1 and continue at the phase matching the user's customization (usually Phase 4 UI). If declined, proceed to the Bootstrap check below.
|
||||
- 4. Bootstrap (no skill load required, only if no template was used): Run `scripts/check-sfdx-project.sh` and report any errors it returns. If the script reports an error, create the SFDX project structure (`sf project generate` or manually create sfdx-project.json).
|
||||
- 5. Checkpoint: SFDX project ready -- proceed to Phase 1
|
||||
|
||||
**Phase 1 -- Scaffolding** (skip if a template was generated in Phase 0)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-metadata-generate`
|
||||
- 2. Execute: Run `sf template generate ui-bundle`, install dependencies (`npm install`), configure meta XML, ui-bundle.json, and CSP trusted sites
|
||||
- 3. Verify: Confirm directory structure and metadata files exist
|
||||
- 2. Execute: Determine hosting target (Experience Site / Custom Application) FIRST. Run `sf template generate ui-bundle --template reactbasic`, install dependencies (`npm install`), configure meta XML (with `<target>`), ui-bundle.json, and CSP trusted sites.
|
||||
- 3. Verify: Confirm directory structure and metadata files exist with hosting target specified
|
||||
- 4. Checkpoint: UI bundle scaffold is ready -- proceed to Phase 2
|
||||
|
||||
**Phase 2 -- Features** (skip if no pre-built features needed)
|
||||
**Phase 2 -- Features** (Required if prompt mentions feature keywords — see "Prompt Classification Keywords" above)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-features-generate`
|
||||
- 2. Execute: Install dependencies, search and install features, integrate example files
|
||||
- 3. Verify: Run `npm run build` to confirm features integrate cleanly
|
||||
- 4. Checkpoint: Features installed -- proceed to Phase 3
|
||||
- **Trigger conditions**: See "Prompt Classification Keywords" above for the full keyword list (by category) and how negative phrasing is handled -- negating one category does not cancel triggers from another.
|
||||
|
||||
**Phase 3 -- Data Access** (skip if no Salesforce data needed)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-salesforce-data-access`
|
||||
- 2. Execute: **Ground entities/fields against the org first** (per the skill), then author queries/mutations **from the verified names**, run codegen, wire into components. Never guess fields or hand-edit generated types.
|
||||
- 3. Verify: Run `npx eslint` on files with GraphQL queries
|
||||
- 2. Execute: Check preconditions (authenticated org, npm dependencies installed). Fetch schema (`npm run graphql:schema`), guard against empty schema. Look up entities, generate queries/mutations, wire into components.
|
||||
- 3. Verify: Run `npx eslint` on files with GraphQL queries. Verify schema is non-empty.
|
||||
- 4. Checkpoint: Data layer ready -- proceed to Phase 4
|
||||
|
||||
**Phase 4 -- UI**
|
||||
**Phase 4 -- UI** (ALWAYS REQUIRED - CANNOT BE SKIPPED)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-frontend-generate`
|
||||
- 2. Execute: Build layout, pages, components, navigation. Replace all boilerplate.
|
||||
- If Phase 2 was skipped: Generate UI components from scratch without feature templates
|
||||
- If Phase 3 was skipped: Use mock data or static content for display
|
||||
- Phase 4 MUST execute even if prior phases were skipped
|
||||
- 3. Verify: Run lint and build -- 0 errors required
|
||||
- 4. Checkpoint: UI complete -- proceed to Phase 5
|
||||
- 4. Checkpoint: UI complete -- proceed to Phase 5 if integrations needed, or stop here if building only
|
||||
|
||||
⚠️ **CRITICAL**: Phase 4 generates the actual React user interface. Skipping it results in a UI bundle with only metadata and no user-facing pages or components. ALWAYS execute Phase 4 for every UI bundle build.
|
||||
|
||||
**Phase 5 -- Integrations** (skip if not requested)
|
||||
- 1. Load skill(s): Invoke `experience-ui-bundle-agentforce-client-generate` (5a) and/or `experience-ui-bundle-file-upload-generate` (5b). If both are needed, they are independent and can be executed in parallel.
|
||||
@ -275,21 +380,76 @@ Execute each phase sequentially. Complete all steps within a phase before moving
|
||||
|
||||
**Phase 6 -- Deployment**
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-deploy`
|
||||
- 2. Execute: Follow the 7-step deployment sequence (auth, build, deploy, permissions, data, schema, final build)
|
||||
- 3. Verify: Confirm deployment succeeds and app is accessible
|
||||
- 4. Checkpoint: App deployed -- proceed to Phase 7 if needed
|
||||
- 2. Execute: Check preconditions (authenticated org, successful build). Follow the 7-step deployment sequence (auth, build, deploy, permissions, data, schema, final build). When available, prefer using a project-level `scripts/org-setup.mjs` automation script over re-deriving the deployment flow each run. Guard the post-deploy schema re-fetch against an empty/stale result (Edge caching) before running final codegen -- retry rather than trusting an empty schema.
|
||||
- 3. Verify: Confirm deployment succeeds, app is accessible, and the re-fetched schema is non-empty
|
||||
- 4. Checkpoint: App deployed -- proceed to Phase 7 if hosting target deployment is needed
|
||||
|
||||
**Phase 7a -- Experience Site** (skip if not requested or if Custom Application chosen)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-site-generate`
|
||||
- 2. Execute: Resolve properties, generate site metadata, deploy
|
||||
- 3. Verify: Confirm site URL is accessible
|
||||
- 3. Verify: Confirm site URL is accessible (hosting target already verified by `scripts/check-hosting-target.sh` in trigger evaluation)
|
||||
- 4. Checkpoint: Site live -- build complete
|
||||
- **Trigger conditions**: Run `scripts/check-hosting-target.sh` and check output for "ExperienceSite" OR prompt matches an Experience Site keyword in "Prompt Classification Keywords" above
|
||||
- **Note**: The `<target>ExperienceSite</target>` was already set in meta XML during Phase 1 -- do not add it again
|
||||
|
||||
**Phase 7b -- Custom Application** (skip if not requested or if Experience Site chosen)
|
||||
- 1. Load skill: Invoke `experience-ui-bundle-custom-app-generate`
|
||||
- 2. Execute: Resolve app properties, generate CustomApplication metadata, add CustomApplication target to meta XML
|
||||
- 3. Verify: Confirm app appears in App Launcher
|
||||
- 2. Execute: Resolve app properties, generate CustomApplication metadata
|
||||
- 3. Verify: Confirm app appears in App Launcher (hosting target already verified by `scripts/check-hosting-target.sh` in trigger evaluation)
|
||||
- 4. Checkpoint: App registered -- build complete
|
||||
- **Trigger conditions**: Run `scripts/check-hosting-target.sh` and check output for "CustomApplication" OR prompt matches a Custom Application keyword in "Prompt Classification Keywords" above
|
||||
- **Note**: The `<target>CustomApplication</target>` was already set in meta XML during Phase 1 -- do not add it again
|
||||
|
||||
### STEP 2.5: Phase Completion Validation
|
||||
|
||||
Before proceeding to STEP 3 (Final Summary), validate that all required phases were executed:
|
||||
|
||||
**Critical Validation (MUST pass):**
|
||||
- [ ] **Phase 0 (Template Offer & Bootstrap) executed**: If no template was used, run `scripts/check-sfdx-project.sh`. If it returns non-zero, STOP and report error:
|
||||
```text
|
||||
ERROR: No SFDX project detected. Phase 0 (Bootstrap) is REQUIRED before scaffolding.
|
||||
Run `sf project generate` (or create sfdx-project.json) before invoking
|
||||
`sf template generate ui-bundle`.
|
||||
```
|
||||
If a template was used in Phase 0, this check is satisfied by the template's own scaffolding — skip re-running the script.
|
||||
|
||||
- [ ] **Phase 1 hosting target resolved**: Run `scripts/check-hosting-target.sh`. If it returns non-zero, STOP and report error:
|
||||
```text
|
||||
ERROR: Hosting target was not resolved in Phase 1. A UI bundle without a <target> in its
|
||||
meta XML will not be visible in the org. Determine Experience Site vs Custom Application
|
||||
(see "Prompt Classification Keywords" above; ask the user if ambiguous) before proceeding
|
||||
past Phase 1 -- do not defer this to Phase 7 and do not record "none"/"skipped".
|
||||
```
|
||||
|
||||
- [ ] **Phase 4 (Frontend) executed**: If Phase 4 was NOT executed, STOP and report error:
|
||||
```text
|
||||
ERROR: Phase 4 (UI/Frontend generation) is REQUIRED for all UI bundle apps.
|
||||
Cannot complete build without generating the React user interface.
|
||||
Please review the phase execution logic and ensure Phase 4 is always executed.
|
||||
```
|
||||
|
||||
- [ ] **Phase 7 hosting infrastructure deployed**: If neither Phase 7a nor Phase 7b was executed, STOP and report error:
|
||||
```text
|
||||
ERROR: Hosting target infrastructure (Phase 7a Experience Site or Phase 7b Custom
|
||||
Application) was not deployed. The app was built but is not reachable by any user.
|
||||
Exactly one of Phase 7a/7b must run -- it is never optional or "skipped".
|
||||
```
|
||||
|
||||
**Warning Validation (log warnings, but can proceed):**
|
||||
- [ ] **Phase 2 execution**: If Phase 2 was skipped but the prompt matches any keyword in "Prompt Classification Keywords" above (data features, navigation, authentication, integrations, or UI category):
|
||||
```text
|
||||
WARNING: Phase 2 (Features) was skipped but prompt contains feature keywords.
|
||||
This may indicate a trigger detection failure. Generated UI may be missing
|
||||
required feature functionality. Consider re-running with Phase 2 included.
|
||||
```
|
||||
|
||||
- [ ] **Phase 3 execution**: If Phase 3 was skipped but prompt mentions Salesforce objects, "GraphQL", "data", or "query":
|
||||
```text
|
||||
WARNING: Phase 3 (Data Access) was skipped but prompt mentions Salesforce data.
|
||||
Generated UI may not connect to backend correctly. Verify data access is working.
|
||||
```
|
||||
|
||||
**Proceed to STEP 3 only if all Critical Validation checks pass (Phase 0, hosting target, Phase 4, Phase 7).**
|
||||
|
||||
### STEP 3: Final Summary
|
||||
|
||||
@ -299,13 +459,14 @@ After all phases complete, present a build summary:
|
||||
UI Bundle App Build Complete: [App Name]
|
||||
|
||||
PHASES COMPLETED:
|
||||
[x] Phase 1: Scaffolding -- [app name] UI bundle created
|
||||
[x] Phase 0: Template Offer & Bootstrap -- [template used: <name> / declined; SFDX project verified/created]
|
||||
[x] Phase 1: Scaffolding -- [app name] UI bundle created with hosting target [Experience Site / Custom Application]
|
||||
[x] Phase 2: Features -- [list of features installed, or "skipped"]
|
||||
[x] Phase 3: Data Access -- [list of entities wired up]
|
||||
[x] Phase 4: UI -- [count] pages, [count] components
|
||||
[x] Phase 5: Integrations -- [list or "none"]
|
||||
[x] Phase 6: Deployment -- deployed to [org]
|
||||
[x] Phase 7: Hosting Target -- [Experience Site URL / Custom Application name / "skipped"]
|
||||
[x] Phase 7: Hosting Target -- [Experience Site URL / Custom Application name] **(never "skipped" -- Phase 1 requires a target, so exactly one of 7a/7b always runs)**
|
||||
|
||||
FILES GENERATED:
|
||||
[list key files and their paths]
|
||||
@ -321,8 +482,10 @@ NEXT STEPS:
|
||||
Before presenting the build as complete, verify:
|
||||
|
||||
- [ ] **Scaffold exists**: UI bundle directory with valid meta XML and ui-bundle.json
|
||||
- [ ] **Hosting target resolved and deployed**: Meta XML contains `<target>ExperienceSite</target>` or `<target>CustomApplication</target>` (never left unset), and the matching Phase 7a or 7b infrastructure was generated and deployed -- not skipped
|
||||
- [ ] **Dependencies installed**: `node_modules/` exists and `package.json` has expected packages
|
||||
- [ ] **Build passes**: `npm run build` produces `dist/` with no errors
|
||||
- [ ] **Dist content exists**: `dist/` contains index.html, JS/CSS bundles, and assets (not just an empty directory)
|
||||
- [ ] **Lint passes**: `npx eslint src/` reports 0 errors
|
||||
- [ ] **No boilerplate**: All placeholder text, default titles, and template content has been replaced
|
||||
- [ ] **Navigation works**: `appLayout.tsx` has real nav items matching created pages
|
||||
@ -337,6 +500,7 @@ Before presenting the build as complete, verify:
|
||||
|
||||
- App purpose is too vague to determine pages or data needs
|
||||
- User wants features that conflict (e.g., "no authentication" + "show user-specific data")
|
||||
- Hosting target cannot be determined (ask: "Is this for internal users (Custom Application) or external users (Experience Site)?")
|
||||
- Target org is unknown and deployment is requested
|
||||
|
||||
### Category 2: Log Warning, Continue
|
||||
|
||||
26
skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh
Executable file
26
skills/experience-ui-bundle-app-coordinate/scripts/check-hosting-target.sh
Executable file
@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check which hosting target is configured in .uibundle-meta.xml files
|
||||
|
||||
# Find all .uibundle-meta.xml files
|
||||
META_FILES=$(find . -name "*.uibundle-meta.xml" -type f 2>/dev/null)
|
||||
|
||||
if [ -z "$META_FILES" ]; then
|
||||
echo "ERROR: No .uibundle-meta.xml files found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check for ExperienceSite target
|
||||
if echo "$META_FILES" | xargs grep -q '<target>ExperienceSite</target>' 2>/dev/null; then
|
||||
echo "ExperienceSite"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Check for CustomApplication target
|
||||
if echo "$META_FILES" | xargs grep -q '<target>CustomApplication</target>' 2>/dev/null; then
|
||||
echo "CustomApplication"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# No valid target found
|
||||
echo "ERROR: No valid hosting target found in .uibundle-meta.xml"
|
||||
exit 1
|
||||
15
skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh
Executable file
15
skills/experience-ui-bundle-app-coordinate/scripts/check-sfdx-project.sh
Executable file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
# Check if sfdx-project.json exists and is valid JSON
|
||||
|
||||
if [ ! -f sfdx-project.json ]; then
|
||||
echo 'ERROR: sfdx-project.json not found'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate JSON format using python (more portable than node)
|
||||
if ! python3 -c "import json; json.load(open('sfdx-project.json'))" 2>/dev/null; then
|
||||
echo 'ERROR: sfdx-project.json is not valid JSON'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo 'sfdx-project.json is valid'
|
||||
@ -1,8 +1,18 @@
|
||||
---
|
||||
name: experience-ui-bundle-metadata-generate
|
||||
description: "MUST activate when the project contains a uiBundles/*/src/ directory and scaffolding a new UI bundle or app, or when editing ui-bundle.json, .uibundle-meta.xml, or CSP trusted site files. Use this skill when scaffolding with sf template generate ui-bundle, configuring ui-bundle.json (routing, headers, outputDir), or registering CSP Trusted Sites. Activate when the task involves files matching *.uibundle-meta.xml, ui-bundle.json, or cspTrustedSites/*.cspTrustedSite-meta.xml."
|
||||
description: "Use this skill when adding a front-end React UI bundle to an existing project or configuring UI bundle metadata and config files. TRIGGER when: adding or scaffolding a new UI bundle inside a project that already exists; running sf template generate ui-bundle; editing ui-bundle.json routing, headers, or output directory; working with *.uibundle-meta.xml files; or registering CSP Trusted Sites, resolving blocked images or fonts or external API calls, or editing cspTrustedSites/*.cspTrustedSite-meta.xml files. DO NOT TRIGGER when: creating a brand-new Salesforce project from scratch, where the whole SFDX starter project (UI bundle plus Experience Site metadata and toolchain) is generated together (use experience-ui-bundle-project-generate)."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
minApiVersion: "51.0"
|
||||
relatedSkills:
|
||||
- "experience-ui-bundle-frontend-generate"
|
||||
- "experience-ui-bundle-custom-app-generate"
|
||||
- "experience-ui-bundle-site-generate"
|
||||
cliTools:
|
||||
- tool: ["sf"]
|
||||
semver: ">=2.0.0"
|
||||
- tool: ["jq"]
|
||||
semver: ">=1.6"
|
||||
---
|
||||
|
||||
# UI Bundle Metadata
|
||||
@ -11,9 +21,9 @@ metadata:
|
||||
|
||||
Use `sf template generate ui-bundle` to create new apps — not create-react-app, Vite, or other generic scaffolds.
|
||||
|
||||
**Always pass `--template reactbasic`** to scaffold a React-based bundle.
|
||||
|
||||
**UI bundle name (`-n`):** Alphanumerical only — no spaces, hyphens, underscores, or special characters.
|
||||
- **Always pass `--template reactbasic`** to scaffold a React-based bundle.
|
||||
- **UI bundle name (`-n`):** Alphanumerical only — no spaces, hyphens, underscores, or special characters.
|
||||
- Pass `--output-dir` to use a different location for template generation.
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
@ -21,10 +31,11 @@ sf template generate ui-bundle -n CoffeeBoutique --template reactbasic
|
||||
```
|
||||
|
||||
After generation:
|
||||
1. Replace all default boilerplate — "React App", "Vite + React", default `<title>`, placeholder text
|
||||
2. Populate the home page with real content (landing section, banners, hero, navigation)
|
||||
3. Update navigation and placeholders (see the `experience-ui-bundle-frontend-generate` skill)
|
||||
4. **Configure a hosting target** — a UI bundle without a `<target>` in its meta XML will not be visible in the org. Use `experience-ui-bundle-custom-app-generate` for internal (App Launcher) apps or `experience-ui-bundle-site-generate` for external (Experience Site) apps.
|
||||
1. **Verify API version** — run `bash <skill_dir>/scripts/check-api-version.sh` from the project root to ensure `sourceApiVersion` in `sfdx-project.json` is 67.0 or higher. The script will automatically update it if needed.
|
||||
2. Replace all default boilerplate — "React App", "Vite + React", default `<title>`, placeholder text
|
||||
3. Populate the home page with real content (landing section, banners, hero, navigation)
|
||||
4. Update navigation and placeholders (see the `experience-ui-bundle-frontend-generate` skill)
|
||||
5. **Configure a hosting target** — a UI bundle without a `<target>` in its meta XML will not be visible in the org. Use `experience-ui-bundle-custom-app-generate` for internal (App Launcher) apps or `experience-ui-bundle-site-generate` for external (Experience Site) apps.
|
||||
|
||||
Always install dependencies before running any scripts in the UI bundle directory.
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
## File location
|
||||
|
||||
```
|
||||
```text
|
||||
force-app/main/default/cspTrustedSites/{Name}.cspTrustedSite-meta.xml
|
||||
```
|
||||
|
||||
@ -256,7 +256,7 @@ Some services split resources across multiple subdomains. Create one CSP Trusted
|
||||
|
||||
If the browser console shows a CSP error like:
|
||||
|
||||
```
|
||||
```text
|
||||
Refused to load the image 'https://example.com/image.png' because it violates
|
||||
the following Content Security Policy directive: "img-src 'self' ..."
|
||||
```
|
||||
|
||||
30
skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh
Executable file
30
skills/experience-ui-bundle-metadata-generate/scripts/check-api-version.sh
Executable file
@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Checks that sourceApiVersion in sfdx-project.json is 67.0 or higher.
|
||||
# If lower, updates it to "67.0".
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_FILE="sfdx-project.json"
|
||||
|
||||
if [[ ! -f "$PROJECT_FILE" ]]; then
|
||||
echo "ERROR: $PROJECT_FILE not found in current directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract sourceApiVersion and check if it's below 67.0
|
||||
version=$(jq -r '.sourceApiVersion // "0"' "$PROJECT_FILE")
|
||||
is_below=$(jq --arg v "$version" -n '($v | split(".") | .[0] | tonumber) < 67')
|
||||
|
||||
if [[ "$is_below" == "true" ]]; then
|
||||
echo "WARNING: sourceApiVersion is $version (< 67.0)"
|
||||
echo "Updating $PROJECT_FILE to set sourceApiVersion to \"67.0\""
|
||||
|
||||
# Update the file
|
||||
jq '.sourceApiVersion = "67.0"' "$PROJECT_FILE" > "$PROJECT_FILE.tmp"
|
||||
mv "$PROJECT_FILE.tmp" "$PROJECT_FILE"
|
||||
|
||||
echo "OK: Updated sourceApiVersion to 67.0"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "OK: sourceApiVersion is $version (>= 67.0)"
|
||||
@ -4,6 +4,7 @@ description: "Use this skill when users need to create, generate, or validate Sa
|
||||
metadata:
|
||||
version: "1.0"
|
||||
minApiVersion: "51.0"
|
||||
relatedSkills: ["platform-value-set-generate", "platform-validation-rule-generate"]
|
||||
---
|
||||
|
||||
# Salesforce Custom Field Generator and Validator
|
||||
@ -20,7 +21,7 @@ Every generated field must include these tags:
|
||||
|
||||
| Attribute | Requirement | Notes |
|
||||
|-----------|-------------|-------|
|
||||
| `<fullName>` | Required | **Field** name only: derive from `<label>` — capitalize each word, replace spaces with `_`, append `__c`. Must start with a letter. E.g., label `Total Contract Value` → `Total_Contract_Value__c`. ⚠️ This rule is for the FIELD name. **Picklist VALUE `<fullName>` is different — keep it exactly as the user spelled it, spaces and all, no `__c`** (e.g. `Closed Won`, NOT `Closed_Won`). See [`references/advanced-picklists.md`](references/advanced-picklists.md) (ref §3). |
|
||||
| `<fullName>` | Required | **Field** name only: derive from `<label>` — capitalize each word, replace spaces with `_`, append `__c`. Must start with a letter. E.g., label `Total Contract Value` → `Total_Contract_Value__c`. This rule is for the FIELD name. **Picklist VALUE `<fullName>` is different — keep it exactly as the user spelled it, spaces and all, no `__c`** (e.g. `Closed Won`, NOT `Closed_Won`). See [`references/advanced-picklists.md`](references/advanced-picklists.md) (ref §3). |
|
||||
| `<label>` | Required | The UI name (Title Case) |
|
||||
| `<description>` | Always include | Explain the business reason *why* this field exists. |
|
||||
| `<inlineHelpText>` | Always include | Actionable end-user guidance that adds value beyond the label (e.g., "Enter the value in USD including tax", not "The amount"). |
|
||||
@ -49,7 +50,7 @@ To ensure deployment success, follow these mathematical constraints:
|
||||
|
||||
### The "Fixed 255" Rule
|
||||
|
||||
**TextArea: always include `<length>255</length>` exactly** — this literal value is required by the Metadata API and **omitting it fails deployment**, even though the UI exposes no length control. Unlike every other type where `length` is a value you calculate, TextArea's is a fixed constant.
|
||||
**TextArea: do NOT include `<length>`** — the Metadata API fixes the length at 255 implicitly and **rejects an explicit `<length>` value** with "Can not specify 'length' for a CustomField of type TextArea". Omit `<length>` entirely; the field only needs `<fullName>`, `<label>`, and `<type>TextArea</type>`.
|
||||
|
||||
### Visible Lines
|
||||
|
||||
@ -76,7 +77,7 @@ Mandatory for Long/Rich text and Multi-select picklists to control UI height.
|
||||
| Phone | `Phone` | Standardizes phone number formatting |
|
||||
| Picklist | `Picklist` | `valueSet` containing EITHER `valueSetDefinition` (inline) OR `valueSetName` (reference); `restricted` (see "Picklist `restricted` default" below; advanced cases in §3.4) |
|
||||
| Text | `Text` | `length` (Max 255) |
|
||||
| Text Area | `TextArea` | `<length>255</length>` |
|
||||
| Text Area | `TextArea` | None — do NOT include `<length>`; the API fixes length at 255 implicitly |
|
||||
| Text (Long) | `LongTextArea` | `length`, `visibleLines` (default 3) |
|
||||
| Text (Rich) | `Html` | `length`, `visibleLines` (default 25) |
|
||||
| Time | `Time` | Stores time only (no date) |
|
||||
@ -112,7 +113,7 @@ Mandatory for Long/Rich text and Multi-select picklists to control UI height.
|
||||
|
||||
### 3.4 Advanced Picklists
|
||||
|
||||
The inline `<valueSetDefinition>` above is the simple case. Full rules and worked ✅/❌
|
||||
The inline `<valueSetDefinition>` above is the simple case. Full rules and worked correct/incorrect
|
||||
examples for everything below are in
|
||||
[`references/advanced-picklists.md`](references/advanced-picklists.md) — load it for any
|
||||
non-trivial picklist. Section numbers in parentheses below (e.g. "ref §1") point to that
|
||||
@ -166,12 +167,12 @@ Master-Detail fields have **strict attribute restrictions** that differ from Loo
|
||||
|
||||
| Attribute | Master-Detail | Lookup |
|
||||
|-----------|---------------|--------|
|
||||
| `<required>` | ❌ FORBIDDEN | ✅ Optional |
|
||||
| `<deleteConstraint>` | ❌ FORBIDDEN (always CASCADE) | ✅ Required (`SetNull`, `Restrict`, `Cascade`) |
|
||||
| `<lookupFilter>` | ❌ FORBIDDEN | ✅ Optional |
|
||||
| `<relationshipOrder>` | ✅ Required (0 or 1) | ❌ Not applicable |
|
||||
| `<reparentableMasterDetail>` | ✅ Optional | ❌ Not applicable |
|
||||
| `<writeRequiresMasterRead>` | ✅ Optional | ❌ Not applicable |
|
||||
| `<required>` | FORBIDDEN | Optional |
|
||||
| `<deleteConstraint>` | FORBIDDEN (always CASCADE) | Required (`SetNull`, `Restrict`, `Cascade`) |
|
||||
| `<lookupFilter>` | FORBIDDEN | Optional |
|
||||
| `<relationshipOrder>` | Required (0 or 1) | Not applicable |
|
||||
| `<reparentableMasterDetail>` | Optional | Not applicable |
|
||||
| `<writeRequiresMasterRead>` | Optional | Not applicable |
|
||||
|
||||
### INCORRECT — Master-Detail with forbidden attributes:
|
||||
|
||||
@ -451,7 +452,7 @@ Before generating CustomField XML, verify:
|
||||
- [ ] Is `precision ≤ 18`?
|
||||
|
||||
### Text Area Checks
|
||||
- [ ] For TextArea: Is `<length>255</length>` explicitly included?
|
||||
- [ ] For TextArea: Is `<length>` **omitted**? (The API rejects an explicit `<length>` value on TextArea fields.)
|
||||
- [ ] For LongTextArea/Html: Is `<visibleLines>` set?
|
||||
|
||||
### Relationship Limit Checks
|
||||
|
||||
@ -21,7 +21,7 @@ A picklist field can either define its values **inline** or **reference an exist
|
||||
value set** (a Global Value Set or a Standard Value Set). The shared set is defined
|
||||
once and reused across many fields.
|
||||
|
||||
### ⭐ HARD RULE: `<valueSet>` is EITHER a reference OR inline — never both
|
||||
### HARD RULE: `<valueSet>` is EITHER a reference OR inline — never both
|
||||
|
||||
A `<valueSet>` element must contain **exactly one** of:
|
||||
|
||||
@ -30,7 +30,7 @@ A `<valueSet>` element must contain **exactly one** of:
|
||||
|
||||
Including both in the same `<valueSet>` is a deployment error.
|
||||
|
||||
#### ❌ INCORRECT — both reference and inline definition:
|
||||
#### INCORRECT — both reference and inline definition:
|
||||
|
||||
```xml
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
@ -54,7 +54,7 @@ Including both in the same `<valueSet>` is a deployment error.
|
||||
|
||||
**Error:** `Value set must reference a value set name or define a value set, but not both.`
|
||||
|
||||
#### ✅ CORRECT — reference a Global Value Set:
|
||||
#### CORRECT — reference a Global Value Set:
|
||||
|
||||
```xml
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
@ -88,7 +88,7 @@ the name you put inside it differs.
|
||||
> local project" warning) is expected org-storage display — keep local metadata on the bare name.
|
||||
> Never append `__c` to a value-set name either.
|
||||
|
||||
#### ✅ CORRECT — reference a Standard Value Set (bare name, no suffix):
|
||||
#### CORRECT — reference a Standard Value Set (bare name, no suffix):
|
||||
|
||||
```xml
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
@ -126,7 +126,7 @@ A **dependent** picklist filters its available values based on the selected valu
|
||||
**dependent** field via a `<controllingField>` element plus one `<valueSettings>` block
|
||||
per (controlling value → dependent value) pair.
|
||||
|
||||
### ⭐ Use the MODERN API 38.0+ form ONLY
|
||||
### Use the MODERN API 38.0+ form ONLY
|
||||
|
||||
| Form | Elements | Status |
|
||||
|------|----------|--------|
|
||||
@ -136,7 +136,7 @@ per (controlling value → dependent value) pair.
|
||||
Never emit the legacy `<picklist>`, `<picklistValues>`, or `<controllingFieldValues>`
|
||||
tags. They are not valid against the modern Metadata API and will fail deployment.
|
||||
|
||||
### ⭐ HARD RULE: both the controlling and dependent field must be `<restricted>true</restricted>`
|
||||
### HARD RULE: both the controlling and dependent field must be `<restricted>true</restricted>`
|
||||
|
||||
A field dependency requires a fixed, admin-defined value set on **both** ends. **Always emit
|
||||
`<restricted>true</restricted>` inside the `<valueSet>` of the controlling field AND the
|
||||
@ -186,7 +186,7 @@ Inside the dependent field's `<valueSet>`, in this order:
|
||||
> EMEA→UK,Germany) you emit **one block for each (controllingValue, dependentValue) pair** — four
|
||||
> pairs = four `<valueSettings>` blocks. And remember: both fields carry `<restricted>true</restricted>`.
|
||||
|
||||
### ✅ CORRECT — State dependent on Country (USA → California, Texas)
|
||||
### CORRECT — State dependent on Country (USA → California, Texas)
|
||||
|
||||
**Controlling field — `Country__c` (a plain restricted picklist):**
|
||||
|
||||
@ -258,7 +258,7 @@ Inside the dependent field's `<valueSet>`, in this order:
|
||||
> `<valueSettings>` block with that country's `<controllingFieldValue>` and
|
||||
> `<valueName>California</valueName>`.
|
||||
|
||||
### ❌ INCORRECT — deprecated legacy form:
|
||||
### INCORRECT — deprecated legacy form:
|
||||
|
||||
```xml
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
@ -282,7 +282,7 @@ legacy dependency elements are not valid in the modern `<valueSet>` structure.
|
||||
|
||||
## 3. Enhanced Value Attributes
|
||||
|
||||
### ⭐ Value-name fidelity — do NOT underscore picklist value names
|
||||
### Value-name fidelity — do NOT underscore picklist value names
|
||||
|
||||
A **picklist value's `<fullName>` is NOT a field API name** and must NOT be transformed.
|
||||
Use the value text **exactly as the user spelled it**, spaces and all. A value the user
|
||||
@ -315,7 +315,7 @@ Inline `<value>` entries (CustomValue subfields) support more than `<fullName>`,
|
||||
|
||||
These are independent of `<default>` and `<label>` and may be combined freely.
|
||||
|
||||
### ✅ CORRECT — Status picklist with colors and an inactive value
|
||||
### CORRECT — Status picklist with colors and an inactive value
|
||||
|
||||
```xml
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
@ -367,7 +367,7 @@ The Metadata API rejects malformed picklist values at deploy time. Two common fa
|
||||
Two `<value>` entries with the same `<fullName>` inside one `<valueSetDefinition>` are
|
||||
rejected.
|
||||
|
||||
#### ❌ INCORRECT — duplicate value:
|
||||
#### INCORRECT — duplicate value:
|
||||
|
||||
```xml
|
||||
<valueSetDefinition>
|
||||
@ -398,7 +398,7 @@ The stricter "only alphanumerics and single underscores, no leading digit, no do
|
||||
or trailing underscore" rule applies to the *field* `<fullName>` (the `__c`-suffixed
|
||||
API name), not to picklist value fullNames.
|
||||
|
||||
#### ❌ INCORRECT — invalid value API name:
|
||||
#### INCORRECT — invalid value API name:
|
||||
|
||||
```xml
|
||||
<value>
|
||||
@ -410,7 +410,7 @@ API name), not to picklist value fullNames.
|
||||
|
||||
**Error:** `Invalid fullName: must begin with a letter and use only alphanumeric characters and underscores`
|
||||
|
||||
#### ✅ CORRECT:
|
||||
#### CORRECT:
|
||||
|
||||
```xml
|
||||
<value>
|
||||
@ -454,7 +454,7 @@ include a `<fullName>`** element — the record type's developer name (e.g. `<fu
|
||||
matching the filename `Internal.recordType-meta.xml`. It's bare (no object prefix); the object
|
||||
comes from the `objects/<Object>/` folder path.
|
||||
|
||||
### ⭐ STEP 1 — Decide if this object needs a BusinessProcess (do this BEFORE writing files)
|
||||
### STEP 1 — Decide if this object needs a BusinessProcess (do this BEFORE writing files)
|
||||
|
||||
A record type on a **BusinessProcess-gated object — Opportunity, Lead, Case, or Solution —
|
||||
will NOT deploy without a `<businessProcess>` reference**, even when it only filters a *custom*
|
||||
@ -472,7 +472,7 @@ developer name instead of generating a new one (confirm via the grounding MCP's
|
||||
if available; otherwise generate a minimal one). For everything else, **do not invent a
|
||||
BusinessProcess** — adding one to a custom-object record type is wrong.
|
||||
|
||||
#### ✅ CORRECT — Opportunity "Enterprise" record type, two deployable files
|
||||
#### CORRECT — Opportunity "Enterprise" record type, two deployable files
|
||||
|
||||
```xml
|
||||
<!-- File 1: objects/Opportunity/businessProcesses/Enterprise_Sales_Process.businessProcess-meta.xml -->
|
||||
@ -519,7 +519,7 @@ BusinessProcess** — adding one to a custom-object record type is wrong.
|
||||
</RecordType>
|
||||
```
|
||||
|
||||
#### ❌ INCORRECT — BusinessProcess file emitted but NOT referenced (most common failure)
|
||||
#### INCORRECT — BusinessProcess file emitted but NOT referenced (most common failure)
|
||||
|
||||
```xml
|
||||
<!-- File 1 (businessProcesses/Enterprise_Sales_Process...) was generated correctly, BUT -->
|
||||
@ -571,7 +571,7 @@ RecordType (filters the field's values; references the BusinessProcess)
|
||||
- For Opportunity/Lead/Case/Solution, the `<businessProcess>` must exist (same package or
|
||||
already in the org) before the RecordType.
|
||||
|
||||
### ⭐ UI-sync gotcha — values may not auto-display after API deploy
|
||||
### UI-sync gotcha — values may not auto-display after API deploy
|
||||
|
||||
When `<picklistValues>` are loaded via the Metadata API, the values are correctly associated
|
||||
under the hood, **but they may not automatically appear as "Selected Values" in the Record
|
||||
|
||||
259
skills/platform-custom-report-type-generate/SKILL.md
Normal file
259
skills/platform-custom-report-type-generate/SKILL.md
Normal file
@ -0,0 +1,259 @@
|
||||
---
|
||||
name: platform-custom-report-type-generate
|
||||
description: "Use this skill when users need to create, generate, or validate Salesforce Custom Report Type metadata. Trigger when users mention custom report types, report types, CRTs, reporting frameworks, cross-object reports, report builder data sources, or ask to expose fields for reporting across related objects. Also use when users mention primary and related objects for reports, inner vs outer joins in reports, report type categories, or encounter deployment errors for .reportType-meta.xml files. Do NOT trigger for: running, editing, or filtering existing reports; creating report folders, dashboards, or list views; or general reporting questions that don't involve authoring a .reportType-meta.xml file."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
minApiVersion: "51.0"
|
||||
---
|
||||
|
||||
## Specification
|
||||
|
||||
# Salesforce Custom Report Type Metadata Knowledge
|
||||
|
||||
## Overview
|
||||
Custom Report Types (CRTs) define the **data framework** for Salesforce reports. They specify a primary object, up to 3 related objects, the relationship (join) between them, and which fields are available in the report builder.
|
||||
|
||||
## Purpose
|
||||
- Enable reporting across custom objects and custom relationships not covered by standard report types
|
||||
- Curate a focused set of fields for report builders (including fields reached via lookup)
|
||||
- Control inner/outer join behavior to include or exclude primary records without related records
|
||||
|
||||
## Configuration
|
||||
|
||||
**File extension:** `.reportType-meta.xml`. The file basename is the report type's developer name (e.g. `AccountsWithProjects.reportType-meta.xml`). Each CRT is a single file, not nested under an object folder.
|
||||
|
||||
### Key Elements
|
||||
|
||||
Top-level `<ReportType>` children:
|
||||
|
||||
| Element | Required | Notes |
|
||||
|---------|----------|-------|
|
||||
| `<fullName>` | Yes | API identifier; must match the file name. Letters, numbers, underscores; must begin with a letter; no spaces; no trailing underscore; no consecutive underscores |
|
||||
| `<label>` | Yes | Human-friendly name shown in the report type picker |
|
||||
| `<description>` | Recommended | State the business "why" — who uses this and what they learn |
|
||||
| `<baseObject>` | Yes | API name of the primary object (e.g. `Account`, `Project__c`). Cannot be changed after initial creation. All objects, including custom and external, are supported (external objects from API 38.0+) |
|
||||
| `<category>` | Recommended | Report builder category — see `references/category-values.md` |
|
||||
| `<deployed>` | Yes | `true` to expose to users; `false` while building/iterating |
|
||||
| `<join>` | Conditional | Adds a related object and its join behavior. Nest further `<join>` blocks for deeper relationships |
|
||||
| `<sections>` | Recommended | Groups of columns available to the report type. Though not strictly required, a report without columns isn't useful |
|
||||
|
||||
`<sections>` (group of columns) sub-elements:
|
||||
|
||||
| Element | Required | Notes |
|
||||
|---------|----------|-------|
|
||||
| `<masterLabel>` | Yes | Section heading shown in the report builder |
|
||||
| `<columns>` | Conditional | One per field exposed in the section |
|
||||
|
||||
`<columns>` (single field) sub-elements:
|
||||
|
||||
| Element | Required | Notes |
|
||||
|---------|----------|-------|
|
||||
| `<field>` | Yes | Field API name (or dotted lookup-traversal path) |
|
||||
| `<table>` | Yes | The object the field belongs to — base object name or dotted relationship path |
|
||||
| `<checkedByDefault>` | Yes | `true` if the column is selected by default in the report builder |
|
||||
| `<displayNameOverride>` | No | Custom column label shown in the report builder, overriding the field's default label |
|
||||
|
||||
## Critical Rules (Read First)
|
||||
|
||||
### Rule 1: If `<fullName>` Is Present, It Must Match the File Name
|
||||
In source format, `fullName` is inherited from `Metadata` and derived from the file name, so the `<fullName>` element is technically optional. The repo convention is to include it. **If you include `<fullName>`, its value must equal the file name (everything before `.reportType-meta.xml`) exactly — same characters, same casing, same underscores.**
|
||||
|
||||
**Wrong** — file name and `<fullName>` differ:
|
||||
- File: `account_projects.reportType-meta.xml`
|
||||
- `<fullName>AccountProjects</fullName>`
|
||||
(Mismatch: file uses `account_projects`, fullName uses `AccountProjects`)
|
||||
|
||||
**Right** — file name and `<fullName>` are identical:
|
||||
- File: `AccountProjects.reportType-meta.xml`
|
||||
- `<fullName>AccountProjects</fullName>`
|
||||
|
||||
### Rule 2: Join Semantics — `outerJoin` Controls Inclusion
|
||||
|
||||
Each `<join>` block has an `<outerJoin>` element that determines which primary records appear in the report:
|
||||
|
||||
| `<outerJoin>` value | Behavior | Report Builder Label |
|
||||
|---------------------|----------|----------------------|
|
||||
| `false` | Inner join — only primary records that HAVE at least one related record | "Each 'A' record must have at least one related 'B' record" |
|
||||
| `true` | Outer join — all primary records, with or without related records | "'A' records may or may not have related 'B' records" |
|
||||
|
||||
**Default when unspecified:** Use `true` (outer join) when the user wants to see all primary records regardless of children. Use `false` when the report only makes sense if children exist.
|
||||
|
||||
### Rule 3: Each Object Needs Its Own `<sections>` Block
|
||||
|
||||
Every object in the CRT (primary + each joined object) must have a corresponding `<sections>` block that lists the fields exposed for reporting. Without a section for an object, none of its fields appear in the report builder.
|
||||
|
||||
- `<masterLabel>` on each section is the section heading in the report builder
|
||||
- `<columns>` entries list the fields — each with a `<field>` (API name) and `<table>` (object API name)
|
||||
- For fields reached via lookup, use the relationship path in `<field>` (e.g. `Owner.Name` with `<table>` set to the owning object)
|
||||
|
||||
### Rule 4: Field API Names, Not Labels
|
||||
|
||||
Use exact API names for fields: standard fields use their defined names (`Name`, `CreatedDate`, `OwnerId`), custom fields use `Field__c`. Custom objects must include `__c`.
|
||||
|
||||
**Wrong:**
|
||||
- `<field>Account Name</field>`
|
||||
|
||||
**Right:**
|
||||
- `<field>Name</field>` with `<table>Account</table>`
|
||||
|
||||
### Rule 5: Relationship Path for Joined Objects
|
||||
|
||||
When adding a `<join>`, the `<relationship>` element must use the **child relationship name** as defined on the lookup/master-detail field pointing from the child object to the parent. For custom relationships, this typically ends in `__r`.
|
||||
|
||||
**Wrong:**
|
||||
- `<relationship>Project</relationship>` (for a custom child relationship)
|
||||
|
||||
**Right:**
|
||||
- `<relationship>Projects__r</relationship>` (child relationship name)
|
||||
- `<relationship>Contacts</relationship>` (standard, non-custom child relationship)
|
||||
|
||||
### Rule 6: Maximum 4 Objects Total in a Join Chain
|
||||
|
||||
A single CRT can join a maximum of **four objects total** (the base object + up to 3 additional objects via nested `<join>` blocks).
|
||||
|
||||
### Rule 7: No Inner Join After an Outer Join
|
||||
|
||||
Once the join chain contains an outer join (`<outerJoin>true</outerJoin>`), every subsequent nested join must also be an outer join. An inner join that follows an outer join earlier in the sequence is not allowed.
|
||||
|
||||
**Wrong:**
|
||||
```xml
|
||||
<join>
|
||||
<outerJoin>true</outerJoin> <!-- outer join first -->
|
||||
<relationship>Contacts</relationship>
|
||||
<join>
|
||||
<outerJoin>false</outerJoin> <!-- WRONG: inner join after outer -->
|
||||
<relationship>Assets</relationship>
|
||||
</join>
|
||||
</join>
|
||||
```
|
||||
|
||||
**Right:**
|
||||
```xml
|
||||
<join>
|
||||
<outerJoin>true</outerJoin>
|
||||
<relationship>Contacts</relationship>
|
||||
<join>
|
||||
<outerJoin>true</outerJoin> <!-- outer stays outer -->
|
||||
<relationship>Assets</relationship>
|
||||
</join>
|
||||
</join>
|
||||
```
|
||||
|
||||
### Rule 8: `<table>` for Joined Objects Uses Dotted Path
|
||||
|
||||
In `<sections>`, the `<table>` element identifies which object in the join chain each column belongs to. For the base object, use the object name directly (e.g. `Account`). For joined objects, use the **dotted relationship path** from the base object.
|
||||
|
||||
| Object in chain | `<table>` value |
|
||||
|-----------------|-----------------|
|
||||
| Base (Account) | `Account` |
|
||||
| First join (Account → Contacts) | `Account.Contacts` |
|
||||
| Nested join (Account → Contacts → Assets) | `Account.Contacts.Assets` |
|
||||
|
||||
### Rule 9: Field Paths Can Traverse Lookups
|
||||
|
||||
`<field>` values may reference fields reached via lookup relationships using dot notation — for example `Owner.Email` (owner User's email) or `ReportsTo.CreatedBy.Contact.Owner.MobilePhone`. The `<table>` must still be the object that owns the starting field.
|
||||
|
||||
### Rule 10: Historical Trending Fields Use `_hst` Suffix
|
||||
|
||||
For a field with `trackTrending=true`, the API name in `<field>` and `<table>` uses the `_hst` suffix:
|
||||
|
||||
```xml
|
||||
<columns>
|
||||
<checkedByDefault>false</checkedByDefault>
|
||||
<field>Field2__c_hst</field>
|
||||
<table>CustomTrendedObject__c.CustomTrendedObject__c_hst</table>
|
||||
</columns>
|
||||
```
|
||||
|
||||
### Rule 11: Primary Object Cannot Be Changed After Deployment
|
||||
|
||||
Once deployed, the `<baseObject>` of a CRT is locked. To change the primary object, create a new CRT and retire the old one.
|
||||
|
||||
### Rule 12: `autogenerated` Is Reserved for Historical Trending
|
||||
|
||||
The `<autogenerated>` element (API 29.0+) marks CRTs that Salesforce created automatically when historical trending was enabled on an object. Do not set this manually on hand-authored CRTs.
|
||||
|
||||
## Generation Workflow
|
||||
|
||||
### Step 1: Gather Requirements
|
||||
- Primary object API name (e.g. `Account`, `Project__c`)
|
||||
- Related objects and the relationship between each (which has the lookup/master-detail to which)
|
||||
- For each relationship: inner join (children required) or outer join (children optional)?
|
||||
- Which fields to expose per object — aim for task-relevant, not the full field list
|
||||
- Audience and category — where should this appear in the report builder picker?
|
||||
- Whether this ships as `deployed=true` now or stays `deployed=false` during iteration
|
||||
|
||||
### Step 2: Examine Existing Examples
|
||||
- Look in the project for in-project CRT patterns
|
||||
- If existing report types have been retrieved from an org, compare against those structures
|
||||
|
||||
### Step 3: Write the Specification
|
||||
Document before authoring:
|
||||
- `fullName` and `label`
|
||||
- `baseObject`
|
||||
- Category and `deployed` state
|
||||
- Join chain: for each related object — relationship name, outer vs inner join
|
||||
- Section layout: one section per object, ordered list of fields
|
||||
- Acceptance criteria: which records should appear when the report runs, which fields are available in the builder
|
||||
|
||||
### Step 4: Author the Metadata File
|
||||
|
||||
Start from the closest example in `examples/` and adapt it to the user's scenario:
|
||||
|
||||
- Primary object only (no joins) → `examples/AccountsWithIndustry.reportType-meta.xml`
|
||||
- Outer join (primary records included even without children) → `examples/AccountsWithProjects.reportType-meta.xml`
|
||||
- Nested inner join (every level requires children) → `examples/AccountProjectsWithTasks.reportType-meta.xml`
|
||||
|
||||
Name the file `<DeveloperName>.reportType-meta.xml`.
|
||||
|
||||
### Step 5: Validate
|
||||
- Well-formed XML with correct namespace (`xmlns="http://soap.sforce.com/2006/04/metadata"`)
|
||||
- File name (without `.reportType-meta.xml`) matches `<fullName>` when `<fullName>` is included
|
||||
- `<baseObject>` is a valid API name and the object is deployed
|
||||
- Every `<relationship>` uses the correct child relationship name (`__r` suffix for custom)
|
||||
- Each object referenced in `<sections>` is part of the CRT (primary or joined)
|
||||
- All `<field>` references exist on the parent `<table>` and use API names (not labels)
|
||||
- `<category>` is a valid Salesforce category value
|
||||
- `<deployed>` is `true` if users need to access the CRT immediately
|
||||
|
||||
## Reference File Index
|
||||
|
||||
| File | When to read |
|
||||
|------|--------------|
|
||||
| `examples/AccountsWithIndustry.reportType-meta.xml` | Step 2 / Step 4 — primary-object-only template |
|
||||
| `examples/AccountsWithProjects.reportType-meta.xml` | Step 2 / Step 4 — outer-join template (primary included even without children) |
|
||||
| `examples/AccountProjectsWithTasks.reportType-meta.xml` | Step 2 / Step 4 — nested inner-join template (every level requires children) |
|
||||
| `references/category-values.md` | Step 3 — to choose a valid `<category>` value from the `ReportTypeCategory` enum |
|
||||
| `references/errors-and-troubleshooting.md` | When fields don't appear in the report builder or join requirements conflict |
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
### Universal Checks
|
||||
- [ ] File extension is `.reportType-meta.xml`
|
||||
- [ ] File basename satisfies the developer-name rules (begins with a letter, only letters/numbers/underscores, no spaces, no trailing underscore, no consecutive underscores)
|
||||
- [ ] If `<fullName>` is included, it matches the file basename exactly (same characters, casing, and underscores)
|
||||
- [ ] `<label>` is human-readable and under 40 characters
|
||||
- [ ] `<description>` explains the business purpose
|
||||
- [ ] `<baseObject>` uses a valid API name and that object is deployed
|
||||
- [ ] `<category>` is a valid `ReportTypeCategory` enum value
|
||||
- [ ] `<deployed>` is set appropriately (`true` for user access, `false` for in-progress iteration)
|
||||
- [ ] `<autogenerated>` is NOT set manually (reserved for historical-trending CRTs)
|
||||
|
||||
### Join Checks
|
||||
- [ ] Each `<join>` uses the correct child **relationship name** (not the lookup field API name)
|
||||
- [ ] Custom relationships use `__r` suffix
|
||||
- [ ] `<outerJoin>` is set intentionally: `true` = optional children, `false` = required children
|
||||
- [ ] No inner join (`<outerJoin>false</outerJoin>`) appears after an outer join earlier in the sequence
|
||||
- [ ] Total object count (base + joins, including nested) is 4 or fewer
|
||||
|
||||
### Section Checks
|
||||
- [ ] Every object in the CRT has a corresponding `<sections>` block
|
||||
- [ ] `<masterLabel>` on each section is descriptive
|
||||
- [ ] Every `<columns>` has both `<field>` (API name) and `<table>` (object API name or dotted path)
|
||||
- [ ] `<checkedByDefault>` is set for each column
|
||||
- [ ] `<table>` for base object is the object API name (e.g. `Account`)
|
||||
- [ ] `<table>` for joined objects uses the dotted relationship path (e.g. `Account.Projects__r`, `Account.Projects__r.Tasks__r`)
|
||||
- [ ] Field references use API names (not labels); custom fields use `__c`
|
||||
- [ ] Lookup traversal fields use dot notation (e.g. `Owner.Email`) with `<table>` set to the object owning the starting field
|
||||
- [ ] Historical trending fields use `_hst` suffix in both `<field>` and `<table>` when applicable
|
||||
- [ ] No duplicate fields within a section
|
||||
@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ReportType xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AccountProjectsWithTasks</fullName>
|
||||
<baseObject>Account</baseObject>
|
||||
<category>other</category>
|
||||
<deployed>true</deployed>
|
||||
<label>Accounts with Projects with Tasks</label>
|
||||
<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>
|
||||
<join>
|
||||
<outerJoin>false</outerJoin>
|
||||
<relationship>Projects__r</relationship>
|
||||
<join>
|
||||
<outerJoin>false</outerJoin>
|
||||
<relationship>Tasks__r</relationship>
|
||||
</join>
|
||||
</join>
|
||||
<sections>
|
||||
<masterLabel>Account Fields</masterLabel>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Name</field>
|
||||
<table>Account</table>
|
||||
</columns>
|
||||
</sections>
|
||||
<sections>
|
||||
<masterLabel>Project Fields</masterLabel>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Name</field>
|
||||
<table>Account.Projects__r</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Status__c</field>
|
||||
<table>Account.Projects__r</table>
|
||||
</columns>
|
||||
</sections>
|
||||
<sections>
|
||||
<masterLabel>Task Fields</masterLabel>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Name</field>
|
||||
<table>Account.Projects__r.Tasks__r</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>false</checkedByDefault>
|
||||
<field>Due_Date__c</field>
|
||||
<table>Account.Projects__r.Tasks__r</table>
|
||||
</columns>
|
||||
</sections>
|
||||
</ReportType>
|
||||
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ReportType xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AccountsWithIndustry</fullName>
|
||||
<baseObject>Account</baseObject>
|
||||
<category>accounts</category>
|
||||
<deployed>true</deployed>
|
||||
<label>Accounts with Industry Detail</label>
|
||||
<description>Report framework for reviewing accounts with industry and revenue fields surfaced.</description>
|
||||
<sections>
|
||||
<masterLabel>Account Fields</masterLabel>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Name</field>
|
||||
<table>Account</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Industry</field>
|
||||
<table>Account</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>false</checkedByDefault>
|
||||
<field>AnnualRevenue</field>
|
||||
<table>Account</table>
|
||||
</columns>
|
||||
</sections>
|
||||
</ReportType>
|
||||
@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ReportType xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>AccountsWithProjects</fullName>
|
||||
<baseObject>Account</baseObject>
|
||||
<category>accounts</category>
|
||||
<deployed>true</deployed>
|
||||
<label>Accounts with or without Projects</label>
|
||||
<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>
|
||||
<join>
|
||||
<outerJoin>true</outerJoin>
|
||||
<relationship>Projects__r</relationship>
|
||||
</join>
|
||||
<sections>
|
||||
<masterLabel>Account Fields</masterLabel>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Name</field>
|
||||
<table>Account</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Industry</field>
|
||||
<table>Account</table>
|
||||
</columns>
|
||||
</sections>
|
||||
<sections>
|
||||
<masterLabel>Project Fields</masterLabel>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Name</field>
|
||||
<table>Account.Projects__r</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>true</checkedByDefault>
|
||||
<field>Status__c</field>
|
||||
<table>Account.Projects__r</table>
|
||||
</columns>
|
||||
<columns>
|
||||
<checkedByDefault>false</checkedByDefault>
|
||||
<field>CreatedDate</field>
|
||||
<table>Account.Projects__r</table>
|
||||
</columns>
|
||||
</sections>
|
||||
</ReportType>
|
||||
@ -0,0 +1,32 @@
|
||||
# `<category>` Values — `ReportTypeCategory` Enumeration
|
||||
|
||||
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.
|
||||
|
||||
| Category value | Typical use |
|
||||
|----------------|-------------|
|
||||
| `accounts` | Accounts & Contacts |
|
||||
| `opportunities` | Opportunities |
|
||||
| `forecasts` | Forecasts |
|
||||
| `cases` | Customer Support Reports |
|
||||
| `leads` | Leads |
|
||||
| `campaigns` | Campaigns |
|
||||
| `activities` | Activities |
|
||||
| `busop` | Business operations |
|
||||
| `products` | Price Books, Products and Assets |
|
||||
| `admin` | Administrative Reports |
|
||||
| `territory` | Territory management |
|
||||
| `territory2` | Territory management (Enterprise Territory Management) — API 31.0+ |
|
||||
| `usage_entitlement` | Usage entitlements |
|
||||
| `wdc` | Work.com / Calibration — API 29.0+ |
|
||||
| `calibration` | Calibration — API 29.0+ |
|
||||
| `other` | Other Reports (default for custom-object-based CRTs without a natural home) |
|
||||
| `content` | Content |
|
||||
| `quotes` | Quotes |
|
||||
| `individual` | Individual (privacy) — API 45.0+ |
|
||||
| `employee` | Employee — API 46.0+ |
|
||||
| `data_cloud` | Data Cloud — API 55.0+ |
|
||||
| `commerce` | Commerce — API 60.0+ |
|
||||
| `flow` | Flow — API 60.0+ |
|
||||
| `semantic_model` | Semantic model — API 60.0+ |
|
||||
|
||||
**When in doubt:** Use `other` for custom-object-based CRTs.
|
||||
@ -0,0 +1,24 @@
|
||||
# Common Authoring Errors
|
||||
|
||||
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.
|
||||
|
||||
| Error | Cause | Fix |
|
||||
|-------|-------|-----|
|
||||
| `Invalid object name 'X'` on `<baseObject>` | Primary object doesn't exist or isn't deployed | Deploy the custom object before the CRT |
|
||||
| `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) |
|
||||
| `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 |
|
||||
| `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 |
|
||||
| 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 |
|
||||
| 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`) |
|
||||
| `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 |
|
||||
| File not found / fullName mismatch | File name doesn't match `<fullName>` | Rename file so `<fullName>.reportType-meta.xml` matches |
|
||||
|
||||
## Handling Conflicting Join Requirements
|
||||
|
||||
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:
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
|
||||
Surface this trade-off to the user rather than picking silently.
|
||||
318
skills/platform-dataspace-access-configure/SKILL.md
Normal file
318
skills/platform-dataspace-access-configure/SKILL.md
Normal file
@ -0,0 +1,318 @@
|
||||
---
|
||||
name: platform-dataspace-access-configure
|
||||
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."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
minApiVersion: "67.0"
|
||||
cliTools:
|
||||
- tool: ["sf"]
|
||||
semver: ">=2.0.0"
|
||||
- tool: ["jq"]
|
||||
semver: ">=1.6"
|
||||
---
|
||||
|
||||
# platform-dataspace-access-configure
|
||||
|
||||
Configure DataSpace access in Salesforce Data Cloud using a two-layer model:
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Decide the Case First
|
||||
|
||||
Pick exactly one case from the table below before writing any files. Each case has a different output shape.
|
||||
|
||||
| Case | User intent | Permission set state | Files to emit |
|
||||
|---|---|---|---|
|
||||
| **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` |
|
||||
| **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 |
|
||||
| **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` |
|
||||
|
||||
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.
|
||||
|
||||
> **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.
|
||||
|
||||
### Case B workflow
|
||||
|
||||
1. Retrieve the existing permission set:
|
||||
```bash
|
||||
sf project retrieve start --metadata PermissionSet:<Name> --target-org <alias>
|
||||
```
|
||||
2. Open the retrieved `permissionsets/<Name>.permissionset-meta.xml`. Keep every element already there.
|
||||
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.
|
||||
4. Write `package.xml` listing the permset in `<members>`.
|
||||
5. Redeploy with `sf project deploy start`.
|
||||
|
||||
---
|
||||
|
||||
## When This Skill Owns the Task
|
||||
|
||||
Trigger this skill when the user wants to:
|
||||
- Create a permission set that grants access to a Data Cloud DataSpace
|
||||
- Add or modify `dataspaceScopes` on an existing permission set
|
||||
- Grant a permission set access to specific DMO / DLO / CIO objects in a DataSpace
|
||||
- Configure `dataAccessLevel` and `objectAccessLevel` for a DataSpace scope
|
||||
- List or remove object access grants for a permission set + DataSpace pair
|
||||
|
||||
Delegate elsewhere when:
|
||||
- The permission set has no DataSpace access at all → `platform-permission-set-generate`
|
||||
- The task is creating the DataSpace itself → `data360-orchestrate`
|
||||
- The task is ingesting data or configuring streams → `data360-prepare`
|
||||
|
||||
---
|
||||
|
||||
## Layer 1 — DataSpace-Level Access (MDAPI)
|
||||
|
||||
Embed a `<dataspaceScopes>` element inside the `PermissionSet` XML. Deploy with MDAPI.
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Data Cloud Analyst</label>
|
||||
<description>Data cloud analyst access to the default dataspace</description>
|
||||
<hasActivationRequired>false</hasActivationRequired>
|
||||
<dataspaceScopes>
|
||||
<dataspaceScope>default</dataspaceScope>
|
||||
<dataAccessLevel>ALL</dataAccessLevel>
|
||||
<objectAccessLevel>BY_POLICY</objectAccessLevel>
|
||||
</dataspaceScopes>
|
||||
</PermissionSet>
|
||||
```
|
||||
|
||||
### Element Rules
|
||||
|
||||
| Element | Required | Valid Values | Purpose |
|
||||
|---|---|---|---|
|
||||
| `<dataspaceScopes>` | yes | parent element (plural) | Container for a single dataspace scope grant |
|
||||
| `<dataspaceScope>` | yes | DataSpace API name (e.g. `default`) | Which DataSpace this grant is for |
|
||||
| `<dataAccessLevel>` | yes | `NONE`, `CONTROLLED_BY_PARENT`, `ALL` | Row-level data access within the DataSpace |
|
||||
| `<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` |
|
||||
|
||||
### Common Mistakes
|
||||
|
||||
- **Wrong parent name** — using `<dataspaceScopeAccess>` instead of `<dataspaceScopes>`. Deployment fails silently or with cryptic errors.
|
||||
- **Wrong child name** — using `<dataspaceScopeName>` instead of `<dataspaceScope>`.
|
||||
- **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.
|
||||
- **Multiple scopes in one element** — `<dataspaceScopes>` grants access to exactly one DataSpace. To grant access to multiple, add multiple `<dataspaceScopes>` blocks.
|
||||
|
||||
### Package Layout (Case A and Case B)
|
||||
|
||||
A deployable bundle for Layer 1 always contains **both** files:
|
||||
|
||||
```text
|
||||
<output-root>/
|
||||
package.xml
|
||||
permissionsets/<Name>.permissionset-meta.xml
|
||||
```
|
||||
|
||||
`package.xml` (required — list every permission set being deployed in `<members>`):
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<types>
|
||||
<members>Data_Cloud_Analyst</members>
|
||||
<name>PermissionSet</name>
|
||||
</types>
|
||||
<version>67.0</version>
|
||||
</Package>
|
||||
```
|
||||
|
||||
Deploy:
|
||||
|
||||
```bash
|
||||
sf project deploy start --source-dir force-app/main/default/permissionsets/ --target-org <alias>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Layer 2 — Object-Level Access (Connect API) — Case C
|
||||
|
||||
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.
|
||||
|
||||
### Resolve the API version first
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
sf org display --target-org <alias> --json | jq -r '.result.apiVersion'
|
||||
```
|
||||
|
||||
- Substitute the returned value (e.g. `67.0`, `68.0`) into the `endpoint` as `v<apiVersion>`.
|
||||
- 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.
|
||||
- If the user explicitly specifies a version in the prompt, use that verbatim.
|
||||
|
||||
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`.
|
||||
|
||||
### `api-request.json` — canonical shape
|
||||
|
||||
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.
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "POST",
|
||||
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"permissionSetName": "Data_Cloud_Analyst",
|
||||
"dataSpaceName": "default",
|
||||
"objectApiName": "Account__dlm"
|
||||
},
|
||||
"expectedResponse": {
|
||||
"status": 201,
|
||||
"body": {
|
||||
"permissionSetName": "Data_Cloud_Analyst",
|
||||
"dataSpaceName": "default",
|
||||
"objectApiName": "Account__dlm"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bulk Grant
|
||||
|
||||
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).
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "POST",
|
||||
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants/actions/bulk-create",
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"permissionSetName": "Data_Cloud_Analyst",
|
||||
"dataSpaceName": "default",
|
||||
"objectApiNames": ["Account__dlm", "Contact__dlm", "Opportunity__dlm"]
|
||||
},
|
||||
"expectedResponse": {
|
||||
"status": 201
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### List Grants
|
||||
|
||||
Same envelope shape with `method: "GET"`, query parameters on the `endpoint`, and no `body`.
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "GET",
|
||||
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants?permissionSetName=Data_Cloud_Analyst&dataSpaceName=default",
|
||||
"headers": {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"expectedResponse": {
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Revoke Grant
|
||||
|
||||
Same envelope shape with `method: "DELETE"`, the object API name as a path segment, and `expectedResponse.status: 204` (No Content).
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "DELETE",
|
||||
"endpoint": "/services/data/v{apiVersion}/ssot/data-governance/object-access-grants/Account__dlm?permissionSetName=Data_Cloud_Analyst&dataSpaceName=default",
|
||||
"headers": {
|
||||
"Accept": "application/json"
|
||||
},
|
||||
"expectedResponse": {
|
||||
"status": 204
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Object Types
|
||||
|
||||
- **DMO** (Data Model Object) — unified profile objects, suffix `__dlm`
|
||||
- **DLO** (Data Lake Object) — raw ingested data, suffix `__dll`
|
||||
- **CIO** (Calculated Insight Object) — computed metrics, suffix `__cio`
|
||||
|
||||
---
|
||||
|
||||
## Combined Setup — Case A + Case C from a Cold Start
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
**Goal:** Grant `Data_Cloud_Analyst` permission set access to `Account__dlm` and `Contact__dlm` in the `default` DataSpace.
|
||||
|
||||
**Step 1 — Deploy PermissionSet with DataSpace scope (MDAPI):**
|
||||
|
||||
```xml
|
||||
<PermissionSet xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<label>Data Cloud Analyst</label>
|
||||
<description>Data cloud analyst access to the default dataspace</description>
|
||||
<hasActivationRequired>false</hasActivationRequired>
|
||||
<dataspaceScopes>
|
||||
<dataspaceScope>default</dataspaceScope>
|
||||
<dataAccessLevel>ALL</dataAccessLevel>
|
||||
<objectAccessLevel>BY_POLICY</objectAccessLevel>
|
||||
</dataspaceScopes>
|
||||
</PermissionSet>
|
||||
```
|
||||
|
||||
```bash
|
||||
sf project deploy start --source-dir permissionsets/ --target-org <alias>
|
||||
```
|
||||
|
||||
**Step 2 — Grant object access (Connect API):**
|
||||
*(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.)*
|
||||
|
||||
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:
|
||||
|
||||
```bash
|
||||
API_VERSION=$(sf org display --target-org <alias> --json | jq -r '.result.apiVersion')
|
||||
|
||||
sf org api rest --target-org <alias> \
|
||||
--method POST \
|
||||
--path "/services/data/v${API_VERSION}/ssot/data-governance/object-access-grants/actions/bulk-create" \
|
||||
--body '{
|
||||
"permissionSetName": "Data_Cloud_Analyst",
|
||||
"dataSpaceName": "default",
|
||||
"objectApiNames": ["Account__dlm", "Contact__dlm"]
|
||||
}'
|
||||
```
|
||||
|
||||
**Step 3 — Verify:**
|
||||
|
||||
```bash
|
||||
sf org api rest --target-org <alias> \
|
||||
--path "/services/data/v${API_VERSION}/ssot/data-governance/object-access-grants?permissionSetName=Data_Cloud_Analyst&dataSpaceName=default"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Rules and Constraints
|
||||
|
||||
| Rule | Reason |
|
||||
|---|---|
|
||||
| Use `<dataspaceScopes>` (plural) as parent, `<dataspaceScope>` (singular) as child | XML schema requirement; other names deploy-fail |
|
||||
| `dataAccessLevel` values: `NONE`, `CONTROLLED_BY_PARENT`, `ALL` only | Other values (e.g. `OWNER`, `ViewAllRows`) are rejected |
|
||||
| `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` |
|
||||
| Prefer `BY_POLICY` when data governance policies exist | Delegates row/column filtering to central policy — no per-object grants needed |
|
||||
| One `<dataspaceScopes>` block per DataSpace | Repeat the block for multiple DataSpaces on the same permission set |
|
||||
| Org must have Data Cloud provisioned to deploy `<dataspaceScopes>` | On non-Data-Cloud orgs, the element is ignored or rejected |
|
||||
| Do not query `DataspaceScope` / `DataspaceScopeAccess` via SOQL | Not queryable; use MDAPI retrieve to inspect existing grants |
|
||||
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
|
||||
| Issue | Resolution |
|
||||
|---|---|
|
||||
| Deployment fails with error `-379999659` | Check enum values — `dataAccessLevel` must be `NONE`/`CONTROLLED_BY_PARENT`/`ALL`; `objectAccessLevel` must be `BY_POLICY`/`ALL_IN_DATASPACE` |
|
||||
| Permission set deploys but users still can't query DataSpace data | Layer 2 not applied — objects need explicit grants if `objectAccessLevel != BY_POLICY` |
|
||||
| Bulk-create returns `AlreadyExists` for some objects | Idempotent — safe to retry; response shows per-object status |
|
||||
| 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 |
|
||||
| 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
|
||||
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."
|
||||
metadata:
|
||||
version: "1.1"
|
||||
relatedSkills: ["platform-soql-query"]
|
||||
cliTools:
|
||||
- tool: ["python3"]
|
||||
semver: ">=3.8.0"
|
||||
---
|
||||
|
||||
# platform-docs-get
|
||||
|
||||
@ -153,27 +153,53 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
||||
http_status = response.status if response else None
|
||||
page.wait_for_timeout(1500)
|
||||
try:
|
||||
page.wait_for_function(
|
||||
r"""
|
||||
() => {
|
||||
const el = document.querySelector('main, article, [role="main"]');
|
||||
const text = (el?.innerText || el?.textContent || '').trim();
|
||||
return text.length > 200;
|
||||
}
|
||||
""",
|
||||
timeout=min(timeout_ms, 15000),
|
||||
)
|
||||
page.wait_for_load_state("networkidle", timeout=min(timeout_ms, 15000))
|
||||
except PlaywrightTimeoutError:
|
||||
pass
|
||||
|
||||
# Wait for content: check both light DOM and shadow DOM content
|
||||
is_developer = "developer.salesforce.com" in host
|
||||
wait_js = r"""
|
||||
() => {
|
||||
// Light DOM check (works for most official sites)
|
||||
const el = document.querySelector('main, article, [role="main"]');
|
||||
const lightText = (el?.innerText || el?.textContent || '').trim();
|
||||
if (lightText.length > 200) return true;
|
||||
|
||||
// Shadow DOM check (needed for developer.salesforce.com)
|
||||
function findDeepText(root, depth) {
|
||||
if (depth > 5 || !root) return '';
|
||||
let best = '';
|
||||
const els = root.querySelectorAll ? root.querySelectorAll('*') : [];
|
||||
for (const child of els) {
|
||||
if (child.shadowRoot) {
|
||||
// Check direct children of shadow root for text
|
||||
for (const sc of child.shadowRoot.children) {
|
||||
const t = (sc.innerText || '').trim();
|
||||
if (t.length > best.length) best = t;
|
||||
}
|
||||
const deeper = findDeepText(child.shadowRoot, depth + 1);
|
||||
if (deeper.length > best.length) best = deeper;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
const shadowText = findDeepText(document, 0);
|
||||
return shadowText.length > 200;
|
||||
}
|
||||
"""
|
||||
try:
|
||||
page.wait_for_load_state("networkidle", timeout=min(timeout_ms, 15000))
|
||||
page.wait_for_function(
|
||||
wait_js,
|
||||
timeout=min(timeout_ms, 20000 if is_developer else 15000),
|
||||
)
|
||||
except PlaywrightTimeoutError:
|
||||
pass
|
||||
page.wait_for_timeout(500)
|
||||
|
||||
payload = page.evaluate(
|
||||
r"""
|
||||
() => {
|
||||
(host) => {
|
||||
function normalize(text) {
|
||||
return String(text || '')
|
||||
.replace(/\u00a0/g, ' ')
|
||||
@ -233,6 +259,14 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
||||
return Array.from(urls);
|
||||
}
|
||||
|
||||
// --- Noise removal: strip cookie/consent overlays before scoring ---
|
||||
document.querySelectorAll(
|
||||
'#onetrust-banner-sdk, #onetrust-consent-sdk, .onetrust-consent-sdk, ' +
|
||||
'[class*="onetrust-pc"], #onetrust-pc-sdk, ' +
|
||||
'[id*="cookie-consent"], [class*="cookie-banner"], ' +
|
||||
'.evidon-consent-button, #evidon-banner'
|
||||
).forEach(function(el) { el.remove(); });
|
||||
|
||||
const title = document.title || normalize(document.querySelector('title')?.innerText || 'Untitled');
|
||||
const childLinks = new Set();
|
||||
for (const root of allRoots()) {
|
||||
@ -258,12 +292,42 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
||||
{ selector: 'main .content, article .content', strategy: 'nested-content', base: 140 },
|
||||
];
|
||||
|
||||
// --- Host-aware selector overrides for developer.salesforce.com ---
|
||||
if (host.includes('developer.salesforce.com')) {
|
||||
selectorConfigs.unshift(
|
||||
{ selector: 'doc-content', strategy: 'dev-doc-content', base: 295 },
|
||||
{ selector: '#maincontent', strategy: 'dev-maincontent', base: 290 },
|
||||
{ selector: '.doc-body', strategy: 'dev-doc-body', base: 285 },
|
||||
{ selector: '#topic-content', strategy: 'dev-topic-content', base: 285 },
|
||||
{ selector: '.topicContent', strategy: 'dev-topic', base: 280 },
|
||||
{ selector: '[class*="docContent"]', strategy: 'dev-doc-content-class', base: 275 },
|
||||
{ selector: 'doc-xml-content .content', strategy: 'dev-xml-content', base: 270 },
|
||||
);
|
||||
}
|
||||
|
||||
// Extract text from an element, including shadow root content
|
||||
function extractText(el) {
|
||||
let text = (el.innerText || el.textContent || '').trim();
|
||||
// If element is a shadow host with no slotted light DOM text,
|
||||
// gather text from its shadow root's children
|
||||
if (text.length < 200 && el.shadowRoot) {
|
||||
let shadowText = '';
|
||||
for (const child of el.shadowRoot.children) {
|
||||
shadowText += (child.innerText || child.textContent || '') + '\n';
|
||||
}
|
||||
if (shadowText.trim().length > text.length) {
|
||||
text = shadowText.trim();
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
const candidates = [];
|
||||
for (const cfg of selectorConfigs) {
|
||||
const nodes = deepQueryAll(cfg.selector);
|
||||
for (const node of nodes) {
|
||||
if (!isVisible(node)) continue;
|
||||
const text = normalize(node.innerText || node.textContent || '');
|
||||
const text = normalize(extractText(node));
|
||||
if (text.length < 200) continue;
|
||||
let score = cfg.base + Math.min(text.length, 5000) / 30;
|
||||
const lowered = text.toLowerCase();
|
||||
@ -306,7 +370,8 @@ def extract_official_salesforce_doc(url: str, timeout_seconds: int, use_stealth:
|
||||
candidateCount: candidates.length,
|
||||
};
|
||||
}
|
||||
"""
|
||||
""",
|
||||
host,
|
||||
)
|
||||
|
||||
text = normalize_text(payload.get("text", ""))
|
||||
|
||||
122
skills/platform-encryption-configure/SKILL.md
Normal file
122
skills/platform-encryption-configure/SKILL.md
Normal file
@ -0,0 +1,122 @@
|
||||
---
|
||||
name: platform-encryption-configure
|
||||
description: "Configure Salesforce Shield Platform Encryption — generate deployable encryption settings and encrypted-field metadata, and answer key-model and lifecycle questions. TRIGGER when: user wants to turn on deterministic encryption, encrypt a field, set up Cache-Only Keys, External Key Management, or replay detection, or mentions Shield Platform Encryption, encryption at rest, deterministic vs probabilistic encryption, encryptionScheme, PlatformEncryptionSettings, EncryptionKeySettings, BYOK, BYOKMS, tenant secrets, key rotation, or .settings-meta.xml / .field-meta.xml for encryption — even when they don't say 'Shield'. SKIP when: user needs a generic custom field with no encryption (use platform-custom-field-generate), needs the raw Metadata API type reference (use platform-metadata-api-context-get), or asks about Classic Encryption (encrypted text fields), which is a different feature. Use this skill for any Platform Encryption configuration, field-encryption, or key-model question."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
minApiVersion: "62.0"
|
||||
relatedSkills:
|
||||
- "platform-custom-field-generate"
|
||||
- "platform-metadata-api-context-get"
|
||||
---
|
||||
|
||||
# Configure Platform Encryption
|
||||
|
||||
Configures Salesforce Shield Platform Encryption by generating the metadata that turns it on and choosing the right settings: which encryption scheme a field should use, which key-management model fits a requirement, and how the tenant-secret lifecycle works. This is a **hybrid** skill — it emits deployable `*.settings-meta.xml` / `*.field-meta.xml` where Platform Encryption exposes a real Metadata API surface, and returns grounded guidance where the operation is UI/REST-only.
|
||||
|
||||
## Scope
|
||||
|
||||
- **In scope**: choosing and applying `encryptionScheme` on a field; enabling deterministic encryption, Cache-Only Keys, External Key Management, and replay detection via `PlatformEncryptionSettings` / `EncryptionKeySettings`; explaining BYOK / BYOKMS / EKM / Cache-Only key models; tenant-secret rotation and destruction semantics; the query behavior of encrypted fields.
|
||||
- **Out of scope**: a plain custom field with no encryption (use `platform-custom-field-generate`); the raw Metadata API field reference (use `platform-metadata-api-context-get`); Classic Encryption (`EncryptedText` fields) — that is a separate, legacy feature; deploying/pushing metadata to an org (that belongs to a deploy lifecycle skill).
|
||||
|
||||
---
|
||||
|
||||
## Required Inputs
|
||||
|
||||
Gather or infer before proceeding:
|
||||
|
||||
- **Question type**: is the user asking for a *deployable artifact* (a settings file, an encrypted field) or *guidance* (which model, what happens when I rotate a key)? Deployable → generate XML from `assets/`. Guidance → answer from `references/`. **A question is guidance whenever the ask is to explain, confirm, or compare** — "is that right?", "what's the relationship?", "can we…?", "is there an ordering requirement?", **"explain the difference between X and Y", "which key model should we use?"** — **even if the user also says they are about to write, deploy, or author settings themselves.** The user writing settings is *their* action; it does not make the skill's deliverable a file. Only an explicit "generate / create / give me the file / here is my field, encrypt it" is an artifact request.
|
||||
- **Key-model choice questions are guidance, not deployable metadata.** "Explain BYOK vs external key management / BYOKMS / EKM / Cache-Only", "which one keeps key material out of Salesforce?", "should we use BYOK or EKM?" → answer them in a **single markdown answer file** (the guidance write-up), not a deployable `*.settings-meta.xml`. **Naming the enabling setting in that answer (e.g. `canExternalKeyManagement`, `enableCacheOnlyKeys`) does NOT turn it into a settings artifact** — cite the field name inline in the answer file; do **not** emit an `EncryptionKey.settings-meta.xml` unless the user explicitly says "generate/create the settings file."
|
||||
- **Field encryption target** (for field work): the object and field API name, and whether the user needs to *filter, sort, or group* on the field (drives deterministic vs probabilistic).
|
||||
- **Key model** (for key work): whether keys are Salesforce-derived (default), customer-supplied (BYOK), stored in an external KMS (BYOKMS/EKM), or fetched on demand (Cache-Only).
|
||||
|
||||
If the request is clear, generate or answer immediately — do not interrogate the user.
|
||||
|
||||
---
|
||||
|
||||
## Workflow
|
||||
|
||||
1. **Classify the request** — deployable artifact vs guidance, using the Required Inputs above. Then **scope the output to exactly what was asked**:
|
||||
- A **guidance** question produces **exactly one markdown answer file** — a single file (e.g. `answer.md`) containing the full written diagnosis/explanation — and **nothing else**. Do not also emit a `*.settings-meta.xml`, a `*.field-meta.xml`, or a second helper doc. This covers every "what happens when…?", "how do I…?", "which model…?", "is X right…?", "can we…?", "what's the relationship / ordering…?" question, including query-behavior and Cache-Only/replay questions. **A clause like "before I write our settings" or "before I author the file" describes the *user's* next step and does NOT turn the question into a deployable-metadata request — write the answer file, not a settings file.**
|
||||
- **Naming a metadata change in a guidance answer does NOT mean emitting the deployable file for it.** A remediation or diagnosis question — *"how do I make the field queryable?"*, *"why did my query fail and how do I fix it?"*, *"which key model keeps material out of Salesforce?"* — is answered **inside the one markdown answer file**, naming the relevant element/scheme inline (e.g. "switch to a `Deterministic*` scheme and enable `enableDeterministicEncryption`", or "use External Key Management — `canExternalKeyManagement`"). Do **not** additionally materialize a `*.field-meta.xml` or `*.settings-meta.xml` to *demonstrate* that change — mentioning the element in the answer is the complete deliverable. Produce a deployable metadata file **only** when the user explicitly says generate/create/give me the field or settings file.
|
||||
- An **artifact** request gets **only** the specific metadata file(s) named — do not add a `DEPLOYMENT_GUIDE.md`, `README.md`, an `EXPLANATION.md`, an org-`settings` file, or any companion artifact the user did not ask for.
|
||||
- If a deploy step or org setting is a prerequisite, state it **inside the one answer file** (for guidance) or **in a code comment inside the one artifact** (for an artifact request) — never as an extra file.
|
||||
|
||||
2. **For field encryption** — read `references/encryption-schemes.md` to choose the scheme, then load `assets/encrypted-field.field-meta.xml` as the starting template. Set `encryptionScheme` to exactly one of the four valid enum values (see the reference). Only `Deterministic*` schemes are filterable.
|
||||
|
||||
> **Write the field file at the SFDX source path, not the root.** A `*.field-meta.xml` **must** live at `objects/<ObjectApiName>/fields/<FieldApiName>__c.field-meta.xml` (e.g. `objects/Patient__c/fields/Diagnosis_Notes__c.field-meta.xml`) — the object folder uses the object's API name (`Patient__c` for a custom object, `Contact` for a standard one) and the file is named after the field API name. Emitting the file at the repo root, in a flat directory, or under any other folder is a structural miss even when the XML itself is correct.
|
||||
|
||||
3. **For org-level encryption settings** — load `assets/PlatformEncryption.settings-meta.xml` (deterministic encryption, field-history encryption, MEK permission) or `assets/EncryptionKey.settings-meta.xml` (Cache-Only, EKM, Data 360, transactional DB, replay detection). Read `references/key-models.md` before setting any key-model field.
|
||||
|
||||
> **Name the output file after the Settings *member*, not the root element, and write it under `settings/`.** A `Settings` file must be `settings/<member>.settings-meta.xml`, where `<member>` is the org's metadata member name — **`EncryptionKey`** (root `<EncryptionKeySettings>`) and **`PlatformEncryption`** (root `<PlatformEncryptionSettings>`). Put it in the `settings/` source folder (e.g. `settings/EncryptionKey.settings-meta.xml`), not the repo root. Naming the key-settings file `Encryption.settings-meta.xml` or `EncryptionKeySettings.settings-meta.xml` fails deployment with *"The object '…' of type Settings metadata does not exist."*
|
||||
|
||||
> **Cache-Only Keys and replay detection are a one-way dependency, not an auto-enable.** You may set `enableReplayDetection` only after `enableCacheOnlyKeys` is `true`; enabling Cache-Only does **not** turn replay detection on by itself. An org can validly run Cache-Only with replay detection off.
|
||||
|
||||
4. **For tenant-secret operations** (rotate, destroy, BYOK upload, Cache-Only callout setup) — read `references/tenant-secret-lifecycle.md`. These are UI/REST-only; capture the guidance in the single markdown answer file, not a deployable metadata file.
|
||||
|
||||
5. **Validate any generated settings XML** — run `scripts/validate-encryption-metadata.sh` with the file path as its argument, and fix anything it reports. It checks the replay-detection dependency and the `encryptionScheme` enum deterministically.
|
||||
|
||||
6. **Compare against the worked example** — verify a generated `EncryptionKeySettings` file against `examples/cache-only-keys.settings-meta.xml`.
|
||||
|
||||
---
|
||||
|
||||
## Rules / Constraints
|
||||
|
||||
| Constraint | Rationale |
|
||||
|-----------|-----------|
|
||||
| `encryptionScheme` must be exactly one of `CaseInsensitiveDeterministicEncryption`, `CaseSensitiveDeterministicEncryption`, `None`, `ProbabilisticEncryption` | These are the only values the Metadata API accepts (`CustomField`, API 44.0+); any other string fails deployment. |
|
||||
| Set `enableReplayDetection` only when `enableCacheOnlyKeys` is `true` | The contract is *"Requires enableCacheOnlyKeys=true before setting enableReplayDetection to true"* — a one-way dependency. |
|
||||
| Use deterministic schemes only when the field must be filtered, sorted, or grouped | Probabilistic is stronger but non-filterable; deterministic trades some cryptographic strength for queryability. |
|
||||
| Never claim a filter/sort/group on a probabilistically-encrypted field silently returns zero rows | The platform **rejects** the query with `INVALID_FIELD` (see gotchas); telling the user it "returns nothing" is factually wrong. |
|
||||
| Do not emit `enableExternalKeyManagement` — the field is `canExternalKeyManagement` | The WSDL element is `canExternalKeyManagement`; the sample in some docs uses a non-existent element name. |
|
||||
| Transactional-DB, EKM, and Data 360 key fields require API 63.0+ | `canEncryptTransactionalDatabase`, `canExternalKeyManagement`, `canManageDataCloudKeys` were introduced in 63.0. |
|
||||
| A **guidance** question produces **exactly one markdown answer file** — never a deployable `*.settings-meta.xml` / `*.field-meta.xml`, and never a second doc | The answer file is the user's reference document — it persists in the workspace and can be shared or revised. Emitting a deployable metadata file for a guidance question is unsolicited configuration that could be accidentally applied; emitting no file leaves the user without a tangible deliverable. |
|
||||
| An **artifact** request emits **only** the metadata file(s) asked for — no companion files | Adding a `DEPLOYMENT_GUIDE.md`/`README.md`/`EXPLANATION.md` or an extra `settings` file the user didn't request is noise. Prerequisites belong in a code comment inside the artifact, not a second file. |
|
||||
|
||||
---
|
||||
|
||||
## Gotchas
|
||||
|
||||
| Issue | Resolution |
|
||||
|-------|------------|
|
||||
| Filtering/sorting/grouping on a probabilistically-encrypted field | The query is rejected with `INVALID_FIELD`: *"field '<Name>' can not be sorted / filtered / grouped in a query call."* Switch the field to a deterministic scheme if queryability is required. |
|
||||
| Assuming Cache-Only Keys auto-enables replay detection | It does not. Set `enableReplayDetection` explicitly, and only after `enableCacheOnlyKeys=true`. |
|
||||
| Case sensitivity in deterministic matching | `CaseSensitiveDeterministicEncryption` matches exact case; `CaseInsensitiveDeterministicEncryption` normalizes case. Choosing wrong silently breaks equality filters. |
|
||||
| Confusing BYOK with BYOKMS/EKM | BYOK = you upload key material Salesforce stores; BYOKMS/EKM = key material stays in your external KMS. See `references/key-models.md`. |
|
||||
| Using `enableExternalKeyManagement` element name | Wrong element. The field is `canExternalKeyManagement`. |
|
||||
| Classic Encryption vs Shield | `encryptionScheme` is Shield only. `EncryptedText` custom fields are the legacy Classic feature and out of scope. |
|
||||
|
||||
---
|
||||
|
||||
## Output Expectations
|
||||
|
||||
Deliverables depend on the request — produce **exactly** these and nothing more:
|
||||
|
||||
- **Guidance questions produce exactly ONE markdown answer file.** Any "what happens when…", "how do I…", "which model…", "is X right…" question — including encrypted-field query behavior, Cache-Only/replay-detection relationships, tenant-secret / BYOK / Cache-Only lifecycle, and key-model choices — is answered by writing a single markdown file (e.g. `answer.md`) that fully captures the diagnosis/explanation. Do **not** additionally emit a deployable `*.settings-meta.xml` / `*.field-meta.xml` or a second doc, and do **not** answer with no file at all — the answer file is the user's persistent reference document.
|
||||
- **Field encryption artifact**: **only** the `*.field-meta.xml` with `encryptionScheme` set, written at `objects/<ObjectApiName>/fields/<FieldApiName>__c.field-meta.xml`. Not a settings file, not a deploy guide. Pick `<type>` to match the request: **`Text`** for a string field up to **255** chars; **`LongTextArea`** only when the field must exceed 255 chars (256+). "Long text … up to 255 characters" is a `Text` field, not `LongTextArea`. Strip the template's instructional comment block from the delivered file — ship clean metadata. **Keep the accompanying chat prose tight — one or two sentences naming the scheme chosen and why (e.g. "ProbabilisticEncryption — strongest at-rest protection; the field can't be filtered/sorted/grouped, which matches your no-query requirement"). Do not restate the whole prompt, enumerate every scheme, or add setup/deployment walkthroughs; the deliverable is the file, not an essay.**
|
||||
- **Org settings artifact**: **only** the settings file the request needs, at `settings/<member>.settings-meta.xml` named after its Metadata API member — `settings/PlatformEncryption.settings-meta.xml` (root `<PlatformEncryptionSettings>`) and/or `settings/EncryptionKey.settings-meta.xml` (root `<EncryptionKeySettings>`). Do **not** name the file after the root element, and do **not** drop it at the repo root.
|
||||
|
||||
Do **not** add companion files (`DEPLOYMENT_GUIDE.md`, `README.md`, an extra org-settings file) that the user did not ask for — state prerequisites in a code comment inside the artifact, or inside the single answer file for guidance. File structure follows the templates in `assets/`.
|
||||
|
||||
---
|
||||
|
||||
## Cross-Skill Integration
|
||||
|
||||
| Need | Delegate to |
|
||||
|------|-------------|
|
||||
| A custom field with no encryption | `platform-custom-field-generate` |
|
||||
| The raw Metadata API type/field reference | `platform-metadata-api-context-get` |
|
||||
|
||||
---
|
||||
|
||||
## Reference File Index
|
||||
|
||||
| File | When to read |
|
||||
|------|-------------|
|
||||
| `assets/encrypted-field.field-meta.xml` | Before generating an encrypted custom field |
|
||||
| `assets/PlatformEncryption.settings-meta.xml` | Before generating org encryption-policy settings (member `PlatformEncryption`) |
|
||||
| `assets/EncryptionKey.settings-meta.xml` | Before generating key-management settings — Cache-Only, EKM, Data 360 (member `EncryptionKey`) |
|
||||
| `references/encryption-schemes.md` | When choosing deterministic vs probabilistic, or explaining encrypted-field query behavior |
|
||||
| `references/key-models.md` | When configuring or explaining BYOK / BYOKMS / EKM / Cache-Only key models |
|
||||
| `references/tenant-secret-lifecycle.md` | When the user asks about key rotation, destruction, or BYOK upload |
|
||||
| `examples/cache-only-keys.settings-meta.xml` | To verify a generated Cache-Only key-settings file |
|
||||
| `scripts/validate-encryption-metadata.sh` | After generating any settings XML — validates the replay dependency and scheme enum |
|
||||
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Key-management settings (Shield Platform Encryption).
|
||||
All fields are optional booleans; include only the ones you intend to change.
|
||||
enableCacheOnlyKeys make the Cache-Only Key Service available
|
||||
enableReplayDetection protect cache-only key callouts with a nonce
|
||||
REQUIRES enableCacheOnlyKeys=true first (one-way dependency).
|
||||
canExternalKeyManagement allow External Key Management (EKM) connections [API 63.0+]
|
||||
canManageDataCloudKeys enable Data 360 key management + initial root key [API 63.0+]
|
||||
canEncryptTransactionalDatabase enable transactional database encryption [API 63.0+]
|
||||
canOptOutOfDerivationWithBYOK allow opting out of key derivation per uploaded key
|
||||
-->
|
||||
<EncryptionKeySettings xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<enableCacheOnlyKeys>true</enableCacheOnlyKeys>
|
||||
<enableReplayDetection>true</enableReplayDetection>
|
||||
<canOptOutOfDerivationWithBYOK>false</canOptOutOfDerivationWithBYOK>
|
||||
</EncryptionKeySettings>
|
||||
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Org-level Platform Encryption policy settings.
|
||||
All fields are optional booleans; include only the ones you intend to change.
|
||||
canEncryptManagedPackageFields allow encrypting fields in installed managed packages
|
||||
enableDeterministicEncryption allow the deterministic scheme on supported fields (enables filtering)
|
||||
enableEncryptFieldHistory apply active key material to field history / feed tracking
|
||||
isMEKForEncryptionRequired require Manage Encryption Keys perm in addition to baseline perms
|
||||
-->
|
||||
<PlatformEncryptionSettings xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<enableDeterministicEncryption>true</enableDeterministicEncryption>
|
||||
<enableEncryptFieldHistory>true</enableEncryptFieldHistory>
|
||||
<isMEKForEncryptionRequired>true</isMEKForEncryptionRequired>
|
||||
<canEncryptManagedPackageFields>false</canEncryptManagedPackageFields>
|
||||
</PlatformEncryptionSettings>
|
||||
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<CustomField xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<fullName>{FIELD_API_NAME}__c</fullName>
|
||||
<label>{FIELD_LABEL}</label>
|
||||
<type>{FIELD_TYPE}</type>
|
||||
<length>{LENGTH}</length>
|
||||
<encryptionScheme>ProbabilisticEncryption</encryptionScheme>
|
||||
</CustomField>
|
||||
<!--
|
||||
TEMPLATE NOTES — delete this comment block before delivering the file.
|
||||
|
||||
<type>: Text for string fields up to 255 chars; LongTextArea only for 256+.
|
||||
"long text up to 255 characters" is Text, NOT LongTextArea.
|
||||
<encryptionScheme> must be exactly one of:
|
||||
ProbabilisticEncryption strongest; NOT filterable/sortable/groupable
|
||||
CaseSensitiveDeterministicEncryption filterable; exact-case equality
|
||||
CaseInsensitiveDeterministicEncryption filterable; case-normalized equality
|
||||
None removes encryption
|
||||
Only deterministic schemes support filter / sort / group. A query that filters
|
||||
a probabilistically-encrypted field is REJECTED with INVALID_FIELD.
|
||||
-->
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Worked example: enabling Cache-Only Keys WITH replay detection.
|
||||
Note the ordering guarantee — enableCacheOnlyKeys=true is present, which is
|
||||
the precondition for enableReplayDetection=true. Removing enableCacheOnlyKeys
|
||||
(or setting it false) while keeping enableReplayDetection=true is invalid.
|
||||
-->
|
||||
<EncryptionKeySettings xmlns="http://soap.sforce.com/2006/04/metadata">
|
||||
<enableCacheOnlyKeys>true</enableCacheOnlyKeys>
|
||||
<enableReplayDetection>true</enableReplayDetection>
|
||||
</EncryptionKeySettings>
|
||||
@ -0,0 +1,34 @@
|
||||
# Encryption Schemes
|
||||
|
||||
Shield Platform Encryption offers two families of scheme, exposed on `CustomField.encryptionScheme` (Metadata API 44.0+). Choosing between them is a trade-off between cryptographic strength and queryability.
|
||||
|
||||
## Valid `encryptionScheme` values
|
||||
|
||||
| Value | Filterable / sortable / groupable | When to use |
|
||||
|-------|:--:|-------------|
|
||||
| `ProbabilisticEncryption` | **No** | Default and strongest. Same plaintext encrypts to different ciphertext each time, so no two values are comparable. Use unless the field must be queried. |
|
||||
| `CaseSensitiveDeterministicEncryption` | Yes | Same plaintext always encrypts to the same ciphertext, preserving exact case. Use for exact-match filters where case matters (e.g. codes, tokens). |
|
||||
| `CaseInsensitiveDeterministicEncryption` | Yes | Deterministic with case normalized before encryption. Use for equality filters on human-entered text (names, emails) where case should not matter. |
|
||||
| `None` | n/a | Removes encryption from the field. |
|
||||
|
||||
Only these four strings are accepted. Any other value fails deployment.
|
||||
|
||||
## Deterministic vs Probabilistic — the decision
|
||||
|
||||
- **Need to filter, sort, group, or use the field in a WHERE clause?** → a deterministic scheme. Deterministic encryption preserves equality so the index can be used.
|
||||
- **No query requirement?** → probabilistic. It is cryptographically stronger because identical plaintexts produce different ciphertexts, defeating frequency analysis.
|
||||
- **Case matters in matching?** → `CaseSensitive...`. Otherwise `CaseInsensitive...`.
|
||||
|
||||
Enabling the deterministic scheme at the org level also requires `enableDeterministicEncryption=true` in `PlatformEncryptionSettings`.
|
||||
|
||||
## Query behavior of encrypted fields — the failure mode
|
||||
|
||||
A common misconception is that filtering or sorting on a **probabilistically**-encrypted field silently drops the clause or returns zero rows. It does not.
|
||||
|
||||
The platform **rejects the query** with an `INVALID_FIELD` error:
|
||||
|
||||
```text
|
||||
INVALID_FIELD: field '<FieldName>' can not be sorted / filtered / grouped in a query call
|
||||
```
|
||||
|
||||
This is enforced server-side (see the platform's `QueryBlockingTest` coverage). The correct guidance to a user hitting this is: the query failed because the field is probabilistically encrypted — re-encrypt it with a deterministic scheme if it must be queryable, or remove the field from the filter/sort/group clause. Never tell the user the query "returns nothing" — it errors.
|
||||
@ -0,0 +1,34 @@
|
||||
# Key Management Models
|
||||
|
||||
Shield Platform Encryption supports several ways to source and control the key material that protects encrypted data. They differ in *where the key material lives* and *who controls it*.
|
||||
|
||||
## The models
|
||||
|
||||
| Model | Where key material lives | Who controls it | Metadata / setting |
|
||||
|-------|--------------------------|-----------------|--------------------|
|
||||
| Salesforce-derived (default) | Salesforce, derived from a tenant secret | Salesforce derivation | none — default |
|
||||
| **BYOK** (Bring Your Own Key) | Salesforce, but from *customer-supplied* material | Customer uploads key material; Salesforce stores it | `canOptOutOfDerivationWithBYOK` controls per-key derivation opt-out |
|
||||
| **BYOKMS / EKM** (External Key Management) | An *external* key store the customer runs | Customer; Salesforce calls out to the KMS | `canExternalKeyManagement` (API 63.0+) |
|
||||
| **Cache-Only Keys** | Outside Salesforce entirely; fetched on demand per callout | Customer hosts a key service; Salesforce caches, never persists | `enableCacheOnlyKeys`, `enableReplayDetection` |
|
||||
| **Data 360 keys** | Salesforce, dedicated Data 360 root key | Customer-managed root key | `canManageDataCloudKeys` (API 63.0+) |
|
||||
| **Transactional DB** | Salesforce | Org policy | `canEncryptTransactionalDatabase` (API 63.0+) |
|
||||
|
||||
## BYOK vs BYOKMS/EKM — the distinction users get wrong
|
||||
|
||||
- **BYOK**: you generate key material and *upload* it. Salesforce stores it and uses it like a derived key. The trust boundary still includes Salesforce storing the material.
|
||||
- **BYOKMS / EKM**: key material *never leaves your external KMS*. Salesforce makes a callout to the KMS to wrap/unwrap. This keeps the material entirely under your control.
|
||||
|
||||
## Cache-Only Keys and replay detection
|
||||
|
||||
Cache-Only Keys go further than EKM: Salesforce fetches key material on every use and never persists it — only caches it transiently.
|
||||
|
||||
`enableReplayDetection` protects those callouts against replay attacks using a nonce. The contract is a **one-way dependency**:
|
||||
|
||||
> *Requires `enableCacheOnlyKeys=true` before setting `enableReplayDetection` to true.*
|
||||
|
||||
Two consequences the agent must respect:
|
||||
|
||||
1. Enabling Cache-Only Keys does **not** auto-enable replay detection. You must set `enableReplayDetection=true` explicitly.
|
||||
2. An org can validly run Cache-Only Keys with replay detection **off**. It is not required.
|
||||
|
||||
Setting `enableReplayDetection=true` without `enableCacheOnlyKeys=true` is an invalid combination and will be rejected.
|
||||
@ -0,0 +1,23 @@
|
||||
# Tenant Secret Lifecycle
|
||||
|
||||
A **tenant secret** is the per-org root from which Shield derives (or wraps) data encryption keys. Its lifecycle operations are **UI/REST-only** — there is no `*.settings-meta.xml` for rotating, destroying, or uploading a tenant secret. When a user asks about these, return guidance and point them at Setup or the REST/Tooling API, not a metadata file.
|
||||
|
||||
## Operations
|
||||
|
||||
| Operation | What it does | Surface |
|
||||
|-----------|--------------|---------|
|
||||
| Generate | Creates a new active tenant secret for a key type | Setup → Key Management, or REST |
|
||||
| Rotate | Generates a new tenant secret; existing data stays readable under prior secrets | Setup / REST |
|
||||
| Destroy | Permanently destroys key material; data encrypted under it becomes unrecoverable | Setup (destructive, requires confirmation) |
|
||||
| BYOK upload | Uploads customer-supplied key material as the tenant secret | Setup / REST |
|
||||
| Cache-Only callout setup | Configures the named credential / callout endpoint the key service is fetched from | Setup (Named Credentials) |
|
||||
|
||||
## Rotation semantics
|
||||
|
||||
Rotating a tenant secret makes a new secret **active**; prior secrets are retained as **archived** so existing ciphertext stays readable. New writes use the active secret. To move existing data onto the new secret, run **background encryption** (a.k.a. the sync/re-encrypt process) — rotation alone does not re-encrypt existing rows.
|
||||
|
||||
For BYOK specifically, the sequence is: rotate to (upload) the new key material, then run background encryption to apply it. Uploading new material does not retroactively re-encrypt existing data on its own.
|
||||
|
||||
## Destruction semantics
|
||||
|
||||
Destroying a tenant secret is **irreversible**. Any data whose keys derive from that secret and has not been re-encrypted under another active secret becomes permanently unreadable. This is intentional (right-to-be-forgotten / crypto-shredding) but must be surfaced as a hard warning — there is no recovery path.
|
||||
54
skills/platform-encryption-configure/scripts/validate-encryption-metadata.sh
Executable file
54
skills/platform-encryption-configure/scripts/validate-encryption-metadata.sh
Executable file
@ -0,0 +1,54 @@
|
||||
#!/bin/bash
|
||||
# Validate a Shield Platform Encryption settings/field XML file for the two
|
||||
# deterministic contracts this skill must never get wrong:
|
||||
# 1. enableReplayDetection=true requires enableCacheOnlyKeys=true (one-way dependency)
|
||||
# 2. encryptionScheme must be one of the four valid enum values
|
||||
#
|
||||
# Usage: validate-encryption-metadata.sh <file.xml>
|
||||
# validate-encryption-metadata.sh --help
|
||||
# Output: prints "OK" on success; prints ERROR lines to stderr and exits 1 on failure.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
|
||||
echo "Usage: validate-encryption-metadata.sh <file.xml>"
|
||||
echo "Checks: replay-detection dependency, encryptionScheme enum."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
FILE="${1:-}"
|
||||
if [[ -z "$FILE" ]]; then
|
||||
echo "ERROR: no file given. Usage: validate-encryption-metadata.sh <file.xml>" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$FILE" ]]; then
|
||||
echo "ERROR: file not found: $FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
errors=0
|
||||
|
||||
# 1. Replay-detection one-way dependency.
|
||||
if grep -q '<enableReplayDetection>true</enableReplayDetection>' "$FILE"; then
|
||||
if ! grep -q '<enableCacheOnlyKeys>true</enableCacheOnlyKeys>' "$FILE"; then
|
||||
echo "ERROR: enableReplayDetection=true requires enableCacheOnlyKeys=true in the same file." >&2
|
||||
errors=$((errors + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. encryptionScheme enum.
|
||||
scheme=$(grep -oE '<encryptionScheme>[^<]*</encryptionScheme>' "$FILE" | sed -E 's/<\/?encryptionScheme>//g' || true)
|
||||
if [[ -n "$scheme" ]]; then
|
||||
case "$scheme" in
|
||||
ProbabilisticEncryption|CaseSensitiveDeterministicEncryption|CaseInsensitiveDeterministicEncryption|None) ;;
|
||||
*)
|
||||
echo "ERROR: invalid encryptionScheme '$scheme'. Must be one of: ProbabilisticEncryption, CaseSensitiveDeterministicEncryption, CaseInsensitiveDeterministicEncryption, None." >&2
|
||||
errors=$((errors + 1))
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [[ "$errors" -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
echo "OK"
|
||||
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: platform-metadata-api-context-get
|
||||
description: "Salesforce Metadata API reference for creating, understanding, or modifying metadata XML files (.object-meta.xml, .flow-meta.xml, etc.) and any of 604 metadata types (CustomObject, Flow, ApexClass, ApexTrigger, Profile, PermissionSet, Layout, ValidationRule, RecordType, EmailTemplate, ...). Use for questions about a type's fields or XML format, or Salesforce DX project structure (force-app/main/default). TRIGGER when: authoring or editing any *-meta.xml file, asking what fields/format a metadata type has, or whenever 'Salesforce metadata' or 'sfdx project' is mentioned. DO NOT TRIGGER when: SOQL/DML/runtime sObject access (use the Enterprise API skill), Tooling API developer records (ApexExecutionOverlayAction, TraceFlag), or non-XML logic (Apex code, LWC JS, Visualforce controllers)."
|
||||
description: "REQUIRED companion for Salesforce metadata generation — load this schema/API-context skill in the SAME turn as ANY metadata generation skill; if you load a generator, you ALSO load this. Use it whenever you create, generate, add, edit, or author metadata or a *-meta.xml file: custom object, custom field, formula field, picklist, lookup, master-detail, validation rule, permission set, profile, custom tab, lightning record page, flexipage, list view, custom application, flow, layout, record type, sharing rules, report, and 604 Metadata API types. It provides the authoritative schema, fields, field properties, required flags, allowed enum values, and XML structure so generated *-meta.xml deploys cleanly — skipping it causes hallucinated element names and deploy failures. Trigger on *-meta.xml, metadata schema, api context, 'Salesforce metadata', or 'sfdx project'. DO NOT use for SOQL, DML, runtime sObject access, or Tooling API records."
|
||||
metadata:
|
||||
version: "1.0"
|
||||
minApiVersion: "67.0"
|
||||
@ -53,12 +53,15 @@ To get information about a specific metadata type:
|
||||
|
||||
### Example Queries (Section-Specific)
|
||||
|
||||
- ✅ "Show me only the 'fields' section from CustomObject.json"
|
||||
- ✅ "What fields are in the 'fields' section of Profile.json?"
|
||||
- ✅ "Load the 'description' and 'fields' sections from Flow.json"
|
||||
- ✅ "Give me just the 'declarative_metadata_sample_definition' from ApexClass.json"
|
||||
- ❌ "Show me the CustomObject metadata type" (too broad - entire file)
|
||||
- ❌ "Load CustomObject.json" (includes unnecessary WSDL and other sections)
|
||||
**Recommended:**
|
||||
- "Show me only the 'fields' section from CustomObject.json"
|
||||
- "What fields are in the 'fields' section of Profile.json?"
|
||||
- "Load the 'description' and 'fields' sections from Flow.json"
|
||||
- "Give me just the 'declarative_metadata_sample_definition' from ApexClass.json"
|
||||
|
||||
**Avoid:**
|
||||
- "Show me the CustomObject metadata type" (too broad - entire file)
|
||||
- "Load CustomObject.json" (includes unnecessary WSDL and other sections)
|
||||
|
||||
## JSON File Structure
|
||||
|
||||
@ -116,7 +119,7 @@ Some metadata types have additional sections specific to their functionality. Se
|
||||
|
||||
### Section-Specific Loading (BEST PRACTICE)
|
||||
|
||||
**⚠️ CRITICAL WARNING: DO NOT use the `read_file` tool (or any whole-file reading tool) on these JSON files!**
|
||||
**CRITICAL WARNING: DO NOT use the `read_file` tool (or any whole-file reading tool) on these JSON files!**
|
||||
|
||||
`read_file` loads the entire file content into your context, defeating the purpose of section-specific consumption. You will waste 60-80% of your token budget loading unnecessary WSDL segments and verbose sections. (Using `Read` on small files such as this SKILL.md or the index table is fine — this rule is only about the large metadata-type JSON files.)
|
||||
|
||||
@ -140,13 +143,13 @@ See [`examples/README.md`](examples/README.md) for complete documentation and us
|
||||
|
||||
### What NOT to Do
|
||||
|
||||
**❌ NEVER use the `read_file` tool on these JSON files**:
|
||||
**NEVER use the `read_file` tool on these JSON files**:
|
||||
```text
|
||||
read_file assets/metadata_api/CustomObject.json # Loads entire file into context!
|
||||
read_file assets/metadata_api/Flow.json # Wastes 60-80% tokens!
|
||||
```
|
||||
|
||||
**❌ NEVER load all files**:
|
||||
**NEVER load all files**:
|
||||
```text
|
||||
read_file assets/metadata_api/*.json # This loads ~15MB of data!
|
||||
```
|
||||
|
||||
Loading…
Reference in New Issue
Block a user