ARCH: добавить policy gate ответа MCP discovery
This commit is contained in:
@@ -169,6 +169,66 @@ describe("assistant living chat runtime adapter", () => {
|
||||
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("replaces unsupported boundary with guarded MCP discovery response when policy allows it", async () => {
|
||||
const executeLlmChat = vi.fn(async () => "raw-llm");
|
||||
const input = buildRuntimeInput({
|
||||
userMessage: "how long has svk been active",
|
||||
modeDecision: { mode: "chat", reason: "unsupported_current_turn_meaning_boundary" },
|
||||
addressRuntimeMeta: {
|
||||
toolGateReason: "unsupported_current_turn_meaning_boundary",
|
||||
orchestrationContract: {
|
||||
unsupported_current_turn_meaning_boundary: true,
|
||||
assistant_turn_meaning: {
|
||||
unsupported_but_understood_family: "counterparty_lifecycle_or_age",
|
||||
explicit_entity_candidates: [
|
||||
{
|
||||
type: "counterparty",
|
||||
value: "SVK"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
mcpDiscoveryRuntimeEntryPoint: {
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||
entry_status: "bridge_executed",
|
||||
hot_runtime_wired: false,
|
||||
discovery_attempted: true,
|
||||
turn_input: { adapter_status: "ready" },
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Confirmed scoped answer.",
|
||||
confirmed_lines: ["Confirmed fact"],
|
||||
inference_lines: ["Bounded inference"],
|
||||
unknown_lines: ["Unconfirmed legal fact"],
|
||||
limitation_lines: ["Limited source window"],
|
||||
next_step_line: null
|
||||
}
|
||||
},
|
||||
reason_codes: ["runtime_entry_point_bridge_executed"]
|
||||
}
|
||||
},
|
||||
executeLlmChat
|
||||
});
|
||||
|
||||
const output = await runAssistantLivingChatRuntime(input);
|
||||
|
||||
expect(output.handled).toBe(true);
|
||||
expect(output.chatText).toContain("Confirmed fact");
|
||||
expect(output.chatText).not.toContain("route");
|
||||
expect(output.chatText).not.toContain("query_documents");
|
||||
expect(output.debug?.living_chat_response_source).toBe("mcp_discovery_response_candidate_guarded");
|
||||
expect(output.debug?.mcp_discovery_response_applied).toBe(true);
|
||||
expect(output.debug?.mcp_discovery_entry_status).toBe("bridge_executed");
|
||||
expect(output.debug?.mcp_discovery_attempted).toBe(true);
|
||||
expect(executeLlmChat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("adds proactive organization offer on first smalltalk turn when multiple organizations are available", async () => {
|
||||
const resolveDataScopeProbe = vi.fn(async () => ({
|
||||
status: "resolved",
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "../src/services/assistantMcpDiscoveryResponsePolicy";
|
||||
|
||||
function entryPoint(overrides: Record<string, unknown> = {}) {
|
||||
return {
|
||||
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
|
||||
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
|
||||
entry_status: "bridge_executed",
|
||||
hot_runtime_wired: false,
|
||||
discovery_attempted: true,
|
||||
turn_input: { adapter_status: "ready" },
|
||||
bridge: {
|
||||
bridge_status: "answer_draft_ready",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: true,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: {
|
||||
answer_mode: "confirmed_with_bounded_inference",
|
||||
headline: "Confirmed scoped answer.",
|
||||
confirmed_lines: ["Confirmed fact"],
|
||||
inference_lines: ["Bounded inference"],
|
||||
unknown_lines: ["Unconfirmed fact"],
|
||||
limitation_lines: ["Limited source window"],
|
||||
next_step_line: null
|
||||
}
|
||||
},
|
||||
reason_codes: ["runtime_entry_point_bridge_executed"],
|
||||
...overrides
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("assistant MCP discovery response policy", () => {
|
||||
it("applies a guarded candidate only for unsupported current-turn boundary replies", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "route is not wired",
|
||||
currentReplySource: "deterministic_unsupported_current_turn_boundary",
|
||||
modeDecisionReason: "unsupported_current_turn_meaning_boundary",
|
||||
addressRuntimeMeta: {
|
||||
mcpDiscoveryRuntimeEntryPoint: entryPoint()
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(true);
|
||||
expect(result.decision).toBe("apply_candidate");
|
||||
expect(result.reply_source).toBe("mcp_discovery_response_candidate_guarded");
|
||||
expect(result.reply_text).toContain("Confirmed fact");
|
||||
expect(result.reply_text).not.toContain("query_documents");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_applied");
|
||||
});
|
||||
|
||||
it("keeps the current reply when the turn is not an unsupported boundary", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "regular chat",
|
||||
currentReplySource: "llm_chat",
|
||||
modeDecisionReason: "living_chat_signal_detected",
|
||||
addressRuntimeMeta: {
|
||||
mcpDiscoveryRuntimeEntryPoint: entryPoint()
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(false);
|
||||
expect(result.decision).toBe("keep_current_reply");
|
||||
expect(result.reply_text).toBe("regular chat");
|
||||
expect(result.reply_source).toBe("llm_chat");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_not_unsupported_boundary");
|
||||
});
|
||||
|
||||
it("keeps the current reply when the candidate has no grounded text", () => {
|
||||
const result = applyAssistantMcpDiscoveryResponsePolicy({
|
||||
currentReply: "route is not wired",
|
||||
currentReplySource: "deterministic_unsupported_current_turn_boundary",
|
||||
modeDecisionReason: "unsupported_current_turn_meaning_boundary",
|
||||
addressRuntimeMeta: {
|
||||
mcpDiscoveryRuntimeEntryPoint: entryPoint({
|
||||
bridge: {
|
||||
bridge_status: "unsupported",
|
||||
user_facing_response_allowed: true,
|
||||
business_fact_answer_allowed: false,
|
||||
requires_user_clarification: false,
|
||||
answer_draft: null
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
expect(result.applied).toBe(false);
|
||||
expect(result.reply_text).toBe("route is not wired");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_not_eligible");
|
||||
expect(result.reason_codes).toContain("mcp_discovery_response_policy_kept_current_reply");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user