ARCH: добавить value-flow pilot MCP discovery

This commit is contained in:
2026-04-20 18:15:26 +03:00
parent e85d456576
commit 49fd08652c
21 changed files with 1100 additions and 66 deletions
@@ -110,7 +110,10 @@ describe("assistant deep turn response runtime adapter", () => {
})
);
expect(runtime.response).toEqual(responsePayload);
expect(runtime.debug).toEqual({ trace_id: "trace-1" });
expect(runtime.debug).toMatchObject({
trace_id: "trace-1",
mcp_discovery_response_applied: false
});
});
it("passes feature flags and followup flags into packaging stage", () => {
@@ -160,4 +163,86 @@ describe("assistant deep turn response runtime adapter", () => {
})
);
});
it("can replace a bad deep partial answer with a guarded MCP discovery candidate", () => {
const runPackagingRuntime = vi.fn(() => ({
messageId: "msg-a1",
investigationStateSnapshot: null,
droppedIntentSegments: [],
analysisContextForContract: null,
routesForDebug: [],
resolvedExecutionState: [],
safeAssistantReplyBase: "bad-base",
safeAssistantReply: "bad deep partial answer",
debug: { trace_id: "trace-1" },
assistantItem: {
message_id: "msg-a1",
session_id: "asst-1",
role: "assistant",
text: "bad deep partial answer",
reply_type: "partial_coverage",
created_at: "2026-04-10T00:00:00.000Z",
trace_id: "trace-1",
debug: null
},
deepAnalysisLogDetails: {}
}));
const runFinalizeDeepTurn = vi.fn((input) => ({
response: {
ok: true,
session_id: "asst-1",
assistant_reply: input.assistantReply,
reply_type: input.replyType,
conversation_item: input.assistantItem,
conversation: [],
debug: input.debug
}
}));
const runtime = runAssistantDeepTurnResponseRuntime(
buildBaseInput({
composition: { reply_type: "partial_coverage" },
addressRuntimeMetaForDeep: {
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: { should_run_discovery: true },
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: "Discovery answer",
confirmed_lines: ["Confirmed fact"],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
},
reason_codes: []
}
},
runPackagingRuntime,
runFinalizeDeepTurn
})
);
expect(runtime.response.assistant_reply).toContain("Discovery answer");
expect(runtime.debug?.mcp_discovery_response_applied).toBe(true);
expect(runFinalizeDeepTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: expect.stringContaining("Discovery answer"),
replyType: "partial_coverage",
assistantItem: expect.objectContaining({
text: expect.stringContaining("Discovery answer")
})
})
);
});
});
@@ -80,6 +80,36 @@ describe("assistant MCP discovery answer adapter", () => {
expect(draft.must_not_claim).toContain("Do not claim rows were checked when mcp_execution_performed=false.");
});
it("turns value-flow evidence into a bounded turnover answer draft", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020"
}
});
const pilot = await executeAssistantMcpDiscoveryPilot(
planner,
buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
])
);
const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot);
const confirmedText = draft.confirmed_lines.join("\n");
expect(draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(draft.headline).toContain("денежных движений");
expect(confirmedText).toContain("3 750,50 руб.");
expect(confirmedText).toContain("2020-01-15");
expect(confirmedText).toContain("2020-02-20");
expect(draft.unknown_lines).toContain("Full turnover outside the checked period is not proven by this MCP discovery pilot");
expect(draft.must_not_claim).toContain("Do not claim full all-time turnover unless the checked period and coverage prove it.");
expect(draft.limitation_lines.join("\n")).not.toContain("pilot_");
});
it("does not leak primitive names or query text into user-facing lines", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
@@ -76,6 +76,49 @@ describe("assistant MCP discovery pilot executor", () => {
expect(deps.executeAddressMcpQuery).not.toHaveBeenCalled();
});
it("executes value-flow query_movements and derives a guarded turnover sum", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020"
}
});
const deps = buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: "2 500,50", Counterparty: "SVK" }
]);
const result = await executeAssistantMcpDiscoveryPilot(planner, deps);
expect(result.pilot_status).toBe("executed");
expect(result.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.mcp_execution_performed).toBe(true);
expect(result.executed_primitives).toEqual(["query_movements"]);
expect(result.skipped_primitives).toEqual(["resolve_entity_reference", "aggregate_by_axis", "probe_coverage"]);
expect(result.evidence.evidence_status).toBe("confirmed");
expect(result.evidence.confirmed_facts[0]).toContain("value-flow rows");
expect(result.source_rows_summary).toBe("2 MCP value-flow rows fetched, 2 matched value-flow scope");
expect(result.derived_value_flow).toMatchObject({
counterparty: "SVK",
period_scope: "2020",
rows_matched: 2,
rows_with_amount: 2,
total_amount: 3750.5,
first_movement_date: "2020-01-15",
latest_movement_date: "2020-02-20",
inference_basis: "sum_of_confirmed_1c_value_flow_rows"
});
expect(result.reason_codes).toContain("pilot_query_movements_mcp_executed");
expect(result.reason_codes).toContain("pilot_derived_value_flow_from_confirmed_rows");
expect(deps.executeAddressMcpQuery).toHaveBeenCalledTimes(1);
const call = deps.executeAddressMcpQuery.mock.calls[0]?.[0];
expect(String(call?.query ?? "")).toContain("ПоступлениеНаРасчетныйСчет");
expect(call?.limit).toBeGreaterThan(0);
});
it("keeps non-lifecycle ready plans unsupported until a dedicated pilot exists", async () => {
const planner = planAssistantMcpDiscovery({
turnMeaning: {
@@ -44,6 +44,38 @@ describe("assistant MCP discovery response candidate", () => {
expect(candidate.reply_text).not.toContain("primitive");
});
it("localizes value-flow evidence without leaking pilot mechanics", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
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: "По данным 1С найдены строки денежных движений; сумму можно называть только в рамках проверенного периода и найденных строк.",
confirmed_lines: [
"1C value-flow rows were found for counterparty SVK",
"По найденным строкам денежных движений в 1С по контрагенту SVK за период 2020 сумма составляет 3 750 руб."
],
inference_lines: ["Counterparty value-flow total was calculated from confirmed 1C movement rows"],
unknown_lines: ["Full turnover outside the checked period is not proven by this MCP discovery pilot"],
limitation_lines: ["pilot_value_flow_uses_query_movements_and_derives_aggregate"],
next_step_line: null
}
}
})
);
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
expect(candidate.reply_text).toContain("В 1С найдены строки денежных движений по контрагенту SVK.");
expect(candidate.reply_text).toContain("3 750 руб.");
expect(candidate.reply_text).toContain("Полный оборот вне проверенного периода этим поиском не подтвержден.");
expect(candidate.reply_text).not.toContain("pilot_");
expect(candidate.reply_text).not.toContain("query_movements");
});
it("returns not applicable when discovery was skipped for an exact supported route", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate({
schema_version: "assistant_mcp_discovery_runtime_entry_point_v1",
@@ -89,6 +89,26 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_not_discovery_ready_chat_candidate");
});
it("applies a guarded candidate for discovery-ready deep partial answers", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "wrong deep partial answer",
currentReplySource: "deep_analysis",
addressRuntimeMeta: {
mcpDiscoveryRuntimeEntryPoint: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true
}
})
}
});
expect(result.applied).toBe(true);
expect(result.reply_source).toBe("mcp_discovery_response_candidate_guarded");
expect(result.reply_text).toContain("Confirmed fact");
expect(result.reason_codes).not.toContain("mcp_discovery_response_policy_not_discovery_ready_deep_candidate");
});
it("keeps the current reply when the candidate has no grounded text", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "route is not wired",
@@ -64,12 +64,17 @@ describe("assistant MCP discovery runtime entry point", () => {
entities: { counterparty: "Группа СВК" },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
},
deps: buildDeps([])
deps: buildDeps([
{ Period: "2020-01-15T00:00:00", Amount: 1250, Counterparty: "SVK" },
{ Period: "2020-02-20T00:00:00", Amount: 2500, Counterparty: "SVK" }
])
});
expect(result.entry_status).toBe("bridge_executed");
expect(result.turn_input.turn_meaning_ref?.explicit_entity_candidates).toEqual(["SVK", "Группа СВК"]);
expect(result.bridge?.bridge_status).toBe("unsupported");
expect(result.bridge?.bridge_status).toBe("answer_draft_ready");
expect(result.bridge?.pilot.pilot_scope).toBe("counterparty_value_flow_query_movements_v1");
expect(result.bridge?.pilot.derived_value_flow?.total_amount).toBe(3750);
expect(result.bridge?.hot_runtime_wired).toBe(false);
expect(result.reason_codes).toContain("mcp_discovery_unsupported_but_understood_turn");
});
@@ -49,6 +49,43 @@ describe("assistant MCP discovery turn input adapter", () => {
expect(result.reason_codes).toContain("mcp_discovery_lifecycle_signal_detected");
});
it("bootstraps value-flow discovery from raw turnover wording when no exact route owns it", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "какой денежный поток был у Группа СВК за 2020 год?",
predecomposeContract: {
entities: { counterparty: "Группа СВК" },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["Группа СВК"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_value_or_turnover",
stale_replay_forbidden: true
});
expect(result.reason_codes).toContain("mcp_discovery_value_flow_signal_detected");
});
it("treats value-flow organization-shaped target as entity candidate when counterparty is absent", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "какой денежный поток был у Группа СВК за 2020 год?",
predecomposeContract: {
entities: { organization: "Группа СВК" },
period: { period_from: "2020-01-01", period_to: "2020-12-31" }
}
});
expect(result.adapter_status).toBe("ready");
expect(result.turn_meaning_ref?.explicit_entity_candidates).toEqual(["Группа СВК"]);
expect(result.turn_meaning_ref?.explicit_organization_scope).toBeUndefined();
});
it("does not activate discovery for supported exact current-turn intent", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
assistantTurnMeaning: {