import assert from "node:assert/strict"; import test from "node:test"; import { buildInferenceState } from "../src/lib/rectification-agentic/core/build-state.ts"; import { openQuestionFromPersistedFocus, persistServerOwnedFocus, shouldSkipDiscriminatorFollowup, stableFollowupQuestionId, } from "../src/lib/rectification-agentic/v9/server-focus.ts"; import { buildChoiceFrame, serverOwnedChoiceCopy } from "../src/lib/rectification-agentic/v9/choice-card.ts"; import type { MethodFollowup } from "../src/lib/rectification-agentic/v9/method-followup.ts"; import type { EventProbeStyleOption } from "../src/lib/rectification-agentic/v9/refinement-packet.ts"; import { fakeAccounting, CASE_ID, FOCUS_ID, USER_ID, activeFocusFixture } from "./rectification-v9-test-support.ts"; const DYNAMIC_STYLE_OPTIONS = [ { label: "明确发生且时间吻合", answer_class: "yes" }, { label: "发生过但程度较弱", answer_class: "weak_yes" }, { label: "明确没有发生", answer_class: "no" }, { label: "这段记不清楚", answer_class: "unsure" }, ] as const; function discriminatorFollowup(overrides: Partial = {}): MethodFollowup { const frame = buildChoiceFrame({ method_id: "dasha_events", ask_theme: "dated_event", domain: "education", user_prompt_hint: "ask", }, { probes: [{ year: 2016, year_label: "2016 年前后", domain: "education", event_family: "升学结果或学习环境出现明显变化", source: "dasha_activation", tracks: ["vimshottari", "narayana"], tracks_agree: true, unique_minute_claim: false, user_meaning: "2016 年前后升学结果或学习环境出现明显变化", role: "distinguish", information_gain: 0.4, semantic_key: "education:2016", style_options: DYNAMIC_STYLE_OPTIONS, }], }); assert.ok(frame); return { method_id: "dasha_events", intent: "distinguish_candidates", ask_theme: "dated_event", domain: "education", kind_hint: null, user_prompt_hint: "ask", must_not_label: false, choice_frame: frame, source: "event_probe", information_gain: 0.4, semantic_key: "education:2016", probe_year: 2016, candidate_ids: ["05:00", "05:20"], expected_outcomes: [ { answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] }, { answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] }, ], ...overrides, }; } function invalidStyleFollowup( styleOptions: readonly EventProbeStyleOption[] | undefined, eventFamily = "升学结果或学习环境出现明显变化", ): MethodFollowup { const valid = discriminatorFollowup(); return { ...valid, choice_frame: buildChoiceFrame({ method_id: "dasha_events", ask_theme: "dated_event", domain: "education", user_prompt_hint: "ask", }, { probes: [{ year: 2016, year_label: "2016 年前后", domain: "education", event_family: eventFamily, source: "dasha_activation", tracks: ["vimshottari", "narayana"], tracks_agree: true, unique_minute_claim: false, user_meaning: "2016 年前后升学结果或学习环境出现明显变化", role: "distinguish", information_gain: 0.4, semantic_key: "education:2016", style_options: styleOptions, }], }), }; } async function assertInvalidChoiceSkipped(followup: MethodFollowup) { const accounting = fakeAccounting({}, { fallback: () => { throw new Error("invalid choice frame must not call the database"); }, }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: null, decisionReceipt: null, followup, }); assert.equal(result.status, "invalid_choice_schema"); assert.equal(accounting.calls.length, 0); } function persistedFocus(questionId = "probe:education:2016") { const copy = serverOwnedChoiceCopy(discriminatorFollowup().choice_frame!); assert.ok(copy); return { id: FOCUS_ID, caseId: CASE_ID, questionId, intent: "distinguish_candidates", targetEvidenceId: null, targetDomain: "education", targetKind: null, expectedAnswerSchema: { choice: copy, semantic_key: "education:2016", candidate_split_hash: "education:2016", }, status: "active" as const, askedAt: "2026-08-27T00:00:00.000Z", resolvedAt: null, }; } test("only a successfully persisted focus can expose its discriminator question", () => { const focus = persistedFocus(); for (const status of ["created", "already_open"] as const) { assert.deepEqual(openQuestionFromPersistedFocus({ status, focus, questionId: focus.questionId, prompt: "2016 年前后 · 升学结果或学习环境出现明显变化", }), { question_id: focus.questionId, prompt: "2016 年前后 · 升学结果或学习环境出现明显变化", status, }); } }); test("an unpersisted, mismatched, or incomplete focus cannot expose a discriminator question", () => { const focus = persistedFocus(); assert.equal(openQuestionFromPersistedFocus({ status: "duplicate_focus", focus, questionId: focus.questionId, prompt: "不能展示", }), null); assert.equal(openQuestionFromPersistedFocus({ status: "created", focus: { ...focus, questionId: "another-question" }, questionId: focus.questionId, prompt: "不能展示", }), null); assert.equal(openQuestionFromPersistedFocus({ status: "created", focus: { ...focus, expectedAnswerSchema: { choice: { prompt: "不完整" } } }, questionId: focus.questionId, prompt: "不能展示", }), null); }); test("complete dynamic event options persist a server-owned focus", async () => { const followup = discriminatorFollowup({ source: "precision_stage", information_gain: 0.2 }); const accounting = fakeAccounting({ set_agentic_rectification_conversation_focus: (_fn, args) => ({ id: FOCUS_ID, case_id: CASE_ID, question_id: args.p_question_id, intent: args.p_intent, target_evidence_id: null, target_domain: args.p_target_domain, target_kind: args.p_target_kind, expected_answer_schema: args.p_expected_answer_schema, status: "active", asked_at: "2026-08-27T00:00:00.000Z", resolved_at: null, idempotent: false, }), }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: null, decisionReceipt: null, followup, }); assert.equal(result.status, "created"); assert.equal(accounting.calls.length, 1); assert.deepEqual(result.focus?.expectedAnswerSchema.choice, { prompt: "2016 年前后 · 升学结果或学习环境出现明显变化", option_a: DYNAMIC_STYLE_OPTIONS[0].label, option_b: DYNAMIC_STYLE_OPTIONS[1].label, option_c: DYNAMIC_STYLE_OPTIONS[2].label, option_d: DYNAMIC_STYLE_OPTIONS[3].label, options: [ { key: "A", label: DYNAMIC_STYLE_OPTIONS[0].label, answer_class: "yes" }, { key: "B", label: DYNAMIC_STYLE_OPTIONS[1].label, answer_class: "weak_yes" }, { key: "C", label: DYNAMIC_STYLE_OPTIONS[2].label, answer_class: "no" }, { key: "D", label: DYNAMIC_STYLE_OPTIONS[3].label, answer_class: "unsure" }, ], }); }); test("missing, duplicate, incomplete, or illegal dynamic options skip focus persistence", async () => { await assertInvalidChoiceSkipped(invalidStyleFollowup(undefined)); await assertInvalidChoiceSkipped(invalidStyleFollowup(DYNAMIC_STYLE_OPTIONS.slice(0, 3))); await assertInvalidChoiceSkipped(invalidStyleFollowup([ ...DYNAMIC_STYLE_OPTIONS, { label: "另一种明确发生", answer_class: "yes" }, ])); await assertInvalidChoiceSkipped(invalidStyleFollowup([ { ...DYNAMIC_STYLE_OPTIONS[0], label: "08:12 左右发生" }, ...DYNAMIC_STYLE_OPTIONS.slice(1), ])); await assertInvalidChoiceSkipped(invalidStyleFollowup(DYNAMIC_STYLE_OPTIONS, " ")); }); test("does not ask an already-answered discriminator probe again", async () => { const followup = discriminatorFollowup(); const active = activeFocusFixture({ expectedAnswerSchema: { probe_id: "education:2016", choice: serverOwnedChoiceCopy(followup.choice_frame!) }, }); const accounting = fakeAccounting({ set_agentic_rectification_conversation_focus: () => { throw new Error("should not create a second focus"); }, }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: { ...active, id: FOCUS_ID, caseId: CASE_ID, questionId: stableFollowupQuestionId(followup), intent: "distinguish_candidates", targetEvidenceId: null, targetDomain: "education", targetKind: null, expectedAnswerSchema: { probe_id: "education:2016" }, status: "active", askedAt: "2026-08-24T00:00:00.000Z", resolvedAt: null, }, decisionReceipt: { inference_state: { answered_probes: [{ probe_id: "education:2016", semantic_key: "education:2016", answer_class: "yes" }], probes: [], }, }, followup, }); assert.equal(result.status, "probe_already_answered"); assert.equal(accounting.calls.length, 0); }); test("zero information gain does not open a discriminator", () => { assert.equal( shouldSkipDiscriminatorFollowup(discriminatorFollowup({ information_gain: 0 })), "zero_information_gain", ); }); test("recorded event quality cannot open a zero-information scoring card", () => { assert.equal( shouldSkipDiscriminatorFollowup(discriminatorFollowup({ choice_kind: "event_quality", information_gain: 0, })), "zero_information_gain", ); }); test("an unmatched scoring identity is skipped instead of being synthesized", async () => { const followup = discriminatorFollowup({ semantic_key: "education.synthetic", candidate_split_hash: "education:synthetic", }); const accounting = fakeAccounting({ set_agentic_rectification_conversation_focus: () => { throw new Error("must not persist an unmatched scoring focus"); }, }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: null, decisionReceipt: { inference_state: { algorithm_version: "birth-time-event-scoring-v1", candidates: [ { id: "05:00", birth_time: "05:00", posterior: 0.5 }, { id: "05:20", birth_time: "05:20", posterior: 0.5 }, ], answered_probes: [], probes: [{ id: "p-live", semantic_key: "education.live", candidate_split_hash: "education:live", domain: "education", year: 2016, question: "live", candidate_ids: ["05:00", "05:20"], expected_outcomes: [ { answer_class: "yes", supports: ["05:00"], conflicts: ["05:20"] }, { answer_class: "no", supports: ["05:20"], conflicts: ["05:00"] }, ], information_gain: 0.2, source: "dasha_activation", }], }, }, followup, }); assert.equal(result.status, "invalid_choice_schema"); assert.equal(accounting.calls.length, 0); }); test("duplicate focus conflict does not throw", async () => { const followup = discriminatorFollowup({ source: "precision_stage", information_gain: 0.2 }); const accounting = fakeAccounting({ set_agentic_rectification_conversation_focus: () => { throw new Error("agentic_rectification_focus_idempotency_conflict"); }, }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: null, decisionReceipt: null, followup, }); assert.equal(result.status, "duplicate_focus"); }); test("contrast probe is not replaced by an already-answered education quality probe", async () => { const followup = discriminatorFollowup({ domain: "education", ask_theme: "education_style", information_gain: 0.16, semantic_key: "varga.d24.05:00/05:06|05:07", candidate_split_hash: "varga.d24.05:00/05:06|05:07", probe_year: undefined, }); const accounting = fakeAccounting({ set_agentic_rectification_conversation_focus: (_fn, args) => ({ id: FOCUS_ID, case_id: CASE_ID, question_id: args.p_question_id, intent: args.p_intent, target_evidence_id: null, target_domain: args.p_target_domain, target_kind: args.p_target_kind, expected_answer_schema: args.p_expected_answer_schema, status: "active", asked_at: "2026-08-25T00:00:00.000Z", resolved_at: null, idempotent: false, }), }); const result = await persistServerOwnedFocus({ accounting: accounting.client, userId: USER_ID, caseId: CASE_ID, activeFocus: null, decisionReceipt: { inference_state: buildInferenceState({ range_start: "05:00", range_end: "05:20", candidates: [ { id: "05:00", time: "05:00", relative_support: 10 }, { id: "05:20", time: "05:20", relative_support: 10 }, ], events: [], probes: [{ id: "probe:education.2016", semantic_key: "education.2016", candidate_split_hash: "education:2016", domain: "education", year: 2016, question: "发挥失常", candidate_ids: [], expected_outcomes: [], information_gain: 0, source: "known_event_quality", }, { id: "contrast:varga.d24.05:00/05:06|05:07", semantic_key: "varga.d24.05:00/05:06|05:07", candidate_split_hash: "varga.d24.05:00/05:06|05:07", domain: "education", year: 0, question: "学业盘候选差异", candidate_ids: ["05:00", "05:06", "05:07"], expected_outcomes: [ { answer_class: "yes", supports: ["05:00"], conflicts: ["05:06", "05:07"] }, { answer_class: "no", supports: ["05:06", "05:07"], conflicts: ["05:00"] }, ], information_gain: 0.16, source: "varga_contrast", }], }), }, followup, }); assert.equal(result.status, "created"); assert.ok(result.focus); const schema = result.focus.expectedAnswerSchema; assert.equal(schema.semantic_key, "varga.d24.05:00/05:06|05:07"); assert.notEqual(schema.probe_id, "probe:education.2016"); assert.match(String(schema.probe_id), /varga\.d24/); });