import { describe, expect, it } from "vitest"; import { resolveAssistantOrchestrationDecision, resolveLivingAssistantModeDecision } from "../src/services/assistantService"; import { buildAddressLlmPredecomposeContractV1, buildAddressSemanticExtractionContractV1 } from "../src/services/address_runtime/predecomposeContract"; describe("assistant living router mode decision", () => { it("returns address_data when address lane already triggered", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "давай", addressLaneTriggered: true, useMock: false, predecomposeMode: "address_query", predecomposeModeConfidence: "high" }); expect(decision.mode).toBe("address_data"); expect(decision.reason).toBe("address_lane_triggered"); }); it("keeps deep pipeline in mock mode to avoid test-env network calls", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "привет", addressLaneTriggered: false, useMock: true, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("mock_mode_keeps_deep_pipeline"); }); it("routes casual non-data phrase to chat mode", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "привет, как дела?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); }); it("keeps deep mode for strong data signal", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "покажи документы по свк за 2020", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("strong_data_signal_detected"); }); it("keeps deep mode for accumulated advances query even when predecompose mode is unsupported", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("strong_data_signal_detected"); }); it("routes short unsupported predecompose prompts to deep fallback instead of chat", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "без воды?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("predecompose_unsupported_mode_fallback_to_deep"); }); it("routes ultra-short deictic follow-up ('тут?') to chat mode", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "тут?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("living_chat_signal_detected"); }); it("routes capability question to chat even when phrase contains 1С", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "и 1с можешь настроить?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_capability_query_detected"); }); it("routes capability question 'ok - what can you do in 1c' to chat", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "\u043e\u043a - \u0447\u0442\u043e \u043c\u043e\u0436\u0435\u0448\u044c \u043f\u043e 1\u0441", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_capability_query_detected"); }); it("routes feature-capability wording to chat", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "а какие фичи по работе с 1с у тебя отработаны максимально?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_capability_query_detected"); }); it("routes data-scope question to chat instead of address lane", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "по какой компании мы можем работать?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_data_scope_query_detected"); }); it("routes 'whose base is this' style question to chat", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "ну база в тебе чья? как называется контора?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_data_scope_query_detected"); }); it("routes 'какая база подрублена?' to data-scope chat mode", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "какая база подрублена?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_data_scope_query_detected"); }); it("routes typo data-scope wording with misspelled company token to chat", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "подскажи плиз с какой компинией можем поработать?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_data_scope_query_detected"); }); it("routes slang data-scope wording 'по каким конторам можем общаться' to chat", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "\u043f\u043e \u043a\u0430\u043a\u0438\u043c \u043a\u043e\u043d\u0442\u043e\u0440\u0430\u043c \u043c\u043e\u0436\u0435\u043c \u043e\u0431\u0449\u0430\u0442\u044c\u0441\u044f?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_data_scope_query_detected"); }); it("routes data-scope wording without question mark when interrogative token is present", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "каза какой компании подключена к 1с", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("chat"); expect(decision.reason).toBe("assistant_data_scope_query_detected"); }); it("does not treat contract ranking data query as data-scope meta question", () => { const decision = resolveLivingAssistantModeDecision({ userMessage: "по альтернативе вопрос. какой самый жирный контракт за всю историю у нее?", addressLaneTriggered: false, useMock: false, predecomposeMode: "unsupported", predecomposeModeConfidence: "low" }); expect(decision.mode).toBe("deep_analysis"); expect(decision.reason).toBe("strong_data_signal_detected"); }); }); describe("assistant orchestration contract", () => { it("routes non-domain emotional follow-up to indexed chat path instead of address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "бля хочу выпилиться от этого ебаного 1с", effectiveAddressUserMessage: "бля хочу выпилиться от этого ебаного 1с", followupContext: { previous_intent: "payables_confirmed_as_of_date", previous_filters: { period_from: "2021-05-01", period_to: "2021-05-31", as_of_date: "2021-05-31" } }, llmPreDecomposeMeta: { applied: false, reason: "no_usable_fragment", predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("non_domain_query_indexed"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("non_domain_query_indexed"); expect(decision.orchestrationContract?.hard_meta_mode).toBe("non_domain"); }); it("keeps address lane for a short net follow-up over grounded value-flow context", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "Рђ какое получилось нетто?", effectiveAddressUserMessage: "Рђ какое получилось нетто?", followupContext: { previous_intent: "customer_revenue_and_payments", previous_filters: { counterparty: "Группа РЎР’Рљ", period_from: "2020-01-01", period_to: "2020-12-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Группа РЎР’Рљ", previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1" }, llmPreDecomposeMeta: { applied: false, reason: "no_usable_fragment", predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.toolGateReason).not.toBe("non_domain_query_indexed"); expect(decision.livingMode).toBe("address_data"); }); it("routes historical capability follow-up over grounded inventory answer to contextual chat", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "а исторические данные ты можешь же показать?", effectiveAddressUserMessage: "а исторические данные ты можешь же показать?", followupContext: null, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false } } as any, sessionItems: [ { role: "assistant", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "inventory_on_hand_as_of_date", capability_id: "confirmed_inventory_on_hand_as_of_date", assistant_active_organization: "альтернатива", address_root_frame_context: { root_intent: "inventory_on_hand_as_of_date", current_frame_kind: "inventory_root", organization: "альтернатива", as_of_date: "2026-04-15" } } } ], useMock: false } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("inventory_history_capability_followup_detected"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("inventory_history_capability_followup_detected"); expect(decision.orchestrationContract?.followup_context_detected).toBe(true); }); it("blocks organization age lookup from re-entering inventory lane on follow-up context", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "а какой возраст у Альтернативы Плюс?", effectiveAddressUserMessage: "Какой возраст у контрагента Альтернатива Плюс?", followupContext: { previous_intent: "inventory_on_hand_as_of_date", previous_filters: { organization: "ООО \"Альтернатива Плюс\"", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, previous_anchor_type: "item", previous_anchor_value: "Модуль прямоугольный 1400*110*750" }, llmPreDecomposeMeta: { applied: true, reason: "normalized_fragment_applied", predecomposeContract: buildAddressLlmPredecomposeContractV1({ sourceMessage: "а какой возраст у Альтернативы Плюс?", canonicalMessage: "Какой возраст у контрагента Альтернатива Плюс?", mode: "address_query", modeConfidence: "high", intent: "unknown", intentConfidence: "low", entities: { counterparty: "Альтернатива Плюс" } }), semanticExtractionContract: buildAddressSemanticExtractionContractV1({ valid: true, applyCanonicalRecommended: true }), llmCanonicalCandidateDetected: true } as any, useMock: false } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("organization_fact_lookup_signal_detected"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("organization_fact_lookup_signal_detected"); }); it("keeps organization activity-age lookup in address lane when the question is about 1C evidence", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "а по Альтернативе Плюс сколько лет активности в базе 1С?", effectiveAddressUserMessage: "а по Альтернативе Плюс сколько лет активности в базе 1С?", followupContext: { previous_intent: "inventory_on_hand_as_of_date", previous_filters: { organization: 'ООО "Альтернатива Плюс"', counterparty: "Альтернатива Плюс", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Альтернатива Плюс" }, llmPreDecomposeMeta: null, useMock: false } as any); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "followup_context_detected"]).toContain( String(decision.toolGateReason) ); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps inventory root restatement after company selection in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "тогда покажи остатки на март 2021", effectiveAddressUserMessage: "тогда покажи остатки на март 2021", followupContext: { previous_intent: "inventory_on_hand_as_of_date", previous_filters: { organization: 'ООО "Альтернатива Плюс"', counterparty: "Альтернатива Плюс", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, previous_anchor_type: "counterparty", previous_anchor_value: "Альтернатива Плюс", root_intent: "inventory_on_hand_as_of_date", root_filters: { organization: 'ООО "Альтернатива Плюс"', counterparty: "Альтернатива Плюс", as_of_date: "2021-03-31", period_from: "2021-03-01", period_to: "2021-03-31" }, current_frame_kind: "inventory_root", root_context_only: true }, llmPreDecomposeMeta: null, useMock: false } as any); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["followup_context_detected", "address_mode_classifier_detected"]).toContain(String(decision.toolGateReason)); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps VAT payable forecast query in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "какой прогноз оплаты ндс за 12 мая 2020", effectiveAddressUserMessage: "какой прогноз оплаты ндс за 12 мая 2020", followupContext: null, llmPreDecomposeMeta: null, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.toolGateReason).toBe("address_mode_classifier_detected"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false); }); it("keeps supported contract analytics query in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "по альтернативе вопрос. какой самый жирный контракт за всю историю у нее?", effectiveAddressUserMessage: "по альтернативе вопрос. какой самый жирный контракт за всю историю у нее?", followupContext: null, llmPreDecomposeMeta: null, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_mode_classifier_detected", "address_intent_resolver_detected"]).toContain(String(decision.toolGateReason)); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps inventory provenance and sale-trace queries in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "От какого поставщика куплен товар Диван трехместный из текущего остатка на складе Основной склад", effectiveAddressUserMessage: "От какого поставщика куплен товар Диван трехместный из текущего остатка на складе Основной склад", followupContext: { previous_intent: "inventory_purchase_provenance_for_item", previous_filters: { as_of_date: "2020-03-31" } }, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "address_query", mode_confidence: "high", intent: "inventory_purchase_provenance_for_item", intent_confidence: "high" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_mode_classifier_detected", "address_intent_resolver_detected"]).toContain(String(decision.toolGateReason)); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps short inventory follow-up 'когда' in address lane when a selected-item provenance context exists", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "когда", effectiveAddressUserMessage: "когда", followupContext: { previous_intent: "inventory_purchase_provenance_for_item", previous_filters: { item: "Столешница 600*3050*26 дуб ниагара", warehouse: "Основной склад", as_of_date: "2019-03-31" } }, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false, reason_codes: ["unsupported_low_confidence_contract"] } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps customer-value ranking question in address lane even when LLM semantic guard rejects canonical rewrite", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "кто больше всего принес денег в 2020", effectiveAddressUserMessage: "кто больше всего принес денег в 2020", followupContext: null, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false, reason_codes: ["unsupported_low_confidence_contract"] } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain( String(decision.toolGateReason) ); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps colloquial 'кто нам больше денег принес' in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u043a\u0442\u043e \u043d\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0435\u043d\u0435\u0433 \u043f\u0440\u0438\u043d\u0435\u0441", effectiveAddressUserMessage: "\u043a\u0442\u043e \u043d\u0430\u043c \u0431\u043e\u043b\u044c\u0448\u0435 \u0434\u0435\u043d\u0435\u0433 \u043f\u0440\u0438\u043d\u0435\u0441", followupContext: null, llmPreDecomposeMeta: null, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain( String(decision.toolGateReason) ); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps short mirror follow-up 'a мы кому' in address lane instead of non-domain chat", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "a мы кому", effectiveAddressUserMessage: "a мы кому", followupContext: null, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false, reason_codes: ["unsupported_low_confidence_contract"] } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("routes unsupported turnover-by-organization query to deep analysis", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 \u0437\u0430 2020 \u0433\u043e\u0434", effectiveAddressUserMessage: "\u041e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0441\u0447\u0435\u0442\u0443 '\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430' \u0437\u0430 2020 \u0433\u043e\u0434.", followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "account_balance_snapshot", intent_confidence: "high" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.livingMode).toBe("deep_analysis"); expect([ "address_signal_unsupported_intent_fallback_to_deep", "aggregate_analytics_signal_fallback_to_deep" ]).toContain(String(decision.toolGateReason)); expect([ "unsupported_address_intent_fallback_to_deep", "aggregate_analytics_signal_fallback_to_deep" ]).toContain(String(decision.livingReason)); }); it("does not route advances-to-shipment risk query to chat when semantic guard rejects canonical rewrite", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить?", effectiveAddressUserMessage: "Где у нас накопились авансы к отгрузкам, которые уже давно пора закрыть или хотя бы перепроверить?", followupContext: null, llmPreDecomposeMeta: { applied: false, llmCanonicalCandidateDetected: false, reason: "normalized_fragment_rejected_semantic_guard", predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false, reason_codes: ["unsupported_low_confidence_contract"] } } as any, useMock: false }); expect(decision.livingMode).toBe("address_data"); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_signal_detected", "address_intent_resolver_detected", "address_mode_classifier_detected"]).toContain( String(decision.toolGateReason) ); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("routes unsupported turnover query to deep even with followup context carryover", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u043a\u0430\u043a\u0438\u0435 \u043e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0435 \u0437\u0430 2020 \u0433\u043e\u0434", effectiveAddressUserMessage: "\u041e\u0431\u043e\u0440\u043e\u0442\u044b \u043f\u043e \u0441\u0447\u0435\u0442\u0443 '\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430' \u0437\u0430 2020 \u0433\u043e\u0434.", followupContext: { previous_intent: "list_documents_by_contract", previous_filters: { organization: "\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441" } }, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "account_balance_snapshot", intent_confidence: "high" }, semanticExtractionContract: { extraction: { query_shape: "AGGREGATE_LOOKUP", aggregation_profile: "management_profile" }, guard_hints: { deep_investigation_signal_detected: false } } } as any, useMock: false }); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.livingMode).toBe("deep_analysis"); expect([ "address_signal_unsupported_intent_fallback_to_deep", "aggregate_analytics_signal_fallback_to_deep" ]).toContain(String(decision.toolGateReason)); expect( decision.orchestrationContract?.semantic_route_arbitration?.followup_semantic_override_to_deep_allowed ).toBe(true); }); it("routes standalone aggregate query to deep even when stale followup context exists and LLM predecompose is unavailable", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "какие обороты по альтернативе за 2020 год", effectiveAddressUserMessage: "какие обороты по альтернативе за 2020 год", followupContext: { previous_intent: "list_documents_by_contract", previous_filters: { organization: "ООО Альтернатива Плюс" } }, llmPreDecomposeMeta: null as any, useMock: true } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("aggregate_analytics_signal_fallback_to_deep"); expect(decision.livingMode).toBe("deep_analysis"); expect(decision.livingReason).toBe("aggregate_analytics_signal_fallback_to_deep"); expect(decision.orchestrationContract?.aggregate_analytics_signal_fallback_to_deep).toBe(true); }); it("keeps profitability ranking query in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u0433\u043e\u0434?", effectiveAddressUserMessage: "\u043a\u0430\u043a\u043e\u0439 \u0441\u0430\u043c\u044b\u0439 \u0434\u043e\u0445\u043e\u0434\u043d\u044b\u0439 \u0433\u043e\u0434?", followupContext: null, llmPreDecomposeMeta: null as any, useMock: false } as any); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain( String(decision.toolGateReason) ); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.aggregate_analytics_signal_fallback_to_deep).toBe(false); }); it("keeps unsupported retrieval query in address lane when LLM runtime is unavailable", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u0413\u0434\u0435 \u0443 \u043d\u0430\u0441 \u043d\u0430\u043a\u043e\u043f\u0438\u043b\u0438\u0441\u044c \u0430\u0432\u0430\u043d\u0441\u044b \u043a \u043e\u0442\u0433\u0440\u0443\u0437\u043a\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u0432\u043d\u043e \u043f\u043e\u0440\u0430 \u0437\u0430\u043a\u0440\u044b\u0442\u044c?", effectiveAddressUserMessage: "\u0413\u0434\u0435 \u0443 \u043d\u0430\u0441 \u043d\u0430\u043a\u043e\u043f\u0438\u043b\u0438\u0441\u044c \u0430\u0432\u0430\u043d\u0441\u044b \u043a \u043e\u0442\u0433\u0440\u0443\u0437\u043a\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u0430\u0432\u043d\u043e \u043f\u043e\u0440\u0430 \u0437\u0430\u043a\u0440\u044b\u0442\u044c?", followupContext: null, llmPreDecomposeMeta: { applied: false, reason: "error:OpenAI API key is missing.", llmCanonicalCandidateDetected: false, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false, reason_codes: ["unsupported_low_confidence_contract", "deep_investigation_signal_detected"], guard_hints: { deep_investigation_signal_detected: true }, extraction: { query_shape: "VERIFY_FACTUAL", aggregation_profile: "unknown" } } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false); expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false); }); it("keeps VAT explain follow-up in address lane when followup context is present", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "почему прогноз к уплате 0?", effectiveAddressUserMessage: "почему прогноз к уплате 0?", followupContext: { previous_intent: "vat_payable_forecast", previous_filters: { period_from: "2020-03-01", period_to: "2020-03-31" }, previous_anchor_type: "unknown", previous_anchor_value: null }, llmPreDecomposeMeta: null, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.toolGateReason).toBe("followup_context_detected"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("routes meta follow-up over grounded inventory answer to chat instead of rerunning address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u0447\u0435 \u0434\u0443\u043c\u0430\u0435\u0448\u044c \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443", effectiveAddressUserMessage: "\u0447\u0435 \u0434\u0443\u043c\u0430\u0435\u0448\u044c \u043d\u0430 \u044d\u0442\u0443 \u0442\u0435\u043c\u0443", followupContext: { previous_intent: "inventory_on_hand_as_of_date", previous_filters: { as_of_date: "2016-06-30", organization: "alt" }, previous_anchor_type: "counterparty", previous_anchor_value: "ALT" }, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false } } as any, sessionItems: [ { role: "assistant", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" } } } ], useMock: false } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("meta_followup_over_grounded_answer"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("meta_followup_over_grounded_answer"); }); it("routes evaluative VAT follow-up 'это много или мало' to contextual chat instead of replaying address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "это много или мало?", effectiveAddressUserMessage: "это много или мало?", followupContext: { previous_intent: "vat_payable_forecast", previous_filters: { period_from: "2020-03-01", period_to: "2020-03-31" }, previous_anchor_type: "unknown", previous_anchor_value: null }, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" }, semanticExtractionContract: { valid: false, apply_canonical_recommended: false } } as any, sessionItems: [ { role: "assistant", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "vat_payable_forecast" } } ], useMock: false } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("meta_followup_over_grounded_answer"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("meta_followup_over_grounded_answer"); }); it("routes memory recap follow-up over prior address context to deterministic chat instead of generic non-domain chat", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "а ты помнишь мы зеркало обсуждали?", effectiveAddressUserMessage: "а ты помнишь мы зеркало обсуждали?", followupContext: null, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: false, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" } } as any, sessionItems: [ { role: "assistant", debug: { execution_lane: "address_query", answer_grounding_check: { status: "grounded" }, detected_intent: "inventory_purchase_provenance_for_item", extracted_filters: { item: "Зеркало для инвалидов поворотное травмобезопасное", as_of_date: "2022-02-28" } } } ], useMock: false } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("memory_recap_followup_detected"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("memory_recap_followup_detected"); }); it("keeps documentary inventory chain verification in address lane for supported exact intent", () => { const question = "Есть ли документально подтвержденная цепочка: поставщик Гамма-мебель, ООО -> товар Шкаф картотечный 1000*400*2100 -> покупатель Департамент капитального ремонта города Москвы"; const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: question, effectiveAddressUserMessage: question, followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "deep_analysis", mode_confidence: "high", intent: "inventory_purchase_to_sale_chain", intent_confidence: "medium" }, semanticExtractionContract: { valid: true, apply_canonical_recommended: true, reason_codes: ["deep_investigation_signal_detected"], guard_hints: { deep_investigation_signal_detected: true }, extraction: { query_shape: "VERIFY_FACTUAL", aggregation_profile: "unknown" } } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false); expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true); expect(decision.orchestrationContract?.semantic_route_arbitration?.strict_deep_investigation_bypass_allowed).toBe(true); }); it("keeps 'a na tekushuyu datu' follow-up in address lane when previous VAT context exists", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "а на текущую дату", effectiveAddressUserMessage: "а на текущую дату", followupContext: { previous_intent: "vat_payable_confirmed_as_of_date", previous_filters: { period_from: "2016-03-01", period_to: "2016-03-31", as_of_date: "2016-03-31" }, previous_anchor_type: "unknown", previous_anchor_value: null }, llmPreDecomposeMeta: { applied: false, reason: "normalized_fragment_rejected_semantic_guard", llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "unsupported", mode_confidence: "low", intent: "unknown", intent_confidence: "low" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps explicit address-mode unknown-intent data query in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?", effectiveAddressUserMessage: "Показать контрагентов с вероятным несогласием между сальдо и актом сверки. Рекомендовать запросить сверку.", followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "address_query", mode_confidence: "high", intent: "unknown", intent_confidence: "low" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false); }); it("keeps confirmed open-contracts query in address lane despite 'unclosed' wording", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "\u041f\u043e\u043a\u0430\u0436\u0438 \u043d\u0435\u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u043d\u0430 2020-12-31", effectiveAddressUserMessage: "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043d\u0435\u0437\u0430\u043a\u0440\u044b\u0442\u044b\u0435 \u0434\u043e\u0433\u043e\u0432\u043e\u0440\u044b \u043f\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044e \u043d\u0430 \u043a\u043e\u043d\u0435\u0446 \u0434\u0435\u043a\u0430\u0431\u0440\u044f 2020 \u0433\u043e\u0434\u0430.", followupContext: { previous_intent: "month_close_costs_20_44" }, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "address_query", mode_confidence: "high", intent: "open_contracts_confirmed_as_of_date", intent_confidence: "medium" } } as any, useMock: false } as any); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.toolGateReason).toBe("address_mode_classifier_detected"); }); it("keeps inventory-on-hand query in address lane", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "Какие товары сейчас лежат на складе", effectiveAddressUserMessage: "Какие товары сейчас лежат на складе", followupContext: null, llmPreDecomposeMeta: null as any, useMock: true } as any); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(["address_intent_resolver_detected", "address_mode_classifier_detected", "address_signal_detected"]).toContain( String(decision.toolGateReason) ); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps slang stock-state query with organization scope in address lane instead of deep fallback", () => { const rawUserMessage = "чекни плиз чо там на складе альтернативы происходит"; const effectiveAddressUserMessage = "проверь, что происходит на складе у компании 'альтернатива'"; const predecomposeContract = buildAddressLlmPredecomposeContractV1({ sourceMessage: rawUserMessage, canonicalMessage: effectiveAddressUserMessage, semanticHints: { scope_target_kind: "organization", scope_target_text: "альтернатива", date_scope_kind: "implicit_current", self_scope_detected: false, selected_object_scope_detected: false } }); const semanticExtractionContract = buildAddressSemanticExtractionContractV1({ sourceMessage: rawUserMessage, canonicalMessage: effectiveAddressUserMessage, predecomposeContract }); const decision = resolveAssistantOrchestrationDecision({ rawUserMessage, effectiveAddressUserMessage, followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract, semanticExtractionContract } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false); expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false); expect(decision.orchestrationContract?.semantic_route_arbitration?.supported_address_intent_detected).toBe(true); }); it("keeps short colloquial stock query with organization scope in address lane instead of chat fallback", () => { const rawUserMessage = "че на складах альтернативы"; const effectiveAddressUserMessage = "что находится на складах у компании 'альтернатива'"; const predecomposeContract = buildAddressLlmPredecomposeContractV1({ sourceMessage: rawUserMessage, canonicalMessage: effectiveAddressUserMessage, semanticHints: { scope_target_kind: "organization", scope_target_text: "альтернатива", date_scope_kind: "implicit_current", self_scope_detected: false, selected_object_scope_detected: false } }); const semanticExtractionContract = buildAddressSemanticExtractionContractV1({ sourceMessage: rawUserMessage, canonicalMessage: effectiveAddressUserMessage, predecomposeContract }); const decision = resolveAssistantOrchestrationDecision({ rawUserMessage, effectiveAddressUserMessage, followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract, semanticExtractionContract } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect([ "address_intent_resolver_detected", "address_mode_classifier_detected", "llm_canonical_data_signal_detected", "address_signal_detected" ]).toContain( String(decision.toolGateReason) ); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("keeps exact open-items lookup in address lane even when semantic guard overflags deep investigation", () => { const rawUserMessage = "хвосты покажи по счету 60 на август 2022"; const effectiveAddressUserMessage = "хвосты по счету 60 на август 2022"; const predecomposeContract = buildAddressLlmPredecomposeContractV1({ sourceMessage: rawUserMessage, canonicalMessage: effectiveAddressUserMessage, semanticHints: { exact_data_request_detected: true, account_scope_kind: "explicit", account_scope_text: "60", date_scope_kind: "explicit_period", date_scope_text: "август 2022" } }); const semanticExtractionContract = buildAddressSemanticExtractionContractV1({ sourceMessage: rawUserMessage, canonicalMessage: effectiveAddressUserMessage, predecomposeContract }); const decision = resolveAssistantOrchestrationDecision({ rawUserMessage, effectiveAddressUserMessage, followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract, semanticExtractionContract: { ...semanticExtractionContract, guard_hints: { ...(semanticExtractionContract.guard_hints ?? {}), deep_investigation_signal_detected: true } } } as any, useMock: false }); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(false); expect( decision.orchestrationContract?.semantic_route_arbitration?.exact_address_intent_protected_from_semantic_deep_hint ).toBe(true); }); it("keeps open-contracts request in address lane even with stale deep followup context when LLM contract is absent", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "Покажи незакрытые договоры на 2020-12-31", effectiveAddressUserMessage: "Покажи незакрытые договоры на 2020-12-31", followupContext: { previous_intent: "month_close_costs_20_44" }, llmPreDecomposeMeta: null as any, useMock: true } as any); expect(decision.runAddressLane).toBe(true); expect(decision.toolGateDecision).toBe("run_address_lane"); expect(decision.livingMode).toBe("address_data"); expect(decision.livingReason).toBe("address_lane_triggered"); }); it("routes 'по каким конторам можем общаться?' to chat/data-scope in orchestration contract", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "по каким конторам можем общаться?", effectiveAddressUserMessage: "по каким контрагентам у нас есть активность или договоры?", followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "address_query", mode_confidence: "medium", intent: "list_documents_by_contract", intent_confidence: "medium" } } as any, useMock: false } as any); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("assistant_data_scope_query_detected"); expect(decision.livingMode).toBe("chat"); expect(decision.livingReason).toBe("assistant_data_scope_query_detected"); }); it("does not force address lane for deep-analysis unknown intent query with date-like token", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "найди какие либо ошибки на 21 мая 2022 года", effectiveAddressUserMessage: "Найти ошибки в бухгалтерии за 21 мая 2022 года.", followupContext: null, llmPreDecomposeMeta: { applied: true, llmCanonicalCandidateDetected: true, predecomposeContract: { mode: "deep_analysis", mode_confidence: "high", intent: "unknown", intent_confidence: "low" } } as any, useMock: false }); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.livingMode).toBe("deep_analysis"); expect(["address_signal_unsupported_intent_fallback_to_deep", "no_address_signal_after_l0"]).toContain( decision.toolGateReason ); }); it("routes risk/anomaly analytics wording to deep pipeline", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "Проверь НДС по счету 19 за 2020-06 и рискованные записи по документам.", effectiveAddressUserMessage: "Проверь НДС по счету 19 за 2020-06 и рискованные записи по документам.", followupContext: null, llmPreDecomposeMeta: null, useMock: false }); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("deep_analysis_signal_fallback_to_deep"); expect(decision.livingMode).toBe("deep_analysis"); expect(decision.livingReason).toBe("deep_analysis_signal_fallback_to_deep"); expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(true); }); it("routes settlement closure verification wording to deep pipeline", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "По оплате поставщику на счете 60 в июле 2020 остался хвост. Проверь закрытие по договору и объекту расчетов.", effectiveAddressUserMessage: "По оплате поставщику на счете 60 в июле 2020 остался хвост. Проверь закрытие по договору и объекту расчетов.", followupContext: null, llmPreDecomposeMeta: null, useMock: false }); expect(decision.runAddressLane).toBe(false); expect(decision.toolGateDecision).toBe("skip_address_lane"); expect(decision.toolGateReason).toBe("deep_analysis_signal_fallback_to_deep"); expect(decision.livingMode).toBe("deep_analysis"); expect(decision.livingReason).toBe("deep_analysis_signal_fallback_to_deep"); expect(decision.orchestrationContract?.deep_analysis_signal_fallback_to_deep).toBe(true); }); });