Files
Jyotisha/frontend/tests/rectification-window-widen-20260907.test.ts
T
jesse-ux b85c4a686a
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s
fix(rectification): anchor candidate windows to civil dates across midnight
Carry explicit local date intervals instead of inferring the day from clock
order. Cluster width, delivery, adoption, and reports keep the actual civil
date; adopted date is stored separately from the reported birth_date.

Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation
thresholds, and Skill version are unchanged. Isolated Linux final-3 gates
passed; four pre-existing Python failures remain. This is not a production
release.
2026-09-21 02:55:00 +08:00

431 lines
15 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;
candidate_intervals?: { start_at: string; end_at: 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_dated_window: (_fn, args) => {
current.value = widenDossier({
range: {
start_time: String(args.p_start_time), end_time: String(args.p_end_time),
candidate_intervals: [{ start_at: "1997-08-08T04:30", end_at: "1997-08-08T05:30" }],
},
withFocus: false,
});
return {
case_id: CASE_ID,
stage: "minute",
candidate_range: current.value.case.candidate_range,
};
},
});
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_dated_window"));
const widenCall = accounting.calls.find((item) => item.fn === "widen_agentic_rectification_dated_window");
assert.equal(widenCall?.args.p_start_time, "04:30");
assert.equal(widenCall?.args.p_end_time, "05:30");
assert.deepEqual(parseV9CaseDossier(current.value)?.case.candidateRange, {
start_time: "04:30", end_time: "05:30",
candidate_intervals: [{ start_at: "1997-08-08T04:30", end_at: "1997-08-08T05: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_dated_window: (_fn, args) => {
current.value = widenDossier({
birthTimeSource: "hospital_record",
range: {
start_time: String(args.p_start_time), end_time: String(args.p_end_time),
candidate_intervals: [{ start_at: "1997-08-08T04:30", end_at: "1997-08-08T05:30" }],
},
withFocus: false,
});
return {
case_id: CASE_ID,
stage: "minute",
candidate_range: current.value.case.candidate_range,
};
},
});
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_dated_window"));
const widenCall = accounting.calls.find((item) => item.fn === "widen_agentic_rectification_dated_window");
assert.equal(widenCall?.args.p_start_time, "04:30");
assert.equal(widenCall?.args.p_end_time, "05:30");
assert.deepEqual(parseV9CaseDossier(current.value)?.case.candidateRange, {
start_time: "04:30", end_time: "05:30",
candidate_intervals: [{ start_at: "1997-08-08T04:30", end_at: "1997-08-08T05:30" }],
});
assert.deepEqual(accounting.profilePatches, []);
});