import { describe, expect, it, vi } from "vitest"; import { runAssistantMcpDiscoveryRuntimeBridge } from "../src/services/assistantMcpDiscoveryRuntimeBridge"; function buildDeps(rows: Array>, error: string | null = null) { return { executeAddressMcpQuery: vi.fn(async () => ({ fetched_rows: rows.length, matched_rows: error ? 0 : rows.length, raw_rows: rows, rows: error ? [] : rows, error })) }; } function buildBidirectionalDeps( incomingRows: Array>, outgoingRows: Array> ) { return { executeAddressMcpQuery: vi .fn() .mockResolvedValueOnce({ fetched_rows: incomingRows.length, matched_rows: incomingRows.length, raw_rows: incomingRows, rows: incomingRows, error: null }) .mockResolvedValueOnce({ fetched_rows: outgoingRows.length, matched_rows: outgoingRows.length, raw_rows: outgoingRows, rows: outgoingRows, error: null }) }; } describe("assistant MCP discovery runtime bridge", () => { it("composes planner, pilot executor, and answer draft without wiring the hot runtime", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }]) }); expect(result.schema_version).toBe("assistant_mcp_discovery_runtime_bridge_v1"); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.hot_runtime_wired).toBe(false); expect(result.pilot.mcp_execution_performed).toBe(true); expect(result.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.user_facing_response_allowed).toBe(true); expect(result.reason_codes).toContain("runtime_bridge_not_wired_to_hot_assistant_answer"); }); it("keeps missing scope as clarification and does not authorize a business fact answer", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.requires_user_clarification).toBe(true); expect(result.pilot.mcp_execution_performed).toBe(false); expect(result.business_fact_answer_allowed).toBe(false); expect(result.answer_draft.next_step_line).toContain("Уточните контрагента"); }); it("keeps ranked value-flow in clarification without asking for a counterparty when only the period is known", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "turnover", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: "top_desc", proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020" }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.requires_user_clarification).toBe(true); expect(result.pilot.mcp_execution_performed).toBe(false); expect(result.planner.selected_chain_id).toBe("value_flow_ranking"); expect(result.answer_draft.headline).toContain("рейтинг"); expect(result.answer_draft.next_step_line).toContain("организацию"); expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента"); }); it("emits a resumable loop state for clarification-driven ranking chains", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "turnover", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: "top_desc", proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020" }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.loop_state).toMatchObject({ loop_status: "awaiting_clarification", selected_chain_id: "value_flow_ranking", asked_domain_family: "counterparty_value", asked_action_family: "turnover", ranking_need: "top_desc", explicit_date_scope: "2020" }); expect(result.loop_state.pending_axes).toContain("organization"); expect(result.loop_state.provided_axes).toContain("aggregate_axis"); expect(result.loop_state.catalog_chain_template_matches[0]).toBe("value_flow_ranking"); expect(result.loop_state.catalog_chain_template_alignment.alignment_status).toBe("selected_matches_top"); expect(result.loop_state.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true); expect(result.reason_codes).toContain("planner_selected_chain_matches_catalog_top"); expect(result.reason_codes).toContain("runtime_bridge_loop_state_awaiting_clarification"); }); it("produces a bounded ranked value-flow answer when period and organization are known", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "turnover", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: "top_desc", proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020", explicit_organization_scope: "ООО Альтернатива Плюс" }, deps: buildDeps([ { Period: "2020-01-10T00:00:00", Amount: 1200, Counterparty: "СВК-А" }, { Period: "2020-03-11T00:00:00", Amount: 800, Counterparty: "СВК-Б" }, { Period: "2020-05-12T00:00:00", Amount: 900, Counterparty: "СВК-А" } ]) }); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.planner.selected_chain_id).toBe("value_flow_ranking"); expect(result.pilot.derived_ranked_value_flow?.ranked_values[0]).toMatchObject({ axis_value: "СВК-А", total_amount: 2100 }); expect(result.answer_draft.confirmed_lines.join("\n")).toContain("СВК-А"); }); it("keeps open bidirectional comparison in clarification without asking for a counterparty when only the period is known", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "net_value_flow", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: "incoming_vs_outgoing", ranking_need: null, proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_date_scope: "2020" }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.requires_user_clarification).toBe(true); expect(result.pilot.mcp_execution_performed).toBe(false); expect(result.planner.selected_chain_id).toBe("value_flow_comparison"); expect(result.answer_draft.headline).toContain("входящий и исходящий"); expect(result.answer_draft.next_step_line).toContain("организацию"); expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента"); }); it("produces a bounded bidirectional comparison answer when period and organization are known", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "net_value_flow", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: "incoming_vs_outgoing", ranking_need: null, proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "net_value_flow", explicit_date_scope: "2020", explicit_organization_scope: "ООО Альтернатива Плюс" }, deps: buildBidirectionalDeps( [ { Period: "2020-01-10T00:00:00", Amount: 3200, Counterparty: "СВК-А" }, { Period: "2020-04-11T00:00:00", Amount: 1800, Counterparty: "СВК-Б" } ], [{ Period: "2020-02-12T00:00:00", Amount: 1400, Counterparty: "СВК-А" }] ) }); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.planner.selected_chain_id).toBe("value_flow_comparison"); expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({ period_scope: "2020", incoming_customer_revenue: { total_amount: 5000 }, outgoing_supplier_payout: { total_amount: 1400 } }); expect(result.answer_draft.confirmed_lines.join("\n")).toContain("получили"); expect(result.answer_draft.confirmed_lines.join("\n")).toContain("заплатили"); }); it("keeps document-ready plans bounded when the pilot finds no confirmed rows", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "document", asked_action_family: "documents", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("checked_sources_only"); expect(result.hot_runtime_wired).toBe(false); expect(result.pilot.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1"); expect(result.pilot.mcp_execution_performed).toBe(true); expect(result.business_fact_answer_allowed).toBe(false); expect(result.reason_codes).toContain("runtime_bridge_status_checked_sources_only"); }); it("bridges inventory stock catalog templates through exact runtime evidence", async () => { const deps = buildDeps([ { Period: "2021-09-30T00:00:00", Item: "Widget A", Quantity: 10, Warehouse: "Main" }, { Period: "2021-09-30T00:00:00", Item: "Widget B", Quantity: 4, Warehouse: "Reserve" } ]); const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "inventory_stock_snapshot", action_family: "stock_snapshot", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: null, proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: [ "fetch_scoped_movements", "aggregate_checked_amounts", "probe_coverage", "explain_evidence_basis" ], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_stock_snapshot"], reason_codes: ["data_need_graph_built", "data_need_graph_family_inventory_stock_snapshot"] }, turnMeaning: { asked_domain_family: "inventory_stock", asked_action_family: "stock_snapshot", explicit_organization_scope: "OOO Alternative Plus", explicit_date_scope: "2021-09-30" }, deps }); const userFacing = [ result.answer_draft.headline, ...result.answer_draft.confirmed_lines, ...result.answer_draft.inference_lines, ...result.answer_draft.unknown_lines, ...result.answer_draft.limitation_lines, result.answer_draft.next_step_line ?? "" ].join("\n"); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference"); expect(result.pilot.pilot_scope).toBe("inventory_route_template_v1"); expect(result.pilot.mcp_execution_performed).toBe(true); expect(result.pilot.executed_primitives).toEqual(["query_movements"]); expect(deps.executeAddressMcpQuery).toHaveBeenCalledWith(expect.objectContaining({ account_scope: ["41.01"] })); expect(result.loop_state).toMatchObject({ loop_status: "ready_for_next_hop", selected_chain_id: "inventory_stock_snapshot", pilot_scope: "inventory_route_template_v1", asked_domain_family: "inventory_stock", asked_action_family: "stock_snapshot", explicit_organization_scope: "OOO Alternative Plus", explicit_date_scope: "2021-09-30" }); expect(result.business_fact_answer_allowed).toBe(true); expect(result.reason_codes).toContain("pilot_inventory_exact_recipe_selected"); expect(result.reason_codes).toContain("pilot_inventory_exact_mcp_executed"); expect(result.reason_codes).not.toContain("pilot_scope_unsupported_for_live_execution"); expect(result.answer_draft.must_not_claim).toEqual( expect.arrayContaining([ "Do not expose inventory_route_template_v1 or MCP primitive names in the user answer.", "Do not claim full inventory coverage outside the checked rows, date, organization, item, or supplier scope." ]) ); expect(result.answer_draft.must_not_claim).not.toContain( "Do not present inventory route-template planning as executed stock, supplier, purchase, or sale evidence." ); expect(userFacing).toContain("exact inventory runtime"); expect(userFacing).toContain("Widget A"); expect(userFacing).not.toContain("inventory_route_template_v1"); expect(userFacing).not.toContain("query_movements"); expect(userFacing).not.toContain("primitive"); expect(userFacing).not.toContain("MCP discovery pilot"); }); it("bridges selected-item inventory provenance templates through exact document evidence", async () => { const deps = buildDeps([ { Period: "2021-08-10T00:00:00", Item: "Widget A", Counterparty: "Supplier One", Registrator: "Purchase 0001" } ]); const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: ["Widget A"], business_fact_family: "inventory_purchase_provenance", action_family: "purchase_provenance", aggregation_need: null, time_scope_need: null, comparison_need: null, ranking_need: null, proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: [ "resolve_entity_reference", "fetch_scoped_documents", "drilldown_related_objects", "probe_coverage", "explain_evidence_basis" ], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unproven_supplier_attribution"], reason_codes: ["data_need_graph_built", "data_need_graph_family_inventory_purchase_provenance"] }, turnMeaning: { asked_domain_family: "inventory_stock", asked_action_family: "purchase_provenance", explicit_entity_candidates: ["Widget A"] }, deps }); const userFacing = [ result.answer_draft.headline, ...result.answer_draft.confirmed_lines, ...result.answer_draft.inference_lines, ...result.answer_draft.unknown_lines, ...result.answer_draft.limitation_lines, result.answer_draft.next_step_line ?? "" ].join("\n"); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.planner.selected_chain_id).toBe("inventory_purchase_provenance"); expect(result.pilot.executed_primitives).toEqual(["query_documents"]); expect(deps.executeAddressMcpQuery).toHaveBeenCalledWith(expect.objectContaining({ account_scope: ["41.01"] })); expect(userFacing).toContain("Widget A"); expect(userFacing).toContain("Supplier One"); expect(userFacing).not.toContain("inventory_route_template_v1"); expect(userFacing).not.toContain("query_documents"); expect(userFacing).not.toContain("primitive"); }); it("keeps selected-item inventory templates in clarification when the item anchor is missing", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "inventory_sale_trace", action_family: "sale_trace", aggregation_need: null, time_scope_need: null, comparison_need: null, ranking_need: null, proof_expectation: "clarification_required", clarification_gaps: ["item"], decomposition_candidates: [ "resolve_entity_reference", "fetch_scoped_documents", "drilldown_related_objects", "probe_coverage", "explain_evidence_basis" ], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unproven_buyer_or_sale_trace"], reason_codes: ["data_need_graph_built", "data_need_graph_family_inventory_sale_trace"] }, turnMeaning: { asked_domain_family: "inventory_stock", asked_action_family: "sale_trace" }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.requires_user_clarification).toBe(true); expect(result.pilot.pilot_scope).toBe("inventory_route_template_v1"); expect(result.pilot.mcp_execution_performed).toBe(false); expect(result.reason_codes).toContain("runtime_bridge_loop_state_awaiting_clarification"); expect(result.loop_state.pending_axes).toContain("item"); }); it("keeps document evidence executable when the planner expands primitives from fact-axis search", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: ["SVK"], business_fact_family: "document_evidence", action_family: "list_documents", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: null, proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: [], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built"] }, turnMeaning: { asked_domain_family: "documents", asked_action_family: "list_documents", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020", unsupported_but_understood_family: "document_evidence" }, deps: buildDeps([{ Period: "2020-01-15T00:00:00", Registrator: "DOC-1", Counterparty: "SVK" }]) }); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.planner.selected_chain_id).toBe("document_evidence"); expect(result.planner.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]); expect(result.planner.reason_codes).toContain("planner_selected_catalog_primitives_from_fact_axis_search"); expect(result.business_fact_answer_allowed).toBe(true); }); it("preserves the answer adapter boundary against internal mechanics leakage", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ turnMeaning: { asked_domain_family: "counterparty_lifecycle", asked_action_family: "activity_duration", explicit_entity_candidates: ["SVK"] }, deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }]) }); const userFacing = [ result.answer_draft.headline, ...result.answer_draft.confirmed_lines, ...result.answer_draft.inference_lines, ...result.answer_draft.unknown_lines, ...result.answer_draft.limitation_lines, result.answer_draft.next_step_line ?? "" ].join("\n"); expect(userFacing).not.toContain("query_documents"); expect(userFacing).not.toContain("runtime_bridge"); expect(userFacing).not.toContain("primitive"); }); it("produces a bounded one-sided value-flow answer for an organization-scoped total without inventing a counterparty", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "turnover", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: null, proof_expectation: "coverage_checked_fact", clarification_gaps: [], decomposition_candidates: ["collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_open_scope_total_without_subject"] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020", explicit_organization_scope: "ООО Альтернатива Плюс" }, deps: buildDeps([ { Period: "2020-01-10T00:00:00", Amount: 3200, Counterparty: "Клиент-А" }, { Period: "2020-05-22T00:00:00", Amount: 1800, Counterparty: "Клиент-Б" } ]) }); expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.planner.selected_chain_id).toBe("value_flow"); expect(result.pilot.derived_value_flow).toMatchObject({ counterparty: null, period_scope: "2020", total_amount: 5000 }); expect(result.answer_draft.confirmed_lines.join("\n")).toContain("5 000"); expect(result.answer_draft.confirmed_lines.join("\n")).not.toContain("контрагенту"); }); it("keeps generic one-sided open totals in organization clarification without asking for a counterparty", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], business_fact_family: "value_flow", action_family: "turnover", aggregation_need: null, time_scope_need: "explicit_period", comparison_need: null, ranking_need: null, proof_expectation: "clarification_required", clarification_gaps: ["organization"], decomposition_candidates: ["collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: [ "data_need_graph_built", "data_need_graph_open_scope_total_without_subject", "data_need_graph_open_scope_total_needs_organization" ] }, turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_date_scope: "2020" }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.requires_user_clarification).toBe(true); expect(result.planner.selected_chain_id).toBe("value_flow"); expect(result.answer_draft.next_step_line).toContain("\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e"); expect(result.answer_draft.next_step_line).not.toContain( "\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430" ); }); it("persists metadata scope and subject-optional flags in the resumable loop state", async () => { const result = await runAssistantMcpDiscoveryRuntimeBridge({ dataNeedGraph: { schema_version: "assistant_data_need_graph_v1", policy_owner: "assistantMcpDiscoveryDataNeedGraph", subject_candidates: [], metadata_scope_hint: "\u041d\u0414\u0421", subject_resolution_optional: true, business_fact_family: "movement_evidence", action_family: "list_movements", aggregation_need: null, time_scope_need: "period_required", comparison_need: null, ranking_need: null, proof_expectation: "clarification_required", clarification_gaps: ["organization", "period"], decomposition_candidates: ["fetch_scoped_movements", "probe_coverage"], forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"], reason_codes: ["data_need_graph_built", "data_need_graph_metadata_scoped_open_lane_without_subject"] }, turnMeaning: { asked_domain_family: "movements", asked_action_family: "list_movements", metadata_scope_hint: "\u041d\u0414\u0421", subject_resolution_optional: true, unsupported_but_understood_family: "movement_evidence" }, deps: buildDeps([]) }); expect(result.bridge_status).toBe("needs_clarification"); expect(result.loop_state).toMatchObject({ loop_status: "awaiting_clarification", selected_chain_id: "movement_evidence", metadata_scope_hint: "\u041d\u0414\u0421", subject_resolution_optional: true }); expect(result.loop_state.pending_axes).toEqual(["organization", "period"]); expect(result.loop_state.explicit_entity_candidates).toEqual([]); }); });