import assert from "node:assert/strict"; import test from "node:test"; import { 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 { fakeAccounting, CASE_ID, FOCUS_ID, USER_ID, activeFocusFixture } from "./rectification-v9-test-support.ts"; 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", }], }); 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, ...overrides, }; } 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("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"); });