ARCH: заложить контракт MCP semantic discovery
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
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: 6, 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user