fix(rectification): guarantee nonterminal turn exits
Independent Staging Quality Gate / validate (push) Successful in 13m25s
Independent Staging Quality Gate / publish (push) Successful in 9m26s

This commit is contained in:
Jesse_Chen
2026-09-01 13:35:51 +08:00
parent 15877069fc
commit e404b6f42b
13 changed files with 966 additions and 125 deletions
@@ -709,12 +709,15 @@ test("answering a discriminator persists the next dated card so GET still has a
assert.equal(schema.semantic_key, EDUCATION_2014_PROBE.semantic_key);
assert.match(schema.choice?.prompt ?? "", /2014/);
assert.doesNotMatch(schema.choice?.prompt ?? "", /2015/);
assert.equal(schema.choice?.prompt, "2014 年前后,有没有升学、转学或换学习环境?");
assert.equal(applied.nextInterviewPersisted, true);
assert.equal(applied.nextChoiceReady, true);
assert.equal(shouldContinueAfterStructuredChoice(applied.nextAction, applied), false);
assert.equal(applied.narration, "接下来请点选下面这一问。");
const persistedPrompt = schema.choice?.prompt;
assert.ok(persistedPrompt);
assert.equal(applied.narration, persistedPrompt);
const turn = accounting.calls.find((call) => call.fn === "append_agentic_rectification_turn");
assert.equal(turn?.args.p_assistant_message, "接下来请点选下面这一问。");
assert.equal(turn?.args.p_assistant_message, persistedPrompt);
const refreshed = parseV9CaseDossier(twoProbeDossier());
assert.ok(refreshed);
@@ -1104,7 +1107,7 @@ test("structured choice narration never persists a fake loading state", () => {
test("the public agent route treats structured choice as a non-model command", () => {
const route = readFileSync(new URL("../src/app/api/rectification/agent/route.ts", import.meta.url), "utf8");
const start = route.indexOf("if (isStructuredChoice)");
const end = route.indexOf("const selectedModel", start);
const end = route.indexOf("const resolvedModel", start);
assert.ok(start >= 0 && end > start);
const block = route.slice(start, end);
assert.match(block, /applyRectificationChoice\(accounting/);
@@ -1202,6 +1205,42 @@ test("choice identity SQL keys stale_question to inactive focus, not question_id
assert.match(route, /选择题请求缺少 actionId、focusId 或 expectedRevision/);
});
test("choice followup narration uses the server-owned prompt", () => {
const prompt = "2014 年前后,有没有升学、转学或换学习环境?";
const followup: MethodFollowup = {
method_id: "d5_education",
intent: "distinguish_candidates",
ask_theme: "education_style",
domain: "education",
kind_hint: null,
user_prompt_hint: "server-owned choice",
must_not_label: false,
choice_frame: {
question_id: "education.2014",
method_id: "d5_education",
period: "2014 年前后",
prompt,
varga: null,
why: "用于区分候选时间",
option_a_hint: "明确发生且时间吻合",
option_b_hint: "发生过但程度较弱",
neither_label: "明确没有发生",
unsure_label: "这段记不清楚",
option_a_answer_class: "yes",
option_b_answer_class: "weak_yes",
option_c_answer_class: "no",
option_d_answer_class: "unsure",
choice_mode: "A/B/C/D",
stop_label: "先这样,先看当前范围",
stop_message: "先这样",
scoring: true,
},
source: "event_probe",
};
assert.equal(spokenFollowupForUser(followup), prompt);
});
test("degraded spoken collect strips discriminator identity", () => {
const followup: MethodFollowup = {
method_id: "dasha_events",