NODEDC_1C/llm_normalizer/backend/tests/assistantAddressLaneRespons...

365 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<string, unknown>) }),
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<string, unknown>) }),
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"
})
})
})
);
});
});