fix(rectification): harden fallback response boundaries

This commit is contained in:
Jesse_Chen
2026-07-31 20:54:10 +08:00
parent a8ca7f24bc
commit b0dab214b6
2 changed files with 96 additions and 7 deletions
+61 -1
View File
@@ -497,7 +497,7 @@ test("final Director repairs a generic acknowledgement and missing evidence-valu
prompts.push(prompt);
return {
object: phase === "repair"
? plan({ publicReply: { acknowledgement: "你提到2020年4月去石油化工研究院实习做研究员。", evidenceExplanation: "这是一条职业状态变化线索,按能力矩阵可参考 D10 与 Vimshottari;目前只是方法映射,不是候选结论。", candidateCommentary: "这段经历的时间和工作状态变化都很明确,可以和其他独立事件交叉比较候选范围。", limitation: null }, publicExplanationGrounding: [{ source: "capability_matrix", factKey: "domain:career" }] })
? plan({ publicReply: { acknowledgement: "你提到2020年4月去石油化工研究院实习做研究员。", evidenceExplanation: "这是一条职业状态变化线索,按能力矩阵可参考 D10 与 Vimshottari;目前只是方法映射,不是实际计算结果。", candidateCommentary: "这段经历的时间和工作状态变化都很明确,可以和其他独立事件交叉比较候选范围。", limitation: null }, publicExplanationGrounding: [{ source: "capability_matrix", factKey: "domain:career" }] })
: plan(),
};
},
@@ -591,6 +591,24 @@ test("public explanations require real capability grounding for the current even
publicExplanationGrounding: [{ source: "capability_matrix", factKey: "domain:career" }],
});
assert.deepEqual(validateRectificationTurnPlan({ plan: grounded, dossier: dossier([internship]), latestAnswer: internship.rawText, phase: "final" }).issues, []);
const inventedResult = plan({
publicReply: { ...base, evidenceExplanation: "D10 已经显示这次职业变化支持较早的候选区间。" },
publicExplanationGrounding: [{ source: "capability_matrix", factKey: "domain:career" }],
});
assert.ok(validateRectificationTurnPlan({ plan: inventedResult, dossier: dossier([internship]), latestAnswer: internship.rawText, phase: "final" }).issues.includes("capability_claim_not_mapping"));
const disguisedInventedResult = plan({
publicReply: { ...base, evidenceExplanation: "这只是方法映射;D10 已经证明这次经历更符合较早出生。" },
publicExplanationGrounding: [{ source: "capability_matrix", factKey: "domain:career" }],
});
assert.ok(validateRectificationTurnPlan({ plan: disguisedInventedResult, dossier: dossier([internship]), latestAnswer: internship.rawText, phase: "final" }).issues.includes("capability_claim_not_mapping"));
const uncaughtSynonym = plan({
publicReply: { ...base, evidenceExplanation: "这只是方法映射;D10 说明该经历对应出生时段偏前。" },
publicExplanationGrounding: [{ source: "capability_matrix", factKey: "domain:career" }],
});
assert.ok(validateRectificationTurnPlan({ plan: uncaughtSynonym, dossier: dossier([internship]), latestAnswer: internship.rawText, phase: "final" }).issues.includes("capability_claim_not_mapping"));
});
test("the final plan cannot repeat a previously asked question", () => {
@@ -604,6 +622,16 @@ test("the final plan cannot repeat a previously asked question", () => {
},
});
assert.ok(validateRectificationTurnPlan({ plan: repeated, dossier: dossier([], [turn(0, { question: previous })]), latestAnswer: "", phase: "final" }).issues.includes("question_repeated"));
const prefixedRepeat = plan({
action: {
type: "ask_question",
focus: { mode: "collect_independent_event", targetEventId: null, domain: null, requestedFacts: ["independent_event"], rationaleCodes: ["test"] },
question: `目前已有 0 条可继续核对的经历。${previous}`,
optionalQuickReplies: [],
},
});
assert.ok(validateRectificationTurnPlan({ plan: prefixedRepeat, dossier: dossier([], [turn(0, { question: previous })]), latestAnswer: "", phase: "final" }).issues.includes("question_repeated"));
const storedPublicReply = composeRectificationPublicTurn({
acknowledgement: "你提到的是2020年4月去研究院实习。",
evidenceExplanation: "这条经历有明确月份,可以和其他经历交叉比较。",
@@ -703,6 +731,38 @@ test("deterministic fallback refines the known year before collecting another ev
assert.doesNotMatch(result.plan.action.question, /另一件|还能想到一件/);
});
test("deterministic fallback pauses instead of repeating a persisted public question", async () => {
const repeatedQuestion = "目前已有 0 条可继续核对的经历。你愿意再讲一件与已有记录不同、时间大致明确的经历吗?没有、记不清或不想回答也可以换个方向。";
const persisted = composeRectificationPublicTurn({
acknowledgement: "本轮信息已经保留。",
evidenceExplanation: null,
candidateUpdate: null,
limitation: null,
question: repeatedQuestion,
});
const result = await runRectificationDirector({
caseValue,
dossier: buildRectificationCaseDossier({
caseValue,
turns: [turn(0, { question: persisted, answer: "2016年9月去外地上大学" })],
events: [],
snapshot: null,
diagnostics: null,
targetDisposition: "not_applicable",
currentTargetEventId: null,
}),
latestAnswer: "",
phase: "final",
diagnostics,
generatePlan: async () => { throw new Error("forced_fallback"); },
});
assert.equal(result.mode, "deterministic_fallback");
assert.equal(result.plan.action.type, "stop_low_confidence");
if (result.plan.action.type !== "stop_low_confidence") return;
assert.deepEqual(result.plan.action.reasonCodes, ["deterministic_fallback_rejected"]);
});
test("deterministic fallback stays domain-neutral when the Agent is unavailable", async () => {
const university = event({
summary: "2016年9月离家去外地上大学",