Files
Jyotisha/frontend/tests/rectification-window-cluster-cap-20260909.test.ts
T
Jesse_ChenandClaude Opus 5.5 6ec8efd5d2
Independent Staging Quality Gate / validate (push) Successful in 13m22s
Independent Staging Quality Gate / publish (push) Successful in 3m42s
feat(rectification): server-written lines in the same warm voice (BUG-1258)
About 30 fixed rectification lines lose the receipt tone: no 「已记录。」
openings (收到 when the range holds, 好 when it moves), 麻烦 instead of 请,
时间 instead of 候选 in what the user reads, and no 「新建对话」 after
adoption. Numbers, clocks, ranges and the honesty boundaries are unchanged.
CHOICE_ACK_RE also recognises the new acknowledgements, so a model sentence
that repeats one is still dropped; the delivery guard matches the delivery
sentence by containment and does not depend on the opening.

Full suite 4973 / fail 24, identical to 61d6f248; build keeps / Static.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-10-07 11:39:34 +08:00

117 lines
7.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts";
import { unionStillValidRange } from "../src/lib/rectification-agentic/core/credible-range.ts";
import { RECTIFICATION_USER_COPY } from "../src/lib/rectification-agentic/user-copy.ts";
import { MACHINE_VOICE_LEXICON } from "../src/lib/rectification-agentic/v9/agent-voice-lexicon.ts";
import { stripVerbalWindowChange } from "../src/lib/rectification-agentic/v9/collect-prompt.ts";
import { parseDeclaredBirthWindow } from "../src/lib/rectification-agentic/v9/declared-window-utterance.ts";
import { RECTIFICATION_SKILL_VERSION } from "../src/lib/rectification-agentic/v9/case-status.ts";
import { replyToDeclaredBirthWindow } from "../src/lib/rectification-agentic/v9/agent-route-typed-message.ts";
import { ndjsonEvents, routeContextFixture, ROUTE_TURN_ID } from "./rectification-agent-route-fixtures.ts";
import { readRectificationAgentRouteFile } from "./rectification-agent-route-surface.ts";
const SKILL = readFileSync(
new URL("../../skills/jyotish-birth-time-rectification/SKILL.md", import.meta.url),
"utf8",
);
test("declared birth-window utterances parse ranges and around-times", () => {
assert.deepEqual(
parseDeclaredBirthWindow("我的出生时间是 14 点 45 到 14 点 50"),
{ kind: "range", start: "14:45", end: "14:50" },
);
assert.deepEqual(
parseDeclaredBirthWindow("14:45–14:50"),
{ kind: "range", start: "14:45", end: "14:50" },
);
assert.deepEqual(
parseDeclaredBirthWindow("14:47 左右"),
{ kind: "around", time: "14:47" },
);
assert.equal(parseDeclaredBirthWindow("没有"), null);
assert.equal(parseDeclaredBirthWindow("2014年入学,大概秋天"), null);
assert.equal(parseDeclaredBirthWindow("继续吧"), null);
assert.equal(parseDeclaredBirthWindow("2024 年 8 月 8 日 20:00 左右分手"), null);
assert.equal(parseDeclaredBirthWindow("下午 3 点到 5 点被车撞"), null);
assert.equal(parseDeclaredBirthWindow("2019 年 10 月 23 日 9 点半入职"), null);
});
test("choice-focus declared window is answered from the route before the model", async () => {
// 原值: route.ts 源码里 `if (action === "message" && parseDeclaredBirthWindow` 早于
// `const selectedModel = isStructuredChoice`(以及早于 `await classifyTurnIntentWithRetry`)。
// 新值: route.ts 先 `await replyToDeclaredBirthWindow(context)` 并在有回复时直接 return,之后才解析模型、才进打字预检
// (分类器只在 handleRectificationTypedMessage 里);再直接调用 replyToDeclaredBirthWindow:
// 报时段的消息落一条确定性 turn、流里给锁定回复和 run.completed,别的消息返回 null、不碰持久层。
// 原因: TASK-rectification-code-split-20260926 这一截原样搬进 agent-route-typed-message.ts;调用断言强于源码顺序。
const routeFile = readRectificationAgentRouteFile("../src/app/api/rectification/agent/route.ts");
const intercept = routeFile.indexOf("const declaredWindowReply = await replyToDeclaredBirthWindow(context);");
const selectedModel = routeFile.indexOf("const selectedModel = isStructuredChoice");
const typedPreflight = routeFile.indexOf("return handleRectificationTypedMessage(");
assert.ok(intercept > 0);
assert.match(routeFile, /if \(declaredWindowReply\) return declaredWindowReply;/);
assert.ok(selectedModel > intercept);
assert.ok(typedPreflight > selectedModel);
const declared = routeContextFixture({ action: "message", message: "大概 14:47 左右" });
const reply = await replyToDeclaredBirthWindow(declared.context);
assert.ok(reply);
assert.equal(reply.status, 200);
assert.deepEqual(await ndjsonEvents(reply), [
{ type: "answer.delta", text: RECTIFICATION_USER_COPY.declaredWindowLockedReply },
{ type: "run.completed", turnId: ROUTE_TURN_ID },
]);
assert.deepEqual(declared.rpcCalls.map((call) => call.fn), ["append_agentic_rectification_turn"]);
assert.equal(declared.rpcCalls[0].args.p_assistant_message, RECTIFICATION_USER_COPY.declaredWindowLockedReply);
const ordinary = routeContextFixture({ action: "message", message: "2019 年换了工作" });
assert.equal(await replyToDeclaredBirthWindow(ordinary.context), null);
assert.deepEqual(ordinary.rpcCalls, []);
assert.equal(
RECTIFICATION_USER_COPY.declaredWindowLockedReply,
// 原值: 搜索范围是开始时按你的资料定的,校正过程中不改。想按别的时间段重来,请先到资料里改出生时间,再新建一次校正。 / 新值: 找的范围是开始时按你的资料定的,中途不改。想换个时间段,先在资料里改出生时间,再重新开始一次校正就好。 / 原因: BUG-1258:校正固定文案改为知心姐姐口吻(产品 2026-10-07 认可样稿)
"找的范围是开始时按你的资料定的,中途不改。想换个时间段,先在资料里改出生时间,再重新开始一次校正就好。",
);
});
test("engine cluster coverage becomes the credible-range right edge", () => {
const state = buildInferenceState({
range_start: "14:00",
range_end: "15:00",
candidates: [{
id: "14:40",
time: "14:40",
relative_support: 40,
cluster_times: ["14:40", "14:41", "14:42", "14:43", "14:44", "14:45"],
cluster_start: "14:40",
cluster_end: "14:45",
}],
events: [],
probes: [],
});
assert.deepEqual(state.candidates[0]?.cluster_range, ["14:40", "14:45"]);
assert.deepEqual(unionStillValidRange(state.candidates), ["14:40", "14:45"]);
});
test("agent body cannot verbally accept a spoken birth window", () => {
assert.ok(MACHINE_VOICE_LEXICON.includes("以你说的为准"));
assert.equal(
stripVerbalWindowChange("明白了,出生时间以你说的 14:45–14:50 为准。"),
"明白了,出生时间。",
);
assert.equal(stripVerbalWindowChange("以你说的为准。"), "");
assert.equal(stripVerbalWindowChange("明白了,以你说的为准。"), "明白了。");
assert.match(SKILL, /不得回答『以你说的为准』或改写搜索窗口/);
// 原值: "10.0.25"
// 原值: "10.0.26"
// 新值: "10.0.27"
// 原因: 出卡精度门槛 + 引导式补经历
// 原因: BUG-668 定向补事第四选项写进 Skill
// 原值: "10.0.28" / 新值: "10.0.29" / 原因: 年月阶段改口述并禁止追问本人
// 原值: "10.0.29" / 新值: "10.0.30" / 原因: D2 出卡句与 D4 卡上百分比规则写进 Skill(2026-09-26)
// 原值: "10.0.30" / 新值: "10.0.31" / 原因: 开场改大白话、题干带例子和示例回答写进 Skill OpeningPolicy(BUG-1049,2026-09-26)
// 原值: "10.0.31" / 新值: "10.0.32" / 原因: 训练门开后只问点选卡、交付正文去吻合率写进 Skill(BUG-1084/1085,2026-09-29)
// 原值: "10.0.32" / 新值: "10.0.33" / 原因: 新案绑定持久盘型交付 Skill,历史精确身份不升级(BUG-1115~1117)
assert.equal(RECTIFICATION_SKILL_VERSION, "10.0.33");
});