关于 automation-sandbox-post-copy-configure
将 Salesforce 沙盒复制后自动化 JSON 配置应用到目标组织的技能。它会在变更前验证配置类型和记录,再按类型处理元数据、计划 Apex 或 Flow 激活状态。
主要功能
- 01验证配置项和目标组织
- 02变更前核验配置 API
- 03通过复合 PATCH 更新元数据
- 04单独处理计划 Apex
- 05检查状态后激活或停用 Flow
适用场景
- 01向 Salesforce 沙盒应用复制后设置
- 02更新出站消息或远程站点设置
- 03根据配置安排 Apex 作业
- 04设置目标组织中的 Flow 激活状态
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill automation-sandbox-post-copy-configure安装后在 Claude Code 中输入/automation-sandbox-post-copy-configure确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/api_request_templates.jsonscheduled_apex_template.apexexamples/sample_config_input.jsonsample_execution_summary.mdsample_scheduled_apex_config.jsonreferences/api_endpoints.mdauthentication.mdexecution_phasing.mdflow_activation_path.mdrules_gotchas.mdscheduled_apex_path.mdscripts/build-scheduled-apex.mjsclassify-patch-result.mjsmap-metadata-key.mjsplan-phases.mjsresolve-target-org.mjssoql-escape-job-name.mjs详细介绍
自动化:沙盒复制后配置
将 Salesforce 沙盒复制后自动化 JSON 配置应用到目标组织。四个规范的 ConfigurationName 值已固定:
OutboundMessages + RemoteSiteSettings → 复合 Metadata PATCH
(步骤 A–E);ScheduledApex → 匿名 Apex(步骤 F),因为
CronTrigger 为只读;Flows → 流激活(步骤 G),使用与 A–E
相同的复合 Metadata 传输方式,固定变更键为
activeVersionNumber,并进行状态感知预检(ALREADY_ACTIVE /
ALREADY_INACTIVE)。非规范名称通过推导并经 describe 验证。
每个 A–E 条目都必须通过步骤 B(describe 返回 200 + Metadata 复合
字段),之后才能规划任何 PATCH;ScheduledApex 跳过步骤 B。
未验证的条目会显示在摘要中并跳过——绝不猜测。
工具限制
只能使用 Bash 工具执行 sf CLI 命令(sf data query --use-tooling-api、sf api request rest、sf org display)。不要
使用 execute_soql 等 MCP 工具——此技能要求的复合 Metadata PATCH
模式无法通过 MCP 封装工具实现。
如果用户没有指定别名,调用 sf 时不要使用 --target-org(运行环境会设置 CLI 默认值);绝不要传入 --target-org default(会出现 NamedOrgNotFoundError)。SOQL-over-REST 使用相同的 OAuth——
不可直接访问数据库或使用非 Salesforce SQL。
停止——在发起任何 API 调用前执行
绝不要凭记忆调用组织。在首次请求前:
- 从用户提供的确切路径(默认
./post-copy-config.json) 读取完整配置 JSON。每个条目都必须包含全部五个键 (ConfigurationName、Label、Fields、IsActive、ExecutionOrder)。如果任何条目格式错误,则中止,并指出 文件路径和条目索引——不要部分应用。**不要 虚构条目。**如果文件不存在,则停止并询问;绝不要根据虚构的标签 制定计划。 - 对配置中的每个不同
ConfigurationName,在规划任何 PATCH 之前 执行推导 + describe 验证步骤(见下文)。无法验证其 API 的条目 绝不能出现在计划的 PATCH 中——必须预先拒绝。 - 除非用户已明确提供目标组织别名,否则请向用户确认。此技能会修改 正在运行的组织——写入错误的组织(例如默认别名指向生产组织)是 代价最高的失败模式。
如果你在未读取配置文件并对每个不同的
ConfigurationName 执行 describe 验证之前,就宣布“我现在将应用……”,
请停止并先完成这些步骤。
每条目的操作流程(此技能的核心)
此技能为下列规范类型提供固定映射,并为其他类型提供先推导后验证的
路径。必须按顺序对每个条目执行以下步骤。无论映射来自固定表还是
推导路径,Step B(describe 验证)都是必需的——摘要中必须显示其 HTTP 状态。
步骤 A — 确定 Tooling API sobject 和查找过滤条件
固定规范项(权威来源 — 必须原样使用,不得替换为其他 sobject 名称):
| ConfigurationName | Tooling API sobject | Record-lookup SOQL |
|---|---|---|
OutboundMessages | WorkflowOutboundMessage | SELECT Id, FullName FROM WorkflowOutboundMessage WHERE EntityDefinition.QualifiedApiName = '<Fields.Object>' — 然后在客户端选择 FullName == '<Fields.Object>.<Label>' 的行。对于此 sobject,SOQL 无法直接按 FullName 过滤。 |
RemoteSiteSettings | RemoteProxy | SELECT Id, SiteName FROM RemoteProxy WHERE SiteName = '<Label>'。 |
ScheduledApex | CronTrigger(只读 — 使用匿名 Apex 路由,见步骤 F) | 见步骤 F — 预检门控:SELECT Id FROM CronTrigger WHERE CronJobDetail.Name = '<escaped>'。 |
Flows | FlowDefinition(通过复合 Metadata PATCH 修改固定的 activeVersionNumber — 见步骤 G) | 见步骤 G — 预检门控:SELECT Id, DeveloperName, ActiveVersion.VersionNumber FROM FlowDefinition WHERE DeveloperName = '<Fields.FlowApiName>'。 |
对于规范项,必须原样使用固定的 sobject 和 SOQL;跳过“推导”步骤。不得臆造 OutboundMessage、RemoteSiteSetting 或 MasterLabel 变体。字段名称解析(覆盖项 + 大小写规则 + 存在性检查)由 scripts/map-metadata-key.mjs 负责。
非规范项:推导一个候选项(通常是 Metadata API 类型名称的单数形式);步骤 B 会排除错误猜测。使用步骤 C 中的通用 SOQL 模式。
步骤 B — 验证候选项存在且支持 Metadata 写入
对每个候选项执行 GET describe:
sf api request rest \
"/services/data/v<apiVersion>/tooling/sobjects/<Candidate>/describe/"
仅当以下两项均成立时,才接受该候选项:
- HTTP 200(该 sobject 存在于此 org 的 Tooling API 中),并且
- describe 响应的
fields数组包含名为Metadata的字段(复合字段 — PATCH 通过此字段写入)。
如果没有候选项同时通过这两项门控,将条目标记为
API_NOT_IDENTIFIED 并跳过。不得猜测 REST 路径——最好的情况是错误路径返回 404,最坏的情况则会更新错误的记录。
步骤 C — 确定记录 Id(SOQL-over-REST)
对于固定规范项:原样运行步骤 A 表格中的 SOQL(替换 <Label> / <Fields.Object>)。不得替换为其他过滤列,例如 MasterLabel — 表中固定的 SOQL 是针对该 sobject 经过测试且正确的过滤条件。
对于非规范(推导的)sobject:查询已验证的 sobject,查找由条目的 Label 标识的记录(如存在 Fields.Object,也使用该字段):
# SOQL to a variable + separate jq call — avoids the most common
# shell-quoting failure in this skill.
SOQL='SELECT Id, FullName FROM <VerifiedSobject> WHERE <UniqueFilter>'
sf data query --use-tooling-api --json --query "$SOQL" \
> /tmp/entry-<Slug>-lookup.json
ID=$(jq -r '.result.records[0].Id // empty' /tmp/entry-<Slug>-lookup.json)
根据步骤 B 的 describe 响应中显示的可查询字段,选择 <UniqueFilter>。如果行中包含 FullName 字段,SOQL 通常会拒绝直接使用 FullName = ... 进行过滤 — 应按 describe 显示的直接列进行过滤(例如 EntityDefinition.QualifiedApiName、DeveloperName、SiteName),并在客户端进行 FullName 匹配(jq -r '.result.records[] | select(.FullName == "<Object>.<Label>") | .Id')。
结果:
- 零行 →
NOT_FOUND(绝不回退到插入)。 - 客户端过滤后有多行 →
AMBIGUOUS(在摘要中列出所有 Id 并跳过;错误的 Id 比没有 Id 更糟)。 - 恰好一行 → 继续步骤 D。
步骤 D — GET 当前 Metadata、执行变更并 PATCH 回去
Metadata 是全量替换——PATCH 中省略的键会被置空。保留步骤 D-1 GET 返回的所有现有键;仅覆盖需要变更的键。摘要中的“计划 PATCH 正文”(应用和试运行)是这个完整合并后的对象——试运行中,冗长或组织专属的值可以显示为 "<preserved-from-GET>",但必须包含所有键。
步骤 D 的每个文件名都必须包含步骤 C 中的记录 <Id>(例如 /tmp/entry-<Id>-meta.json)——并行阶段的条目共用工作目录,使用共用名称会相互覆盖。
- GET 当前
Metadata→/tmp/entry-<Id>-meta.json。 - 解析变更键(仅适用于通用路径;
Flows使用步骤 G 固定的activeVersionNumber,ScheduledApex使用步骤 F 的匿名 Apex——这两种情况都不得运行此脚本)——对于每个Fields.<Xxx>,运行node scripts/map-metadata-key.mjs "<ConfigurationName>" "<ConfigFieldName>" /tmp/entry-<Id>-meta.json。如果返回{"status":"OK","key":...},则使用返回的键。如果返回{"status":"FIELD_MAP_UNKNOWN",...},则标记该条目并跳过。不得跳过此脚本并通过首字母小写的简单规则推断:RemoteSiteSettings.Fields.RemoteSiteUrl映射到Metadata.url(而非remoteSiteUrl);此类覆盖规则由脚本处理。 - PATCH——通过 heredoc 构建
/tmp/entry-<Id>-mutation.json,使用jq --slurpfile m /tmp/entry-<Id>-mutation.json '. + $m[0] | {Metadata: .}' /tmp/entry-<Id>-meta.json > /tmp/entry-<Id>-patch.json合并(保留 JSON 类型),使用-b @/tmp/entry-<Id>-patch.json发起 PATCH,并将响应正文保存到/tmp/entry-<Id>-response.json。完整 bash 命令见references/api_endpoints.md§步骤 D。绝不能使用--arg(它会把布尔值和数字转成字符串,并且特殊字符会导致问题)。使用for attempt in 1 2; do <cmd> && break; done包装 PATCH——如果发生 shell/jq 引号错误(HTTP 请求尚未发出时退出码非零),则重试一次。 - 分类——运行
node scripts/classify-patch-result.mjs "<httpCode>" /tmp/entry-<Id>-response.json。退出码为 0 →SUCCESS;退出码为 2 →FAILED(解析后的错误会输出到标准输出)。204 响应正文非空时,结果为FAILED。
步骤 E — 验证变更已生效
重新读取记录,确认 PATCH 已生效:
- 如果变更的字段是该 sobject 上可直接通过 SOQL 查询的列(极少见——大多数可写入
Metadata的字段都不满足此条件),按 Id 执行 SELECT 即可。 - 否则,GET 记录并检查
.Metadata中对应的键。
如果读回的值与请求值不匹配,记录 FAILED_VERIFY——PATCH 返回了 204,但效果不可见(通常是命名或权限问题)。
步骤 F — Scheduled Apex(匿名 Apex 路径)
阅读 references/scheduled_apex_path.md——步骤 A–E 不适用(CronTrigger 为只读)。该参考文档包含 F-1→F-4 的匿名 Apex 操作流程(预检 AMBIGUOUS 门控、代码片段构建、sf apex run、验证、试运行)以及步骤 F 的摘要格式。
步骤 G — Flow 激活/停用(Flows)
阅读 references/flow_activation_path.md——步骤 A 和 B 仍适用(FlowDefinition 是指定的 sobject;步骤 B 会确认复合字段 Metadata)。该参考文档包含 G-1→G-4 的复合 Metadata PATCH 操作流程,其中变更键固定为 activeVersionNumber(不适用步骤 D-2 的键推导)、决策规则(FlowApiName 验证、ALREADY_ACTIVE / ALREADY_INACTIVE 短路处理、禁止使用的同级键)、试运行约定以及 Flows 的摘要格式。
IsActive 语义
IsActive: true→ 按照步骤 A–E 执行 PATCH(Flows则执行步骤 G)。IsActive: false→ 不执行 PATCH。记录SKIPPED_INACTIVE,并在 Follow-ups 中添加一个条目,包含该项的ConfigurationName和Label,以便客户知道该项未被改动。
原因:IsActive: false 表示“在此沙盒中未启用”,而不是“停用现有记录”——默默停用正在运行的集成,其影响范围远大于保留原状。
标准输出格式(始终使用此格式)
生成一份 Markdown 摘要,写入 ./post-copy-<mode>-summary.md(mode 为 dry-run 或 apply),并打印给用户。不得生成 JSON 旁置文件(plan/phases.json、requests/*.request.json 等)——在 Markdown 中内联所有计划中和实际发出的请求。
Flows 和 ScheduledApex 使用特定步骤的摘要格式——采用带标签的叙述块,替代下方通用格式中的 Planned request: 块(阶段表格保持不变);请参阅各自参考文档的“Summary shape”部分。其他所有 ConfigurationName 均使用通用格式。
阶段枚举由脚本管理。 运行 node scripts/plan-phases.mjs <config.json>,并原样使用其 phases[]——每个条目都包含 ordinal(标题使用,从 1 开始计数)和 executionOrder(原始值,用于 (ExecutionOrder = <raw>));稀疏值会折叠;IsActive:false 条目会预先标记为 SKIP_INACTIVE。请参阅 references/execution_phasing.md 中的完整示例。
目标组织解析由脚本管理。 运行 node scripts/resolve-target-org.mjs;将返回的 .alias 填入标题。切勿原样输出 <env:SF_TARGET_ORG> 或 $SF_TARGET_ORG。
对于 dry-run 条目,HTTP 列为 —。摘要结尾使用:
未发出任何 PATCH 请求。要应用更改,请在不带 dry-run 标志的情况下重新运行。
# Post-Copy Configure Run — <N> entries <planned|applied> against `<alias>`
Config file: `<path>`
Target org: `<alias>`
Mode: <dry-run|apply>
## Phase <ExecutionOrder> — <count> entr(y|ies)
Planned request:
- Method: `PATCH`
- Path: `/services/data/v<apiVersion>/tooling/sobjects/<VerifiedSobject>/<Id>`
- Body: `<FULL merged Metadata: every key from the Step D-1 GET overlaid with the mutated keys. Verbose/org-specific values may render as "<preserved-from-GET>", but every key must be present. NEVER emit a minimal body.>`
| ConfigurationName | Label | Object | Sobject | Describe | Outcome | HTTP |
|-------------------|-------|--------|---------|----------|---------|------|
| <name> | <lbl> | <obj> | <VerifiedSobject> | <200 or 404> | <outcome> | <code> |
## Totals
| Outcome | Count |
|---------|-------|
| <state> | <n> |
## Follow-ups
- <bullet per SKIPPED_INACTIVE / API_NOT_IDENTIFIED / AMBIGUOUS / FIELD_MAP_UNKNOWN / NOT_FOUND entry>
列语义:Object = 条目的 Fields.Object,或 —。Sobject = 步骤 A 解析出的 sobject。Describe = 步骤 B 的 HTTP 状态(200 表示已验证,404 → API_NOT_IDENTIFIED)——此列为必填,以便显示跳过的步骤 B。结果状态词汇:SUCCESS、NOT_FOUND、AMBIGUOUS、API_NOT_IDENTIFIED、FIELD_MAP_UNKNOWN、FAILED、FAILED_VERIFY、SKIPPED_INACTIVE、SKIPPED、DRY_RUN、DELETE_NOT_SUPPORTED、ALREADY_ACTIVE(Flows:请求的版本已激活——不执行 PATCH)、ALREADY_INACTIVE(Flows:请求停用,但已处于未激活状态——不执行 PATCH)、NOT_ATTEMPTED。
脚本仅供内部使用——切勿将原始 node scripts/… 标准输出或“我运行了 node …”之类的叙述内联到摘要中;应使用 JSON,采用返回的值,并按上述格式准确呈现。
范围
- 范围内:将 post-copy 配置 JSON 应用于实时组织——按
ExecutionOrder分阶段,推导并验证 sobject,通过 SOQL-over-REST 解析 Id,通过复合Metadata执行 PATCH,并报告结果。 - 范围外:根据 SOP 生成配置 JSON(委托给
automation-sandbox-post-copy-config-generate);部署 metadata-XML;ATF Java 编排器;对 describe 探测失败的条目猜测 API 路径(API_NOT_IDENTIFIED→ 停止)。
实时组织变更工具:优先先使用 --dry-run;切勿静默地通过不同端点重试失败的条目。
必需输入
应用前收集或推断:
- 配置文件路径:来自
automation-sandbox-post-copy-config-generate的 JSON(默认./post-copy-config.json)。缺失 → 停止并询问。 - 目标组织别名 / 用户名:绝不假设默认值;如果未提供,使用
sf org list --json列出并询问。 - 试运行标志(默认
false):允许读取,禁止写入——这不代表“没有网络调用”。仍需运行步骤 B describe GET、步骤 C SOQL、步骤 D-1 GET;计划中预期的 PATCH 请求体只有针对真实 Metadata 块时才准确。跳过步骤 D-3(PATCH)和 E(验证)(Flows:参见步骤 G——G-3 GET + PATCH 均跳过,G-1 + G-2 仍会运行)。读取失败时(401、404、NamedOrgNotFoundError)应报告错误并停止——绝不回退到基于内存的计划。首次运行优先使用试运行。 - 出错后继续(默认
true):true = 失败的条目不会中止当前阶段;false = 在当前阶段中途终止。
如果用户提供了明确的配置路径和目标别名,无需进一步询问即可继续。
工作流
每一步都通过 Bash 执行真实的 sf CLI 命令。绝不要只描述计划而不运行命令——此技能会修改目标组织。
-
读取 + 验证配置 JSON——使用 Read 工具加载。 每个条目都必须包含五个顶层键;格式错误 → 立即停止。
-
解析目标组织 + API 版本——
sf org display --json。 解析结果以确认别名可用,并提取result.apiVersion。 不要打印原始 JSON——其中包含访问令牌。 -
规划阶段——运行
node scripts/plan-phases.mjs <config.json>并遍历其phases[]输出。有关并发上限,请参见references/execution_phasing.md。 -
逐条目 describe 验证——对于每个不同的
ConfigurationName≠ScheduledApex,运行一次步骤 A + 步骤 B, 并缓存已验证的 sobject。步骤 B 失败 → 该名称对应的每个条目都标记为API_NOT_IDENTIFIED。ScheduledApex跳过 describe 验证(步骤 F 使用匿名 Apex,而非复合 Metadata)。Flows会执行 describe 验证(PATCH 通过FlowDefinition上的复合 Metadata 传输);只跳过步骤 D 的变更键推导(步骤 G-3 使用固定键)。 -
逐条目应用——处理每个阶段中的每个条目:
- plan-phases 返回
SKIP_INACTIVE→ 标记为SKIPPED_INACTIVE+ 添加 Follow-ups 项,然后继续。 ScheduledApex→ 步骤 F(F-1→F-4)。试运行:仅执行 F-1 + F-2; 记录为DRY_RUN。Flows→ 步骤 G(G-1→G-4)。ALREADY_ACTIVE/ALREADY_INACTIVE预检会跳过 PATCH。试运行: 仅执行 G-1 + G-2;记录为DRY_RUN。- 其他所有条目 → 步骤 C(Id)、步骤 D(GET+修改+PATCH)、步骤 E
(验证)。试运行:仅执行 C + D-1/D-2;记录为
DRY_RUN。
- plan-phases 返回
-
阶段之间——当前阶段的所有条目完成后,才能开始下一阶段。
-
写入摘要文件——按照“Canonical output shape”章节的要求,以 Markdown 格式写入
./post-copy-<mode>-summary.md;同时打印给用户。绝不写入访问令牌或原始的sf org display输出。在摘要中遮蔽 URL 内嵌的凭据(https://user:***@host/...);实际 PATCH 请求体使用原样 URL。
规则与注意事项
关键不变量(绝不插入;对每个
ConfigurationName 执行 describe 门控;复合 Metadata PATCH 保留完整的现有块;IsActive: false → SKIPPED_INACTIVE;绝不打印原始访问令牌;阶段内并发上限为 5)以及每种运行时故障的规范响应(运行中遇到 401、429、包含凭据的 URL、Fields.Action = "Delete"、describe 404、SOQL 查询返回零条或多条记录)都记录在 references/rules_gotchas.md 中。偏离步骤 A–E 流程前,先阅读该文件。
跨技能集成
| 需求 | 委托给 |
|---|---|
| 将客户 SOP 转换为此技能使用的 JSON 配置 | automation-sandbox-post-copy-config-generate |
| 在复合式 Metadata Tooling 模式之外部署元数据 XML | 匹配的 generating-* 技能 + 元数据部署流程 |
| 在目标组织中创建尚不存在的新记录 | platform-metadata-deploy |
| 分配 API 调用所需的权限集 | dx-org-permission-set-assign |
参考文件索引
| 文件 | 阅读时机 |
|---|---|
references/api_endpoints.md | 步骤 A–E — 完整通用流程 + OBM/RSS 操作示例 |
references/scheduled_apex_path.md | 步骤 F — ScheduledApex 的匿名 Apex 路径 |
references/flow_activation_path.md | 步骤 G — 流激活/停用(Flows) |
references/execution_phasing.md | 步骤 3 — plan-phases.mjs 背后的阶段分组模型 |
references/authentication.md | 步骤 2 — 会话检查 + 运行中途的 401 处理 |
references/rules_gotchas.md | 偏离步骤 A–E 之前 — 不变量 + 失败响应 |
scripts/plan-phases.mjs | 步骤 3 — 阶段规划器 |
scripts/map-metadata-key.mjs | 步骤 D-2 — Metadata 键解析器 |
scripts/classify-patch-result.mjs | 步骤 D-4 — HTTP 结果分类器 |
scripts/resolve-target-org.mjs | 输出结构 — 目标组织别名解析器 |
scripts/build-scheduled-apex.mjs | 步骤 F-2 — 匿名 Apex 代码片段构建器 |
scripts/soql-escape-job-name.mjs | 步骤 F-1/F-4 — 对 JobName 进行 SOQL 转义 |
assets/api_request_templates.json | 步骤 A–E — describe/lookup/GET+PATCH 模板 |
assets/scheduled_apex_template.apex | 步骤 F-2 — 匿名 Apex 模板 |
examples/sample_config_input.json | 步骤 1 — 配置 JSON 结构 |
examples/sample_scheduled_apex_config.json | 步骤 1 — ScheduledApex 条目结构 |
examples/sample_execution_summary.md | 步骤 7 — 摘要报告结构 |