import { describe, expect, it } from "vitest"; import { buildAssistantMcpDiscoveryTurnInput } from "../src/services/assistantMcpDiscoveryTurnInputAdapter"; describe("assistant MCP discovery turn input adapter", () => { it("maps unsupported assistant turn meaning into a discovery-ready value-flow input", () => { const result = buildAssistantMcpDiscoveryTurnInput({ assistantTurnMeaning: { schema_version: "assistant_turn_meaning_v1", asked_domain_family: "counterparty", asked_action_family: "counterparty_value_or_turnover", unsupported_but_understood_family: "counterparty_value_or_turnover", stale_replay_forbidden: true, explicit_entity_candidates: [{ type: "counterparty", value: "SVK", source: "current_turn_loose_entity_tail" }] }, predecomposeContract: { entities: { counterparty: "Группа СВК", organization: "Альтернатива" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["SVK", "Группа СВК"]); expect(result.turn_meaning_ref?.explicit_organization_scope).toBe("Альтернатива"); expect(result.turn_meaning_ref?.explicit_date_scope).toBe("2020"); expect(result.turn_meaning_ref?.stale_replay_forbidden).toBe(true); }); it("bootstraps lifecycle discovery from raw user wording and predecompose scope", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "Сколько лет мы работаем с Группа СВК?", predecomposeContract: { entities: { counterparty: "Группа СВК" }, period: { period_from: null, period_to: null, as_of_date: null } } }); expect(result.adapter_status).toBe("ready"); expect(result.source_signal).toBe("predecompose_contract"); expect(result.semantic_data_need).toBe("counterparty lifecycle evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["Группа СВК"], unsupported_but_understood_family: "counterparty_lifecycle", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_lifecycle_signal_detected"); }); it("bootstraps value-flow discovery from raw turnover wording when no exact route owns it", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "какой денежный поток был у Группа СВК за 2020 год?", predecomposeContract: { entities: { counterparty: "Группа СВК" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_value_or_turnover", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_value_flow_signal_detected"); }); it("treats value-flow organization-shaped target as entity candidate when counterparty is absent", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "какой денежный поток был у Группа СВК за 2020 год?", predecomposeContract: { entities: { organization: "Группа СВК" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } } }); expect(result.adapter_status).toBe("ready"); expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["Группа СВК"]); expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined(); }); it("keeps payout wording as outgoing supplier-payout discovery", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "сколько мы заплатили Группа СВК за 2020 год?", predecomposeContract: { entities: { counterparty: "Группа СВК" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } } }); expect(result.adapter_status).toBe("ready"); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_payouts_or_outflow", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_payout_signal_detected"); }); it("keeps net cash wording as bidirectional value-flow discovery", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "какое нетто по деньгам с Группа СВК за 2020: сколько получили и сколько заплатили?", predecomposeContract: { entities: { counterparty: "Группа СВК" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } } }); expect(result.adapter_status).toBe("ready"); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected"); expect(result.reason_codes).not.toContain("mcp_discovery_payout_signal_detected"); }); it("captures monthly aggregation as part of bidirectional value-flow meaning", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "какое нетто по деньгам с Группа СВК за 2020 год по месяцам: сколько получили и сколько заплатили помесячно?", predecomposeContract: { entities: { counterparty: "Группа СВК" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } } }); expect(result.adapter_status).toBe("ready"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", asked_aggregation_axis: "month", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020" }); expect(result.reason_codes).toContain("mcp_discovery_monthly_aggregation_signal_detected"); }); it("bootstraps metadata discovery from raw schema wording", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "какие регистры и поля есть в 1С по НДС?" }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("raw_text"); expect(result.semantic_data_need).toBe("1C metadata evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "metadata", asked_action_family: "inspect_fields", unsupported_but_understood_family: "1c_metadata_surface", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_signal_detected"); }); it("treats broad 1C object wording as metadata surface discovery instead of narrowing to catalog-only", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "какие объекты 1С есть по НДС?" }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("raw_text"); expect(result.semantic_data_need).toBe("1C metadata evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "metadata", asked_action_family: "inspect_surface", explicit_entity_candidates: ["НДС"], unsupported_but_understood_family: "1c_metadata_surface", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_signal_detected"); expect(result.reason_codes).toContain("mcp_discovery_metadata_scope_hint_from_raw_text"); }); it("bootstraps entity-resolution discovery from raw counterparty search wording", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "найди в 1С контрагента Группа СВК" }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("raw_text"); expect(result.semantic_data_need).toBe("entity discovery evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "entity_resolution", asked_action_family: "search_business_entity", explicit_entity_candidates: ["Группа СВК"], unsupported_but_understood_family: "entity_resolution", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_entity_resolution_signal_detected"); expect(result.reason_codes).toContain("mcp_discovery_entity_scope_from_raw_entity_search"); }); it("seeds document evidence follow-up from prior entity-resolution grounding", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "по нему документы за 2020 год", followupContext: { previous_discovery_pilot_scope: "entity_resolution_search_v1", previous_anchor_type: "counterparty", previous_anchor_value: "Группа СВК", previous_discovery_entity_candidates: ["Группа СВК", "СВК"] } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("document evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_entity_resolution_grounded_document_followup"); expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context"); }); it("seeds movement evidence follow-up from prior entity-resolution grounding", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а теперь по нему движения за 2020 год", followupContext: { previous_discovery_pilot_scope: "entity_resolution_search_v1", previous_discovery_entity_candidates: ["Группа СВК", "СВК"] } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_entity_resolution_grounded_movement_followup"); expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context"); }); it("overrides a wrong exact document intent when a grounded document follow-up asks to switch into movements", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а теперь по нему движения за 2020 год", assistantTurnMeaning: { asked_domain_family: "inventory", asked_action_family: "purchase_documents", explicit_intent_candidate: "inventory_purchase_documents_for_item", explicit_entity_candidates: [{ value: "нему" }] }, predecomposeContract: { entities: { counterparty: "нему" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } }, followupContext: { previous_discovery_pilot_scope: "counterparty_document_evidence_query_documents_v1", previous_filters: { counterparty: "Группа СВК", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_discovery_entity_candidates: ["Группа СВК", "СВК"] } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("assistant_turn_meaning"); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_document_evidence_grounded_movement_followup"); expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn"); }); it("overrides a supported exact turnover intent when a grounded entity follow-up asks about incoming money", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "сколько получили по нему за 2020 год", assistantTurnMeaning: { asked_domain_family: "counterparty", asked_action_family: "turnover", explicit_intent_candidate: "customer_revenue_and_payments", explicit_entity_candidates: [{ value: "нему" }] }, predecomposeContract: { entities: { counterparty: "нему" }, period: { period_from: "2020-01-01", period_to: "2020-12-31" } }, followupContext: { previous_discovery_pilot_scope: "entity_resolution_search_v1", previous_filters: { counterparty: "Группа СВК" }, previous_discovery_entity_candidates: ["Группа СВК", "СВК"] } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_value_or_turnover", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup"); expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn"); }); it("overrides a supported exact payout intent when a grounded value-flow follow-up switches from incoming to outgoing", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а теперь сколько заплатили?", assistantTurnMeaning: { asked_domain_family: "counterparty", asked_action_family: "turnover", explicit_intent_candidate: "customer_revenue_and_payments" }, followupContext: { previous_discovery_pilot_scope: "counterparty_value_flow_query_movements_v1", previous_filters: { counterparty: "Группа СВК", organization: "ООО Альтернатива Плюс", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Группа СВК" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["Группа СВК"], explicit_organization_scope: "ООО Альтернатива Плюс", explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_payouts_or_outflow", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup"); expect(result.reason_codes).toContain("mcp_discovery_payout_signal_detected"); expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn"); }); it("overrides a supported exact net intent when a grounded payout follow-up switches into net flow", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а какое нетто?", assistantTurnMeaning: { asked_domain_family: "counterparty", asked_action_family: "turnover", explicit_intent_candidate: "customer_revenue_and_payments" }, followupContext: { previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1", previous_filters: { counterparty: "Группа СВК", period_from: "2021-01-01", period_to: "2021-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Группа СВК" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("counterparty value-flow evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_entity_candidates: ["Группа СВК"], explicit_date_scope: "2021", unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup"); expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected"); expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn"); }); it("seeds short monthly follow-up from prior bidirectional discovery context", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а по месяцам?", followupContext: { previous_discovery_pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1", previous_filters: { counterparty: "Группа СВК", organization: "ООО Альтернатива Плюс", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Группа СВК" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", asked_aggregation_axis: "month", explicit_entity_candidates: ["Группа СВК"], explicit_organization_scope: "ООО Альтернатива Плюс", explicit_date_scope: "2020", unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_seeded_from_followup_context"); expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context"); expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context"); }); it("seeds short metadata follow-up from prior metadata discovery context", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а по регистрам?", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_filters: { counterparty: "НДС" }, previous_anchor_type: "counterparty", previous_anchor_value: "НДС" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("1C metadata evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "metadata", asked_action_family: "inspect_registers", explicit_entity_candidates: ["НДС"], unsupported_but_understood_family: "1c_metadata_surface", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_seeded_from_followup_context"); expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context"); }); it("pivots grounded metadata follow-up into document evidence when the next lane is explicit", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "then documents", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_route_family: "document_evidence", previous_discovery_metadata_selected_entity_set: "Документ", previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("document evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_grounded_document_followup"); expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context"); }); it("pivots grounded metadata follow-up into movement evidence when the next lane is explicit", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "then movements", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_route_family: "movement_evidence", previous_discovery_metadata_selected_entity_set: "РегистрНакопления", previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_grounded_movement_followup"); expect(result.reason_codes).toContain("mcp_discovery_counterparty_from_followup_context"); }); it("continues from grounded metadata into document evidence on a generic downstream follow-up", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "давай дальше", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_route_family: "document_evidence", previous_discovery_metadata_selected_entity_set: "Документ", previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("document evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_grounded_lane_continuation"); }); it("continues from grounded metadata into movement evidence on a generic downstream follow-up", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "continue with data", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_route_family: "movement_evidence", previous_discovery_metadata_selected_entity_set: "РегистрНакопления", previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_grounded_lane_continuation"); }); it("resolves ambiguous metadata surface into document lane when the follow-up explicitly asks for documents", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "по документам", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("document evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_resolved_to_document_lane"); }); it("resolves ambiguous metadata surface into movement lane when the follow-up explicitly asks for movements", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "по движениям", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_resolved_to_movement_lane"); }); it("resolves ambiguous metadata surface into document lane from semantic document hints", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "покажи счет-фактуры", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("document evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_resolved_to_document_lane"); }); it("resolves ambiguous metadata surface into movement lane from semantic movement hints without turning it into value-flow", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "покажи платежи", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_resolved_to_movement_lane"); expect(result.reason_codes).not.toContain("mcp_discovery_value_flow_signal_detected"); }); it("does not resolve metadata ambiguity into movement lane when confirmed ambiguity sets contain documents only", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "по движениям", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.reason_codes).not.toContain("mcp_discovery_metadata_ambiguity_resolved_to_movement_lane"); }); it("continues from ambiguous metadata into document lane when ambiguity choice set collapses to documents on a generic downstream follow-up", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "continue with data", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "invoice"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("document evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_collapsed_to_document_lane"); }); it("continues from ambiguous metadata into movement lane when ambiguity choice set collapses to movements on a generic downstream follow-up", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "continue with data", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["РегистрНакопления", "movement"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.semantic_data_need).toBe("movement evidence"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "movements", asked_action_family: "list_movements", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "movement_evidence", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_collapsed_to_movement_lane"); }); it("requires an explicit lane choice on a generic downstream follow-up when metadata ambiguity stays mixed", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "давай дальше", followupContext: { previous_discovery_pilot_scope: "metadata_inspection_v1", previous_discovery_metadata_ambiguity_detected: true, previous_discovery_metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], previous_filters: { counterparty: "SVK", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "SVK" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.source_signal).toBe("followup_context"); expect(result.semantic_data_need).toBe("metadata lane clarification"); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "metadata", asked_action_family: "resolve_next_lane", explicit_entity_candidates: ["SVK"], metadata_ambiguity_entity_sets: ["Документ", "РегистрНакопления"], explicit_date_scope: "2020", unsupported_but_understood_family: "metadata_lane_choice_clarification", stale_replay_forbidden: true }); expect(result.reason_codes).toContain("mcp_discovery_metadata_ambiguity_requires_lane_choice"); }); it("switches the checked year on a short payout follow-up while keeping prior discovery counterparty", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "а теперь за 2021?", followupContext: { previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1", previous_filters: { counterparty: "Группа СВК", organization: "ООО Альтернатива Плюс", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Группа СВК" } }); expect(result.adapter_status).toBe("ready"); expect(result.should_run_discovery).toBe(true); expect(result.turn_meaning_ref).toMatchObject({ asked_domain_family: "counterparty_value", asked_action_family: "payout", explicit_entity_candidates: ["Группа СВК"], explicit_organization_scope: "ООО Альтернатива Плюс", explicit_date_scope: "2021", unsupported_but_understood_family: "counterparty_payouts_or_outflow", stale_replay_forbidden: true }); }); it("does not activate discovery for supported exact current-turn intent", () => { const result = buildAssistantMcpDiscoveryTurnInput({ assistantTurnMeaning: { asked_domain_family: "counterparty", asked_action_family: "list_documents", explicit_intent_candidate: "list_documents_by_counterparty", explicit_entity_candidates: [{ value: "SVK" }], stale_replay_forbidden: false } }); expect(result.adapter_status).toBe("not_applicable"); expect(result.should_run_discovery).toBe(false); expect(result.turn_meaning_ref).toBeNull(); expect(result.reason_codes).toContain("mcp_discovery_not_applicable_for_supported_exact_turn"); }); it("never serializes object candidates as [object Object]", () => { const result = buildAssistantMcpDiscoveryTurnInput({ assistantTurnMeaning: { asked_domain_family: "counterparty", asked_action_family: "counterparty_value_or_turnover", unsupported_but_understood_family: "counterparty_value_or_turnover", explicit_entity_candidates: [{ type: "counterparty", value: "SVK" }] } }); expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["SVK"]); expect(result.turn_meaning_ref?.explicit_entity_candidates).not.toContain("[object Object]"); }); it("prefers the raw cleaned entity anchor over canonicalized turn-meaning pollution", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "\u043d\u0430\u0439\u0434\u0438 \u0432 1\u0421 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430 \u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a", assistantTurnMeaning: { asked_domain_family: "counterparty", asked_action_family: "search_business_entity", explicit_entity_candidates: [{ value: "\u0441 \u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a' \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 1\u0421" }] }, predecomposeContract: { entities: { counterparty: "\u0441 \u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a' \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 1\u0421" } } }); expect(result.adapter_status).toBe("ready"); expect(result.turn_meaning_ref?.explicit_entity_candidates?.[0]).toBe("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"); expect(result.turn_meaning_ref?.explicit_entity_candidates).not.toContain( "\u0441 \u043d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435\u043c '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a' \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 1\u0421" ); }); it("does not concatenate effectiveMessage into the raw entity anchor", () => { const result = buildAssistantMcpDiscoveryTurnInput({ userMessage: "\u043d\u0430\u0439\u0434\u0438 \u0432 1\u0421 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430 \u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a", effectiveMessage: "\u043d\u0430\u0439\u0442\u0438 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a' \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 1\u0421" }); expect(result.adapter_status).toBe("ready"); expect(result.turn_meaning_ref?.explicit_entity_candidates?.[0]).toBe("\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a"); expect(result.turn_meaning_ref?.explicit_entity_candidates).not.toContain( "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a \u043d\u0430\u0439\u0442\u0438 \u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430 \u0441 \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u0435\u043c '\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a'" ); }); });