@feng0531/dsh-code-review 0.87.6 → 0.87.7
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/CHANGELOG.md +6 -0
- package/RELEASE-NOTES.md +9 -9
- package/package.json +1 -1
- package/src/candidate-verification.mjs +6 -2
- package/src/report-findings.mjs +19 -10
- package/src/report-summary.mjs +18 -0
- package/src/review-diagnostics.mjs +4 -4
- package/src/review.mjs +1 -0
- package/src/verification-loop.mjs +30 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
本文件介绍对使用者有意义的变化。安装与参数见 [README](README.md),本次发布正文见 [发布说明](RELEASE-NOTES.md)。历史提交和安装包保留在 [GitHub Releases](https://github.com/f-e-n-g-0531/dsh-code-review/releases);本文不把开发日志当作功能介绍。
|
|
4
4
|
|
|
5
|
+
## 0.87.7 — 清晰结论与有界复核纠正
|
|
6
|
+
|
|
7
|
+
- 报告区分已复核不确定与复核未完成;尚未成立的候选使用待证条件、影响假说与待验证方案,不再以确定影响和必改建议呈现。
|
|
8
|
+
- 开启协议纠正时,不完整复核批次可在原调用额度内纠正一次,复用本次已读原文及回执,不重启取证;完整响应仍须全部严格校验。最终答复模式不追加纠正。
|
|
9
|
+
- 保留原预算、截止时间和来源授权,记录纠正成本;本版不宣称真实审核提速,组取证续跑涉及分配安全边界,未贸然上线。
|
|
10
|
+
|
|
5
11
|
## 0.87.6 — 迁移复核成套取证
|
|
6
12
|
|
|
7
13
|
- 复用宿主已校验的迁移契约,为匹配候选有界预取旧入口、旧委托实现和新入口;同套证据原子接纳,容量、预算或输入不满足时回退普通检索,减少模型重新寻找已知路径的往返。
|
package/RELEASE-NOTES.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# v0.87.
|
|
1
|
+
# v0.87.7 发布说明
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## 先看清楚结论,再减少复核返工
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
报告区分复核支持、已复核不确定、复核未完成与已反驳。候选未成立时,以待证条件、影响假说与待验证方案表达,避免把主审描述误读为独立确认的缺陷;完整证据仍保留在技术附录。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 不完整批次的有界纠正
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
启用协议纠正时,复核返回不完整批次可再请求一次完整判定。使用同次调用最后已发送的证据与新鲜回执,不重启检索、不重新签发整套证据;仍计入相同模型调用额度、输入限制和截止时间。仅接纳少项且已有成员均为合法身份、无证据无归因的 uncertain 批次进行纠正;含支持、回执或归因的部分响应仍直接失败,避免漏项掩盖非法证据。身份、回执、原文及修改前后归因继续严格校验;再次不完整即失败。检索最终答复模式不追加纠正。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
执行诊断单列复核批次纠正的调用与原因,不将纠正当成缺陷成立。正常完整批次不增加调用;纠正可能增加一次模型往返,不承诺每次审核更快。
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
## 验证与边界
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
本版采用离线正反回归、全量测试、真实宿主离线兼容检查及冻结标签四矩阵门禁。没有重新运行真实业务审核,也没有将直接审核的历史结论当作真值。分组取证续跑需要可回滚的额度转移,尚未改动该失败回退语义。
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
发布不自动安装或重启当前宿主。安装见 [README](README.md),门禁见 [开发说明](DEVELOPMENT.md)。
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { validateRegressionAssessment } from './regression-assessment.mjs';
|
|
2
2
|
import { validateEvidenceReference } from './evidence-reference.mjs';
|
|
3
3
|
// Offline only. Candidate ids come from the host, not the model.
|
|
4
|
-
export function
|
|
4
|
+
export function validateCandidateVerificationEnvelope(value, candidateIds) {
|
|
5
5
|
if (!Array.isArray(candidateIds) || candidateIds.length > 50 || candidateIds.some(id => typeof id !== 'string' || !id || id.length > 200) || new Set(candidateIds).size !== candidateIds.length) throw new Error('Invalid candidate identities');
|
|
6
|
-
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).length !== 1 || !Array.isArray(value.verdicts) || value.verdicts.length !== candidateIds.length) throw new Error('Incomplete verification response');
|
|
6
|
+
if (!value || typeof value !== 'object' || Array.isArray(value) || Object.keys(value).length !== 1 || !Array.isArray(value.verdicts) || value.verdicts.length !== candidateIds.length) throw Object.assign(new Error('Incomplete verification response'), { code: 'MODEL_INCOMPLETE_VERIFICATION' });
|
|
7
7
|
const seen = new Set();
|
|
8
8
|
// Validate the entire envelope before any budgeted evidence read.
|
|
9
9
|
for (const item of value.verdicts) {
|
|
@@ -13,6 +13,10 @@ export function validateCandidateVerification(value, candidateIds, read, resolve
|
|
|
13
13
|
if (!['supported', 'refuted', 'uncertain'].includes(item.verdict) || typeof item.reason !== 'string' || !item.reason.trim() || item.reason.length > 8000) throw new Error('Invalid verdict explanation');
|
|
14
14
|
if (!Array.isArray(item.evidence) || item.evidence.length > 8 || (item.verdict !== 'uncertain' && !item.evidence.length)) throw new Error('Missing or excessive evidence');
|
|
15
15
|
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function validateCandidateVerification(value, candidateIds, read, resolveEvidence = value => value, comparison = {}) {
|
|
19
|
+
validateCandidateVerificationEnvelope(value, candidateIds);
|
|
16
20
|
const results = new Map(value.verdicts.map(item => {
|
|
17
21
|
const evidence = item.evidence.map(ref => validateEvidenceReference(resolveEvidence(ref), read));
|
|
18
22
|
const regression = validateRegressionAssessment(item.regression, evidence, comparison.catalog ?? [], comparison.fileIds?.[item.candidateId], { ...comparison, candidateId: item.candidateId });
|
package/src/report-findings.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { groupDuplicateFindings } from './finding-groups.mjs';
|
|
2
|
-
import { comparisonValidated } from './report-summary.mjs';
|
|
2
|
+
import { comparisonValidated, verificationPresentation } from './report-summary.mjs';
|
|
3
3
|
|
|
4
4
|
const safe = value => Array.from(String(value ?? '')).map(c => {
|
|
5
5
|
if (c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127) return ' ';
|
|
@@ -30,23 +30,32 @@ export function findingsMarkdown(report) {
|
|
|
30
30
|
const findings = Array.isArray(report.findings) ? report.findings : [];
|
|
31
31
|
const duplicate = new Map();
|
|
32
32
|
for (const group of groupDuplicateFindings(findings)) for (const i of group.findingIndices.slice(1)) duplicate.set(i, group.findingIndices[0]);
|
|
33
|
-
const sections = new Map([['本次新增(复核支持)',[]],['当前源码问题(复核支持)',[]],['
|
|
34
|
-
findings.forEach((f,i)=>{
|
|
33
|
+
const sections = new Map([['本次新增(复核支持)',[]],['当前源码问题(复核支持)',[]],['复核后不确定',[]],['复核未完成',[]],['原有问题(复核支持)',[]],['已反驳',[]]]);
|
|
34
|
+
findings.forEach((f,i)=>{
|
|
35
|
+
const state = verificationPresentation(f, report);
|
|
36
|
+
const label = state === 'uncertain' ? '复核后不确定' : state === 'incomplete' ? '复核未完成' : findingDisposition(f,report);
|
|
37
|
+
if (!duplicate.has(i)) sections.get(label).push({f,i});
|
|
38
|
+
});
|
|
35
39
|
const output = ['', '## 审查发现'];
|
|
36
40
|
if (!findings.length) return [...output, '本轮没有形成问题记录;这不等于已经排除未完成范围内的问题。'];
|
|
37
|
-
output.push('
|
|
41
|
+
output.push('以下分开列示复核支持、复核后不确定、复核未完成和已反驳的记录;证据引用通过校验不等于运行复现或因果证明。');
|
|
38
42
|
for (const [label, items] of sections) {
|
|
39
43
|
if (!items.length) continue;
|
|
40
44
|
output.push('', '### ' + label);
|
|
41
45
|
for (const {f,i} of items) {
|
|
42
46
|
const severity = ({critical:'严重',high:'高',medium:'中',low:'低'})[f.severity] ?? safe(f.severity ?? '未分级');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (
|
|
47
|
+
const hypothesis = label === '复核后不确定' || label === '复核未完成' || label === '已反驳';
|
|
48
|
+
output.push('', '#### ' + (i+1) + '. ' + (hypothesis ? '主审假说:' : '') + safe(f.title) + '(' + (hypothesis ? '主审拟议严重程度' : '严重程度') + ':' + severity + ')', '- 位置:' + location(f));
|
|
49
|
+
if (hypothesis) output.push('- 阅读方式:以下为主审记录的线索和假说,不是确定事实或必改建议;原始字段及完整证据保留在技术附录。');
|
|
50
|
+
const fields = hypothesis ? [['evidence','已知差异/线索(主审记录,非缺陷证明)'],['trigger','待证触发条件'],['impact','影响假说']] : [['trigger','什么情况下发生'],['impact','可能造成的影响'],['evidence','已看到的依据']];
|
|
51
|
+
for (const [key,title] of fields) if (f[key]) output.push('- ' + title + ':' + safe(f[key]));
|
|
52
|
+
if (label === '复核后不确定') output.push('- 复核结论:本候选已完成复核记录,但缺陷或新增归因仍未确定。' + safe(f.verification?.reason ?? '现有证据不足以支持或排除该假说。'));
|
|
53
|
+
if (label === '复核未完成') output.push('- 复核进度:尚未完成有效复核,不能当作已复核不确定或已确认缺陷。' + safe(f.verification?.reason ?? '尚无有效复核结论。'));
|
|
46
54
|
if (label === '已反驳') output.push('- 反证结论:' + safe(f.verification?.reason ?? '复核反驳该候选;不计为待修缺陷。'));
|
|
47
|
-
if (f.attribution?.beforeBehavior) output.push('-
|
|
48
|
-
if (f.attribution?.afterBehavior) output.push('-
|
|
49
|
-
if (
|
|
55
|
+
if (f.attribution?.beforeBehavior) output.push('- 修改前' + (hypothesis ? '(主审描述,待核实)' : '') + ':' + safe(f.attribution.beforeBehavior));
|
|
56
|
+
if (f.attribution?.afterBehavior) output.push('- 修改后' + (hypothesis ? '(主审描述,待核实)' : '') + ':' + safe(f.attribution.afterBehavior));
|
|
57
|
+
if (hypothesis) output.push('- 待验证方案:先核对触发条件、实际调用路径及修改前后行为,再决定是否需要修复。' + (f.suggestion ? '原主审建议(待验证,不是必改项):' + safe(f.suggestion) : '原记录未提供建议。'));
|
|
58
|
+
else if (f.suggestion) output.push('- 建议:' + safe(f.suggestion));
|
|
50
59
|
const related = [...duplicate].filter(([,first])=>first===i).map(([index])=>location(findings[index]));
|
|
51
60
|
if (related.length) output.push('- 相同证据与解释的其他位置:' + related.join(';') + '。正文合并展示,原始候选仍保留。');
|
|
52
61
|
if (f.reused || f.priorRun) output.push('- 来源:包含既往审核结果,不表示本轮重新取证。');
|
package/src/report-summary.mjs
CHANGED
|
@@ -17,6 +17,19 @@ export function comparisonValidated(finding) {
|
|
|
17
17
|
return old.every(i => typeof evidence[i].sourceId === 'string' && evidence[i].sourceId) && fresh.every(i => typeof evidence[i].sourceId === 'string' && evidence[i].sourceId && old.every(j => evidence[j].sourceId !== evidence[i].sourceId));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Rendering-only states. A completed uncertain verdict may legitimately have no
|
|
21
|
+
// evidence (the verifier contract allows this); it is not an unfinished run.
|
|
22
|
+
export function verificationPresentation(finding, report = {}) {
|
|
23
|
+
const v = finding?.verification;
|
|
24
|
+
if (v?.status !== 'completed' || !['supported', 'uncertain', 'refuted'].includes(v.verdict)) return 'incomplete';
|
|
25
|
+
const evidenceValid = rows(v.evidence).length > 0 && v.evidence.every(ref => ref?.status === 'references-validated');
|
|
26
|
+
if (v.verdict === 'refuted' && evidenceValid) return 'refuted';
|
|
27
|
+
if (v.verdict !== 'supported' || !evidenceValid) return 'uncertain';
|
|
28
|
+
if (report.mode === 'project' || report.reviewMode === 'project' || report.projectScope || finding.attribution?.causality === 'current-source-only') return 'current';
|
|
29
|
+
if (comparisonValidated(finding) && ['introduced', 'preexisting'].includes(v.regression.classification)) return v.regression.classification;
|
|
30
|
+
return 'uncertain';
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
/**
|
|
21
34
|
* Pure presentation data for Markdown/Lead renderers. Counts are candidate records,
|
|
22
35
|
* not proven defects. coverage counts main-review entries (directories included in
|
|
@@ -86,6 +99,11 @@ export function reportSummaryMarkdown(report = {}) {
|
|
|
86
99
|
const labels = { introduced: '复核支持本次新增', unresolved: '待核实', preexisting: '复核支持原有', refuted: '复核反驳', current: '当前源码复核支持' };
|
|
87
100
|
output.push('- 候选复核:' + Object.entries(labels).filter(([key]) => counts[key] > 0).map(([key, label]) => label + ' ' + counts[key]).join(';') + '。');
|
|
88
101
|
}
|
|
102
|
+
const states = rows(report.findings).map(finding => verificationPresentation(finding, report));
|
|
103
|
+
const uncertain = states.filter(state => state === 'uncertain').length;
|
|
104
|
+
const incomplete = states.filter(state => state === 'incomplete').length;
|
|
105
|
+
output.push('- 复核状态分列:复核支持 ' + (counts.introduced + counts.preexisting + counts.current) + ';已复核不确定 ' + uncertain + ';复核未完成(未复核或未完成有效复核) ' + incomplete + (counts.refuted ? ';已反驳 ' + counts.refuted : '') + '。');
|
|
106
|
+
if (counts.unresolved) output.push('- “待核实”为合计:已复核不确定与复核未完成分开列示,前者不表示复核未执行;均不能直接视为缺陷或必改项。');
|
|
89
107
|
const unit = coverage.unit === 'files' ? '个文件' : '个变更条目(含目录)';
|
|
90
108
|
const labels = { completed: '主审完成', excluded: '排除', blocked: '阻断', failed: '失败', cancelled: '取消', pending: '待审', running: '执行中', interrupted: '中断', unaccounted: '状态未记账' };
|
|
91
109
|
output.push('- 覆盖:' + coverage.total + ' ' + unit + ';' + Object.entries(labels).filter(([key]) => coverage[key] > 0).map(([key, label]) => label + ' ' + coverage[key]).join(',') + (coverage.total ? '。' : '未执行。'));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { REVIEW_RUNTIME } from './runtime-identity.mjs';
|
|
2
2
|
const PHASES = ['business-grouping', 'risk-planning', 'primary', 'group-primary', 'file-interaction', 'window-synthesis', 'verification'];
|
|
3
|
-
const PURPOSES = ['primary-initial', 'retrieval-followup', 'budget-finalization', 'json-correction', 'business-grouping', 'risk-planning', 'group-primary', 'window-synthesis', 'file-interaction', 'verification'];
|
|
3
|
+
const PURPOSES = ['primary-initial', 'retrieval-followup', 'budget-finalization', 'json-correction', 'business-grouping', 'risk-planning', 'group-primary', 'window-synthesis', 'file-interaction', 'verification', 'verification-correction'];
|
|
4
4
|
const READER_FIELDS = ['calls', 'outputBytes', 'maxCalls', 'maxOutputBytes', 'remainingCalls', 'remainingOutputBytes', 'maxResultBytes', 'cacheHits', 'deliveries', 'cacheEntries', 'cacheBytes'];
|
|
5
5
|
const JSON_ISSUES = ['property-name', 'colon', 'separator', 'invalid-escape', 'control-character', 'unterminated-string', 'unexpected-end', 'trailing-content', 'syntax'];
|
|
6
|
-
const CORRECTION_CODES = ['MODEL_INVALID_JSON', 'MODEL_INVALID_REVIEW_RESPONSE', 'MODEL_INVALID_FINDING_IDENTITY', 'MODEL_INVALID_FINDING_SEVERITY', 'MODEL_MISSING_FINDING_FIELD', 'MODEL_INVALID_ANCHOR', 'MODEL_ANCHOR_MISMATCH', 'RETRIEVAL_PROTOCOL_ERROR'];
|
|
6
|
+
const CORRECTION_CODES = ['MODEL_INVALID_JSON', 'MODEL_INVALID_REVIEW_RESPONSE', 'MODEL_INVALID_FINDING_IDENTITY', 'MODEL_INVALID_FINDING_SEVERITY', 'MODEL_MISSING_FINDING_FIELD', 'MODEL_INVALID_ANCHOR', 'MODEL_ANCHOR_MISMATCH', 'RETRIEVAL_PROTOCOL_ERROR', 'MODEL_INCOMPLETE_VERIFICATION'];
|
|
7
7
|
const LIMIT_CODES = ['READER_CALL_BUDGET_EXCEEDED', 'READER_OUTPUT_BUDGET_EXCEEDED', 'RETRIEVAL_ROUND_BUDGET_EXCEEDED'];
|
|
8
8
|
const RECOVERY_KINDS = ['native-tool', 'text-compatible', 'trailing-prose'];
|
|
9
9
|
const FALLBACK_REASONS = ['mode-notauto', 'smallchange', 'disabled', 'project', 'laterround', 'multi-batch', 'incomplete-edit-scope', 'input-budget'];
|
|
@@ -29,7 +29,7 @@ export function reviewDiagnostics(report, { reviewWallMs = null, executionWallMs
|
|
|
29
29
|
const readerBudget = Object.fromEntries(READER_FIELDS.map(field => [field, number(report.readerBudget?.[field])]));
|
|
30
30
|
const correctionCounts = new Map();
|
|
31
31
|
for (const event of sources.flatMap(f => f.modelCorrections ?? [])) {
|
|
32
|
-
if (!['explicit-json-correction', 'retrieval-protocol-correction', 'review-format-correction'].includes(event?.action) || !CORRECTION_CODES.includes(event.code)) continue;
|
|
32
|
+
if (!['explicit-json-correction', 'retrieval-protocol-correction', 'review-format-correction', 'verification-response-correction'].includes(event?.action) || !CORRECTION_CODES.includes(event.code)) continue;
|
|
33
33
|
const jsonIssue = JSON_ISSUES.includes(event.jsonIssue) ? event.jsonIssue : null;
|
|
34
34
|
const key = JSON.stringify([event.code, jsonIssue]);
|
|
35
35
|
correctionCounts.set(key, (correctionCounts.get(key) ?? 0) + 1);
|
|
@@ -70,7 +70,7 @@ export function diagnosticsMarkdown(d) {
|
|
|
70
70
|
output.push('', '累计调用墙钟可能并发重叠,且包含生命周期观察/初始检查点等待,不是纯提供方耗时,也不能从总耗时中相减当作本地开销。');
|
|
71
71
|
} else output.push('- 逐阶段模型观测不可用(未产生或旧报告未记录),不按零耗时解释。');
|
|
72
72
|
if (d.modelCallPurposes?.length) {
|
|
73
|
-
const labels = { 'primary-initial': '阶段初始', 'retrieval-followup': '检索后续', 'budget-finalization': '预算受限最终答复', 'json-correction': 'JSON纠正', 'business-grouping': '业务分组', 'risk-planning': '风险计划', 'group-primary': '分组主审', 'window-synthesis': '窗口综合', 'file-interaction': '跨文件综合', verification: '复核' };
|
|
73
|
+
const labels = { 'primary-initial': '阶段初始', 'retrieval-followup': '检索后续', 'budget-finalization': '预算受限最终答复', 'json-correction': 'JSON纠正', 'business-grouping': '业务分组', 'risk-planning': '风险计划', 'group-primary': '分组主审', 'window-synthesis': '窗口综合', 'file-interaction': '跨文件综合', verification: '复核', 'verification-correction': '复核批次纠正' };
|
|
74
74
|
output.push('- 调用目的:' + d.modelCallPurposes.filter(row => PURPOSES.includes(row.purpose)).map(row => labels[row.purpose] + ' ' + (number(row.calls) ?? '不可用')).join(';') + '。');
|
|
75
75
|
}
|
|
76
76
|
if (d.readerBudget && READER_FIELDS.some(field => number(d.readerBudget[field]) !== null)) {
|
package/src/review.mjs
CHANGED
|
@@ -312,6 +312,7 @@ export async function reviewSnapshot(snapshot, model, options = {}) {
|
|
|
312
312
|
const onProtocolEvent = async info => {
|
|
313
313
|
if (info.action === 'json-transport-recovery') (state.modelRecoveries ??= []).push(info);
|
|
314
314
|
if (info.action === 'retrieval-protocol-correction') pendingStageCorrection = { action: info.action, code: 'RETRIEVAL_PROTOCOL_ERROR' };
|
|
315
|
+
if (info.action === 'verification-response-correction' && info.code === 'MODEL_INCOMPLETE_VERIFICATION') (state.modelCorrections ??= []).push({ action: info.action, code: info.code });
|
|
315
316
|
await options.onProgress?.({ stage: 'protocol-correction', fileId: file.id, path: file.path, ...info });
|
|
316
317
|
};
|
|
317
318
|
const trackedModel = request => {
|
|
@@ -6,7 +6,7 @@ import { modelWireBudget } from './model-wire-budget.mjs';
|
|
|
6
6
|
import { createEvidenceReceipts } from './evidence-receipts.mjs';
|
|
7
7
|
import { retrievalLoop, retrievalRequest } from './retrieval-loop.mjs';
|
|
8
8
|
import { verificationSeeds } from './verification-seeds.mjs';
|
|
9
|
-
import { validateCandidateVerification } from './candidate-verification.mjs';
|
|
9
|
+
import { validateCandidateVerification, validateCandidateVerificationEnvelope } from './candidate-verification.mjs';
|
|
10
10
|
|
|
11
11
|
export { VERIFICATION_INSTRUCTIONS };
|
|
12
12
|
|
|
@@ -44,7 +44,34 @@ export async function verificationLoop(model, request, candidates, scope, option
|
|
|
44
44
|
const resolveEvidence = value => (seedReceipts.get(value?.receiptId) ?? receipts.resolve)(value);
|
|
45
45
|
// No caller/model-supplied seed inputs: every initial result above was freshly
|
|
46
46
|
// read under this invocation's receipt table and the shared approved budget.
|
|
47
|
-
|
|
47
|
+
let lastRequest;
|
|
48
|
+
const response = await retrievalLoop(value => { lastRequest = value; return model(value); }, verificationRequest, retrievalScope, { ...options, initialRetrieved });
|
|
49
|
+
let accepted = response;
|
|
50
|
+
try { validateCandidateVerificationEnvelope(response, ids); } catch (error) {
|
|
51
|
+
request.signal?.throwIfAborted();
|
|
52
|
+
// Correct only a structurally incomplete batch, before any final evidence
|
|
53
|
+
// reads. Never retry bad identities, invented receipts or regression failures.
|
|
54
|
+
if (options.correctProtocol !== true || error.code !== 'MODEL_INCOMPLETE_VERIFICATION' || JSON.parse(lastRequest.input).retrievalFinalization) throw error;
|
|
55
|
+
// Admit only an otherwise valid subset of evidence-free uncertain entries.
|
|
56
|
+
// Do not let a missing member hide an invalid identity, receipt or regression.
|
|
57
|
+
if (!response || Object.keys(response).length !== 1 || !Array.isArray(response.verdicts) || response.verdicts.length >= ids.length) throw error;
|
|
58
|
+
const subset = response.verdicts.map(item => item?.candidateId);
|
|
59
|
+
validateCandidateVerificationEnvelope(response, subset);
|
|
60
|
+
if (subset.some(id => !ids.includes(id)) || response.verdicts.some(item => item.verdict !== 'uncertain' || item.evidence.length || Object.hasOwn(item, 'regression'))) throw error;
|
|
61
|
+
const correction = { ...lastRequest, callPurpose: 'verification-correction', instructions: lastRequest.instructions + '\n本次仅纠正不完整的候选复核批次。必须为本次 candidates 每个 candidateId 恰好返回一个 verdict;不可漏项、重复或添加身份。仅返回完整 verdicts,不得 requests,不得再次检索。沿用已提供的原文回执;缺证据的候选明确 uncertain 并说明缺失条件,不得编造支持或删除候选。全部结果仍须通过原校验。' };
|
|
62
|
+
const max = options.maxInputBytes ?? 96 * 1024;
|
|
63
|
+
if (!(request.nativeOutputTools === true ? modelWireBudget(correction, max).fits : Buffer.byteLength(correction.input) + Buffer.byteLength(correction.instructions) <= max)) throw error;
|
|
64
|
+
options.beforeCall?.();
|
|
65
|
+
request.signal?.throwIfAborted();
|
|
66
|
+
await request.onProtocolEvent?.({ action: 'verification-response-correction', code: error.code });
|
|
67
|
+
request.signal?.throwIfAborted();
|
|
68
|
+
const next = await model(correction);
|
|
69
|
+
request.signal?.throwIfAborted();
|
|
70
|
+
const serialized = typeof next === 'string' ? next : JSON.stringify(next);
|
|
71
|
+
if (typeof serialized !== 'string' || Buffer.byteLength(serialized) > 128 * 1024) throw new Error('Verification response too large');
|
|
72
|
+
accepted = JSON.parse(serialized);
|
|
73
|
+
validateCandidateVerificationEnvelope(accepted, ids);
|
|
74
|
+
}
|
|
48
75
|
request.signal?.throwIfAborted();
|
|
49
|
-
return validateCandidateVerification(
|
|
76
|
+
return validateCandidateVerification(accepted, ids, scope.read, resolveEvidence, { enableMigrationRegression: options.enableMigrationRegression === true && !project, migrationContext, catalog: scope.catalog(), fileIds: Object.fromEntries(ids.map((id, i) => [id, candidates[i].fileId])) });
|
|
50
77
|
}
|