Files
Jyotisha/frontend/tests/rectification-window-widen-20260907.test.ts
T
Jesse_ChenandCursor 8e31680b45 fix(rectification): honor declared birth-time uncertainty and split windows over two hours (BUG-571–573)
Intake stores how sure the user is; rectification now searches that range, offers a one-click widen when event fit is low at the edge, and trisects windows longer than two hours before the minute grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-07 12:18:50 +08:00

410 lines
14 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { applyRectificationChoice } from "../src/lib/rectification-agentic/v9/answer-choice.ts";
import { CHOICE_ACTION } from "../src/lib/rectification-agentic/v9/choice-action.ts";
import { decideFromDossier } from "../src/lib/rectification-agentic/v9/decision-from-dossier.ts";
import {
buildWidenWindowFrame,
shouldSuggestWindowWiden,
} from "../src/lib/rectification-agentic/v9/window-widen.ts";
import { clockSpanMinutes, splitWindowIntoThirds } from "../src/lib/rectification-agentic/v9/search-window.ts";
import { evidenceLedgerFingerprint, parseV9CaseDossier } from "../src/lib/rectification-agentic/v9/tool-service.ts";
import { WIDEN_WINDOW_INTENT, WIDEN_WINDOW_KIND } from "../src/lib/rectification-agentic/v9/block-scan.ts";
import {
CASE_ID,
FOCUS_ID,
SESSION_ID,
TURN_ID,
USER_ID,
activeFocusFixture,
candidateSnapshotFixture,
dossierFixture,
fakeAccounting,
receiptHandlers,
} from "./rectification-v9-test-support.ts";
const TRAINING_EVIDENCE = [
{
id: "e-edu",
source_turn_id: TURN_ID,
subject: "self",
event_kind: "education_start",
domain: "education",
occurred_from: "2016-09-01",
occurred_to: "2016-09-30",
date_precision: "month",
summary: "education start",
status: "confirmed",
supersedes_evidence_id: null,
created_at: "2026-09-07T00:00:00.000Z",
},
{
id: "e-rel",
source_turn_id: TURN_ID,
subject: "self",
event_kind: "relationship_end",
domain: "relationship",
occurred_from: "2024-08-08",
occurred_to: "2024-08-08",
date_precision: "day",
summary: "relationship end",
status: "confirmed",
supersedes_evidence_id: null,
created_at: "2026-09-07T00:00:01.000Z",
},
{
id: "e-move",
source_turn_id: TURN_ID,
subject: "self",
event_kind: "relocation",
domain: "relocation",
occurred_from: "2023-07-01",
occurred_to: "2023-07-31",
date_precision: "month",
summary: "relocation",
status: "confirmed",
supersedes_evidence_id: null,
created_at: "2026-09-07T00:00:02.000Z",
},
{
id: "e-job",
source_turn_id: TURN_ID,
subject: "self",
event_kind: "career_entry",
domain: "career",
occurred_from: "2018-07-01",
occurred_to: "2018-07-31",
date_precision: "month",
summary: "career entry",
status: "confirmed",
supersedes_evidence_id: null,
created_at: "2026-09-07T00:00:03.000Z",
},
] as const;
function widenSchema(reportedTime = "05:00", window = { start_time: "04:45", end_time: "05:15" }) {
const frame = buildWidenWindowFrame({ reportedTime, currentWindow: window });
assert.ok(frame);
const windows = {
A: { radius: 30, start_time: "04:30", end_time: "05:30" },
B: { radius: 60, start_time: "04:00", end_time: "06:00" },
};
return {
choice: {
prompt: frame.prompt,
option_a: frame.option_a_hint,
option_b: frame.option_b_hint,
option_c: frame.neither_label,
option_d: frame.unsure_label,
options: [
{ key: "A", label: frame.option_a_hint, answer_class: frame.option_a_answer_class, role: "primary" },
{ key: "B", label: frame.option_b_hint, answer_class: frame.option_b_answer_class, role: "primary" },
{ key: "C", label: frame.neither_label, answer_class: frame.option_c_answer_class, role: "primary" },
{ key: "D", label: frame.unsure_label, answer_class: frame.option_d_answer_class, role: "primary" },
],
},
choice_kind: WIDEN_WINDOW_KIND,
scoring: false,
widen_windows: windows,
};
}
function widenDossier(extra: {
representativeTime?: string;
range?: { start_time: string; end_time: string };
declinedFingerprint?: string | null;
birthTimeSource?: string;
evidence?: unknown[];
withFocus?: boolean;
} = {}) {
const range = extra.range ?? { start_time: "04:45", end_time: "05:15" };
const schema = widenSchema("05:00", range);
return dossierFixture({
candidateRange: range,
stage: "minute",
reportedBirthTime: "05:00",
birthTimeSource: extra.birthTimeSource ?? "approximate",
widenDeclinedAtFingerprint: extra.declinedFingerprint ?? null,
evidence: extra.evidence ?? [...TRAINING_EVIDENCE],
latestResult: candidateSnapshotFixture({
representativeTime: extra.representativeTime ?? "04:45",
decisionReceipt: {
event_fit_rate: {
matched: 1,
total: 4,
percent: 25,
band: "low",
label: "事件吻合率",
user_meaning: "这是相对拟合,不是已确认唯一出生分钟。",
unique_minute_claim: false,
},
},
}),
conversationSummary: {
confirmed_evidence_summary: [],
pending_revisions: [],
active_focus: extra.withFocus === false
? null
: activeFocusFixture({
intent: WIDEN_WINDOW_INTENT,
targetDomain: null,
targetKind: null,
questionId: "widen_window:search_range:holdout",
expectedAnswerSchema: schema,
}),
declined_skipped_topics: [],
candidate_divergence_summary: null,
missing_evidence_categories: [],
last_result_policy: null,
summary_version: 1,
updated_at: "2026-09-07T00:00:00.000Z",
},
});
}
function choiceHandlers(current: { value: ReturnType<typeof widenDossier> }) {
return {
...receiptHandlers,
get_agentic_rectification_case_dossier: () => current.value,
get_agentic_rectification_case_compute: () => ({
case_id: CASE_ID,
skill_version: "9.0.0",
baseline_profile_fingerprint: "a".repeat(64),
baseline_birth_snapshot: {
birth_date: "1997-08-08",
latitude: 36.42,
longitude: 114.2,
timezone_id: "Asia/Shanghai",
timezone_offset: 8,
birth_time_source: current.value.case.birth_time_source,
reported_birth_time: "05:00",
},
candidate_range: current.value.case.candidate_range,
}),
append_agentic_rectification_turn: () => ({ turn_id: TURN_ID, idempotent: false }),
apply_agentic_rectification_choice_action: (_fn: string, args: Record<string, unknown>) => ({
action_id: args.p_action_id,
status: "applied",
idempotent: false,
question_id: args.p_question_id,
option_id: args.p_option_id,
revision: 1,
narration: args.p_narration,
focus_status: args.p_focus_status,
}),
set_agentic_rectification_conversation_focus: (_fn: string, args: Record<string, unknown>) => ({
focus: {
id: FOCUS_ID,
case_id: CASE_ID,
question_id: args.p_question_id,
intent: args.p_intent,
expected_answer_schema: args.p_expected_answer_schema,
status: "active",
asked_at: "2026-09-07T00:00:00.000Z",
resolved_at: null,
},
idempotent: false,
}),
persist_agentic_rectification_inference_transition: () => {
throw new Error("widen must not rewrite inference");
},
};
}
test("exclusive span 120 stays minute; 240 splits into three non-overlapping thirds", () => {
assert.equal(clockSpanMinutes("04:00", "06:00"), 120);
assert.equal(clockSpanMinutes("03:00", "07:00"), 240);
const thirds = splitWindowIntoThirds({ start_time: "03:00", end_time: "07:00" });
assert.equal(thirds.length, 3);
assert.equal(thirds[0]?.start_time, "03:00");
assert.equal(thirds[2]?.end_time, "07:00");
assert.notEqual(thirds[0]?.end_time, thirds[1]?.start_time);
});
test("fit low plus a representative on the edge asks a four-option widen card", () => {
const parsed = parseV9CaseDossier(widenDossier({ withFocus: false }));
assert.ok(parsed);
const decision = decideFromDossier(parsed);
assert.equal(decision.nextAction, "ask_window_widen");
assert.equal(decision.sessionOutcome, "widen_window");
assert.equal(decision.canAdopt, false);
const frame = buildWidenWindowFrame({
reportedTime: "05:00",
currentWindow: { start_time: "04:45", end_time: "05:15" },
});
assert.ok(frame);
assert.equal(frame.choice_kind, WIDEN_WINDOW_KIND);
assert.match(frame.option_a_hint, /半小时/);
assert.match(frame.option_b_hint, /一小时/);
assert.match(frame.neither_label, /不放宽/);
assert.ok(frame.unsure_label.length >= 4);
const labels = [frame.option_a_hint, frame.option_b_hint, frame.neither_label, frame.unsure_label];
assert.equal(new Set(labels).size, 4);
});
test("fit low with a representative in the middle of the window does not suggest widen", () => {
const parsed = parseV9CaseDossier(widenDossier({
representativeTime: "05:00",
withFocus: false,
}));
assert.ok(parsed);
assert.notEqual(decideFromDossier(parsed).nextAction, "ask_window_widen");
assert.equal(
shouldSuggestWindowWiden({
trainingGateOpen: true,
fitBand: "low",
fitTotal: 4,
representativeNearEdge: false,
stage: "minute",
accepted: false,
reportedTime: "05:00",
declinedForFingerprint: false,
currentRadius: 15,
}),
false,
);
});
test("answering A widens the window, patches approximate radius, and does not rewrite answered probes", async () => {
const current = { value: widenDossier() };
const accounting = fakeAccounting({
...choiceHandlers(current),
widen_agentic_rectification_case_window: (_fn, args) => {
current.value = widenDossier({
range: { start_time: String(args.p_start_time), end_time: String(args.p_end_time) },
withFocus: false,
});
return {
case_id: CASE_ID,
stage: "minute",
candidate_range: { start_time: args.p_start_time, end_time: args.p_end_time },
};
},
});
const askedBefore = accounting.calls.filter((item) => item.fn.includes("inference")).length;
const receipt = await applyRectificationChoice(accounting.client, {
userId: USER_ID,
caseId: CASE_ID,
sessionId: SESSION_ID,
actionId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa3",
action: CHOICE_ACTION,
focusId: FOCUS_ID,
optionId: "A",
expectedRevision: 0,
});
assert.equal(receipt.snapshotCurrent, false);
assert.ok(accounting.calls.some((item) => item.fn === "widen_agentic_rectification_case_window"));
const widenCall = accounting.calls.find((item) => item.fn === "widen_agentic_rectification_case_window");
assert.equal(widenCall?.args.p_start_time, "04:30");
assert.equal(widenCall?.args.p_end_time, "05:30");
assert.deepEqual(accounting.profilePatches, [{
birth_time_source: "approximate",
uncertainty_before_minutes: 30,
uncertainty_after_minutes: 30,
}]);
assert.equal(
accounting.calls.filter((item) => item.fn.includes("inference")).length,
askedBefore,
);
});
test("answering C records the fingerprint and does not ask again until evidence changes", async () => {
const current = { value: widenDossier() };
const parsedBefore = parseV9CaseDossier(current.value);
assert.ok(parsedBefore);
const fingerprint = evidenceLedgerFingerprint(parsedBefore.evidence);
const accounting = fakeAccounting({
...choiceHandlers(current),
set_agentic_rectification_widen_declined: (_fn, args) => {
current.value = widenDossier({
declinedFingerprint: String(args.p_fingerprint),
withFocus: false,
});
return {
case_id: CASE_ID,
widen_declined_at_fingerprint: args.p_fingerprint,
};
},
});
await applyRectificationChoice(accounting.client, {
userId: USER_ID,
caseId: CASE_ID,
sessionId: SESSION_ID,
actionId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa4",
action: CHOICE_ACTION,
focusId: FOCUS_ID,
optionId: "C",
expectedRevision: 0,
});
const declined = parseV9CaseDossier(current.value);
assert.ok(declined);
assert.equal(declined.case.widenDeclinedAtFingerprint, fingerprint);
assert.notEqual(decideFromDossier(declined, { currentEvidenceFingerprint: fingerprint }).nextAction, "ask_window_widen");
const freshEvidence = [
...TRAINING_EVIDENCE,
{
...TRAINING_EVIDENCE[3],
id: "e-job-2",
occurred_from: "2019-03-01",
occurred_to: "2019-03-31",
summary: "later career",
},
];
const later = parseV9CaseDossier(widenDossier({
declinedFingerprint: fingerprint,
evidence: freshEvidence,
withFocus: false,
}));
assert.ok(later);
assert.equal(decideFromDossier(later).nextAction, "ask_window_widen");
});
test("already ±120 does not suggest a widen card", () => {
assert.equal(
shouldSuggestWindowWiden({
trainingGateOpen: true,
fitBand: "low",
fitTotal: 4,
representativeNearEdge: true,
stage: "minute",
accepted: false,
reportedTime: "05:00",
declinedForFingerprint: false,
currentRadius: 120,
}),
false,
);
});
test("hospital_record answering A widens the window but does not rewrite the archive source", async () => {
const current = { value: widenDossier({ birthTimeSource: "hospital_record" }) };
const accounting = fakeAccounting({
...choiceHandlers(current),
widen_agentic_rectification_case_window: (_fn, args) => {
current.value = widenDossier({
birthTimeSource: "hospital_record",
range: { start_time: String(args.p_start_time), end_time: String(args.p_end_time) },
withFocus: false,
});
return {
case_id: CASE_ID,
stage: "minute",
candidate_range: { start_time: args.p_start_time, end_time: args.p_end_time },
};
},
});
await applyRectificationChoice(accounting.client, {
userId: USER_ID,
caseId: CASE_ID,
sessionId: SESSION_ID,
actionId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaa5",
action: CHOICE_ACTION,
focusId: FOCUS_ID,
optionId: "A",
expectedRevision: 0,
});
assert.ok(accounting.calls.some((item) => item.fn === "widen_agentic_rectification_case_window"));
assert.deepEqual(accounting.profilePatches, []);
});