import { describe, expect, it } from "vitest"; import { planAssistantMcpDiscovery } from "../src/services/assistantMcpDiscoveryPlanner"; import { buildAssistantMcpDiscoveryRuntimeDryRun } from "../src/services/assistantMcpDiscoveryRuntimeAdapter"; describe("assistant MCP discovery runtime adapter", () => { it("turns a catalog-compatible value-flow plan into an execution-ready dry-run package", () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"], explicit_date_scope: "2020" } }); const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner); expect(dryRun.adapter_status).toBe("dry_run_ready"); expect(dryRun.mcp_execution_performed).toBe(false); expect(dryRun.user_facing_fallback).toBeNull(); expect(dryRun.execution_steps.map((step) => step.primitive_id)).toEqual([ "resolve_entity_reference", "query_movements", "aggregate_by_axis", "probe_coverage" ]); expect(dryRun.execution_steps.every((step) => step.step_status === "ready")).toBe(true); expect(dryRun.execution_steps.every((step) => step.dry_run_only)).toBe(true); expect(dryRun.evidence_gate).toEqual({ required: true, expected_inputs: [ "probe_results", "confirmed_facts", "inferred_facts", "unknown_facts", "source_rows_summary", "query_limitations" ], answer_may_use_raw_model_claims: false }); expect(dryRun.reason_codes).toContain("runtime_dry_run_ready_without_mcp_execution"); }); it("keeps execution in clarification state when catalog axes are missing", () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "counterparty_value", asked_action_family: "turnover", explicit_entity_candidates: ["SVK"] } }); const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner); const movementStep = dryRun.execution_steps.find((step) => step.primitive_id === "query_movements"); expect(dryRun.adapter_status).toBe("needs_clarification"); expect(dryRun.mcp_execution_performed).toBe(false); expect(dryRun.user_facing_fallback).toBe("ask_for_missing_scope_before_mcp_discovery"); expect(movementStep?.step_status).toBe("missing_axes"); expect(movementStep?.missing_axis_options).toContainEqual(["period", "counterparty"]); expect(dryRun.reason_codes).toContain("runtime_dry_run_needs_clarification_before_mcp_execution"); }); it("keeps blocked planner output blocked before any MCP execution", () => { const planner = planAssistantMcpDiscovery({}); planner.planner_status = "blocked"; planner.discovery_plan.plan_status = "blocked"; planner.catalog_review.review_status = "catalog_blocked"; const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner); expect(dryRun.adapter_status).toBe("blocked"); expect(dryRun.mcp_execution_performed).toBe(false); expect(dryRun.user_facing_fallback).toBe("explain_that_runtime_policy_blocked_mcp_discovery"); expect(dryRun.execution_steps.every((step) => step.step_status === "blocked")).toBe(true); expect(dryRun.reason_codes).toContain("runtime_dry_run_blocked_before_mcp_execution"); }); it("uses source-summary stop conditions for metadata-only dry runs", () => { const planner = planAssistantMcpDiscovery({ turnMeaning: { asked_domain_family: "metadata", asked_action_family: "inspect_catalog" } }); const dryRun = buildAssistantMcpDiscoveryRuntimeDryRun(planner); expect(dryRun.adapter_status).toBe("dry_run_ready"); expect(dryRun.execution_steps).toHaveLength(1); expect(dryRun.execution_steps[0]).toMatchObject({ primitive_id: "inspect_1c_metadata", evidence_floor: "source_summary", stop_condition: "stop_after_allowed_probe_returns_source_summary_or_limitation" }); }); });