142 lines
6.1 KiB
TypeScript
142 lines
6.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
buildAssistantMcpDiscoveryPlan,
|
||
isAssistantMcpDiscoveryPrimitive,
|
||
resolveAssistantMcpDiscoveryEvidence
|
||
} from "../src/services/assistantMcpDiscoveryPolicy";
|
||
|
||
describe("assistant MCP discovery policy", () => {
|
||
it("allows guarded MCP primitives and keeps raw model claims outside the answer path", () => {
|
||
const plan = buildAssistantMcpDiscoveryPlan({
|
||
semanticDataNeed: "counterparty turnover evidence",
|
||
turnMeaning: {
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_entity_candidates: ["Группа СВК"],
|
||
explicit_date_scope: "2020"
|
||
},
|
||
proposedPrimitives: ["resolve_entity_reference", "query_movements", "drop_database"],
|
||
requiredAxes: ["counterparty", "period", "amount"],
|
||
maxProbeCount: 99,
|
||
maxRowsPerProbe: 9999
|
||
});
|
||
|
||
expect(plan.plan_status).toBe("allowed");
|
||
expect(plan.allowed_primitives).toEqual(["resolve_entity_reference", "query_movements"]);
|
||
expect(plan.rejected_primitives).toEqual(["drop_database"]);
|
||
expect(plan.requires_evidence_gate).toBe(true);
|
||
expect(plan.answer_may_use_raw_model_claims).toBe(false);
|
||
expect(plan.execution_budget).toEqual({ max_probe_count: 36, max_rows_per_probe: 500 });
|
||
expect(plan.reason_codes).toContain("model_proposed_unregistered_mcp_primitive");
|
||
});
|
||
|
||
it("blocks model-planned probes when no proposed primitive survives the runtime allowlist", () => {
|
||
const plan = buildAssistantMcpDiscoveryPlan({
|
||
semanticDataNeed: "direct SQL from model",
|
||
turnMeaning: {
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_entity_candidates: ["СВК"]
|
||
},
|
||
proposedPrimitives: ["raw_sql", "filesystem_read"],
|
||
requiredAxes: ["counterparty"]
|
||
});
|
||
|
||
expect(plan.plan_status).toBe("blocked");
|
||
expect(plan.allowed_primitives).toEqual([]);
|
||
expect(plan.rejected_primitives).toEqual(["raw_sql", "filesystem_read"]);
|
||
expect(plan.reason_codes).toContain("no_allowed_mcp_primitives_after_runtime_filter");
|
||
});
|
||
|
||
it("separates confirmed, inferred and unknown facts before answer composition", () => {
|
||
const plan = buildAssistantMcpDiscoveryPlan({
|
||
semanticDataNeed: "activity duration from 1C evidence",
|
||
turnMeaning: {
|
||
asked_domain_family: "counterparty_lifecycle",
|
||
asked_action_family: "activity_duration",
|
||
explicit_entity_candidates: ["СВК"]
|
||
},
|
||
proposedPrimitives: ["resolve_entity_reference", "query_documents", "probe_coverage"],
|
||
requiredAxes: ["counterparty", "document_date"]
|
||
});
|
||
|
||
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
||
plan,
|
||
probeResults: [
|
||
{ primitive_id: "resolve_entity_reference", status: "ok", rows_received: 1, rows_matched: 1 },
|
||
{ primitive_id: "query_documents", status: "ok", rows_received: 4, rows_matched: 4 }
|
||
],
|
||
confirmedFacts: ["first confirmed 1C activity is 2020-01-15"],
|
||
inferredFacts: ["activity duration can be estimated from first and latest 1C activity"],
|
||
unknownFacts: ["legal registration date is not proven by these rows"],
|
||
sourceRowsSummary: "5 allowed MCP rows: 1 entity match, 4 documents"
|
||
});
|
||
|
||
expect(evidence.evidence_status).toBe("confirmed");
|
||
expect(evidence.coverage_status).toBe("full");
|
||
expect(evidence.answer_permission).toBe("confirmed_answer");
|
||
expect(evidence.confirmed_facts).toHaveLength(1);
|
||
expect(evidence.inferred_facts).toHaveLength(1);
|
||
expect(evidence.unknown_facts).toHaveLength(1);
|
||
expect(evidence.reason_codes).toContain("confirmed_facts_with_allowed_mcp_evidence");
|
||
});
|
||
|
||
it("permits only bounded inference when probes found rows but no confirmed fact", () => {
|
||
const plan = buildAssistantMcpDiscoveryPlan({
|
||
semanticDataNeed: "counterparty business age inference",
|
||
turnMeaning: {
|
||
asked_domain_family: "counterparty_lifecycle",
|
||
asked_action_family: "age_or_activity_duration",
|
||
explicit_entity_candidates: ["СВК"]
|
||
},
|
||
proposedPrimitives: ["query_documents"],
|
||
requiredAxes: ["counterparty", "document_date"]
|
||
});
|
||
|
||
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
||
plan,
|
||
probeResults: [{ primitive_id: "query_documents", status: "ok", rows_received: 3, rows_matched: 0 }],
|
||
inferredFacts: ["activity is visible in 1C documents for 2020"],
|
||
unknownFacts: ["legal age remains unknown"],
|
||
sourceRowsSummary: "3 document rows checked"
|
||
});
|
||
|
||
expect(evidence.evidence_status).toBe("inferred_only");
|
||
expect(evidence.coverage_status).toBe("partial");
|
||
expect(evidence.answer_permission).toBe("bounded_inference");
|
||
expect(evidence.confidence_reason).toBe("only_inferred_facts_available_from_allowed_mcp_probe_rows");
|
||
});
|
||
|
||
it("blocks evidence when execution reports a primitive outside the runtime plan", () => {
|
||
const plan = buildAssistantMcpDiscoveryPlan({
|
||
semanticDataNeed: "counterparty turnover evidence",
|
||
turnMeaning: {
|
||
asked_domain_family: "counterparty_value",
|
||
asked_action_family: "turnover",
|
||
explicit_entity_candidates: ["СВК"]
|
||
},
|
||
proposedPrimitives: ["query_movements"],
|
||
requiredAxes: ["counterparty"]
|
||
});
|
||
|
||
const evidence = resolveAssistantMcpDiscoveryEvidence({
|
||
plan,
|
||
probeResults: [
|
||
{ primitive_id: "query_movements", status: "ok", rows_received: 2, rows_matched: 2 },
|
||
{ primitive_id: "raw_sql", status: "ok", rows_received: 10, rows_matched: 10 }
|
||
],
|
||
confirmedFacts: ["turnover is 100"],
|
||
sourceRowsSummary: "12 rows"
|
||
});
|
||
|
||
expect(evidence.evidence_status).toBe("blocked");
|
||
expect(evidence.answer_permission).toBe("checked_sources_only");
|
||
expect(evidence.reason_codes).toContain("probe_result_used_primitive_outside_runtime_plan");
|
||
});
|
||
|
||
it("exports the reviewed primitive predicate for future runtime adapters", () => {
|
||
expect(isAssistantMcpDiscoveryPrimitive("query_movements")).toBe(true);
|
||
expect(isAssistantMcpDiscoveryPrimitive("raw_sql")).toBe(false);
|
||
});
|
||
});
|