Files
Jyotisha/frontend/tests/rectification-host-fallback.test.ts
T
jesseandClaude Opus 5.5 fd34df9b65
Independent Staging Quality Gate / validate (push) Failing after 9m22s
Independent Staging Quality Gate / publish (push) Skipped
fix: rectification recap no longer repeats a date the event already carries (BUG-1496, issue #161)
Staging showed 「记下了:2016-09 2016 年 9 月上大学。」. evidenceRecapLine only
recognised an unspaced 「2016年」 as the phrase's own date, so a spaced year or
an ISO / slash date got the label in front again. It now recognises
`\d{4}\s*年` and `\d{4}[-/.]\d{1,2}`; phrases without a date still get the
label. Regression test fails before the fix and passes after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:06:31 +08:00

123 lines
5.8 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
batchResultFromToolChunk,
composeHostFallbackNarration,
DEFAULT_RETRY_CONSTRAINT,
isToolInputRejection,
publicWriteToolCompleted,
retryConstraintForAttempt,
turnExpectsEvidenceWrite,
UNWRITTEN_EVIDENCE_RETRY_CONSTRAINT,
} from "../src/lib/rectification-agentic/v9/host-fallback.ts";
test("host fallback recap uses only batch return lines", () => {
assert.equal(composeHostFallbackNarration(null), null);
assert.equal(composeHostFallbackNarration({}), "记下了。");
assert.equal(
composeHostFallbackNarration({
accepted_recaps: [
{ display_date_label: "2016年9月", event_phrase: "入学" },
{ display_date_label: "2020年6月", event_phrase: "毕业" },
],
}),
// 原值: "记下了:2016年9月 入学、2020年6月 毕业。"。新值: 中文日期标签与事件之间不留空格。原因: BUG-1136——复述每轮都由服务器写,「2016年9月 入学」读着别扭;ISO 标签(2016-09 入学)保留空格。
"记下了:2016年9月入学、2020年6月毕业。",
);
assert.equal(
composeHostFallbackNarration({
accepted_recaps: [
{ display_date_label: "2016-09", event_phrase: "2016年9月上大学(本科入学)" },
{ display_date_label: "2020-06", event_phrase: "2020年6月毕业" },
],
}),
"记下了:2016年9月上大学(本科入学)、2020年6月毕业。",
);
assert.equal(
composeHostFallbackNarration({
accepted_recaps: [
{ display_date_label: "2016-09", event_phrase: "2016-09 上大学" },
],
}),
"记下了:2016-09 上大学。",
);
assert.equal(
composeHostFallbackNarration({
items: [
{ outcome: "accepted", display_date_label: "2018年7月", event_phrase: "入职" },
{ outcome: "rejected", display_date_label: "1999年", event_phrase: "不该出现" },
],
}),
// 原值: "记下了:2018年7月 入职。"。新值/原因同上(BUG-1136)。
"记下了:2018年7月入职。",
);
});
test("write-tool completion is limited to batch, set-focus and compare", () => {
const none = new Map<string, "completed" | "failed">([
["rectification-read-case", "completed"],
]);
assert.equal(publicWriteToolCompleted(none), false);
const batch = new Map<string, "completed" | "failed">([
["rectification-record-evidence-batch", "completed"],
]);
assert.equal(publicWriteToolCompleted(batch), true);
const failedBatch = new Map<string, "completed" | "failed">([
["rectification-record-evidence-batch", "failed"],
]);
assert.equal(publicWriteToolCompleted(failedBatch), false);
});
test("schema rejection envelopes are not treated as batch recaps or write completion", () => {
const envelope = {
error: true,
message: "Tool input validation failed",
validationErrors: { errors: [], fields: { kind: "debt" } },
};
assert.equal(isToolInputRejection(envelope), true);
assert.equal(composeHostFallbackNarration(envelope), null);
assert.equal(batchResultFromToolChunk({
type: "tool-result",
payload: { toolName: "rectification-record-evidence-batch", result: envelope },
}), null);
assert.equal(isToolInputRejection({ error: true, message: "no fields" }), false);
});
test("year fallback is gone; only explicit evidence expectedWrite triggers the guard", () => {
assert.equal(turnExpectsEvidenceWrite("opening", "evidence"), false);
assert.equal(turnExpectsEvidenceWrite("read_only", "none"), false);
assert.equal(turnExpectsEvidenceWrite("evidence", "evidence"), true);
assert.equal(turnExpectsEvidenceWrite("evidence", "none"), false);
assert.equal(turnExpectsEvidenceWrite("evidence", "unknown"), false);
assert.equal(turnExpectsEvidenceWrite("evidence", undefined), false);
assert.equal(retryConstraintForAttempt("evidence_not_written"), UNWRITTEN_EVIDENCE_RETRY_CONSTRAINT);
assert.equal(retryConstraintForAttempt("empty_stream"), DEFAULT_RETRY_CONSTRAINT);
});
test("case snapshot receipts expose host_fallback origin from the durable phase", () => {
const route = readFileSync(
new URL("../src/lib/rectification-agentic/v9/case-dossier-response.ts", import.meta.url),
"utf8",
);
assert.match(route, /answer_origin: "host_fallback"/);
assert.match(route, /answer\.host_fallback/);
});
// BUG-1496: a phrase that already carries its date must not get the label in
// front again. Staging showed 「记下了:2016-09 2016 年 9 月上大学。」.
test("the recap does not repeat a date the event phrase already carries", async () => {
const { evidenceRecapLine } = await import("../src/lib/rectification-agentic/v9/host-fallback.ts");
assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "2016 年 9 月上大学" }), "2016 年 9 月上大学");
assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "2016年9月上大学" }), "2016年9月上大学");
assert.equal(evidenceRecapLine({ display_date_label: "2024-04-07", event_phrase: "2024-04-07 入职" }), "2024-04-07 入职");
assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "2016/9 上大学" }), "2016/9 上大学");
// Unchanged: a phrase without its own date still gets the label.
assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "上大学" }), "2016-09 上大学");
assert.equal(evidenceRecapLine({ display_date_label: "2016年", event_phrase: "入学" }), "2016年入学");
assert.equal(evidenceRecapLine({ display_date_label: "2016 年 9 月", event_phrase: "去北京工作" }), "2016 年 9 月去北京工作");
// A number that is not a year is not a date.
assert.equal(evidenceRecapLine({ display_date_label: "2016-09", event_phrase: "考了 600 分" }), "2016-09 考了 600 分");
});