@alipay/alipay-aipay 1.5.8-beta3 → 1.5.8-beta4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +2 -1
- package/skill/references/integration/modules/scripts/sandbox_config.sh +20 -0
- package/skill/references/normal/alipay-cli-env.md +3 -0
- package/skill/references/normal/scripts/common.sh +8 -0
- package/skill/references/normal/scripts/onboarding_discovery_runner.mjs +17 -11
- package/skill/references/onboarding/flow.md +5 -2
- package/skill/references/onboarding/modules/error-handling.md +5 -4
- package/skill/references/onboarding/modules/scripts/app.sh +58 -10
- package/skill/references/onboarding/modules/scripts/ar_sign_apply.sh +4 -1
- package/skill/references/onboarding/modules/scripts/auth.sh +5 -1
- package/skill/references/onboarding/modules/scripts/error_handler.sh +99 -86
- package/skill/references/onboarding/modules/scripts/network_retry.sh +25 -5
- package/skill/references/onboarding/modules/scripts/query_sign_status.sh +4 -0
- package/skill/references/onboarding/modules/scripts/service.sh +20 -7
- package/skill/references/onboarding/modules/scripts/upload_screenshots.sh +53 -18
package/package.json
CHANGED
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.5.8-
|
|
1
|
+
1.5.8-beta4
|
|
@@ -167,8 +167,9 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" integration-message win
|
|
|
167
167
|
- `READY`:退出码为 0;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_ENSURE_ACTION=CREATED|VERIFIED`、`SANDBOX_CONFIG_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_READY` 内部事实。
|
|
168
168
|
- `CREATE_PENDING`:退出码为 0;脚本 stdout 已渲染唯一 `sandbox.configuration.pending/CREATE` 正文;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_PENDING_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_PENDING_CREATE` 内部事实;不得出现 READY 标记。
|
|
169
169
|
- `VERIFY_PENDING`:退出码为 0;脚本 stdout 已渲染唯一 `sandbox.configuration.pending/VERIFY` 正文;唯一带 `ALIPAY_AIPAY_INTERNAL:` 前缀的 `SANDBOX_PENDING_PATH=<同一目标项目绝对路径>/.alipay-sandbox.json` 和 `FLOW:SANDBOX_CONFIG_PENDING_VERIFY` 内部事实;不得出现 READY 标记。
|
|
170
|
+
- `LOCAL_FS_RECOVERY`:仅创建分支、退出码 `77`、stdout 为空,且唯一包含 `SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE` 与 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`。这证明 `createAnonymousSandbox` 调用次数为 0;宿主支持时允许同一 `sandbox ensure` 命令访问两个 CLI 私有状态目录后原样重试一次。拒绝、不支持或再次命中时不再申请、不执行原始 MCP,直接记录 `sandboxConfigState=CREATE_PENDING` 并继续步骤 2,不新增第二份对客消息或用户确认。
|
|
170
171
|
8. 所有 `ALIPAY_AIPAY_INTERNAL:` 行只用于流程转移;即使宿主合并 stdout/stderr,也必须整行剔除后再判断对客正文。命中 `READY` 时剔除后 stdout 为空,不发送“环境检查完成”“沙箱已创建/已复核”等消息,记录 `sandboxConfigState=READY` 并直接进入步骤 2;命中待配置终态时,脚本 stdout 已包含唯一对客正文,Agent 原文发送且不得转述,分别记录 `sandboxConfigState=CREATE_PENDING|VERIFY_PENDING` 后直接进入步骤 2。两类终态都禁止追加 `jq`、`cat`、`grep`、`ls`、`stat` 或临时脚本;待配置状态还禁止读取、引用或复制当前无效/不存在配置中的任何字段。
|
|
171
|
-
9.
|
|
172
|
+
9. 除上项精确的 `LOCAL_FS_RECOVERY` 外,命令非 0、终态标记缺失/重复/冲突、路径不一致或输出含 `SANDBOX_ERROR` 时属于脚本执行协议失败,仍停留在步骤 1;不得把这种执行异常降级为待配置,也不得临场改写 jq、重放原始 MCP 命令或自行检查敏感配置。CLI 已执行后才出现本地状态权限证据时,创建请求属于 `MAYBE_SENT`,脚本不得返回恢复 marker 或重放,`ensure` 直接使用既有 `CREATE_PENDING` 正文和终态继续代码开发。Windows 配置检查未通过仍按 Windows 登记分支处理。项目内自建 JSON、Agent 自报或补充诊断结果都不能替代上述终态。
|
|
172
173
|
10. 准备阶段不得渲染 `sandbox.environment.summary` 或 `sandbox.environment.reminder`,不得展示沙箱买家密码,也不得输出“沙箱测试通过”。只有 `sandboxConfigState=READY` 且代码开发完成并通过配置后置校验后,才按步骤 5 的摘要展示规则对客展示沙箱环境配置。
|
|
173
174
|
|
|
174
175
|
### 步骤 2:产品决策
|
|
@@ -96,6 +96,13 @@ if [ "$MODE" = "ensure" ] || [ "$MODE" = "reverify" ]; then
|
|
|
96
96
|
while true; do
|
|
97
97
|
ensure_output=$(bash "$0" "$ENSURE_MODE" "$PROJECT_PATH" "$LANGUAGE" 2>&1)
|
|
98
98
|
ensure_rc=$?
|
|
99
|
+
if [ "$ensure_rc" -eq "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE" ] &&
|
|
100
|
+
[ "$(printf '%s\n' "$ensure_output" | grep -Fxc 'ALIPAY_AIPAY_INTERNAL:SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE')" -eq 1 ] &&
|
|
101
|
+
[ "$(printf '%s\n' "$ensure_output" | grep -Fxc 'ALIPAY_AIPAY_INTERNAL:CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION')" -eq 1 ]; then
|
|
102
|
+
emit_control "SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE"
|
|
103
|
+
emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
|
|
104
|
+
exit "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE"
|
|
105
|
+
fi
|
|
99
106
|
if [ "$ensure_rc" -eq 0 ] &&
|
|
100
107
|
[ "$(printf '%s\n' "$ensure_output" | grep -Fxc "ALIPAY_AIPAY_INTERNAL:SANDBOX_CONFIG_PATH=$CONFIG_PATH")" -eq 1 ] &&
|
|
101
108
|
[ "$(printf '%s\n' "$ensure_output" | grep -Fxc 'ALIPAY_AIPAY_INTERNAL:FLOW:SANDBOX_CONFIG_READY')" -eq 1 ] &&
|
|
@@ -230,10 +237,19 @@ if [ "$MODE" = "create" ]; then
|
|
|
230
237
|
ensure_gitignore_protection || { echo "SANDBOX_ERROR:无法为沙箱配置建立 Git 忽略保护" >&2; exit 1; }
|
|
231
238
|
PLATFORM=$(bash "$DETECTOR") || PLATFORM="unknown"
|
|
232
239
|
init_platform_id
|
|
240
|
+
if ! require_alipay_cli_local_state_access; then
|
|
241
|
+
emit_control "SANDBOX_LOCAL_FS_PERMISSION_MODE=CREATE"
|
|
242
|
+
emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
|
|
243
|
+
exit "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE"
|
|
244
|
+
fi
|
|
233
245
|
attempt=1
|
|
234
246
|
while true; do
|
|
235
247
|
raw=$(env -u PRODUCT PLATFORM="$PLATFORM" PLATFORM_ID="${PLATFORM_ID:-}" alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox --data '{"request":{"appType":"PUBLICAPP"}}' --json 2>&1)
|
|
236
248
|
rc=$?
|
|
249
|
+
if alipay_cli_has_local_state_permission_error "$raw"; then
|
|
250
|
+
echo "SANDBOX_ERROR:快速沙箱创建结果不明,禁止自动重复创建" >&2
|
|
251
|
+
exit 1
|
|
252
|
+
fi
|
|
237
253
|
[ "$rc" -eq 0 ] && break
|
|
238
254
|
if printf '%s' "$raw" | grep -qiE '版本过旧|version.*old'; then break; fi
|
|
239
255
|
if [ "$attempt" -lt 3 ] && printf '%s' "$raw" | grep -qiE 'MCP_SERVICE_ERROR|SERVICE_UNSTABLE|timeout|timed out|connection|network|ECONN|socket|temporar'; then
|
|
@@ -255,6 +271,10 @@ if [ "$MODE" = "create" ]; then
|
|
|
255
271
|
sleep 2
|
|
256
272
|
raw=$(env -u PRODUCT PLATFORM="$PLATFORM" PLATFORM_ID="${PLATFORM_ID:-}" alipay-cli mcp call alipay-anonymous-sandbox.createAnonymousSandbox --data '{"request":{"appType":"PUBLICAPP"}}' --json 2>&1)
|
|
257
273
|
rc=$?
|
|
274
|
+
if alipay_cli_has_local_state_permission_error "$raw"; then
|
|
275
|
+
echo "SANDBOX_ERROR:快速沙箱创建结果不明,禁止自动重复创建" >&2
|
|
276
|
+
exit 1
|
|
277
|
+
fi
|
|
258
278
|
[ "$rc" -eq 0 ] || { echo "SANDBOX_ERROR:证书密钥临时错误重试后 CLI 调用失败" >&2; exit 1; }
|
|
259
279
|
outer=$(extract_json_payload "$raw") || { echo "SANDBOX_ERROR:重试输出中未找到唯一合法 JSON 对象" >&2; exit 1; }
|
|
260
280
|
text=$(echo "$outer" | jq -r '.result.content[0].text // .content[0].text // empty')
|
|
@@ -39,6 +39,9 @@ which alipay-cli 2>/dev/null && alipay-cli version
|
|
|
39
39
|
- 若命令已经因网络失败,重试时必须复用同一条命令并申请可联网权限;不要把该失败解释为用户网络故障。
|
|
40
40
|
- `auth.sh init|confirm|mismatch` 返回 `AUTH_FLOW:RETRY_WITH_NETWORK`:当前环境未取得可确认 CLI 结果;取联网权限后重试同一完整命令,不得解释为业务或 login/logout 失败,也不得改为用户手工操作。
|
|
41
41
|
- `auth.sh init|wait|confirm|mismatch` 返回 `AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`:仅缺 CLI 状态目录权限。允许同一 runtime 命令访问 `$HOME/.alipay-cli/credentials`、`${XDG_DATA_HOME:-$HOME/.local/share}/alipay-cli/logs` 后原样重试一次;仅供 CLI 使用,Agent 禁止读取、列举、打印或复制已有内容,不改变其他脚本边界。
|
|
42
|
+
- 签约、服务、应用、截图上传及匿名沙箱创建等受控 runtime 动作同样属于“联网 + CLI 私有状态读写”命令。脚本在参数和上下文校验后、首次 `alipay-cli` 调用前,对上述两个目录执行私有随机文件的创建、固定内容写入、同目录 rename 和删除探测;同一 Shell 进程后续分页、轮询或写后核验不重复探测,不读取、列举或改动任何已有凭据和日志。
|
|
43
|
+
- 非 auth 动作在 CLI 调用前探测失败时只输出 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`,本次 CLI 调用次数为 0;允许当前同一 runtime 命令访问两个目录后原样重试一次。discovery 将其收口为唯一 `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`。拒绝、不支持或重试仍命中同一 marker 时停止申请,按当前 flow 的既有分支失败或待配置边界继续,不新增业务确认或普通对客文案。
|
|
44
|
+
- CLI 已执行后才出现本地状态权限证据时,只读动作仍可按上述 marker 原样重试一次;写动作一律视为 `MAYBE_SENT`,只走现有查询核验或 `UNKNOWN`,不得输出本地权限重试 marker、不得重放原写请求。stdout 成功 JSON 与 stderr 本地权限强证据冲突时仍按本规则阻断。
|
|
42
45
|
- `app.sh reuse|verify-key|verify-key-and-audit|audit` 已取得 RSA2 支付宝公钥但 `${XDG_CONFIG_HOME:-$HOME/.config}` 探测失败时,返回 `APP_FLOW:RETRY_WITH_LOCAL_CONFIG_PERMISSION` 和 `ALIPAY_PUBLIC_KEY_EXPORT_STATUS=RETRY_WITH_LOCAL_CONFIG_PERMISSION`。仅允许同一 runtime 命令访问该目录后原样重试一次,用于写 `<appId>-alipayPublicKey.keytext`;Agent 禁止检查已有文件。仍失败按目录文案收口。
|
|
43
46
|
- 本地纯解析、文件检查、`bash -n`、`jq` 校验等不联网命令仍可在默认沙箱内执行。
|
|
44
47
|
|
|
@@ -106,6 +106,14 @@ require_alipay_cli_local_state_access() {
|
|
|
106
106
|
ALIPAY_CLI_LOCAL_STATE_ACCESS_CONFIRMED=true
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
require_alipay_cli_local_state_access_or_emit() {
|
|
110
|
+
if require_alipay_cli_local_state_access; then
|
|
111
|
+
return 0
|
|
112
|
+
fi
|
|
113
|
+
emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
|
|
114
|
+
return "$ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE"
|
|
115
|
+
}
|
|
116
|
+
|
|
109
117
|
alipay_aipay_config_dir() {
|
|
110
118
|
local config_home="${XDG_CONFIG_HOME:-}"
|
|
111
119
|
if [ -z "$config_home" ]; then
|
|
@@ -259,13 +259,14 @@ function hasAuthBlocker(result) {
|
|
|
259
259
|
return internalLines(result.stderr).some((line) => [
|
|
260
260
|
'AUTH_FLOW:AUTH_REQUIRED',
|
|
261
261
|
'AUTH_FLOW:SCOPE_MISMATCH',
|
|
262
|
-
'AUTH_FLOW:MCC_MISMATCH'
|
|
263
|
-
'AUTH_FLOW:RETRY_WITH_NETWORK',
|
|
264
|
-
'AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION',
|
|
265
|
-
'AUTH_FLOW:LOGOUT_STILL_LOGGED_IN'
|
|
262
|
+
'AUTH_FLOW:MCC_MISMATCH'
|
|
266
263
|
].includes(line));
|
|
267
264
|
}
|
|
268
265
|
|
|
266
|
+
function hasLocalFsRecovery(result) {
|
|
267
|
+
return internalLines(result.stderr).includes('CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION');
|
|
268
|
+
}
|
|
269
|
+
|
|
269
270
|
function hasNetworkRecovery(result) {
|
|
270
271
|
return internalLines(result.stderr).some((line) =>
|
|
271
272
|
line.startsWith('NETWORK_RETRY_EXHAUSTED=') && line.endsWith(':RETRY_WITH_NETWORK'));
|
|
@@ -300,7 +301,8 @@ async function runDiscovery(args) {
|
|
|
300
301
|
|
|
301
302
|
const executedResults = [signingResult, serviceResult, applicationResult].filter(Boolean);
|
|
302
303
|
const globalBlocker = executedResults.some(hasAuthBlocker) ? 'AUTHORIZATION_INVALID' : '';
|
|
303
|
-
const
|
|
304
|
+
const retryWithLocalFs = !globalBlocker && executedResults.some(hasLocalFsRecovery);
|
|
305
|
+
const retryWithNetwork = !globalBlocker && !retryWithLocalFs && executedResults.some(hasNetworkRecovery);
|
|
304
306
|
const envelope = {
|
|
305
307
|
schema_ver: 1,
|
|
306
308
|
product_type: productType,
|
|
@@ -312,19 +314,23 @@ async function runDiscovery(args) {
|
|
|
312
314
|
};
|
|
313
315
|
if (globalBlocker) envelope.global_blocker = globalBlocker;
|
|
314
316
|
emitEnvelope(envelope);
|
|
315
|
-
if (
|
|
317
|
+
if (retryWithLocalFs) {
|
|
318
|
+
process.stderr.write(`${internalPrefix}DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION\n`);
|
|
319
|
+
} else if (retryWithNetwork) {
|
|
316
320
|
process.stderr.write(`${internalPrefix}DISCOVERY_FLOW:RETRY_WITH_NETWORK\n`);
|
|
317
321
|
}
|
|
318
322
|
|
|
319
|
-
const customerOutputResults =
|
|
320
|
-
?
|
|
321
|
-
:
|
|
323
|
+
const customerOutputResults = retryWithLocalFs
|
|
324
|
+
? []
|
|
325
|
+
: globalBlocker
|
|
326
|
+
? executedResults.filter(hasAuthBlocker)
|
|
327
|
+
: executedResults;
|
|
322
328
|
const customerOutputs = customerOutputResults
|
|
323
329
|
.map((result) => result.stdout.trim())
|
|
324
330
|
.filter(Boolean);
|
|
325
331
|
let uniqueCustomerOutputs = [...new Set(customerOutputs)];
|
|
326
332
|
if (globalBlocker) uniqueCustomerOutputs = uniqueCustomerOutputs.slice(0, 1);
|
|
327
|
-
if (!globalBlocker) {
|
|
333
|
+
if (!globalBlocker && !retryWithLocalFs) {
|
|
328
334
|
uniqueCustomerOutputs.push(renderDiscoverySummary(productType, {
|
|
329
335
|
signStatus: envelope.signing.query_status === 'SUCCESS' ? envelope.signing.sign_status : 'QUERY_FAILED',
|
|
330
336
|
serviceFlow: envelope.service.query_status === 'SUCCESS' ? envelope.service.flow : 'QUERY_FAILED',
|
|
@@ -332,7 +338,7 @@ async function runDiscovery(args) {
|
|
|
332
338
|
}));
|
|
333
339
|
}
|
|
334
340
|
if (uniqueCustomerOutputs.length > 0) process.stdout.write(`${uniqueCustomerOutputs.join('\n\n')}\n`);
|
|
335
|
-
return globalBlocker || retryWithNetwork ? 2 : 0;
|
|
341
|
+
return globalBlocker || retryWithLocalFs || retryWithNetwork ? 2 : 0;
|
|
336
342
|
}
|
|
337
343
|
|
|
338
344
|
export async function runOnboardingDiscoveryCli(argv = process.argv) {
|
|
@@ -255,9 +255,9 @@ Step 1 的产品/MCC 唯一事实校验和方案送达、Step 2 的环境 READY
|
|
|
255
255
|
node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-discovery run --product-type "$PRODUCT_TYPE" --sales-code "$SALES_CODE" [--reuse-sign-receipt "$AUTH_SIGN_REUSE_RECEIPT"]
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
-
只接受唯一 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_RESULT=<json>`。runner stdout
|
|
258
|
+
只接受唯一 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_RESULT=<json>`。runner stdout 保留底层脚本已有的受控错误正文,并在没有全局授权阻断或本地权限恢复时追加原 `onboarding.discovery.summary` 标准正文;必须原文送达,不得手写、压缩或再次调用 `discovery-summary`。envelope 中每个分支只允许 `SUCCESS|QUERY_FAILED|NOT_APPLICABLE`:成功分支携带原脚本的确定状态和候选,失败不得伪装为空列表;存在 `global_blocker=AUTHORIZATION_INVALID` 时停止查询组,按 Step 3 重新授权,不进入 Step 4。不存在授权阻断且任一只读子查询命中 CLI 私有状态 preflight/调用后权限证据时,runner 只输出一个 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`,不输出摘要或网络恢复 marker;否则底层重试耗尽具有 DNS、连接、timeout、socket/TLS、`fetch failed`、`network_error`、`网络连接失败` 或连接前沙箱禁网强证据时,runner 额外且至多输出一个 `ALIPAY_AIPAY_INTERNAL:DISCOVERY_FLOW:RETRY_WITH_NETWORK`。支付宝侧明确服务异常、业务错误、裸 `permission denied` 或结构异常不得触发这两个恢复 marker。
|
|
259
259
|
|
|
260
|
-
命中 `DISCOVERY_FLOW:RETRY_WITH_NETWORK` 时先暂存本次 stdout,不对客发送;宿主支持时申请联网权限,并以完全相同的产品、salesCode 和签约复用回执原样重试同一条 discovery 命令一次。重试成功只送达重试后的 stdout 并继续;权限被拒绝、宿主不支持时送达首次 stdout 一次;重试仍返回同一 marker 时只送达重试后的 stdout
|
|
260
|
+
命中 `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 时 stdout 必须为空;宿主支持时只允许当前同一 discovery 命令访问两个 CLI 私有状态目录并原样重试一次。拒绝、不支持或再次命中时停止申请,按最新 envelope 保留失败分支,不渲染额外恢复文案。命中 `DISCOVERY_FLOW:RETRY_WITH_NETWORK` 时先暂存本次 stdout,不对客发送;宿主支持时申请联网权限,并以完全相同的产品、salesCode 和签约复用回执原样重试同一条 discovery 命令一次。重试成功只送达重试后的 stdout 并继续;权限被拒绝、宿主不支持时送达首次 stdout 一次;重试仍返回同一 marker 时只送达重试后的 stdout 一次并停止再次申请。两类恢复都不新增对客文案、确认点或 closeout 枚举。优先级固定为 `AUTHORIZATION_INVALID`、本地权限、网络权限,禁止同时进入两个恢复分支。
|
|
261
261
|
|
|
262
262
|
不存在全局授权阻断时,执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding branch-summary` 加载 Step 4 和三个分支共用的 Step 5 约束;共用区块只加载一次,不得整份读取后续 flow。
|
|
263
263
|
|
|
@@ -275,6 +275,7 @@ Step 5 直接使用对应 phase 的完整命令;仅出现脚本异常、需要
|
|
|
275
275
|
|
|
276
276
|
- MCP 认证失败或产品/scope/MCC 授权不匹配:discovery runner 子查询只上报认证失效事实,不在并发子进程内 logout;立即停止查询组,不再处理剩余查询结果。授权不匹配时调用 Step 3 的 `auth.sh mismatch`,由该命令统一 logout 并重新授权。重新授权后仅恢复尚未执行的查询,保留重新授权前已经成功取得且主体未变化的结果。若无法确认仍为同一账号主体,则重新执行全部适用查询。
|
|
277
277
|
- 强网络证据且无授权阻断:只按上文 `DISCOVERY_FLOW:RETRY_WITH_NETWORK` 契约申请权限并原样重试 discovery 一次;连接前沙箱明确禁网不在子脚本内浪费两次固定间隔重试。远端服务异常仍使用既有分支失败和收口,不误报为本地联网权限。
|
|
278
|
+
- CLI 私有状态权限且无授权阻断:只按上文 `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 契约恢复同一只读查询组一次;不得改走 auth mismatch、不得解释为网络或支付宝侧查询失败。
|
|
278
279
|
- 单个业务失败、响应结构错误或 MCP 服务失败,但当前会话仍有效:把该分支标记为查询失败,继续其他不依赖该结果的只读查询,最后一次性输出“已取得结果 + 失败分支”摘要。不得把失败视为空列表;应用查询失败时禁止应用复用/创建,服务查询失败时禁止服务复用/新建/修改。
|
|
279
280
|
- 签约查询失败:只阻断签约分支;可继续应用/服务查询、候选展示、资源决策及其独立资料采集和写操作。不得推断签约状态,不得采集签约截图或签约所需 APP 名称,`signStatus` 和签约 `materialStatus` 保持未设置。签约查询恢复成功后,只有状态为 `NOT_SIGNED` 才进入签约材料类别。应用或服务查询失败也只阻断对应分支;认证失败、授权不匹配或无法确认登录主体一致时仍是全局阻断。
|
|
280
281
|
|
|
@@ -313,6 +314,8 @@ node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" onboarding-message mate
|
|
|
313
314
|
|
|
314
315
|
按“签约 -> 服务 -> 应用”默认顺序,一次只处理一个实际适用且可推进的类别;顺序不构成分支依赖。认证、授权或主体变化时全局停止并重新校验;单分支失败只登记该分支恢复动作,继续其他独立分支,不清除已验证材料或成功事实。全部适用分支已处理,或失败、未知及外部待办均已登记后,才执行 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding closeout`;仍有可推进分支时不得提前收口。
|
|
315
316
|
|
|
317
|
+
本阶段的签约、服务、应用和截图 runtime 命令在 CLI 调用前返回唯一 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` 时,当前业务调用次数为 0;允许同一命令访问 CLI 私有状态目录后原样重试一次,不重收材料、不重开页面、不新增确认。拒绝、不支持或再次命中时停止申请并按当前分支既有失败边界收口。CLI 已执行后出现同类证据时,读操作仍可进入该恢复;签约提交、服务保存、应用创建/公钥页/提审和截图上传等写操作只走既有核验或 `UNKNOWN`,禁止因权限 marker 重放。
|
|
318
|
+
|
|
316
319
|
轮到某类别且即将执行其业务动作时,先执行对应 loader:签约用 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding signing --product-type "$PRODUCT_TYPE"`,仅按量付费服务用 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding service --product-type "$PRODUCT_TYPE"`,应用用 `node "<SKILL_DIR>/references/normal/scripts/runtime.mjs" flow onboarding application --product-type "$PRODUCT_TYPE"`。loader 自动记录该分支 stage;未轮到或不适用的分支不得加载、不得记录。
|
|
317
320
|
|
|
318
321
|
<!-- ALIPAY_AIPAY_FLOW:ONBOARDING_BRANCH_SUMMARY:END -->
|
|
@@ -6,24 +6,25 @@
|
|
|
6
6
|
|
|
7
7
|
| 内部分类 | 已验证触发 | 恢复边界 |
|
|
8
8
|
| --- | --- | --- |
|
|
9
|
-
| `MCP_AUTH_ERROR` | HTTP 401
|
|
9
|
+
| `MCP_AUTH_ERROR` | 非 JSON 传输错误中的 HTTP 401/认证强证据;JSON 顶层或登记 error/response 层中的数字/字符串 `401`、`unauthorized`、`Authorization is empty`、非法认证信息、`用户未登录`、`not_logged_in` 等明确登录失效证据;已认证资源动作顶层或 `data.logged_in=false` | 停止查询组;logout 后置条件成立后重授权。仍登录则阻断;不可确认或无网络时只重试同一动作,不解释为用户本机或支付宝业务失败。成功候选、说明或请求示例中的相同文本不构成认证错误 |
|
|
10
10
|
| `AUTH_MISMATCH` | `mccCode is not auth`、`salesProductCodes is not auth`、`scope is not auth` | 停止当前动作,只进 `auth.sh mismatch`;logout 后置条件未成立前禁重授权 |
|
|
11
11
|
| `MCP_SERVICE_ERROR` / `SERVICE_UNSTABLE` | 已识别连接、网络或服务异常 | 按下方有限重试规则处理;耗尽后记录受影响分支,依赖允许时继续其他独立分支,最终统一收口 |
|
|
12
12
|
| `DISCOVERY_FLOW:RETRY_WITH_NETWORK` | discovery 子查询重试耗尽,且原始输出包含 DNS、连接、timeout、socket/TLS、`fetch failed`、`network_error`、`网络连接失败` 或连接前沙箱禁网强证据 | 只在无授权阻断时输出一次;暂存首次 stdout,申请联网权限后原样重试同一 discovery 命令一次。拒绝、不支持或再次命中时停止申请,只送达最新一次既有 stdout |
|
|
13
|
-
| `
|
|
13
|
+
| `DISCOVERY_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION` | discovery 任一子查询在 CLI 调用前探测失败,或只读调用后命中 CLI 私有状态权限证据 | 授权阻断优先;本次不渲染 discovery 摘要,取得两个 CLI 私有状态目录权限后原样重试同一 discovery 命令一次,不改产品、salesCode 或签约复用回执 |
|
|
14
|
+
| `CLI_LOCAL_FS_PERMISSION` | 受控 CLI 动作 preflight 失败;或原始 stdout+stderr 命中私有状态写失败强证据,或私有状态路径与权限错误组合 | auth 使用唯一 `AUTH_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`;其他未发送或只读动作使用唯一 `CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION`。取得目录权限后原样重试一次;写动作已执行则保持 `MAYBE_SENT` 并只核验/标记 `UNKNOWN`,不得重放 |
|
|
14
15
|
| `APP_LOCAL_CONFIG_PERMISSION` | 已取 RSA2 支付宝公钥但 config 探针失败 | stdout 空;输出两个本地权限 marker,取权后原样重试当前 app 命令一次,不重开公钥页 |
|
|
15
16
|
| `OPEN_ERROR_KIND` | URL 已交付但 opener 失败 | 保留 URL 兜底;诊断不对客,不改写为业务、网络或授权失败 |
|
|
16
17
|
| `ERROR:<code>` | 当前响应中存在可识别业务错误 | 展示登记且脱敏的业务错误,修正业务条件前不重试 |
|
|
17
18
|
| `CLI_ERROR:<message>` | CLI 失败、异常结构或结果不可唯一解析 | 隐藏原始输出,停止并按登记 CLI 错误恢复,不猜成功或空结果 |
|
|
18
19
|
| `SUCCESS` | 唯一合法成功结构 | 才允许进入当前脚本的业务字段解析 |
|
|
19
20
|
|
|
20
|
-
发现阶段的单分支失败不得伪装为空列表,也不得回滚其他已成功分支;认证失效或授权不匹配属于查询组全局阻断,重新授权后恢复尚未执行的查询,主体一致性无法确认时重新查询全部适用状态。`logged_in=false` 只在已认证的签约、服务、应用和上传等资源动作中属于认证失效;`auth.sh whoami` 的成功未登录结果仍是正常登录入口,失败响应即使携带该字段也不能证明已退出。
|
|
21
|
+
发现阶段的单分支失败不得伪装为空列表,也不得回滚其他已成功分支;认证失效或授权不匹配属于查询组全局阻断,重新授权后恢复尚未执行的查询,主体一致性无法确认时重新查询全部适用状态。`logged_in=false` 只在已认证的签约、服务、应用和上传等资源动作中属于认证失效;`auth.sh whoami` 的成功未登录结果仍是正常登录入口,失败响应即使携带该字段也不能证明已退出。discovery 的恢复优先级固定为真实认证/授权阻断、本地 CLI 状态权限、网络权限、普通分支失败;`AUTH_FLOW:RETRY_WITH_NETWORK|RETRY_WITH_LOCAL_FS_PERMISSION|LOGOUT_STILL_LOGGED_IN` 不是 discovery 子查询的认证失效证据,不得映射为 `AUTHORIZATION_INVALID`。
|
|
21
22
|
|
|
22
23
|
discovery runner 和截图上传这类并发/分组动作不得让每个子进程各自 logout。子脚本在分组环境下只上报认证失效事实;由 flow 登记的唯一授权恢复入口执行一次 logout 后置条件确认。三张截图同时认证失败时只允许产生一个全局认证恢复 marker。
|
|
23
24
|
|
|
24
25
|
## 解包与业务错误
|
|
25
26
|
|
|
26
|
-
`unwrap_mcp` 兼容纯业务 JSON、唯一 MCP `content[0].text`
|
|
27
|
+
`unwrap_mcp` 兼容纯业务 JSON、唯一 MCP `content[0].text` 信封和混合日志。所有受控 CLI 捕获先合并 stdout/stderr 检查 `写入临时文件失败|删除凭据文件失败|can't rename log file` 强证据,或 `poll_state.enc.tmp|session.enc|.alipay-cli/credentials|alipay-cli/logs` 与 `permission denied|operation not permitted|EACCES|EPERM` 组合;命中时不得按成功 JSON 或 `network_error` 处理,裸后端 `permission denied` 不算本地权限。其他 CLI 捕获再执行唯一性解析:单个 JSON 加普通日志可接受,相同 JSON 重复可去重,多个不同 JSON 或成功/失败冲突不得选择任一路,也不得回显原始响应。每次 MCP 调用先把唯一原始结果交给 `handle_error`,返回成功后才调用 `unwrap_mcp` 和当前脚本的业务解析;不得统一假定成功字段位于 `.success`。认证分类只扫描真实响应错误层,禁止递归扫描成功候选、业务说明或请求示例中的错误样例。
|
|
27
28
|
|
|
28
29
|
每次 logout 失败后只执行一次 `whoami` 后置核验;logout 明确成功时无需额外核验,最多尝试 3 次 logout。`whoami` 后置核验必须先满足唯一 JSON、退出码为 0 且 `detect_error=SUCCESS`,再读取类型正确的 `logged_in/is_expired`;业务失败响应即使携带 `logged_in=false` 也不能证明已退出。logout 网络/服务异常、冲突输出或结果无法确认时,即使 `whoami` 仍显示已登录,也只能转 `AUTH_FLOW:RETRY_WITH_NETWORK` 并重试同一动作。logout 是可由 `whoami` 验证最终状态的幂等恢复动作:明确业务失败且仍登录时,脚本内部使用“初次 + 2 次”固定预算、间隔 3 秒重试完全相同的 logout;任一次确认已退出即继续,全部耗尽且仍登录才转 `AUTH_FLOW:LOGOUT_STILL_LOGGED_IN`。所有失败分支都不得执行 login。每个 auth 动作只允许产生一个最终 `AUTH_FLOW:*`,具体恢复 marker 已由错误处理器输出时,调用方不得追加 `AUTH_FLOW:FAILED`。
|
|
29
30
|
|
|
@@ -314,11 +314,15 @@ query_security_key() {
|
|
|
314
314
|
REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
|
|
315
315
|
fi
|
|
316
316
|
|
|
317
|
+
require_alipay_cli_local_state_access_or_emit || return "$?"
|
|
317
318
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
318
319
|
run_network_retry RESULT read application_security_key -- alipay-cli mcp call apprelease.queryApplicationSecurityKey \
|
|
319
320
|
-d "$REQUEST_JSON" \
|
|
320
321
|
--json
|
|
321
322
|
RETRY_RC=$?
|
|
323
|
+
if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
324
|
+
return "$RETRY_RC"
|
|
325
|
+
fi
|
|
322
326
|
if [ "$RETRY_RC" -ne 0 ]; then
|
|
323
327
|
echo "❌ 应用安全密钥查询因网络或服务异常在自动重试两次后仍未恢复"
|
|
324
328
|
return 1
|
|
@@ -343,11 +347,15 @@ query_application_info() {
|
|
|
343
347
|
local APP_ID="$1"
|
|
344
348
|
|
|
345
349
|
REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
|
|
350
|
+
require_alipay_cli_local_state_access_or_emit || return "$?"
|
|
346
351
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
347
352
|
run_network_retry RESULT read application_info -- alipay-cli mcp call apprelease.queryApplicationInfo \
|
|
348
353
|
-d "$REQUEST_JSON" \
|
|
349
354
|
--json
|
|
350
355
|
RETRY_RC=$?
|
|
356
|
+
if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
357
|
+
return "$RETRY_RC"
|
|
358
|
+
fi
|
|
351
359
|
if [ "$RETRY_RC" -ne 0 ]; then
|
|
352
360
|
echo "❌ 应用信息查询因网络或服务异常在自动重试两次后仍未恢复"
|
|
353
361
|
return 1
|
|
@@ -500,7 +508,7 @@ ensure_rsa2_application_key_ready() {
|
|
|
500
508
|
local PUBLIC_KEY="${2:-}"
|
|
501
509
|
local ATTEMPT=1
|
|
502
510
|
local MAX_ATTEMPTS=20
|
|
503
|
-
local INTERVAL_SECONDS=2
|
|
511
|
+
local INTERVAL_SECONDS=2 QUERY_RC
|
|
504
512
|
KEY_VERIFY_RESULT=""
|
|
505
513
|
|
|
506
514
|
if [ "${ALIPAY_AIPAY_RECOVERY_QUERY_ONCE:-0}" = "1" ]; then
|
|
@@ -512,7 +520,13 @@ ensure_rsa2_application_key_ready() {
|
|
|
512
520
|
fi
|
|
513
521
|
|
|
514
522
|
while [ "$ATTEMPT" -le "$MAX_ATTEMPTS" ]; do
|
|
515
|
-
|
|
523
|
+
query_security_key "$APP_ID" "$PUBLIC_KEY"
|
|
524
|
+
QUERY_RC=$?
|
|
525
|
+
if [ "$QUERY_RC" -ne 0 ]; then
|
|
526
|
+
if [ "$QUERY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
527
|
+
KEY_VERIFY_RESULT="LOCAL_FS_PERMISSION"
|
|
528
|
+
return "$QUERY_RC"
|
|
529
|
+
fi
|
|
516
530
|
KEY_VERIFY_RESULT="QUERY_FAILED"
|
|
517
531
|
return 1
|
|
518
532
|
fi
|
|
@@ -563,8 +577,11 @@ export_alipay_public_key_from_security_business() {
|
|
|
563
577
|
|
|
564
578
|
verify_key_and_export_alipay_public_key() {
|
|
565
579
|
local APP_ID="$1"
|
|
580
|
+
local QUERY_RC
|
|
566
581
|
|
|
567
|
-
query_security_key "$APP_ID"
|
|
582
|
+
query_security_key "$APP_ID"
|
|
583
|
+
QUERY_RC=$?
|
|
584
|
+
[ "$QUERY_RC" -eq 0 ] || return "$QUERY_RC"
|
|
568
585
|
export_alipay_public_key_from_security_business "$APP_ID"
|
|
569
586
|
}
|
|
570
587
|
|
|
@@ -578,11 +595,15 @@ app_list() {
|
|
|
578
595
|
init_app_cli_product || exit 1
|
|
579
596
|
REQUEST_JSON=$(jq -n --arg applicationType "$APP_APPLICATION_TYPE" '{request:{appTypes:[$applicationType]}}')
|
|
580
597
|
|
|
598
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
581
599
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
582
600
|
run_network_retry RESULT read application_list -- alipay-cli mcp call apprelease.queryApplicationList \
|
|
583
601
|
-d "$REQUEST_JSON" \
|
|
584
602
|
--json
|
|
585
603
|
RETRY_RC=$?
|
|
604
|
+
if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
605
|
+
exit "$RETRY_RC"
|
|
606
|
+
fi
|
|
586
607
|
if [ "$RETRY_RC" -ne 0 ]; then
|
|
587
608
|
echo "❌ 应用列表查询因网络或服务异常在自动重试两次后仍未恢复"
|
|
588
609
|
emit_control "FLOW:ERROR"
|
|
@@ -685,6 +706,7 @@ app_create() {
|
|
|
685
706
|
exit 1
|
|
686
707
|
}
|
|
687
708
|
|
|
709
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
688
710
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
689
711
|
run_network_retry RESULT write application_create -- alipay-cli mcp call apprelease.createApplication \
|
|
690
712
|
-d "$REQUEST_JSON" \
|
|
@@ -694,6 +716,8 @@ app_create() {
|
|
|
694
716
|
echo "❌ 应用创建响应不明;现有应用列表无法把同类型新应用唯一归属于本次创建,结果标记为 UNKNOWN,禁止自动重复创建"
|
|
695
717
|
emit_control "APPLICATION_CREATE_RESULT=UNKNOWN"
|
|
696
718
|
exit 1
|
|
719
|
+
elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
720
|
+
exit "$RETRY_RC"
|
|
697
721
|
elif [ "$RETRY_RC" -ne 0 ]; then
|
|
698
722
|
echo "❌ 应用创建因明确未发送的网络异常在自动重试两次后仍未恢复"
|
|
699
723
|
emit_control "APPLICATION_CREATE_RESULT=FAILED"
|
|
@@ -766,6 +790,7 @@ app_key() {
|
|
|
766
790
|
--arg publicKey "$PUBLIC_KEY" \
|
|
767
791
|
'{request:{appId:$appId,signType:"RSA2",publicKey:$publicKey}}')
|
|
768
792
|
|
|
793
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
769
794
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
770
795
|
run_network_retry RESULT write application_key_confirm_page -- alipay-cli mcp call apprelease.createKeyConfirmPage \
|
|
771
796
|
-d "$REQUEST_JSON" \
|
|
@@ -775,6 +800,8 @@ app_key() {
|
|
|
775
800
|
echo "❌ 公钥确认页生成响应不明,结果标记为 UNKNOWN;不得自动生成第二个页面"
|
|
776
801
|
emit_control "KEY_PAGE_RESULT=UNKNOWN"
|
|
777
802
|
exit 1
|
|
803
|
+
elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
804
|
+
exit "$RETRY_RC"
|
|
778
805
|
elif [ "$RETRY_RC" -ne 0 ]; then
|
|
779
806
|
echo "❌ 公钥确认页因明确未发送的网络异常在自动重试两次后仍未恢复"
|
|
780
807
|
emit_control "KEY_PAGE_RESULT=FAILED"
|
|
@@ -872,13 +899,14 @@ submit_application_audit() {
|
|
|
872
899
|
local APP_ID="$1"
|
|
873
900
|
REQUEST_JSON=$(jq -n --arg appId "$APP_ID" '{request:{appId:$appId}}')
|
|
874
901
|
|
|
902
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
875
903
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
876
904
|
run_network_retry RESULT write application_audit -- alipay-cli mcp call apprelease.submitApplicationAudit \
|
|
877
905
|
-d "$REQUEST_JSON" \
|
|
878
906
|
--json
|
|
879
907
|
RETRY_RC=$?
|
|
880
908
|
if [ "$RETRY_RC" -eq 75 ]; then
|
|
881
|
-
if query_application_info "$APP_ID"; then
|
|
909
|
+
if ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER=1 query_application_info "$APP_ID"; then
|
|
882
910
|
ACTUAL_STATUS=$(extract_application_status)
|
|
883
911
|
case "$ACTUAL_STATUS" in
|
|
884
912
|
AUDITING|ON_LINE)
|
|
@@ -893,6 +921,8 @@ submit_application_audit() {
|
|
|
893
921
|
echo "❌ 应用提审响应不明且现有状态无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复提审"
|
|
894
922
|
emit_control "APPLICATION_AUDIT_RESULT=UNKNOWN"
|
|
895
923
|
exit 1
|
|
924
|
+
elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
925
|
+
exit "$RETRY_RC"
|
|
896
926
|
elif [ "$RETRY_RC" -ne 0 ]; then
|
|
897
927
|
echo "❌ 应用提审因明确未发送的网络异常在自动重试两次后仍未恢复"
|
|
898
928
|
emit_control "APPLICATION_AUDIT_RESULT=FAILED"
|
|
@@ -962,7 +992,9 @@ app_verify_key() {
|
|
|
962
992
|
fi
|
|
963
993
|
validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
|
|
964
994
|
|
|
965
|
-
|
|
995
|
+
ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"
|
|
996
|
+
VERIFY_RC=$?
|
|
997
|
+
if [ "$VERIFY_RC" -eq 0 ]; then
|
|
966
998
|
export_alipay_public_key_from_security_business "$APP_ID"
|
|
967
999
|
EXPORT_RC=$?
|
|
968
1000
|
[ "$EXPORT_RC" -eq 0 ] || exit "$EXPORT_RC"
|
|
@@ -970,7 +1002,9 @@ app_verify_key() {
|
|
|
970
1002
|
emit_control_field "APP_ID" "$APP_ID" || exit 1
|
|
971
1003
|
emit_control "FLOW:KEY_CONFIRMED"
|
|
972
1004
|
else
|
|
973
|
-
if [ "$KEY_VERIFY_RESULT" = "
|
|
1005
|
+
if [ "$KEY_VERIFY_RESULT" = "LOCAL_FS_PERMISSION" ]; then
|
|
1006
|
+
exit "$VERIFY_RC"
|
|
1007
|
+
elif [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
|
|
974
1008
|
if ! render_key_pending; then
|
|
975
1009
|
emit_control "FLOW:ERROR"
|
|
976
1010
|
exit 1
|
|
@@ -997,8 +1031,12 @@ app_verify_key_and_audit() {
|
|
|
997
1031
|
[ -n "$APP_ID" ] || { echo "❌ 请提供 appId"; exit 1; }
|
|
998
1032
|
validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
|
|
999
1033
|
|
|
1000
|
-
|
|
1001
|
-
|
|
1034
|
+
ensure_rsa2_application_key_ready "$APP_ID" "$PUBLIC_KEY"
|
|
1035
|
+
VERIFY_RC=$?
|
|
1036
|
+
if [ "$VERIFY_RC" -ne 0 ]; then
|
|
1037
|
+
if [ "$KEY_VERIFY_RESULT" = "LOCAL_FS_PERMISSION" ]; then
|
|
1038
|
+
exit "$VERIFY_RC"
|
|
1039
|
+
elif [ "$KEY_VERIFY_RESULT" = "PENDING" ]; then
|
|
1002
1040
|
if ! render_key_pending; then
|
|
1003
1041
|
emit_control "FLOW:ERROR"
|
|
1004
1042
|
exit 1
|
|
@@ -1034,7 +1072,9 @@ app_reuse() {
|
|
|
1034
1072
|
fi
|
|
1035
1073
|
validate_control_value "$APP_ID" || { echo "❌ appId 包含非法控制字符"; exit 1; }
|
|
1036
1074
|
|
|
1037
|
-
|
|
1075
|
+
query_application_info "$APP_ID"
|
|
1076
|
+
QUERY_RC=$?
|
|
1077
|
+
if [ "$QUERY_RC" -eq 0 ]; then
|
|
1038
1078
|
APP_STATUS=$(extract_application_status)
|
|
1039
1079
|
if [ "$APP_STATUS" != "ON_LINE" ]; then
|
|
1040
1080
|
echo "❌ 仅允许复用 ON_LINE(已上线)应用,当前应用状态: ${APP_STATUS:-未知}"
|
|
@@ -1042,11 +1082,16 @@ app_reuse() {
|
|
|
1042
1082
|
exit 1
|
|
1043
1083
|
fi
|
|
1044
1084
|
else
|
|
1085
|
+
if [ "$QUERY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
1086
|
+
exit "$QUERY_RC"
|
|
1087
|
+
fi
|
|
1045
1088
|
emit_control "FLOW:ERROR"
|
|
1046
1089
|
exit 1
|
|
1047
1090
|
fi
|
|
1048
1091
|
|
|
1049
|
-
|
|
1092
|
+
query_security_key "$APP_ID"
|
|
1093
|
+
QUERY_RC=$?
|
|
1094
|
+
if [ "$QUERY_RC" -eq 0 ]; then
|
|
1050
1095
|
RSA2_KEY_INFO=$(extract_rsa2_application_key_info)
|
|
1051
1096
|
RSA2_ALIPAY_KEY_INFO=$(extract_rsa2_alipay_key_info)
|
|
1052
1097
|
ALIPAY_PUBLIC_KEY=$(extract_alipay_public_key "$RSA2_ALIPAY_KEY_INFO")
|
|
@@ -1067,6 +1112,9 @@ app_reuse() {
|
|
|
1067
1112
|
exit 1
|
|
1068
1113
|
fi
|
|
1069
1114
|
else
|
|
1115
|
+
if [ "$QUERY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
1116
|
+
exit "$QUERY_RC"
|
|
1117
|
+
fi
|
|
1070
1118
|
emit_control "FLOW:ERROR"
|
|
1071
1119
|
exit 1
|
|
1072
1120
|
fi
|
|
@@ -213,12 +213,13 @@ else
|
|
|
213
213
|
exit 1
|
|
214
214
|
fi
|
|
215
215
|
|
|
216
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
216
217
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
217
218
|
run_network_retry RESULT write sign_apply -- alipay-cli mcp call ar-sign.apply \
|
|
218
219
|
-d "$REQUEST_JSON" --json
|
|
219
220
|
RETRY_RC=$?
|
|
220
221
|
if [ "$RETRY_RC" -eq 75 ]; then
|
|
221
|
-
RECONCILE_OUTPUT=$(bash "${SCRIPT_DIR}/query_sign_status.sh" --sales-code "$SALES_CODE" --product-type "$PRODUCT_TYPE" 2>&1 || true)
|
|
222
|
+
RECONCILE_OUTPUT=$(ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER=1 bash "${SCRIPT_DIR}/query_sign_status.sh" --sales-code "$SALES_CODE" --product-type "$PRODUCT_TYPE" 2>&1 || true)
|
|
222
223
|
RECONCILED_STATUS=$(printf '%s\n' "$RECONCILE_OUTPUT" | sed -n 's/^ALIPAY_AIPAY_INTERNAL:SIGN_STATUS=//p' | tail -1)
|
|
223
224
|
if [ "$RECONCILED_STATUS" = "SIGN_SUBMITTED" ] || [ "$RECONCILED_STATUS" = "SIGNED_EFFECTIVE" ]; then
|
|
224
225
|
emit_control "SIGN_APPLY_RESULT=SUCCESS"
|
|
@@ -232,6 +233,8 @@ if [ "$RETRY_RC" -eq 75 ]; then
|
|
|
232
233
|
echo "❌ 签约提交响应不明且现有查询无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复提交"
|
|
233
234
|
emit_control "SIGN_APPLY_RESULT=UNKNOWN"
|
|
234
235
|
exit 1
|
|
236
|
+
elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
237
|
+
exit "$RETRY_RC"
|
|
235
238
|
elif [ "$RETRY_RC" -ne 0 ]; then
|
|
236
239
|
echo "❌ 签约提交因明确未发送的网络异常在自动重试两次后仍未恢复"
|
|
237
240
|
emit_control "SIGN_APPLY_RESULT=FAILED"
|
|
@@ -513,10 +513,14 @@ validate_current_authorization() {
|
|
|
513
513
|
mcc_match="true"
|
|
514
514
|
if [ "$scope_match" = "true" ]; then
|
|
515
515
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
516
|
-
run_network_retry mcc_raw read authorization_mcc_check -- alipay-cli mcp call ar-query.queryArInfosBySalesProd \
|
|
516
|
+
ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER=1 run_network_retry mcc_raw read authorization_mcc_check -- alipay-cli mcp call ar-query.queryArInfosBySalesProd \
|
|
517
517
|
-d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
|
|
518
518
|
--json
|
|
519
519
|
retry_rc=$?
|
|
520
|
+
if [ "$retry_rc" -eq "$AUTH_LOCAL_STATE_PERMISSION_EXIT_CODE" ]; then
|
|
521
|
+
auth_local_state_permission_failure
|
|
522
|
+
return 1
|
|
523
|
+
fi
|
|
520
524
|
if [ "$retry_rc" -ne 0 ]; then
|
|
521
525
|
echo "❌ 授权 MCC 校验因网络或服务异常在自动重试两次后仍未恢复"
|
|
522
526
|
emit_control "AUTH_FLOW:RETRY_WITH_NETWORK"
|
|
@@ -179,10 +179,20 @@ extract_first_named_field() {
|
|
|
179
179
|
extract_error_object() {
|
|
180
180
|
echo "$1" | jq -c '
|
|
181
181
|
[
|
|
182
|
-
|
|
182
|
+
.,
|
|
183
|
+
(.error? | objects),
|
|
184
|
+
(.data? | objects),
|
|
185
|
+
(.data.error? | objects),
|
|
186
|
+
(.response? | objects),
|
|
187
|
+
(.data.response? | objects),
|
|
188
|
+
(.errorContext.errorStack[]? | objects)
|
|
189
|
+
|
|
|
183
190
|
select(
|
|
184
191
|
((.errorCode? | type) == "string" and .errorCode != "") or
|
|
185
|
-
((.errorCode? | type) == "
|
|
192
|
+
((.errorCode? | type) == "number") or
|
|
193
|
+
((.errorCode? | type) == "object" and
|
|
194
|
+
(((.errorCode.code? // "") | tostring) != "" or
|
|
195
|
+
(((.errorCode.status? // "") | tostring) != "")))
|
|
186
196
|
)
|
|
187
197
|
][0] // empty
|
|
188
198
|
' 2>/dev/null
|
|
@@ -298,8 +308,15 @@ extract_error_object_field() {
|
|
|
298
308
|
extract_success_state() {
|
|
299
309
|
local JSON="$1"
|
|
300
310
|
echo "$JSON" | jq -r '
|
|
301
|
-
if
|
|
302
|
-
|
|
311
|
+
if .success? == false or
|
|
312
|
+
((.data? | type) == "object" and .data.success? == false) or
|
|
313
|
+
((.response? | type) == "object" and .response.success? == false) or
|
|
314
|
+
((.data.response? | type) == "object" and .data.response.success? == false) then "false"
|
|
315
|
+
elif .success? == true or
|
|
316
|
+
((.data? | type) == "object" and .data.success? == true) or
|
|
317
|
+
((.response? | type) == "object" and .response.success? == true) or
|
|
318
|
+
((.data.response? | type) == "object" and .data.response.success? == true) or
|
|
319
|
+
(((.code? // "") | tostring) == "10000") then "true"
|
|
303
320
|
else "null"
|
|
304
321
|
end
|
|
305
322
|
' 2>/dev/null
|
|
@@ -342,42 +359,32 @@ detect_error() {
|
|
|
342
359
|
# 先解包 MCP 信封,再进行错误检测
|
|
343
360
|
local UNWRAPPED=$(unwrap_mcp "$CLI_RESULT")
|
|
344
361
|
|
|
345
|
-
# 1. 优先检测 MCP
|
|
346
|
-
#
|
|
347
|
-
if echo "$UNWRAPPED" |
|
|
348
|
-
"
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"用户未登录|not[ _-]+logged[ _-]+in|(authorization|authentication|login)[-[:space:]_:=]+(is[-[:space:]_:=]+)?required|(access|auth|authentication|authorization)[ _-]+token[-[:space:]_:=]+(is[-[:space:]_:=]+)?(missing|expired)|(missing|expired)[-[:space:]_:=]+(access|auth|authentication|authorization)[ _-]+token"; then
|
|
372
|
-
echo "MCP_AUTH_ERROR"
|
|
373
|
-
return
|
|
374
|
-
fi
|
|
375
|
-
|
|
376
|
-
if echo "$UNWRAPPED" | jq -e --arg context "$ERROR_CONTEXT" '
|
|
377
|
-
def is_auth_code:
|
|
378
|
-
type == "string" and (
|
|
379
|
-
ascii_downcase as $code |
|
|
380
|
-
[
|
|
362
|
+
# 1. 优先检测 MCP 认证错误。非 JSON 只接受传输层强证据;JSON 只检查
|
|
363
|
+
# 顶层响应和登记的 error/response 层,不递归扫描成功候选或示例数据。
|
|
364
|
+
if ! echo "$UNWRAPPED" | jq -e . >/dev/null 2>&1; then
|
|
365
|
+
if echo "$UNWRAPPED" | grep -qiE \
|
|
366
|
+
"HTTP[[:space:]]*401|Authorization is empty|非法的?认证信息|用户未登录|not[ _-]+logged[ _-]+in|(authorization|authentication|login)[-[:space:]_:=]+(is[-[:space:]_:=]+)?required|(access|auth|authentication|authorization)[ _-]+token[-[:space:]_:=]+(is[-[:space:]_:=]+)?(missing|expired)|(^|[^[:alnum:]_])(missing|expired)[-[:space:]_:=]+(access|auth|authentication|authorization)[ _-]+token([^[:alnum:]_]|$)|(^|[^[:alnum:]_])token[-[:space:]_:=]+(is[-[:space:]_:=]+)?(missing|expired)([^[:alnum:]_]|$)"; then
|
|
367
|
+
echo "MCP_AUTH_ERROR"
|
|
368
|
+
return
|
|
369
|
+
fi
|
|
370
|
+
elif echo "$UNWRAPPED" | jq -e --arg context "$ERROR_CONTEXT" '
|
|
371
|
+
def failure_shaped:
|
|
372
|
+
type == "object" and (
|
|
373
|
+
.success? == false or
|
|
374
|
+
(.error? != null) or
|
|
375
|
+
(.errorCode? != null) or
|
|
376
|
+
((.errorMessage? | type) == "string" and .errorMessage != "") or
|
|
377
|
+
((.errorMsg? | type) == "string" and .errorMsg != "") or
|
|
378
|
+
(((.code? // "") | tostring) as $code |
|
|
379
|
+
$code != "" and $code != "0" and $code != "10000")
|
|
380
|
+
);
|
|
381
|
+
def auth_code:
|
|
382
|
+
if type != "string" and type != "number" then false
|
|
383
|
+
else
|
|
384
|
+
(tostring | ascii_downcase | gsub("^[[:space:]]+|[[:space:]]+$"; "")) as $code |
|
|
385
|
+
([
|
|
386
|
+
"401",
|
|
387
|
+
"unauthorized",
|
|
381
388
|
"not_logged_in",
|
|
382
389
|
"login_required",
|
|
383
390
|
"unauthenticated",
|
|
@@ -387,66 +394,73 @@ detect_error() {
|
|
|
387
394
|
"access_token_expired",
|
|
388
395
|
"auth_token_missing",
|
|
389
396
|
"auth_token_expired"
|
|
390
|
-
] |
|
|
391
|
-
|
|
397
|
+
] | index($code) != null)
|
|
398
|
+
end;
|
|
399
|
+
def auth_message:
|
|
400
|
+
type == "string" and test(
|
|
401
|
+
"HTTP[[:space:]]*401|Authorization is empty|非法的?认证信息|用户未登录|not[ _-]+logged[ _-]+in|(authorization|authentication|login)[-[:space:]_:=]+(is[-[:space:]_:=]+)?required|(access|auth|authentication|authorization)[ _-]+token[-[:space:]_:=]+(is[-[:space:]_:=]+)?(missing|expired)|(^|[^[:alnum:]_])(missing|expired)[-[:space:]_:=]+(access|auth|authentication|authorization)[ _-]+token([^[:alnum:]_]|$)|(^|[^[:alnum:]_])token[-[:space:]_:=]+(is[-[:space:]_:=]+)?(missing|expired)([^[:alnum:]_]|$)";
|
|
402
|
+
"i"
|
|
392
403
|
);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
(
|
|
397
|
-
(
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
)
|
|
409
|
-
(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
404
|
+
. as $root |
|
|
405
|
+
([
|
|
406
|
+
$root,
|
|
407
|
+
($root.data? | objects | select(failure_shaped)),
|
|
408
|
+
($root.response? | objects | select(failure_shaped)),
|
|
409
|
+
($root.data.response? | objects | select(failure_shaped))
|
|
410
|
+
]) as $response_nodes |
|
|
411
|
+
([
|
|
412
|
+
($root.error? | objects),
|
|
413
|
+
($root.errorCode? | objects),
|
|
414
|
+
($root.data.error? | objects),
|
|
415
|
+
($root.data.errorCode? | objects),
|
|
416
|
+
($root.response.error? | objects),
|
|
417
|
+
($root.data.response.error? | objects),
|
|
418
|
+
($root.errorContext.errorStack[]? | objects)
|
|
419
|
+
]) as $error_nodes |
|
|
420
|
+
(($response_nodes + $error_nodes) | any(.[];
|
|
421
|
+
[
|
|
422
|
+
.code?, .errorCode?, .status?, .reason?,
|
|
423
|
+
(.errorCode? | objects | (.code?, .status?, .reason?))
|
|
424
|
+
] | any(.[]; auth_code)
|
|
425
|
+
)) or
|
|
426
|
+
($response_nodes | any(.[];
|
|
427
|
+
failure_shaped and
|
|
428
|
+
([.message?, .errorMessage?, .errorMsg?, .msg?, .subMsg?] | any(.[]; auth_message))
|
|
429
|
+
)) or
|
|
430
|
+
($error_nodes | any(.[];
|
|
431
|
+
[.message?, .errorMessage?, .errorMsg?, .msg?, .subMsg?] | any(.[]; auth_message)
|
|
432
|
+
)) or
|
|
433
|
+
($context == "MCP_AUTHENTICATED_CALL" and (
|
|
434
|
+
(($root.logged_in? | type) == "boolean" and $root.logged_in == false) or
|
|
435
|
+
(($root.data? | type) == "object" and
|
|
436
|
+
($root.data.logged_in? | type) == "boolean" and $root.data.logged_in == false) or
|
|
437
|
+
(($root.data? | type) == "object" and
|
|
438
|
+
([$root.data.code?, $root.data.errorCode?, $root.data.status?, $root.data.reason?] |
|
|
439
|
+
any(.[]; auth_code)))
|
|
440
|
+
))
|
|
417
441
|
' >/dev/null 2>&1; then
|
|
418
442
|
echo "MCP_AUTH_ERROR"
|
|
419
443
|
return
|
|
420
444
|
fi
|
|
421
445
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if [ "$ERROR_CONTEXT" = "MCP_AUTHENTICATED_CALL" ] &&
|
|
425
|
-
echo "$UNWRAPPED" | jq -e '
|
|
426
|
-
(type == "object") and (
|
|
427
|
-
((.logged_in? | type) == "boolean" and .logged_in == false) or
|
|
428
|
-
((.data? | type) == "object" and
|
|
429
|
-
(.data.logged_in? | type) == "boolean" and .data.logged_in == false)
|
|
430
|
-
)
|
|
431
|
-
' >/dev/null 2>&1; then
|
|
432
|
-
echo "MCP_AUTH_ERROR"
|
|
433
|
-
return
|
|
434
|
-
fi
|
|
446
|
+
local SUCCESS_STATE
|
|
447
|
+
SUCCESS_STATE=$(extract_success_state "$UNWRAPPED")
|
|
435
448
|
|
|
436
|
-
# 2. MCP
|
|
437
|
-
|
|
449
|
+
# 2. MCP 服务不稳定(后端返回的服务异常)。已登记成功响应中的
|
|
450
|
+
# 候选描述和示例不参与错误关键词分类。
|
|
451
|
+
if [ "$SUCCESS_STATE" != "true" ] && echo "$UNWRAPPED" | grep -qiE "MCP.*服务.*不稳定|服务暂时不可用"; then
|
|
438
452
|
echo "SERVICE_UNSTABLE"
|
|
439
453
|
return
|
|
440
454
|
fi
|
|
441
455
|
|
|
442
456
|
# 3. MCP 调用失败(网络/连接错误)
|
|
443
|
-
if echo "$UNWRAPPED" | grep -qiE "MCP 调用失败|connection refused|timeout|network[ _]error|网络连接失败"; then
|
|
457
|
+
if [ "$SUCCESS_STATE" != "true" ] && echo "$UNWRAPPED" | grep -qiE "MCP 调用失败|connection refused|timeout|network[ _]error|网络连接失败"; then
|
|
444
458
|
echo "MCP_SERVICE_ERROR"
|
|
445
459
|
return
|
|
446
460
|
fi
|
|
447
461
|
|
|
448
462
|
# 4. 授权信息不匹配(MCC/产品/scope 未授权)
|
|
449
|
-
if echo "$UNWRAPPED" | grep -qiE "mccCode.*is not auth|salesProductCodes.*is not auth|scope.*is not auth"; then
|
|
463
|
+
if [ "$SUCCESS_STATE" != "true" ] && echo "$UNWRAPPED" | grep -qiE "mccCode.*is not auth|salesProductCodes.*is not auth|scope.*is not auth"; then
|
|
450
464
|
echo "AUTH_MISMATCH"
|
|
451
465
|
return
|
|
452
466
|
fi
|
|
@@ -464,9 +478,8 @@ detect_error() {
|
|
|
464
478
|
fi
|
|
465
479
|
|
|
466
480
|
# 6. CLI 命令本身的错误(success: false,从解包后的 JSON 提取)
|
|
467
|
-
# 注意: MCP
|
|
468
|
-
|
|
469
|
-
if [ "$SUCCESS" = "false" ]; then
|
|
481
|
+
# 注意: MCP 业务响应可能嵌套在登记的 data/response 层。
|
|
482
|
+
if [ "$SUCCESS_STATE" = "false" ]; then
|
|
470
483
|
local ERROR_MSG=$(extract_error_message "$UNWRAPPED")
|
|
471
484
|
echo "CLI_ERROR:$ERROR_MSG"
|
|
472
485
|
return
|
|
@@ -40,7 +40,7 @@ run_network_retry() {
|
|
|
40
40
|
|
|
41
41
|
local max_retries="${NETWORK_RETRY_MAX_RETRIES:-2}"
|
|
42
42
|
local delay_seconds="${NETWORK_RETRY_DELAY_SECONDS:-3}"
|
|
43
|
-
local attempt=0 stdout_file stderr_file stdout stderr rc classification transient dispatch
|
|
43
|
+
local attempt=0 stdout_file stderr_file stdout stderr combined result_payload rc classification transient dispatch
|
|
44
44
|
stdout_file=$(mktemp "${TMPDIR:-/tmp}/alipay-aipay-stdout.XXXXXX") || return 1
|
|
45
45
|
stderr_file=$(mktemp "${TMPDIR:-/tmp}/alipay-aipay-stderr.XXXXXX") || { rm -f "$stdout_file"; return 1; }
|
|
46
46
|
chmod 600 "$stdout_file" "$stderr_file" 2>/dev/null || true
|
|
@@ -54,10 +54,26 @@ run_network_retry() {
|
|
|
54
54
|
rc=$?
|
|
55
55
|
stdout=$(cat "$stdout_file")
|
|
56
56
|
stderr=$(cat "$stderr_file")
|
|
57
|
+
combined=$(printf '%s\n%s' "$stdout" "$stderr")
|
|
57
58
|
classification=""
|
|
58
59
|
transient=false
|
|
59
60
|
dispatch="MAYBE_SENT"
|
|
60
61
|
|
|
62
|
+
if type alipay_cli_has_local_state_permission_error >/dev/null 2>&1 &&
|
|
63
|
+
alipay_cli_has_local_state_permission_error "$combined"; then
|
|
64
|
+
NETWORK_RETRY_CLASS="CLI_LOCAL_FS_PERMISSION"
|
|
65
|
+
NETWORK_DISPATCH_STATE="MAYBE_SENT"
|
|
66
|
+
NETWORK_ATTEMPTS="$attempt"
|
|
67
|
+
printf -v "$result_var" '%s' "$stdout"
|
|
68
|
+
if [ "$operation" = "read" ]; then
|
|
69
|
+
if [ "${ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER:-0}" != "1" ]; then
|
|
70
|
+
emit_control "CLI_FLOW:RETRY_WITH_LOCAL_FS_PERMISSION"
|
|
71
|
+
fi
|
|
72
|
+
return "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}"
|
|
73
|
+
fi
|
|
74
|
+
return 75
|
|
75
|
+
fi
|
|
76
|
+
|
|
61
77
|
local stdout_is_business_json=false unwrapped_stdout=""
|
|
62
78
|
if [ -n "$stdout" ]; then
|
|
63
79
|
classification=$(detect_error "$stdout" 2>/dev/null || true)
|
|
@@ -91,7 +107,11 @@ run_network_retry() {
|
|
|
91
107
|
NETWORK_RETRY_CLASS="$classification"
|
|
92
108
|
NETWORK_DISPATCH_STATE="$dispatch"
|
|
93
109
|
NETWORK_ATTEMPTS="$attempt"
|
|
94
|
-
|
|
110
|
+
result_payload="$stdout"
|
|
111
|
+
if [ "$stdout_is_business_json" != true ] && [ "$rc" -ne 0 ] && [ -n "$stderr" ]; then
|
|
112
|
+
result_payload="$stderr"
|
|
113
|
+
fi
|
|
114
|
+
printf -v "$result_var" '%s' "$result_payload"
|
|
95
115
|
|
|
96
116
|
if [ "$transient" != true ]; then
|
|
97
117
|
return 0
|
|
@@ -101,13 +121,13 @@ run_network_retry() {
|
|
|
101
121
|
return 75
|
|
102
122
|
fi
|
|
103
123
|
|
|
104
|
-
if network_retry_is_explicit_network_restriction "$
|
|
105
|
-
network_retry_emit_exhausted "$action_name" "$classification" "$dispatch" "$attempt" "$
|
|
124
|
+
if network_retry_is_explicit_network_restriction "$combined"; then
|
|
125
|
+
network_retry_emit_exhausted "$action_name" "$classification" "$dispatch" "$attempt" "$combined"
|
|
106
126
|
return 76
|
|
107
127
|
fi
|
|
108
128
|
|
|
109
129
|
if [ "$attempt" -gt "$max_retries" ]; then
|
|
110
|
-
network_retry_emit_exhausted "$action_name" "$classification" "$dispatch" "$attempt" "$
|
|
130
|
+
network_retry_emit_exhausted "$action_name" "$classification" "$dispatch" "$attempt" "$combined"
|
|
111
131
|
return 76
|
|
112
132
|
fi
|
|
113
133
|
sleep "$delay_seconds"
|
|
@@ -74,11 +74,15 @@ fi
|
|
|
74
74
|
init_alipay_cli_product "$EXPECTED_PRODUCT_TYPE" || exit 1
|
|
75
75
|
|
|
76
76
|
# 查询签约状态
|
|
77
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
77
78
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
78
79
|
run_network_retry RESULT read query_sign_status -- alipay-cli mcp call ar-query.queryArInfosBySalesProd \
|
|
79
80
|
-d "{\"request\":{\"salesProductCodes\":[\"${SALES_CODE}\"]},\"ctx\":{}}" \
|
|
80
81
|
--json
|
|
81
82
|
RETRY_RC=$?
|
|
83
|
+
if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
84
|
+
exit "$RETRY_RC"
|
|
85
|
+
fi
|
|
82
86
|
if [ "$RETRY_RC" -ne 0 ]; then
|
|
83
87
|
echo "❌ 签约状态查询因网络或服务异常在自动重试两次后仍未恢复"
|
|
84
88
|
exit 1
|
|
@@ -97,11 +97,15 @@ fetch_service_page() {
|
|
|
97
97
|
--argjson offset "$OFFSET" \
|
|
98
98
|
'{request:{limit:$limit,offset:$offset}}')
|
|
99
99
|
|
|
100
|
+
require_alipay_cli_local_state_access_or_emit || return "$?"
|
|
100
101
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
101
102
|
run_network_retry RESULT read service_list_page -- alipay-cli mcp call a2a-pay-service.discoverBazaarServicesForMcp \
|
|
102
103
|
-d "$REQUEST_JSON" \
|
|
103
104
|
--json
|
|
104
105
|
RETRY_RC=$?
|
|
106
|
+
if [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
107
|
+
return "$RETRY_RC"
|
|
108
|
+
fi
|
|
105
109
|
if [ "$RETRY_RC" -ne 0 ]; then
|
|
106
110
|
echo "❌ 服务列表当前分页因网络或服务异常在自动重试两次后仍未恢复"
|
|
107
111
|
return 1
|
|
@@ -176,8 +180,10 @@ fetch_service_list() {
|
|
|
176
180
|
SERVICE_LIST='[]'
|
|
177
181
|
|
|
178
182
|
while true; do
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
fetch_service_page "$OFFSET" "$LIMIT"
|
|
184
|
+
FETCH_RC=$?
|
|
185
|
+
if [ "$FETCH_RC" -ne 0 ]; then
|
|
186
|
+
return "$FETCH_RC"
|
|
181
187
|
fi
|
|
182
188
|
|
|
183
189
|
if [ "$PAGE_FORMAT" = "legacy" ]; then
|
|
@@ -234,8 +240,10 @@ fetch_service_list() {
|
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
service_list() {
|
|
237
|
-
|
|
238
|
-
|
|
243
|
+
fetch_service_list
|
|
244
|
+
FETCH_RC=$?
|
|
245
|
+
if [ "$FETCH_RC" -ne 0 ]; then
|
|
246
|
+
exit "$FETCH_RC"
|
|
239
247
|
fi
|
|
240
248
|
|
|
241
249
|
SERVICE_CANDIDATES=$(echo "$SERVICE_LIST" | jq -c '[.[] | {
|
|
@@ -313,8 +321,10 @@ service_save() {
|
|
|
313
321
|
|
|
314
322
|
if [ -z "$SERVICE_ID" ]; then
|
|
315
323
|
# 创建新服务(不传 serviceId)
|
|
316
|
-
|
|
317
|
-
|
|
324
|
+
fetch_service_list
|
|
325
|
+
FETCH_RC=$?
|
|
326
|
+
if [ "$FETCH_RC" -ne 0 ]; then
|
|
327
|
+
exit "$FETCH_RC"
|
|
318
328
|
fi
|
|
319
329
|
if [ "$SERVICE_COUNT" -ge "$SERVICE_CREATE_LIMIT" ]; then
|
|
320
330
|
echo "❌ 已达到服务数量上限(${SERVICE_CREATE_LIMIT}个),无法创建新服务"
|
|
@@ -343,6 +353,7 @@ service_save() {
|
|
|
343
353
|
|
|
344
354
|
fi
|
|
345
355
|
|
|
356
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
346
357
|
export PLATFORM="${DEV_TOOL_NAME}" PLATFORM_ID="${PLATFORM_ID:-}"
|
|
347
358
|
run_network_retry RESULT write service_save -- alipay-cli mcp call a2a-pay-service.saveBazaarServiceForMcp \
|
|
348
359
|
-d "$REQUEST_JSON" \
|
|
@@ -354,7 +365,7 @@ service_save() {
|
|
|
354
365
|
emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
|
|
355
366
|
exit 1
|
|
356
367
|
fi
|
|
357
|
-
if fetch_service_list; then
|
|
368
|
+
if ALIPAY_AIPAY_SUPPRESS_LOCAL_FS_RETRY_MARKER=1 fetch_service_list; then
|
|
358
369
|
MATCH_COUNT=$(echo "$SERVICE_LIST" | jq --arg id "$SERVICE_ID" --arg name "$SERVICE_NAME" --arg desc "$SERVICE_DESC" --arg url "$RESOURCE_URL" --arg pricing "$PRICING" --arg schema "$SCHEMA_URL" '
|
|
359
370
|
[.[] | select(
|
|
360
371
|
.serviceId == $id and .serviceName == $name and .serviceDesc == $desc and
|
|
@@ -374,6 +385,8 @@ service_save() {
|
|
|
374
385
|
echo "❌ 服务修改响应不明且现有列表无法排除传播延迟,结果标记为 UNKNOWN,禁止自动重复修改"
|
|
375
386
|
emit_control "SERVICE_SAVE_RESULT=UNKNOWN"
|
|
376
387
|
exit 1
|
|
388
|
+
elif [ "$RETRY_RC" -eq "${ALIPAY_CLI_LOCAL_STATE_PERMISSION_EXIT_CODE:-77}" ]; then
|
|
389
|
+
exit "$RETRY_RC"
|
|
377
390
|
elif [ "$RETRY_RC" -ne 0 ]; then
|
|
378
391
|
if [ -z "$SERVICE_ID" ]; then
|
|
379
392
|
echo "❌ 服务创建因明确未发送的网络异常在自动重试两次后仍未恢复"
|
|
@@ -62,28 +62,54 @@ if [ ! -f "$PAY_IMG" ]; then
|
|
|
62
62
|
exit 1
|
|
63
63
|
fi
|
|
64
64
|
|
|
65
|
+
require_alipay_cli_local_state_access_or_emit || exit "$?"
|
|
66
|
+
|
|
65
67
|
# 使用 mktemp 创建并发安全的临时文件。模板需以 XXXXXX 结尾以兼容 macOS/BSD mktemp。
|
|
66
68
|
TMP_BASE="${TMPDIR:-/tmp}"
|
|
67
69
|
TMP_BASE="${TMP_BASE%/}"
|
|
68
|
-
TMP_HOME
|
|
69
|
-
TMP_SHOP
|
|
70
|
-
TMP_PAY
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
TMP_HOME=""
|
|
71
|
+
TMP_SHOP=""
|
|
72
|
+
TMP_PAY=""
|
|
73
|
+
TMP_HOME_ERR=""
|
|
74
|
+
TMP_SHOP_ERR=""
|
|
75
|
+
TMP_PAY_ERR=""
|
|
76
|
+
|
|
77
|
+
# 清理临时文件,包括只创建了部分文件时的失败分支。
|
|
73
78
|
cleanup_upload_tmp() {
|
|
74
|
-
|
|
79
|
+
local tmp_file
|
|
80
|
+
for tmp_file in "$TMP_HOME" "$TMP_SHOP" "$TMP_PAY" "$TMP_HOME_ERR" "$TMP_SHOP_ERR" "$TMP_PAY_ERR"; do
|
|
81
|
+
[ -z "$tmp_file" ] || rm -f "$tmp_file"
|
|
82
|
+
done
|
|
75
83
|
}
|
|
76
84
|
trap cleanup_upload_tmp EXIT
|
|
77
85
|
|
|
86
|
+
TMP_HOME=$(mktemp "${TMP_BASE}/upload_home.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
|
|
87
|
+
TMP_SHOP=$(mktemp "${TMP_BASE}/upload_shop.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
|
|
88
|
+
TMP_PAY=$(mktemp "${TMP_BASE}/upload_pay.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
|
|
89
|
+
TMP_HOME_ERR=$(mktemp "${TMP_BASE}/upload_home_err.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
|
|
90
|
+
TMP_SHOP_ERR=$(mktemp "${TMP_BASE}/upload_shop_err.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
|
|
91
|
+
TMP_PAY_ERR=$(mktemp "${TMP_BASE}/upload_pay_err.XXXXXX") || { echo "❌ 创建临时文件失败"; exit 1; }
|
|
92
|
+
|
|
78
93
|
# 并行上传 3 张截图。PLATFORM_ID 与 PRODUCT 均由公共上下文初始化。
|
|
79
|
-
PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" alipay-cli file upload "$HOME_IMG" -s payMerchantcodeSkill --json
|
|
80
|
-
|
|
81
|
-
PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" alipay-cli file upload "$
|
|
82
|
-
|
|
94
|
+
PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" alipay-cli file upload "$HOME_IMG" -s payMerchantcodeSkill --json >"$TMP_HOME" 2>"$TMP_HOME_ERR" &
|
|
95
|
+
PID_HOME=$!
|
|
96
|
+
PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" alipay-cli file upload "$SHOP_IMG" -s payMerchantcodeSkill --json >"$TMP_SHOP" 2>"$TMP_SHOP_ERR" &
|
|
97
|
+
PID_SHOP=$!
|
|
98
|
+
PLATFORM="${DEV_TOOL_NAME:-unknown}" PLATFORM_ID="${PLATFORM_ID:-}" PRODUCT="${PRODUCT:-}" alipay-cli file upload "$PAY_IMG" -s payMerchantcodeSkill --json >"$TMP_PAY" 2>"$TMP_PAY_ERR" &
|
|
99
|
+
PID_PAY=$!
|
|
100
|
+
wait "$PID_HOME"; HOME_UPLOAD_RC=$?
|
|
101
|
+
wait "$PID_SHOP"; SHOP_UPLOAD_RC=$?
|
|
102
|
+
wait "$PID_PAY"; PAY_UPLOAD_RC=$?
|
|
103
|
+
|
|
104
|
+
upload_combined_output() {
|
|
105
|
+
local stdout_file="$1" stderr_file="$2"
|
|
106
|
+
printf '%s\n%s' "$(cat "$stdout_file")" "$(cat "$stderr_file")"
|
|
107
|
+
}
|
|
83
108
|
|
|
84
109
|
# 解析图片引用值(兼容 file upload 返回 fileKey 或 URL 字段)
|
|
85
110
|
parse_image_ref() {
|
|
86
|
-
local RAW
|
|
111
|
+
local RAW
|
|
112
|
+
RAW=$(upload_combined_output "$1" "$2")
|
|
87
113
|
if [ "$(detect_error "$RAW" "MCP_AUTHENTICATED_CALL")" != "SUCCESS" ]; then
|
|
88
114
|
return 1
|
|
89
115
|
fi
|
|
@@ -114,15 +140,20 @@ parse_image_ref() {
|
|
|
114
140
|
' 2>/dev/null
|
|
115
141
|
}
|
|
116
142
|
|
|
117
|
-
HOME_REF=$(parse_image_ref "$TMP_HOME")
|
|
118
|
-
SHOP_REF=$(parse_image_ref "$TMP_SHOP")
|
|
119
|
-
PAY_REF=$(parse_image_ref "$TMP_PAY")
|
|
143
|
+
HOME_REF=$(parse_image_ref "$TMP_HOME" "$TMP_HOME_ERR")
|
|
144
|
+
SHOP_REF=$(parse_image_ref "$TMP_SHOP" "$TMP_SHOP_ERR")
|
|
145
|
+
PAY_REF=$(parse_image_ref "$TMP_PAY" "$TMP_PAY_ERR")
|
|
120
146
|
|
|
121
147
|
# 校验上传结果(含认证错误检测)
|
|
122
148
|
check_upload_error() {
|
|
123
|
-
local RAW
|
|
149
|
+
local RAW
|
|
124
150
|
local ERROR_TYPE
|
|
151
|
+
RAW=$(upload_combined_output "$1" "$2")
|
|
125
152
|
ERROR_TYPE=$(detect_error "$RAW" "MCP_AUTHENTICATED_CALL")
|
|
153
|
+
if [ "$ERROR_TYPE" = "CLI_LOCAL_FS_PERMISSION" ]; then
|
|
154
|
+
UPLOAD_LOCAL_FS_AFTER_DISPATCH=true
|
|
155
|
+
return 1
|
|
156
|
+
fi
|
|
126
157
|
if [ "$ERROR_TYPE" = "MCP_AUTH_ERROR" ] && [ "${UPLOAD_AUTH_ERROR_SEEN:-false}" = "true" ]; then
|
|
127
158
|
return 1
|
|
128
159
|
fi
|
|
@@ -136,22 +167,23 @@ check_upload_error() {
|
|
|
136
167
|
}
|
|
137
168
|
|
|
138
169
|
ERRORS=""
|
|
170
|
+
UPLOAD_LOCAL_FS_AFTER_DISPATCH=false
|
|
139
171
|
if [ -z "$HOME_REF" ]; then
|
|
140
|
-
if ! check_upload_error "$TMP_HOME"; then
|
|
172
|
+
if ! check_upload_error "$TMP_HOME" "$TMP_HOME_ERR"; then
|
|
141
173
|
ERRORS="$ERRORS - 首页截图上传失败(认证或服务异常)"$'\n'
|
|
142
174
|
else
|
|
143
175
|
ERRORS="$ERRORS - 首页截图上传后未返回可用于签约的图片引用值"$'\n'
|
|
144
176
|
fi
|
|
145
177
|
fi
|
|
146
178
|
if [ -z "$SHOP_REF" ]; then
|
|
147
|
-
if ! check_upload_error "$TMP_SHOP"; then
|
|
179
|
+
if ! check_upload_error "$TMP_SHOP" "$TMP_SHOP_ERR"; then
|
|
148
180
|
ERRORS="$ERRORS - 商品页截图上传失败(认证或服务异常)"$'\n'
|
|
149
181
|
else
|
|
150
182
|
ERRORS="$ERRORS - 商品页截图上传后未返回可用于签约的图片引用值"$'\n'
|
|
151
183
|
fi
|
|
152
184
|
fi
|
|
153
185
|
if [ -z "$PAY_REF" ]; then
|
|
154
|
-
if ! check_upload_error "$TMP_PAY"; then
|
|
186
|
+
if ! check_upload_error "$TMP_PAY" "$TMP_PAY_ERR"; then
|
|
155
187
|
ERRORS="$ERRORS - 支付页截图上传失败(认证或服务异常)"$'\n'
|
|
156
188
|
else
|
|
157
189
|
ERRORS="$ERRORS - 支付页截图上传后未返回可用于签约的图片引用值"$'\n'
|
|
@@ -159,6 +191,9 @@ if [ -z "$PAY_REF" ]; then
|
|
|
159
191
|
fi
|
|
160
192
|
|
|
161
193
|
if [ -n "$ERRORS" ]; then
|
|
194
|
+
if [ "$UPLOAD_LOCAL_FS_AFTER_DISPATCH" = true ]; then
|
|
195
|
+
emit_control "SCREENSHOT_UPLOAD_RESULT=UNKNOWN"
|
|
196
|
+
fi
|
|
162
197
|
echo "❌ 截图上传失败:"
|
|
163
198
|
printf "%s" "$ERRORS"
|
|
164
199
|
exit 1
|