@alipay/alipay-aipay 1.8.2 → 1.8.3
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 +3 -3
- package/skill/VERSION +1 -1
- package/skill/references/integration/flow.md +27 -30
- package/skill/references/integration/modules/checklist.md +4 -6
- package/skill/references/integration/modules/sandbox/sandbox-setup-guide.md +2 -2
- package/skill/references/normal/integration-worker.md +1 -1
- package/skill/references/normal/json/customer-messages.json +1 -1
- package/skill/references/normal/scripts/flow_loader.mjs +9 -0
- package/skill/references/normal/scripts/integration_message_runner.mjs +81 -14
- package/skill/references/normal/scripts/render_customer_message.mjs +6 -6
- package/skill/references/runtime-entry.md +1 -1
- package/skill-pre/VERSION +1 -1
- package/skill-pre/references/integration/flow.md +27 -30
- package/skill-pre/references/integration/modules/checklist.md +4 -6
- package/skill-pre/references/integration/modules/sandbox/sandbox-setup-guide.md +2 -2
- package/skill-pre/references/normal/integration-worker.md +1 -1
- package/skill-pre/references/normal/json/customer-messages.json +1 -1
- package/skill-pre/references/normal/scripts/flow_loader.mjs +9 -0
- package/skill-pre/references/normal/scripts/integration_message_runner.mjs +81 -14
- package/skill-pre/references/normal/scripts/render_customer_message.mjs +6 -6
- package/skill-pre/references/runtime-entry.md +1 -1
|
@@ -750,9 +750,83 @@ export function inspectImplementation(variant, projectPath, snapshot = null) {
|
|
|
750
750
|
|
|
751
751
|
findings.sort((left, right) => `${left.code}:${left.path || ''}:${left.line || 0}`
|
|
752
752
|
.localeCompare(`${right.code}:${right.path || ''}:${right.line || 0}`));
|
|
753
|
+
for (const finding of findings) {
|
|
754
|
+
finding.id = crypto.createHash('sha256')
|
|
755
|
+
.update(JSON.stringify([variant, currentSnapshot.fingerprint, finding]))
|
|
756
|
+
.digest('hex');
|
|
757
|
+
}
|
|
753
758
|
return { projectPath: canonicalPath, variant, findings };
|
|
754
759
|
}
|
|
755
760
|
|
|
761
|
+
function reviewedAuditFindings(audit, rawReview, snapshot) {
|
|
762
|
+
if (rawReview === undefined) return audit.findings.map((finding) => ({ ...finding, reviewDecision: 'UNREVIEWED' }));
|
|
763
|
+
if (typeof rawReview !== 'string' || rawReview.length > 65536) throw new Error('audit-review 必须是受控 JSON 数组');
|
|
764
|
+
let reviews;
|
|
765
|
+
try { reviews = JSON.parse(rawReview); } catch { throw new Error('audit-review 不是合法 JSON'); }
|
|
766
|
+
if (!Array.isArray(reviews)) throw new Error('audit-review 必须是 JSON 数组');
|
|
767
|
+
const findings = new Map(audit.findings.map((finding) => [finding.id, finding]));
|
|
768
|
+
const reviewedIds = new Set();
|
|
769
|
+
const decisions = new Map();
|
|
770
|
+
const excluded = new Set();
|
|
771
|
+
const sources = new Set(implementationSourceFiles(snapshot.canonicalPath, snapshot.sources, snapshot.contents));
|
|
772
|
+
for (const review of reviews) {
|
|
773
|
+
if (!review || typeof review !== 'object' || Array.isArray(review)
|
|
774
|
+
|| Object.keys(review).some((key) => !['findingId', 'decision', 'evidence'].includes(key))
|
|
775
|
+
|| !findings.has(review.findingId) || reviewedIds.has(review.findingId)) {
|
|
776
|
+
throw new Error('audit-review 必须逐项绑定当前产品和项目快照的唯一 findingId');
|
|
777
|
+
}
|
|
778
|
+
reviewedIds.add(review.findingId);
|
|
779
|
+
if (!['FALSE_POSITIVE', 'CONFIRMED', 'UNRESOLVED'].includes(review.decision)) {
|
|
780
|
+
throw new Error('audit-review decision 仅支持 FALSE_POSITIVE、CONFIRMED、UNRESOLVED');
|
|
781
|
+
}
|
|
782
|
+
decisions.set(review.findingId, review.decision);
|
|
783
|
+
if (!Array.isArray(review.evidence) || review.evidence.length === 0 || review.evidence.length > 20) {
|
|
784
|
+
throw new Error('audit-review 每项必须提供当前生产代码的定位和核对依据');
|
|
785
|
+
}
|
|
786
|
+
for (const evidence of review.evidence) {
|
|
787
|
+
if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence)
|
|
788
|
+
|| Object.keys(evidence).some((key) => !['path', 'line', 'reason'].includes(key))
|
|
789
|
+
|| typeof evidence.path !== 'string' || !evidence.path || evidence.path.includes('\\')
|
|
790
|
+
|| path.posix.isAbsolute(evidence.path) || path.win32.isAbsolute(evidence.path)
|
|
791
|
+
|| evidence.path.split('/').some((part) => !part || part === '.' || part === '..')
|
|
792
|
+
|| !Number.isInteger(evidence.line) || evidence.line < 1
|
|
793
|
+
|| typeof evidence.reason !== 'string' || evidence.reason.trim().length < 12 || evidence.reason.length > 2000
|
|
794
|
+
|| /[\u0000-\u001f\u007f]/u.test(evidence.reason)) {
|
|
795
|
+
throw new Error('audit-review evidence 必须包含规范化相对 path、有效 line 和具体单行 reason');
|
|
796
|
+
}
|
|
797
|
+
const file = path.resolve(snapshot.canonicalPath, evidence.path);
|
|
798
|
+
const source = snapshot.contents.get(file)?.toString('utf8');
|
|
799
|
+
const lines = source === undefined ? [] : stripSourceComments(source, path.extname(file).toLowerCase()).split(/\r?\n/u);
|
|
800
|
+
if (!sources.has(file) || !lines?.[evidence.line - 1]?.trim()) {
|
|
801
|
+
throw new Error('audit-review evidence 必须指向当前快照内非空的生产源码行');
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
// The runner validates the snapshot and locations; the Agent owns the code reasoning.
|
|
805
|
+
// Unreviewed, confirmed and unresolved findings remain blocking.
|
|
806
|
+
if (review.decision === 'FALSE_POSITIVE') excluded.add(review.findingId);
|
|
807
|
+
}
|
|
808
|
+
return audit.findings.filter((finding) => !excluded.has(finding.id))
|
|
809
|
+
.map((finding) => ({ ...finding, reviewDecision: decisions.get(finding.id) || 'UNREVIEWED' }));
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function mergeAuditItems(failedItems, findings) {
|
|
813
|
+
let items = failedItems && failedItems !== '无' ? [failedItems] : [];
|
|
814
|
+
if (items[0]?.trim().startsWith('[')) {
|
|
815
|
+
try {
|
|
816
|
+
const parsed = JSON.parse(items[0]);
|
|
817
|
+
if (Array.isArray(parsed) && parsed.every((item) => typeof item === 'string')) items = parsed;
|
|
818
|
+
} catch { /* Preserve plain text. */ }
|
|
819
|
+
}
|
|
820
|
+
items = items.flatMap((item) => item.split(';')).map((item) => item.trim()).filter(Boolean);
|
|
821
|
+
for (const finding of findings) {
|
|
822
|
+
const location = finding.path ? `(${finding.path}${finding.line ? `:${finding.line}` : ''})` : '';
|
|
823
|
+
const prefix = finding.reviewDecision === 'CONFIRMED' ? '' : '静态扫描疑点尚未排除:';
|
|
824
|
+
const item = `${prefix}${finding.message}${location}`;
|
|
825
|
+
if (!items.includes(item)) items.push(item);
|
|
826
|
+
}
|
|
827
|
+
return [...new Set(items)].join(';');
|
|
828
|
+
}
|
|
829
|
+
|
|
756
830
|
function scanProjectRoute(productType, projectPath, projectSelection) {
|
|
757
831
|
if (!['aipay', 'webpay', 'apppay'].includes(productType)) {
|
|
758
832
|
throw new Error('ROUTE_INSPECT_ERROR:productType 非法');
|
|
@@ -1255,10 +1329,8 @@ function runServiceNotReady(args) {
|
|
|
1255
1329
|
|
|
1256
1330
|
function runSafetyCloseout(args) {
|
|
1257
1331
|
const variant = required(args, 'variant');
|
|
1258
|
-
if (
|
|
1259
|
-
return requireCustomerMessageDelivery('integration.safety.closeout', renderMessage('integration.safety.closeout', variant, {
|
|
1260
|
-
acceptanceSummary: required(args, 'acceptance-summary')
|
|
1261
|
-
}));
|
|
1332
|
+
if (variant !== 'APPPAY') throw new Error('步骤 7 提醒仅适用于 APPPAY;AIPAY、WEBPAY 直接进入最终 checklist,上线提醒由最终 summary 输出');
|
|
1333
|
+
return requireCustomerMessageDelivery('integration.safety.closeout', renderMessage('integration.safety.closeout', variant, {}));
|
|
1262
1334
|
}
|
|
1263
1335
|
|
|
1264
1336
|
function runWindowsSandboxSummary(args) {
|
|
@@ -1348,26 +1420,21 @@ async function runChecklistResult(args) {
|
|
|
1348
1420
|
}
|
|
1349
1421
|
const input = checklistInputFromArgs(args) || await readStdin();
|
|
1350
1422
|
const audit = inspectImplementation(variant, projectPath, snapshot);
|
|
1351
|
-
|
|
1423
|
+
const findings = reviewedAuditFindings(audit, args['audit-review'], snapshot);
|
|
1424
|
+
if (findings.length === 0 && finalizesAuditFindings) {
|
|
1352
1425
|
throw new Error('finalize-audit-findings 仅用于收口实际 audit findings');
|
|
1353
1426
|
}
|
|
1354
|
-
if (
|
|
1427
|
+
if (findings.length > 0) {
|
|
1355
1428
|
if (args['finalize-audit-findings'] !== 'true') {
|
|
1356
1429
|
process.stderr.write(`${internalPrefix}CHECKLIST_AUDIT=${JSON.stringify({
|
|
1357
1430
|
state: 'REPAIR_REQUIRED',
|
|
1358
|
-
findings
|
|
1431
|
+
findings
|
|
1359
1432
|
})}\n`);
|
|
1360
1433
|
return null;
|
|
1361
1434
|
}
|
|
1362
|
-
const auditItems = audit.findings.map((finding) => {
|
|
1363
|
-
const location = finding.path ? `(${finding.path}${finding.line ? `:${finding.line}` : ''})` : '';
|
|
1364
|
-
return `${finding.message}${location}`;
|
|
1365
|
-
}).join(';');
|
|
1366
1435
|
input.overallResult = '未通过';
|
|
1367
1436
|
input.blockingDefectState = 'PRESENT';
|
|
1368
|
-
input.failedItems = input.failedItems
|
|
1369
|
-
? `${input.failedItems};${auditItems}`
|
|
1370
|
-
: auditItems;
|
|
1437
|
+
input.failedItems = mergeAuditItems(input.failedItems, findings);
|
|
1371
1438
|
if (input.executionMode === '完整接入') {
|
|
1372
1439
|
input.nextFlowReminder = '代码开发校验通过后将自动进入支付产品-产品开通;未通过时停留在代码开发。';
|
|
1373
1440
|
}
|
|
@@ -1587,8 +1587,8 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1587
1587
|
if (variables.overallResult === '未通过' && !hasFailedItems) {
|
|
1588
1588
|
throw new Error('代码开发结果为未通过时必须列出未通过项');
|
|
1589
1589
|
}
|
|
1590
|
-
if (variables.overallResult
|
|
1591
|
-
throw new Error('
|
|
1590
|
+
if (hasBlockingDefect && variables.overallResult !== '未通过') {
|
|
1591
|
+
throw new Error('存在代码、安全或必要依据阻塞缺口时,代码开发结果必须为未通过');
|
|
1592
1592
|
}
|
|
1593
1593
|
if (variables.overallResult !== '通过' && !sandboxPending && !hasBlockingDefect && !manualOnly) {
|
|
1594
1594
|
throw new Error('沙箱已就绪且没有阻塞缺口时,非通过结果只能来自人工待验证项');
|
|
@@ -1614,12 +1614,12 @@ export function renderMessage(messageId, variantName, variables, telemetryContex
|
|
|
1614
1614
|
if (variantName === 'WEBPAY' && variables.overallResult === '通过' && variables.webpayExperienceResult === '未提供浏览器付款入口或完整操作指引,需先补齐') {
|
|
1615
1615
|
throw new Error('AI 网页应用收款未交付付款入口时不得标记代码开发通过');
|
|
1616
1616
|
}
|
|
1617
|
-
if (manualOnly) {
|
|
1618
|
-
normalized.failedItems = `人工待验证项未完成:${normalized.manualItems}`;
|
|
1619
|
-
}
|
|
1620
1617
|
normalized.checklistDetails = variables.executionMode === '仅代码开发'
|
|
1621
1618
|
? ''
|
|
1622
|
-
:
|
|
1619
|
+
: [
|
|
1620
|
+
...(hasFailedItems ? [`未完成项:${normalized.failedItems}`] : []),
|
|
1621
|
+
...(hasManualItems ? [`人工待验证:${normalized.manualItems}`] : [])
|
|
1622
|
+
].join('\n');
|
|
1623
1623
|
const productName = { AIPAY: 'AI 按量付费', WEBPAY: 'AI 网页应用收款', APPPAY: 'AI 移动应用收款' }[variantName];
|
|
1624
1624
|
normalized.launchGuidance = variables.executionMode === '仅代码开发'
|
|
1625
1625
|
? productionReadinessBlock(productName, {
|
|
@@ -16,7 +16,7 @@ AI 网页应用收款覆盖 PC 网页和手机浏览器/H5,接口固定 `alipa
|
|
|
16
16
|
7. **沙箱结论**:真实快速沙箱配置不得用占位值,唯一例外是 AI 按量付费沙箱 `service_id=api_mock_service_id`,且生产禁用。`CREATE_PENDING|VERIFY_PENDING` 只允许继续代码实现,阻止字段使用、摘要、后置校验、联调和付款体验。字段校验不等于支付测试;仅完整 402 链路满足 flow 才可称沙箱测试通过。
|
|
17
17
|
8. **异步通知**:AI 网页应用收款/AI 移动应用收款必须实现通知处理和交易查询。`notify_url` 是可选参数;生产也允许省略。未配置时不触发该地址的异步通知,必须由商家服务端通过 `alipay.trade.query` 主动确认支付结果,校验订单、金额、支付状态及归属,幂等更新订单;即使用户关闭页面,仍须继续处理查询重试和未决订单。如使用异步通知,配置商家已部署、支付宝服务端可公网访问的 HTTPS 地址并完成验签、关键字段校验、幂等、`success` 回写和补偿查询。不能只因没有公网通知地址阻止流程或判定不能上线;通知处理代码仍须实现,实际代码或支付结果确认链路缺陷仍是阻断项。
|
|
18
18
|
9. **受控页面**:授权链接仅由 `auth.sh` 按固定产品/MCC 生成,只允许 `https://aipay.alipay.com/cli-auth` 及登记参数,禁用 CLI `verification_url`;公钥页和系统 opener 只走登记脚本。失败后宿主内置浏览器只可打开已交付的同一 URL 一次;仍失败才提示手动打开,再有限轮询。禁止搜索、改写或自动操作;opener/config 写失败不算业务失败。
|
|
19
|
-
10. **对客输出**:指定 runner/renderer/脚本时,stdout 是唯一对客正文,须原文回复且不加旁白;工具输出记录、折叠面板或日志不等于已对客发送。展示命令只执行登记单条命令,禁 `2>&1`、管道、`grep/sed/awk/tee`、临时包装或拆回 `jq/printf/renderer`。除 `self_update.mjs` 的 `SELF_UPDATE:*` stderr 终态外,内部事实行均以 `ALIPAY_AIPAY_INTERNAL:` 开头;宿主合并输出时仍整行剔除,严禁对客。`integration.start.confirm`、`onboarding.plan.confirm`、`sandbox.configuration.pending`、`sandbox.environment.summary`、`integration.safety.closeout` 和 `integration.checklist.result`
|
|
19
|
+
10. **对客输出**:指定 runner/renderer/脚本时,stdout 是唯一对客正文,须原文回复且不加旁白;工具输出记录、折叠面板或日志不等于已对客发送。展示命令只执行登记单条命令,禁 `2>&1`、管道、`grep/sed/awk/tee`、临时包装或拆回 `jq/printf/renderer`。除 `self_update.mjs` 的 `SELF_UPDATE:*` stderr 终态外,内部事实行均以 `ALIPAY_AIPAY_INTERNAL:` 开头;宿主合并输出时仍整行剔除,严禁对客。`integration.start.confirm`、`onboarding.plan.confirm`、`sandbox.configuration.pending`、`sandbox.environment.summary`、`integration.safety.closeout` 和 `integration.checklist.result` 在 flow 适用且成功渲染时必须各自产生唯一 `ALIPAY_AIPAY_INTERNAL:CUSTOMER_MESSAGE_DELIVERY_REQUIRED=<messageId>`;下一动作先将剔除内部行后的 stdout 原文作为独立可见消息送达,不得以工具面板、折叠输出、进度话术或最终答复代替,也不得在送达前继续调用工具。送达后同回合按 flow 继续,不等待回复。成功沙箱准备 `READY` 时 stdout 仍按 flow 静默,不构成交付消息;只有待配置正文和后置摘要需要交付。所有 `postRenderHintType=scanReturn` 的页面消息都是交付屏障(当前为 `auth.page`、`application.key.page`):先独立送达页面 stdout,再执行受控 opener 和有限轮询,不合并 pending/收口;不要求确认。阻塞正文后等待;剔除内部行后正文为空则直接转移。
|
|
20
20
|
11. **确定性转移**:固定脚本终态后立即转移;只执行动作处登记命令,禁止追加 `jq/cat/grep/curl` 或临时脚本重复解析。失败只走登记恢复;项目自检不绕过 Skill 脚本或打印敏感配置。
|
|
21
21
|
|
|
22
22
|
## 标准消息
|