@alipay/alipay-aipay 1.8.2 → 1.8.4
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/onboarding_message_runner.mjs +80 -250
- package/skill/references/normal/scripts/render_customer_message.mjs +6 -6
- package/skill/references/onboarding/flow.md +9 -9
- package/skill/references/onboarding/modules/product-sign.md +1 -1
- package/skill/references/onboarding/modules/scripts/mcc.sh +151 -290
- package/skill/references/runtime-entry.md +2 -2
- 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/onboarding_message_runner.mjs +80 -250
- package/skill-pre/references/normal/scripts/render_customer_message.mjs +6 -6
- package/skill-pre/references/onboarding/flow.md +9 -9
- package/skill-pre/references/onboarding/modules/product-sign.md +1 -1
- package/skill-pre/references/onboarding/modules/scripts/mcc.sh +151 -290
- package/skill-pre/references/runtime-entry.md +2 -2
|
@@ -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
|
}
|
|
@@ -259,13 +259,6 @@ function assertMccQuery(query) {
|
|
|
259
259
|
}
|
|
260
260
|
|
|
261
261
|
const mccSelectionReceiptPrefix = 'AIPAY_MCC_SELECTION_V1.';
|
|
262
|
-
const ignoredMccQueryTokens = new Set([
|
|
263
|
-
'服务', '企业', '个人', '提供', '适用', '用于', '平台', '项目', '申请', '商家', '公司',
|
|
264
|
-
'互联', '联网', '互联网', '在线', '用品', '收费', '缴费', '费用', '适用于', '经营',
|
|
265
|
-
'业务', '相关', '主体', '从事', '网站', '网页', '购买', '买票', '购票', '票务', '售票',
|
|
266
|
-
'预约', '预定', '退票', '支付', '付款', '收款', '下单', '订单', '交易', '销售', '售卖',
|
|
267
|
-
'商品', '产品'
|
|
268
|
-
]);
|
|
269
262
|
|
|
270
263
|
function activeMccRunId(env = process.env) {
|
|
271
264
|
const candidate = env.ALIPAY_AIPAY_RUN_ID
|
|
@@ -397,8 +390,10 @@ function mccCatalogForAgent(records) {
|
|
|
397
390
|
}
|
|
398
391
|
|
|
399
392
|
function parseAgentCandidateRefs(args, records) {
|
|
400
|
-
|
|
401
|
-
|
|
393
|
+
if (!Object.prototype.hasOwnProperty.call(args, 'agent-candidate-refs-json')) {
|
|
394
|
+
return { provided: false, codes: [] };
|
|
395
|
+
}
|
|
396
|
+
const raw = args['agent-candidate-refs-json'];
|
|
402
397
|
let parsed;
|
|
403
398
|
try {
|
|
404
399
|
parsed = JSON.parse(raw);
|
|
@@ -416,7 +411,7 @@ function parseAgentCandidateRefs(args, records) {
|
|
|
416
411
|
}
|
|
417
412
|
codes.push(refs.get(ref));
|
|
418
413
|
}
|
|
419
|
-
return { codes };
|
|
414
|
+
return { provided: true, codes };
|
|
420
415
|
}
|
|
421
416
|
|
|
422
417
|
function mccTableSummary(records) {
|
|
@@ -454,190 +449,12 @@ function directMccCode(query, records) {
|
|
|
454
449
|
return { isCode: true, matches: [...records.keys()].filter((code) => code.endsWith(`_${raw}`)) };
|
|
455
450
|
}
|
|
456
451
|
|
|
457
|
-
function
|
|
458
|
-
const grams = new Set();
|
|
459
|
-
const searchableText = meaningfulMccSearch(value);
|
|
460
|
-
for (const segment of searchableText.split(/\s+/u).filter(Boolean)) {
|
|
461
|
-
const characters = [...segment];
|
|
462
|
-
for (let index = 0; index <= characters.length - size; index += 1) {
|
|
463
|
-
const gram = characters.slice(index, index + size).join('');
|
|
464
|
-
if (/^[\p{L}\p{N}]+$/u.test(gram)) grams.add(gram);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
return grams;
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
function meaningfulMccSearch(value) {
|
|
471
|
-
let searchableText = normalizeSearch(value);
|
|
472
|
-
const ignoredTerms = [...ignoredMccQueryTokens]
|
|
473
|
-
.sort((left, right) => [...right].length - [...left].length);
|
|
474
|
-
for (const term of ignoredTerms) searchableText = searchableText.replaceAll(term, ' ');
|
|
475
|
-
return searchableText
|
|
476
|
-
.trim()
|
|
477
|
-
.replace(/\s+/gu, ' ')
|
|
478
|
-
.split(' ')
|
|
479
|
-
.filter((segment) => [...segment].length >= 2)
|
|
480
|
-
.join(' ');
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
function parseMccSemanticTerms(args) {
|
|
484
|
-
const raw = optional(args, 'semantic-terms-json');
|
|
485
|
-
if (raw === undefined) return [];
|
|
486
|
-
let parsed;
|
|
487
|
-
try {
|
|
488
|
-
parsed = JSON.parse(raw);
|
|
489
|
-
} catch {
|
|
490
|
-
throw new Error('semantic-terms-json 必须是合法 JSON 字符串数组');
|
|
491
|
-
}
|
|
492
|
-
if (!Array.isArray(parsed) || parsed.length > 12 || parsed.some((term) => typeof term !== 'string')) {
|
|
493
|
-
throw new Error('semantic-terms-json 必须是最多 12 项的 JSON 字符串数组');
|
|
494
|
-
}
|
|
495
|
-
const normalized = [];
|
|
496
|
-
let totalLength = 0;
|
|
497
|
-
for (const rawTerm of parsed) {
|
|
498
|
-
if (/\u0000|[\u0001-\u001f\u007f]/u.test(rawTerm)) {
|
|
499
|
-
throw new Error('semantic-terms-json 包含控制字符');
|
|
500
|
-
}
|
|
501
|
-
const term = normalizeSearch(rawTerm);
|
|
502
|
-
const termLength = [...term.replace(/\s+/gu, '')].length;
|
|
503
|
-
if (termLength < 2 || termLength > 40) {
|
|
504
|
-
throw new Error('semantic-terms-json 每项长度必须为 2 到 40 个字符');
|
|
505
|
-
}
|
|
506
|
-
if (/(?:A\d{4}\s*)?B\d{4}/iu.test(term)) {
|
|
507
|
-
throw new Error('semantic-terms-json 只能包含业务概念词,禁止提供 MCC 编码');
|
|
508
|
-
}
|
|
509
|
-
totalLength += termLength;
|
|
510
|
-
if (!normalized.includes(term)) normalized.push(term);
|
|
511
|
-
}
|
|
512
|
-
if (totalLength > 240) throw new Error('semantic-terms-json 总长度不得超过 240 个字符');
|
|
513
|
-
return normalized.sort((left, right) => (left < right ? -1 : left > right ? 1 : 0));
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
function mccDocumentFrequency(indexedRecords) {
|
|
517
|
-
const frequencies = new Map();
|
|
518
|
-
for (const indexed of indexedRecords) {
|
|
519
|
-
const documentGrams = new Set([
|
|
520
|
-
...indexed.nameBigrams,
|
|
521
|
-
...indexed.nameTrigrams,
|
|
522
|
-
...indexed.businessBigrams,
|
|
523
|
-
...indexed.businessTrigrams
|
|
524
|
-
]);
|
|
525
|
-
for (const gram of documentGrams) frequencies.set(gram, (frequencies.get(gram) || 0) + 1);
|
|
526
|
-
}
|
|
527
|
-
return frequencies;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
function weightedMccOverlap(queryGrams, documentGrams, frequencies, recordCount, size) {
|
|
531
|
-
let weight = 0;
|
|
532
|
-
for (const gram of queryGrams) {
|
|
533
|
-
if (!documentGrams.has(gram)) continue;
|
|
534
|
-
const documentCount = frequencies.get(gram) || 0;
|
|
535
|
-
weight += (Math.log((recordCount + 1) / (documentCount + 1)) + 1) * (size === 3 ? 1.6 : 1);
|
|
536
|
-
}
|
|
537
|
-
return weight;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
function mccPhraseCoverage(haystackSegments, needleSegments) {
|
|
541
|
-
let coverage = 0;
|
|
542
|
-
for (const needle of needleSegments) {
|
|
543
|
-
if (ignoredMccQueryTokens.has(needle)) continue;
|
|
544
|
-
const needleLength = [...needle].length;
|
|
545
|
-
for (const haystack of haystackSegments) {
|
|
546
|
-
if (!haystack.includes(needle)) continue;
|
|
547
|
-
coverage = Math.max(coverage, needleLength / [...haystack].length);
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
return coverage;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function mccTextEvidence(value, indexed, frequencies, recordCount) {
|
|
554
|
-
const normalizedValue = normalizeSearch(value);
|
|
555
|
-
const valueSegments = meaningfulMccSearch(normalizedValue).split(' ').filter(Boolean);
|
|
556
|
-
const valueBigrams = mccNgrams(normalizedValue, 2);
|
|
557
|
-
const valueTrigrams = mccNgrams(normalizedValue, 3);
|
|
558
|
-
const exactName = indexed.name === normalizedValue
|
|
559
|
-
|| (normalizedValue.split(' ').length === 1 && indexed.nameSegments.includes(normalizedValue));
|
|
560
|
-
const nameContainsValueCoverage = mccPhraseCoverage(indexed.nameEvidenceSegments, valueSegments);
|
|
561
|
-
const valueContainsNameCoverage = mccPhraseCoverage(valueSegments, indexed.nameEvidenceSegments);
|
|
562
|
-
const businessContainsValueCoverage = mccPhraseCoverage(indexed.businessEvidenceSegments, valueSegments);
|
|
563
|
-
const nameBigram = weightedMccOverlap(valueBigrams, indexed.nameBigrams, frequencies, recordCount, 2);
|
|
564
|
-
const nameTrigram = weightedMccOverlap(valueTrigrams, indexed.nameTrigrams, frequencies, recordCount, 3);
|
|
565
|
-
const businessBigram = weightedMccOverlap(valueBigrams, indexed.businessBigrams, frequencies, recordCount, 2);
|
|
566
|
-
const businessTrigram = weightedMccOverlap(valueTrigrams, indexed.businessTrigrams, frequencies, recordCount, 3);
|
|
567
|
-
const matchedBigrams = new Set([
|
|
568
|
-
...[...valueBigrams].filter((gram) => indexed.nameBigrams.has(gram)),
|
|
569
|
-
...[...valueBigrams].filter((gram) => indexed.businessBigrams.has(gram))
|
|
570
|
-
]);
|
|
571
|
-
const matchedTrigrams = new Set([
|
|
572
|
-
...[...valueTrigrams].filter((gram) => indexed.nameTrigrams.has(gram)),
|
|
573
|
-
...[...valueTrigrams].filter((gram) => indexed.businessTrigrams.has(gram))
|
|
574
|
-
]);
|
|
575
|
-
const rareBigramDocumentLimit = Math.max(1, Math.ceil(recordCount * 0.02));
|
|
576
|
-
const hasRareBigram = [...matchedBigrams].some((gram) => (
|
|
577
|
-
(frequencies.get(gram) || recordCount) <= rareBigramDocumentLimit
|
|
578
|
-
));
|
|
579
|
-
const hasLexicalEvidence = matchedTrigrams.size > 0
|
|
580
|
-
|| matchedBigrams.size >= 2
|
|
581
|
-
|| (valueBigrams.size <= 2 && hasRareBigram);
|
|
582
|
-
const phraseEvidence = nameContainsValueCoverage > 0
|
|
583
|
-
|| valueContainsNameCoverage > 0
|
|
584
|
-
|| businessContainsValueCoverage > 0;
|
|
585
|
-
return {
|
|
586
|
-
eligible: exactName || phraseEvidence || hasLexicalEvidence,
|
|
587
|
-
exactName,
|
|
588
|
-
score: (exactName ? 100000 : 0)
|
|
589
|
-
+ (nameContainsValueCoverage * 6000)
|
|
590
|
-
+ (valueContainsNameCoverage * 3000)
|
|
591
|
-
+ (businessContainsValueCoverage * 2500)
|
|
592
|
-
+ ((nameBigram + nameTrigram) * 120)
|
|
593
|
-
+ ((businessBigram + businessTrigram) * 40)
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
function deterministicMccMatches(query, records, { limit = 8, semanticTerms = [] } = {}) {
|
|
452
|
+
function directMccName(query, records) {
|
|
598
453
|
const normalizedQuery = assertMccQuery(query);
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
record,
|
|
604
|
-
name,
|
|
605
|
-
nameSegments: name.split(' ').filter(Boolean),
|
|
606
|
-
nameEvidenceSegments: meaningfulMccSearch(name).split(' ').filter(Boolean),
|
|
607
|
-
businessEvidenceSegments: meaningfulMccSearch(businesses).split(' ').filter(Boolean),
|
|
608
|
-
nameBigrams: mccNgrams(name, 2),
|
|
609
|
-
nameTrigrams: mccNgrams(name, 3),
|
|
610
|
-
businessBigrams: mccNgrams(businesses, 2),
|
|
611
|
-
businessTrigrams: mccNgrams(businesses, 3)
|
|
612
|
-
};
|
|
613
|
-
});
|
|
614
|
-
const frequencies = mccDocumentFrequency(indexedRecords);
|
|
615
|
-
const scored = [];
|
|
616
|
-
|
|
617
|
-
for (const indexed of indexedRecords) {
|
|
618
|
-
const originalEvidence = mccTextEvidence(normalizedQuery, indexed, frequencies, indexedRecords.length);
|
|
619
|
-
const semanticEvidence = semanticTerms
|
|
620
|
-
.map((term) => mccTextEvidence(term, indexed, frequencies, indexedRecords.length))
|
|
621
|
-
.filter((evidence) => evidence.eligible)
|
|
622
|
-
.sort((left, right) => right.score - left.score)[0];
|
|
623
|
-
if (!originalEvidence.eligible && !semanticEvidence) continue;
|
|
624
|
-
scored.push({
|
|
625
|
-
record: indexed.record,
|
|
626
|
-
score: originalEvidence.score + ((semanticEvidence?.score || 0) * 0.7),
|
|
627
|
-
exactName: originalEvidence.exactName,
|
|
628
|
-
originalEligible: originalEvidence.eligible,
|
|
629
|
-
semanticEligible: Boolean(semanticEvidence),
|
|
630
|
-
semanticExactName: Boolean(semanticEvidence?.exactName)
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
const exactMatches = scored.filter(({ exactName }) => exactName);
|
|
635
|
-
const ranked = exactMatches.length > 0
|
|
636
|
-
? exactMatches
|
|
637
|
-
: scored;
|
|
638
|
-
return ranked
|
|
639
|
-
.sort((left, right) => right.score - left.score || left.record.code.localeCompare(right.record.code))
|
|
640
|
-
.slice(0, limit);
|
|
454
|
+
return sortedMccRecords(records)
|
|
455
|
+
.filter((record) => normalizeSearch(record.name) === normalizedQuery)
|
|
456
|
+
.map((record) => record.code)
|
|
457
|
+
.slice(0, 8);
|
|
641
458
|
}
|
|
642
459
|
|
|
643
460
|
function createMccSelectionReceipt(productType, query, result, mccRunContext = captureMccRunContext()) {
|
|
@@ -647,7 +464,6 @@ function createMccSelectionReceipt(productType, query, result, mccRunContext = c
|
|
|
647
464
|
mccRunContext.runId,
|
|
648
465
|
productType,
|
|
649
466
|
assertMccQuery(query),
|
|
650
|
-
JSON.stringify(result.semanticTerms || []),
|
|
651
467
|
table.sha256,
|
|
652
468
|
table.recordCount,
|
|
653
469
|
codes
|
|
@@ -655,8 +471,8 @@ function createMccSelectionReceipt(productType, query, result, mccRunContext = c
|
|
|
655
471
|
return `${mccSelectionReceiptPrefix}${codes}.${createHash('sha256').update(binding).digest('base64url')}`;
|
|
656
472
|
}
|
|
657
473
|
|
|
658
|
-
function resolveMccSelection(productType, query, selectedInput, receipt, records,
|
|
659
|
-
const candidateCodes = mccSelectionReceiptCodes(productType, query, receipt, records,
|
|
474
|
+
function resolveMccSelection(productType, query, selectedInput, receipt, records, mccRunContext) {
|
|
475
|
+
const candidateCodes = mccSelectionReceiptCodes(productType, query, receipt, records, mccRunContext);
|
|
660
476
|
if (!candidateCodes) {
|
|
661
477
|
const semanticStatus = typeof receipt === 'string' && receipt.startsWith(mccSelectionReceiptPrefix)
|
|
662
478
|
? 'SELECTION_RECEIPT_STALE'
|
|
@@ -665,10 +481,8 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
665
481
|
query,
|
|
666
482
|
productType,
|
|
667
483
|
records,
|
|
668
|
-
semanticTerms,
|
|
669
484
|
semanticStatus,
|
|
670
|
-
mccRunContext
|
|
671
|
-
agentCandidateCodes
|
|
485
|
+
mccRunContext
|
|
672
486
|
);
|
|
673
487
|
return {
|
|
674
488
|
...refreshed,
|
|
@@ -685,8 +499,7 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
685
499
|
deterministicCandidateCodes: [],
|
|
686
500
|
deterministicStrongCandidateCodes: [],
|
|
687
501
|
deterministicWeakCandidateCodes: [],
|
|
688
|
-
|
|
689
|
-
semanticTerms,
|
|
502
|
+
agentCandidateCodes: [],
|
|
690
503
|
mergedCandidateCodes: candidateCodes,
|
|
691
504
|
mccTableSummary: currentTable,
|
|
692
505
|
candidates: candidateCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext))
|
|
@@ -699,15 +512,14 @@ function resolveMccSelection(productType, query, selectedInput, receipt, records
|
|
|
699
512
|
deterministicCandidateCodes: [],
|
|
700
513
|
deterministicStrongCandidateCodes: [],
|
|
701
514
|
deterministicWeakCandidateCodes: [],
|
|
702
|
-
|
|
703
|
-
semanticTerms,
|
|
515
|
+
agentCandidateCodes: [],
|
|
704
516
|
mergedCandidateCodes: [selectedCode],
|
|
705
517
|
mccTableSummary: currentTable,
|
|
706
518
|
candidates: [candidate]
|
|
707
519
|
};
|
|
708
520
|
}
|
|
709
521
|
|
|
710
|
-
function mccSelectionReceiptCodes(productType, query, receipt, records,
|
|
522
|
+
function mccSelectionReceiptCodes(productType, query, receipt, records, mccRunContext = captureMccRunContext()) {
|
|
711
523
|
if (typeof receipt !== 'string' || !receipt.startsWith(mccSelectionReceiptPrefix)) return null;
|
|
712
524
|
const body = receipt.slice(mccSelectionReceiptPrefix.length);
|
|
713
525
|
const separator = body.lastIndexOf('.');
|
|
@@ -718,8 +530,7 @@ function mccSelectionReceiptCodes(productType, query, receipt, records, semantic
|
|
|
718
530
|
|| candidateCodes.some((code) => !records.has(code))
|
|
719
531
|
|| receipt !== createMccSelectionReceipt(productType, query, {
|
|
720
532
|
mergedCandidateCodes: candidateCodes,
|
|
721
|
-
mccTableSummary: currentTable
|
|
722
|
-
semanticTerms
|
|
533
|
+
mccTableSummary: currentTable
|
|
723
534
|
}, mccRunContext)) {
|
|
724
535
|
return null;
|
|
725
536
|
}
|
|
@@ -730,10 +541,9 @@ function resolveMccResultFromRecords(
|
|
|
730
541
|
query,
|
|
731
542
|
productType,
|
|
732
543
|
records,
|
|
733
|
-
semanticTerms = [],
|
|
734
544
|
semanticStatus,
|
|
735
545
|
mccRunContext = captureMccRunContext(),
|
|
736
|
-
|
|
546
|
+
agentReview = { provided: false, codes: [] }
|
|
737
547
|
) {
|
|
738
548
|
const directCode = directMccCode(query, records);
|
|
739
549
|
if (directCode.isCode) {
|
|
@@ -746,9 +556,7 @@ function resolveMccResultFromRecords(
|
|
|
746
556
|
deterministicCandidateCodes: [code],
|
|
747
557
|
deterministicStrongCandidateCodes: [code],
|
|
748
558
|
deterministicWeakCandidateCodes: [],
|
|
749
|
-
semanticCandidateCodes: [],
|
|
750
559
|
agentCandidateCodes: [],
|
|
751
|
-
semanticTerms,
|
|
752
560
|
mergedCandidateCodes: [code],
|
|
753
561
|
mccTableSummary: mccTableSummary(records),
|
|
754
562
|
candidates: [candidate]
|
|
@@ -761,52 +569,69 @@ function resolveMccResultFromRecords(
|
|
|
761
569
|
deterministicCandidateCodes: finalCodes,
|
|
762
570
|
deterministicStrongCandidateCodes: finalCodes,
|
|
763
571
|
deterministicWeakCandidateCodes: [],
|
|
764
|
-
semanticCandidateCodes: [],
|
|
765
572
|
agentCandidateCodes: [],
|
|
766
|
-
semanticTerms,
|
|
767
573
|
mergedCandidateCodes: finalCodes,
|
|
768
574
|
mccTableSummary: mccTableSummary(records),
|
|
769
575
|
candidates: finalCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext))
|
|
770
576
|
};
|
|
771
577
|
}
|
|
772
578
|
|
|
773
|
-
const
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
579
|
+
const exactNameCodes = directMccName(query, records);
|
|
580
|
+
if (exactNameCodes.length > 0) {
|
|
581
|
+
const candidates = exactNameCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext));
|
|
582
|
+
return {
|
|
583
|
+
matchType: candidates.length === 1 ? 'UNIQUE' : 'MULTIPLE',
|
|
584
|
+
semanticStatus: 'NOT_REQUIRED_EXACT_NAME',
|
|
585
|
+
deterministicCandidateCodes: exactNameCodes,
|
|
586
|
+
deterministicStrongCandidateCodes: exactNameCodes,
|
|
587
|
+
deterministicWeakCandidateCodes: [],
|
|
588
|
+
agentCandidateCodes: [],
|
|
589
|
+
mergedCandidateCodes: exactNameCodes,
|
|
590
|
+
mccTableSummary: mccTableSummary(records),
|
|
591
|
+
candidates
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (!agentReview.provided) {
|
|
596
|
+
return {
|
|
597
|
+
matchType: 'NONE',
|
|
598
|
+
semanticStatus: semanticStatus || 'AGENT_CATALOG_REVIEW_REQUIRED',
|
|
599
|
+
deterministicCandidateCodes: [],
|
|
600
|
+
deterministicStrongCandidateCodes: [],
|
|
601
|
+
deterministicWeakCandidateCodes: [],
|
|
602
|
+
agentCandidateCodes: [],
|
|
603
|
+
mergedCandidateCodes: [],
|
|
604
|
+
mccTableSummary: mccTableSummary(records),
|
|
605
|
+
candidates: [],
|
|
606
|
+
agentReview: {
|
|
607
|
+
required: true,
|
|
608
|
+
attempt: 1,
|
|
609
|
+
maxAttempts: 2,
|
|
610
|
+
reason: 'NATURAL_LANGUAGE_MATCH',
|
|
611
|
+
instructions: {
|
|
612
|
+
keepExplicitConstraints: true,
|
|
613
|
+
keepUnspecifiedDimensionsOpen: true,
|
|
614
|
+
excludeContradictions: true,
|
|
615
|
+
rankByRelevance: true,
|
|
616
|
+
maxCandidates: 8
|
|
617
|
+
},
|
|
618
|
+
catalog: mccCatalogForAgent(records)
|
|
619
|
+
}
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const finalCodes = agentReview.codes;
|
|
781
624
|
const candidates = finalCodes.map((code) => mccCandidateFromRecord(records.get(code), productType, mccRunContext));
|
|
782
|
-
const needsAgentReview = agentCandidateCodes.length === 0
|
|
783
|
-
&& (
|
|
784
|
-
matches.length === 0
|
|
785
|
-
|| deterministicCodes.length !== 1
|
|
786
|
-
|| (semanticTerms.length > 0 && deterministicCodes.length === 0 && !hasExactSemanticName)
|
|
787
|
-
);
|
|
788
625
|
return {
|
|
789
626
|
matchType: candidates.length === 1 ? 'UNIQUE' : candidates.length === 0 ? 'NONE' : 'MULTIPLE',
|
|
790
|
-
semanticStatus: semanticStatus
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
deterministicStrongCandidateCodes: deterministicCodes,
|
|
627
|
+
semanticStatus: semanticStatus || 'AGENT_CATALOG_REVIEW_USED',
|
|
628
|
+
deterministicCandidateCodes: [],
|
|
629
|
+
deterministicStrongCandidateCodes: [],
|
|
794
630
|
deterministicWeakCandidateCodes: [],
|
|
795
|
-
|
|
796
|
-
agentCandidateCodes,
|
|
797
|
-
semanticTerms,
|
|
631
|
+
agentCandidateCodes: finalCodes,
|
|
798
632
|
mergedCandidateCodes: finalCodes,
|
|
799
633
|
mccTableSummary: mccTableSummary(records),
|
|
800
|
-
candidates
|
|
801
|
-
...(needsAgentReview ? {
|
|
802
|
-
agentReview: {
|
|
803
|
-
required: true,
|
|
804
|
-
attempt: 1,
|
|
805
|
-
maxAttempts: 1,
|
|
806
|
-
reason: 'LOW_CONFIDENCE_LOCAL_MATCH',
|
|
807
|
-
catalog: mccCatalogForAgent(records)
|
|
808
|
-
}
|
|
809
|
-
} : {})
|
|
634
|
+
candidates
|
|
810
635
|
};
|
|
811
636
|
}
|
|
812
637
|
|
|
@@ -856,7 +681,9 @@ async function runMccResolvePlan(args) {
|
|
|
856
681
|
const selected = optional(args, 'select');
|
|
857
682
|
const selectionReceipt = optional(args, 'selection-receipt');
|
|
858
683
|
const selectionState = optional(args, 'selection-state');
|
|
859
|
-
|
|
684
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
685
|
+
throw new Error('mcc-resolve-plan 不再接受 --semantic-terms-json;自然语言只使用一次受限目录判断');
|
|
686
|
+
}
|
|
860
687
|
if ((selected === undefined) !== (selectionReceipt === undefined)) {
|
|
861
688
|
throw new Error('--select 与 --selection-receipt 必须同时提供');
|
|
862
689
|
}
|
|
@@ -872,8 +699,8 @@ async function runMccResolvePlan(args) {
|
|
|
872
699
|
const records = recordsForArgs(args);
|
|
873
700
|
const agentReview = parseAgentCandidateRefs(args, records);
|
|
874
701
|
let result = selected === undefined
|
|
875
|
-
? resolveMccResultFromRecords(query, productType, records,
|
|
876
|
-
: resolveMccSelection(productType, query, selected, selectionReceipt, records,
|
|
702
|
+
? resolveMccResultFromRecords(query, productType, records, undefined, mccRunContext, agentReview)
|
|
703
|
+
: resolveMccSelection(productType, query, selected, selectionReceipt, records, mccRunContext);
|
|
877
704
|
if (selectionState === 'STALE') {
|
|
878
705
|
result = {
|
|
879
706
|
...result,
|
|
@@ -910,14 +737,16 @@ async function runMccMatchCodes(args) {
|
|
|
910
737
|
}
|
|
911
738
|
await startTelemetryRun(rootIntent, productType);
|
|
912
739
|
const query = required(args, 'query');
|
|
913
|
-
|
|
740
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
741
|
+
throw new Error('mcc-match-codes 不再接受 --semantic-terms-json;自然语言只使用一次受限目录判断');
|
|
742
|
+
}
|
|
914
743
|
if (args['agent-mcc-status'] !== undefined || args['agent-mcc-codes'] !== undefined) {
|
|
915
744
|
throw new Error('mcc-match-codes 不接受 --agent-mcc-status 或 --agent-mcc-codes;MCC 匹配只使用本轮动态 catalog');
|
|
916
745
|
}
|
|
917
746
|
const records = recordsForArgs(args);
|
|
918
747
|
const agentReview = parseAgentCandidateRefs(args, records);
|
|
919
748
|
const mccRunContext = captureMccRunContext();
|
|
920
|
-
const result = resolveMccResultFromRecords(query, productType, records,
|
|
749
|
+
const result = resolveMccResultFromRecords(query, productType, records, undefined, mccRunContext, agentReview);
|
|
921
750
|
const { candidates: _internalCandidates, ...controlResult } = result;
|
|
922
751
|
process.stderr.write(`ALIPAY_AIPAY_INTERNAL:MCC_RESOLUTION=${JSON.stringify(controlResult)}\n`);
|
|
923
752
|
return '';
|
|
@@ -927,14 +756,15 @@ function runMccValidateSelectionCache(args) {
|
|
|
927
756
|
const productType = required(args, 'product-type');
|
|
928
757
|
productFor({ ...args, 'product-type': productType });
|
|
929
758
|
const records = recordsForArgs(args);
|
|
930
|
-
|
|
759
|
+
if (args['semantic-terms-json'] !== undefined) {
|
|
760
|
+
throw new Error('mcc-validate-selection-cache 不再接受 --semantic-terms-json');
|
|
761
|
+
}
|
|
931
762
|
const mccRunContext = captureMccRunContext();
|
|
932
763
|
if (!mccSelectionReceiptCodes(
|
|
933
764
|
productType,
|
|
934
765
|
required(args, 'query'),
|
|
935
766
|
required(args, 'selection-receipt'),
|
|
936
767
|
records,
|
|
937
|
-
semanticTerms,
|
|
938
768
|
mccRunContext
|
|
939
769
|
)) {
|
|
940
770
|
throw new Error('MCC 候选详情缓存与本轮选择回执不一致');
|
|
@@ -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, {
|