import { describe, expect, it, vi } from "vitest"; import { runAssistantAddressLaneResponseRuntime } from "../src/services/assistantAddressLaneResponseRuntimeAdapter"; describe("assistant address lane response runtime adapter", () => { it("builds debug payload and finalizes address turn", () => { const finalizeAddressTurn = vi.fn(() => ({ response: { ok: true } })); const runtime = runAssistantAddressLaneResponseRuntime({ sessionId: "asst-1", userMessage: "raw", effectiveAddressUserMessage: "canon", addressLane: { handled: true, reply_text: "answer", reply_type: "factual", debug: { extracted_filters: { organization: "ООО Ромашка" } } }, carryoverMeta: { followupContext: { previous_intent: "list_documents" } }, llmPreDecomposeMeta: { attempted: true }, knownOrganizations: ["ООО Ромашка", "ООО Лютик"], activeOrganization: "ООО Ромашка", sanitizeOutgoingAssistantText: (text) => String(text ?? "").trim(), buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record) }), buildAddressFollowupOffer: () => ({ suggestion: "continue_previous" }), mergeKnownOrganizations: (items) => Array.from(new Set(items)), toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null), appendItem: () => {}, getSession: () => ({ session_id: "asst-1", updated_at: "", items: [], investigation_state: null } as any), persistSession: () => {}, cloneConversation: (items) => items, logEvent: () => {}, messageIdFactory: () => "msg-1", finalizeAddressTurn }); expect(finalizeAddressTurn).toHaveBeenCalledWith( expect.objectContaining({ assistantReply: "answer", replyType: "factual", llmPreDecomposeMeta: { attempted: true } }) ); expect(runtime.response).toEqual({ ok: true }); expect(runtime.debug).toEqual( expect.objectContaining({ assistant_known_organizations: ["ООО Ромашка", "ООО Лютик"], assistant_active_organization: "ООО Ромашка", address_followup_offer: { suggestion: "continue_previous" }, assistant_runtime_contract_v1: expect.objectContaining({ schema_version: "assistant_runtime_contracts_v1" }), assistant_truth_answer_policy_v1: expect.objectContaining({ schema_version: "assistant_truth_answer_policy_runtime_v1", policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter" }), coverage_gate_contract: expect.objectContaining({ schema_version: "assistant_truth_answer_policy_runtime_v1" }), answer_shape_contract: expect.objectContaining({ reply_type: "factual" }), assistant_state_transition_v1: expect.objectContaining({ schema_version: "assistant_state_transition_runtime_v1", state_owner: "assistantStateTransitionRuntimeAdapter" }), state_transition_contract: expect.objectContaining({ schema_version: "assistant_state_transition_runtime_v1" }), assistant_capability_binding_v1: expect.objectContaining({ schema_version: "assistant_capability_runtime_binding_v1", binding_owner: "assistantCapabilityRuntimeBindingAdapter" }), capability_binding_contract: expect.objectContaining({ schema_version: "assistant_capability_runtime_binding_v1" }), capability_binding_response_guard: expect.objectContaining({ schema_version: "assistant_capability_binding_response_guard_v1", applied: false }), assistant_mcp_discovery_entry_point_v1: null, mcp_discovery_attempted: false, mcp_discovery_hot_runtime_wired: false }) ); }); it("attaches MCP discovery summary from predecompose runtime meta without changing the reply", () => { const runtime = runAssistantAddressLaneResponseRuntime({ sessionId: "asst-mcp", userMessage: "raw", effectiveAddressUserMessage: "raw", addressLane: { handled: true, reply_text: "answer", reply_type: "partial_coverage", debug: {} }, llmPreDecomposeMeta: { mcpDiscoveryRuntimeEntryPoint: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", entry_status: "bridge_executed", hot_runtime_wired: false, discovery_attempted: true, turn_input: { adapter_status: "ready" }, bridge: { bridge_status: "answer_draft_ready", user_facing_response_allowed: true, business_fact_answer_allowed: true, requires_user_clarification: false, answer_draft: { answer_mode: "confirmed_with_bounded_inference" } }, reason_codes: ["runtime_entry_point_bridge_executed"] } }, knownOrganizations: [], activeOrganization: null, sanitizeOutgoingAssistantText: (text) => String(text ?? ""), buildAddressDebugPayload: () => ({}), buildAddressFollowupOffer: () => null, mergeKnownOrganizations: (items) => items, toNonEmptyString: () => null, appendItem: () => {}, getSession: () => ({ session_id: "asst-mcp", updated_at: "", items: [], investigation_state: null } as any), persistSession: () => {}, cloneConversation: (items) => items, logEvent: () => {}, messageIdFactory: () => "msg-mcp", finalizeAddressTurn: () => ({ response: { ok: true } }) }); expect(runtime.response).toEqual({ ok: true }); expect(runtime.debug).toEqual( expect.objectContaining({ mcp_discovery_entry_status: "bridge_executed", mcp_discovery_attempted: true, mcp_discovery_bridge_status: "answer_draft_ready", mcp_discovery_answer_mode: "confirmed_with_bounded_inference", mcp_discovery_business_fact_answer_allowed: true, mcp_discovery_hot_runtime_wired: false }) ); }); it("can replace a stale address exact answer with a guarded MCP discovery candidate", () => { const finalizeAddressTurn = vi.fn((input) => ({ response: { ok: true, assistant_reply: input.assistantReply, reply_type: input.replyType, debug: input.debug } })); const runtime = runAssistantAddressLaneResponseRuntime({ sessionId: "asst-mcp-address", userMessage: "сколько мы заплатили СВК за 2020?", effectiveAddressUserMessage: "сколько мы заплатили СВК за 2020?", addressLane: { handled: true, reply_text: "stale documents answer", reply_type: "factual", debug: {} }, llmPreDecomposeMeta: { mcpDiscoveryRuntimeEntryPoint: { schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", entry_status: "bridge_executed", hot_runtime_wired: false, discovery_attempted: true, turn_input: { adapter_status: "ready", should_run_discovery: true }, bridge: { bridge_status: "answer_draft_ready", user_facing_response_allowed: true, business_fact_answer_allowed: true, requires_user_clarification: false, answer_draft: { answer_mode: "confirmed_with_bounded_inference", headline: "Discovery payout answer", confirmed_lines: ["1C supplier-payout rows were found for counterparty SVK"], inference_lines: ["Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"], unknown_lines: ["Full supplier-payout amount outside the checked period is not proven by this MCP discovery pilot"], limitation_lines: [], next_step_line: null } }, reason_codes: ["runtime_entry_point_bridge_executed"] } }, knownOrganizations: [], activeOrganization: null, sanitizeOutgoingAssistantText: (text) => String(text ?? ""), buildAddressDebugPayload: () => ({}), buildAddressFollowupOffer: () => null, mergeKnownOrganizations: (items) => items, toNonEmptyString: () => null, appendItem: () => {}, getSession: () => ({ session_id: "asst-mcp-address", updated_at: "", items: [], investigation_state: null } as any), persistSession: () => {}, cloneConversation: (items) => items, logEvent: () => {}, messageIdFactory: () => "msg-mcp-address", finalizeAddressTurn }); expect(finalizeAddressTurn).toHaveBeenCalledWith( expect.objectContaining({ assistantReply: expect.stringContaining("Discovery payout answer"), replyType: "partial_coverage", debug: expect.objectContaining({ mcp_discovery_response_applied: true }) }) ); expect(String((runtime.response as any).assistant_reply)).toContain("исходящих платежей/списаний"); }); it("keeps debug bounded to shadow contracts when optional enrichment is absent", () => { const runtime = runAssistantAddressLaneResponseRuntime({ sessionId: "asst-2", userMessage: "raw", effectiveAddressUserMessage: "raw", addressLane: { handled: true, reply_text: "answer", reply_type: "partial_coverage", debug: {} }, knownOrganizations: [], activeOrganization: null, sanitizeOutgoingAssistantText: (text) => String(text ?? ""), buildAddressDebugPayload: () => ({}), buildAddressFollowupOffer: () => null, mergeKnownOrganizations: (items) => items, toNonEmptyString: () => null, appendItem: () => {}, getSession: () => ({ session_id: "asst-2", updated_at: "", items: [], investigation_state: null } as any), persistSession: () => {}, cloneConversation: (items) => items, logEvent: () => {}, messageIdFactory: () => "msg-2", finalizeAddressTurn: () => ({ response: { ok: true } }) }); expect(runtime.debug).toEqual( expect.objectContaining({ assistant_runtime_contract_v1: expect.objectContaining({ transition_contract_id: null, capability_contract_id: null, truth_gate_contract_status: "unknown" }), assistant_truth_answer_policy_v1: expect.objectContaining({ schema_version: "assistant_truth_answer_policy_runtime_v1" }), coverage_gate_contract: expect.objectContaining({ coverage_status: "blocked", truth_mode: "unsupported" }), answer_shape_contract: expect.objectContaining({ answer_shape: "blocked_no_answer", reply_type: "partial_coverage" }), assistant_state_transition_v1: expect.objectContaining({ application_status: "unresolved", effective_carryover_depth: "none" }), transition_contract_id: null, capability_contract_id: null, truth_gate_contract_status: "unknown", carryover_eligibility: "none", state_transition_id: null, state_transition_status: "unresolved", effective_carryover_depth: "none", capability_binding_status: "not_applicable", capability_binding_action: "observe_only", capability_binding_violations: [], capability_binding_response_guard: expect.objectContaining({ applied: false, action: "observe_only" }) }) ); expect(runtime.response).toEqual({ ok: true }); }); it("guards blocked capability binding responses before finalizing the turn", () => { const finalizeAddressTurn = vi.fn(() => ({ response: { ok: true } })); runAssistantAddressLaneResponseRuntime({ sessionId: "asst-3", userMessage: "кто поставщик?", effectiveAddressUserMessage: "кто поставщик?", addressLane: { handled: true, reply_text: "unsafe factual answer", reply_type: "factual", debug: { capability_id: "inventory_inventory_purchase_provenance_for_item", limited_reason_category: "missing_anchor", missing_required_filters: ["item"] } }, knownOrganizations: [], activeOrganization: null, sanitizeOutgoingAssistantText: (text) => String(text ?? ""), buildAddressDebugPayload: (addressDebug) => ({ ...(addressDebug as Record) }), buildAddressFollowupOffer: () => null, mergeKnownOrganizations: (items) => items, toNonEmptyString: (value) => (typeof value === "string" && value.trim() ? value.trim() : null), appendItem: () => {}, getSession: () => ({ session_id: "asst-3", updated_at: "", items: [], investigation_state: null } as any), persistSession: () => {}, cloneConversation: (items) => items, logEvent: () => {}, messageIdFactory: () => "msg-3", finalizeAddressTurn }); expect(finalizeAddressTurn).toHaveBeenCalledWith( expect.objectContaining({ assistantReply: expect.stringContaining("Нужно уточнение"), replyType: "partial_coverage", debug: expect.objectContaining({ capability_binding_status: "blocked", capability_binding_action: "clarify", capability_binding_response_guard: expect.objectContaining({ applied: true, action: "clarify", guarded_reply_type: "partial_coverage" }) }) }) ); }); });