NODEDC_1C/llm_normalizer/backend/tests/assistantMcpDiscoveryRuntim...

716 lines
31 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it, vi } from "vitest";
import { runAssistantMcpDiscoveryRuntimeBridge } from "../src/services/assistantMcpDiscoveryRuntimeBridge";
function buildDeps(rows: Array<Record<string, unknown>>, error: string | null = null) {
return {
executeAddressMcpQuery: vi.fn(async () => ({
fetched_rows: rows.length,
matched_rows: error ? 0 : rows.length,
raw_rows: rows,
rows: error ? [] : rows,
error
}))
};
}
function buildBidirectionalDeps(
incomingRows: Array<Record<string, unknown>>,
outgoingRows: Array<Record<string, unknown>>
) {
return {
executeAddressMcpQuery: vi
.fn()
.mockResolvedValueOnce({
fetched_rows: incomingRows.length,
matched_rows: incomingRows.length,
raw_rows: incomingRows,
rows: incomingRows,
error: null
})
.mockResolvedValueOnce({
fetched_rows: outgoingRows.length,
matched_rows: outgoingRows.length,
raw_rows: outgoingRows,
rows: outgoingRows,
error: null
})
};
}
describe("assistant MCP discovery runtime bridge", () => {
it("composes planner, pilot executor, and answer draft without wiring the hot runtime", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
turnMeaning: {
asked_domain_family: "counterparty_lifecycle",
asked_action_family: "activity_duration",
explicit_entity_candidates: ["SVK"]
},
deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }])
});
expect(result.schema_version).toBe("assistant_mcp_discovery_runtime_bridge_v1");
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.hot_runtime_wired).toBe(false);
expect(result.pilot.mcp_execution_performed).toBe(true);
expect(result.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.user_facing_response_allowed).toBe(true);
expect(result.reason_codes).toContain("runtime_bridge_not_wired_to_hot_assistant_answer");
});
it("keeps missing scope as clarification and does not authorize a business fact answer", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["SVK"]
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.requires_user_clarification).toBe(true);
expect(result.pilot.mcp_execution_performed).toBe(false);
expect(result.business_fact_answer_allowed).toBe(false);
expect(result.answer_draft.next_step_line).toContain("Уточните контрагента");
});
it("keeps ranked value-flow in clarification without asking for a counterparty when only the period is known", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: "top_desc",
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "2020"
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.requires_user_clarification).toBe(true);
expect(result.pilot.mcp_execution_performed).toBe(false);
expect(result.planner.selected_chain_id).toBe("value_flow_ranking");
expect(result.answer_draft.headline).toContain("рейтинг");
expect(result.answer_draft.next_step_line).toContain("организацию");
expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента");
});
it("emits a resumable loop state for clarification-driven ranking chains", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: "top_desc",
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "2020"
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.loop_state).toMatchObject({
loop_status: "awaiting_clarification",
selected_chain_id: "value_flow_ranking",
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
ranking_need: "top_desc",
explicit_date_scope: "2020"
});
expect(result.loop_state.pending_axes).toContain("organization");
expect(result.loop_state.provided_axes).toContain("aggregate_axis");
expect(result.loop_state.catalog_chain_template_matches[0]).toBe("value_flow_ranking");
expect(result.loop_state.catalog_chain_template_alignment.alignment_status).toBe("selected_matches_top");
expect(result.loop_state.catalog_chain_template_alignment.selected_chain_matches_top).toBe(true);
expect(result.reason_codes).toContain("planner_selected_chain_matches_catalog_top");
expect(result.reason_codes).toContain("runtime_bridge_loop_state_awaiting_clarification");
});
it("produces a bounded ranked value-flow answer when period and organization are known", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: "top_desc",
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_ranked_axis_values", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "2020",
explicit_organization_scope: "ООО Альтернатива Плюс"
},
deps: buildDeps([
{ Period: "2020-01-10T00:00:00", Amount: 1200, Counterparty: "СВК-А" },
{ Period: "2020-03-11T00:00:00", Amount: 800, Counterparty: "СВК-Б" },
{ Period: "2020-05-12T00:00:00", Amount: 900, Counterparty: "СВК-А" }
])
});
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.planner.selected_chain_id).toBe("value_flow_ranking");
expect(result.pilot.derived_ranked_value_flow?.ranked_values[0]).toMatchObject({
axis_value: "СВК-А",
total_amount: 2100
});
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("СВК-А");
});
it("keeps open bidirectional comparison in clarification without asking for a counterparty when only the period is known", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "net_value_flow",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: "incoming_vs_outgoing",
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_date_scope: "2020"
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.requires_user_clarification).toBe(true);
expect(result.pilot.mcp_execution_performed).toBe(false);
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
expect(result.answer_draft.headline).toContain("входящий и исходящий");
expect(result.answer_draft.next_step_line).toContain("организацию");
expect(result.answer_draft.next_step_line).not.toContain("Уточните контрагента");
});
it("produces a bounded bidirectional comparison answer when period and organization are known", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "net_value_flow",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: "incoming_vs_outgoing",
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_incoming_movements", "collect_outgoing_movements", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_comparison_incoming_vs_outgoing"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_date_scope: "2020",
explicit_organization_scope: "ООО Альтернатива Плюс"
},
deps: buildBidirectionalDeps(
[
{ Period: "2020-01-10T00:00:00", Amount: 3200, Counterparty: "СВК-А" },
{ Period: "2020-04-11T00:00:00", Amount: 1800, Counterparty: "СВК-Б" }
],
[{ Period: "2020-02-12T00:00:00", Amount: 1400, Counterparty: "СВК-А" }]
)
});
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
period_scope: "2020",
incoming_customer_revenue: {
total_amount: 5000
},
outgoing_supplier_payout: {
total_amount: 1400
}
});
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("получили");
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("заплатили");
});
it("executes explicit-counterparty bidirectional comparison without period as bounded all-time scope", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: ["SVK"],
business_fact_family: "value_flow",
action_family: "net_value_flow",
aggregation_need: null,
time_scope_need: "all_time_scope",
comparison_need: "incoming_vs_outgoing",
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: [
"resolve_entity_reference",
"collect_incoming_movements",
"collect_outgoing_movements",
"probe_coverage"
],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: [
"data_need_graph_built",
"data_need_graph_comparison_incoming_vs_outgoing",
"data_need_graph_subject_bidirectional_value_flow_defaults_to_all_time_scope"
]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: ["SVK"],
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting"
},
deps: buildBidirectionalDeps(
[
{ Period: "2020-01-10T00:00:00", Amount: 3200, Counterparty: "SVK" },
{ Period: "2021-04-11T00:00:00", Amount: 1800, Counterparty: "SVK" }
],
[{ Period: "2022-02-12T00:00:00", Amount: 1400, Counterparty: "SVK" }]
)
});
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.requires_user_clarification).toBe(false);
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.planner.selected_chain_id).toBe("value_flow_comparison");
expect(result.planner.required_axes).toContain("all_time_scope");
expect(result.pilot.mcp_execution_performed).toBe(true);
expect(result.pilot.derived_bidirectional_value_flow).toMatchObject({
period_scope: null,
incoming_customer_revenue: {
total_amount: 5000
},
outgoing_supplier_payout: {
total_amount: 1400
}
});
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("SVK");
});
it("keeps document-ready plans bounded when the pilot finds no confirmed rows", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
turnMeaning: {
asked_domain_family: "document",
asked_action_family: "documents",
explicit_entity_candidates: ["SVK"]
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("checked_sources_only");
expect(result.hot_runtime_wired).toBe(false);
expect(result.pilot.pilot_scope).toBe("counterparty_document_evidence_query_documents_v1");
expect(result.pilot.mcp_execution_performed).toBe(true);
expect(result.business_fact_answer_allowed).toBe(false);
expect(result.reason_codes).toContain("runtime_bridge_status_checked_sources_only");
});
it("bridges inventory stock catalog templates through exact runtime evidence", async () => {
const deps = buildDeps([
{ Period: "2021-09-30T00:00:00", Item: "Widget A", Quantity: 10, Warehouse: "Main" },
{ Period: "2021-09-30T00:00:00", Item: "Widget B", Quantity: 4, Warehouse: "Reserve" }
]);
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "inventory_stock_snapshot",
action_family: "stock_snapshot",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: [
"fetch_scoped_movements",
"aggregate_checked_amounts",
"probe_coverage",
"explain_evidence_basis"
],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_stock_snapshot"],
reason_codes: ["data_need_graph_built", "data_need_graph_family_inventory_stock_snapshot"]
},
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "stock_snapshot",
explicit_organization_scope: "OOO Alternative Plus",
explicit_date_scope: "2021-09-30"
},
deps
});
const userFacing = [
result.answer_draft.headline,
...result.answer_draft.confirmed_lines,
...result.answer_draft.inference_lines,
...result.answer_draft.unknown_lines,
...result.answer_draft.limitation_lines,
result.answer_draft.next_step_line ?? ""
].join("\n");
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.answer_draft.answer_mode).toBe("confirmed_with_bounded_inference");
expect(result.pilot.pilot_scope).toBe("inventory_route_template_v1");
expect(result.pilot.mcp_execution_performed).toBe(true);
expect(result.pilot.executed_primitives).toEqual(["query_movements"]);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledWith(expect.objectContaining({ account_scope: ["41.01"] }));
expect(result.loop_state).toMatchObject({
loop_status: "ready_for_next_hop",
selected_chain_id: "inventory_stock_snapshot",
pilot_scope: "inventory_route_template_v1",
asked_domain_family: "inventory_stock",
asked_action_family: "stock_snapshot",
explicit_organization_scope: "OOO Alternative Plus",
explicit_date_scope: "2021-09-30"
});
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.reason_codes).toContain("pilot_inventory_exact_recipe_selected");
expect(result.reason_codes).toContain("pilot_inventory_exact_mcp_executed");
expect(result.reason_codes).not.toContain("pilot_scope_unsupported_for_live_execution");
expect(result.answer_draft.must_not_claim).toEqual(
expect.arrayContaining([
"Do not expose inventory_route_template_v1 or MCP primitive names in the user answer.",
"Do not claim full inventory coverage outside the checked rows, date, organization, item, or supplier scope."
])
);
expect(result.answer_draft.must_not_claim).not.toContain(
"Do not present inventory route-template planning as executed stock, supplier, purchase, or sale evidence."
);
expect(userFacing).toContain("exact inventory runtime");
expect(userFacing).toContain("Widget A");
expect(userFacing).not.toContain("inventory_route_template_v1");
expect(userFacing).not.toContain("query_movements");
expect(userFacing).not.toContain("primitive");
expect(userFacing).not.toContain("MCP discovery pilot");
});
it("bridges selected-item inventory provenance templates through exact document evidence", async () => {
const deps = buildDeps([
{
Period: "2021-08-10T00:00:00",
Item: "Widget A",
Counterparty: "Supplier One",
Registrator: "Purchase 0001"
}
]);
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: ["Widget A"],
business_fact_family: "inventory_purchase_provenance",
action_family: "purchase_provenance",
aggregation_need: null,
time_scope_need: null,
comparison_need: null,
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: [
"resolve_entity_reference",
"fetch_scoped_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unproven_supplier_attribution"],
reason_codes: ["data_need_graph_built", "data_need_graph_family_inventory_purchase_provenance"]
},
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "purchase_provenance",
explicit_entity_candidates: ["Widget A"]
},
deps
});
const userFacing = [
result.answer_draft.headline,
...result.answer_draft.confirmed_lines,
...result.answer_draft.inference_lines,
...result.answer_draft.unknown_lines,
...result.answer_draft.limitation_lines,
result.answer_draft.next_step_line ?? ""
].join("\n");
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.planner.selected_chain_id).toBe("inventory_purchase_provenance");
expect(result.pilot.executed_primitives).toEqual(["query_documents"]);
expect(deps.executeAddressMcpQuery).toHaveBeenCalledWith(expect.objectContaining({ account_scope: ["41.01"] }));
expect(userFacing).toContain("Widget A");
expect(userFacing).toContain("Supplier One");
expect(userFacing).not.toContain("inventory_route_template_v1");
expect(userFacing).not.toContain("query_documents");
expect(userFacing).not.toContain("primitive");
});
it("keeps selected-item inventory templates in clarification when the item anchor is missing", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "inventory_sale_trace",
action_family: "sale_trace",
aggregation_need: null,
time_scope_need: null,
comparison_need: null,
ranking_need: null,
proof_expectation: "clarification_required",
clarification_gaps: ["item"],
decomposition_candidates: [
"resolve_entity_reference",
"fetch_scoped_documents",
"drilldown_related_objects",
"probe_coverage",
"explain_evidence_basis"
],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unproven_buyer_or_sale_trace"],
reason_codes: ["data_need_graph_built", "data_need_graph_family_inventory_sale_trace"]
},
turnMeaning: {
asked_domain_family: "inventory_stock",
asked_action_family: "sale_trace"
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.requires_user_clarification).toBe(true);
expect(result.pilot.pilot_scope).toBe("inventory_route_template_v1");
expect(result.pilot.mcp_execution_performed).toBe(false);
expect(result.reason_codes).toContain("runtime_bridge_loop_state_awaiting_clarification");
expect(result.loop_state.pending_axes).toContain("item");
});
it("keeps document evidence executable when the planner expands primitives from fact-axis search", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: ["SVK"],
business_fact_family: "document_evidence",
action_family: "list_documents",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: [],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built"]
},
turnMeaning: {
asked_domain_family: "documents",
asked_action_family: "list_documents",
explicit_entity_candidates: ["SVK"],
explicit_date_scope: "2020",
unsupported_but_understood_family: "document_evidence"
},
deps: buildDeps([{ Period: "2020-01-15T00:00:00", Registrator: "DOC-1", Counterparty: "SVK" }])
});
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.planner.selected_chain_id).toBe("document_evidence");
expect(result.planner.proposed_primitives).toEqual(["resolve_entity_reference", "query_documents", "probe_coverage"]);
expect(result.planner.reason_codes).toContain("planner_selected_catalog_primitives_from_fact_axis_search");
expect(result.business_fact_answer_allowed).toBe(true);
});
it("preserves the answer adapter boundary against internal mechanics leakage", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
turnMeaning: {
asked_domain_family: "counterparty_lifecycle",
asked_action_family: "activity_duration",
explicit_entity_candidates: ["SVK"]
},
deps: buildDeps([{ Период: "2020-01-15T00:00:00", Регистратор: "CP_CUSTOMER_ACTIVITY_FIRST" }])
});
const userFacing = [
result.answer_draft.headline,
...result.answer_draft.confirmed_lines,
...result.answer_draft.inference_lines,
...result.answer_draft.unknown_lines,
...result.answer_draft.limitation_lines,
result.answer_draft.next_step_line ?? ""
].join("\n");
expect(userFacing).not.toContain("query_documents");
expect(userFacing).not.toContain("runtime_bridge");
expect(userFacing).not.toContain("primitive");
});
it("produces a bounded one-sided value-flow answer for an organization-scoped total without inventing a counterparty", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: null,
proof_expectation: "coverage_checked_fact",
clarification_gaps: [],
decomposition_candidates: ["collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_open_scope_total_without_subject"]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "2020",
explicit_organization_scope: "ООО Альтернатива Плюс"
},
deps: buildDeps([
{ Period: "2020-01-10T00:00:00", Amount: 3200, Counterparty: "Клиент-А" },
{ Period: "2020-05-22T00:00:00", Amount: 1800, Counterparty: "Клиент-Б" }
])
});
expect(result.bridge_status).toBe("answer_draft_ready");
expect(result.business_fact_answer_allowed).toBe(true);
expect(result.planner.selected_chain_id).toBe("value_flow");
expect(result.pilot.derived_value_flow).toMatchObject({
counterparty: null,
period_scope: "2020",
total_amount: 5000
});
expect(result.answer_draft.confirmed_lines.join("\n")).toContain("5 000");
expect(result.answer_draft.confirmed_lines.join("\n")).not.toContain("контрагенту");
});
it("keeps generic one-sided open totals in organization clarification without asking for a counterparty", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
business_fact_family: "value_flow",
action_family: "turnover",
aggregation_need: null,
time_scope_need: "explicit_period",
comparison_need: null,
ranking_need: null,
proof_expectation: "clarification_required",
clarification_gaps: ["organization"],
decomposition_candidates: ["collect_scoped_movements", "aggregate_checked_amounts", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: [
"data_need_graph_built",
"data_need_graph_open_scope_total_without_subject",
"data_need_graph_open_scope_total_needs_organization"
]
},
turnMeaning: {
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_date_scope: "2020"
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.requires_user_clarification).toBe(true);
expect(result.planner.selected_chain_id).toBe("value_flow");
expect(result.answer_draft.next_step_line).toContain("\u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044e");
expect(result.answer_draft.next_step_line).not.toContain(
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0430"
);
});
it("persists metadata scope and subject-optional flags in the resumable loop state", async () => {
const result = await runAssistantMcpDiscoveryRuntimeBridge({
dataNeedGraph: {
schema_version: "assistant_data_need_graph_v1",
policy_owner: "assistantMcpDiscoveryDataNeedGraph",
subject_candidates: [],
metadata_scope_hint: "\u041d\u0414\u0421",
subject_resolution_optional: true,
business_fact_family: "movement_evidence",
action_family: "list_movements",
aggregation_need: null,
time_scope_need: "period_required",
comparison_need: null,
ranking_need: null,
proof_expectation: "clarification_required",
clarification_gaps: ["organization", "period"],
decomposition_candidates: ["fetch_scoped_movements", "probe_coverage"],
forbidden_overclaim_flags: ["no_raw_model_claims", "no_unchecked_fact_totals"],
reason_codes: ["data_need_graph_built", "data_need_graph_metadata_scoped_open_lane_without_subject"]
},
turnMeaning: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
metadata_scope_hint: "\u041d\u0414\u0421",
subject_resolution_optional: true,
unsupported_but_understood_family: "movement_evidence"
},
deps: buildDeps([])
});
expect(result.bridge_status).toBe("needs_clarification");
expect(result.loop_state).toMatchObject({
loop_status: "awaiting_clarification",
selected_chain_id: "movement_evidence",
metadata_scope_hint: "\u041d\u0414\u0421",
subject_resolution_optional: true
});
expect(result.loop_state.pending_axes).toEqual(["organization", "period"]);
expect(result.loop_state.explicit_entity_candidates).toEqual([]);
});
});