NODEDC_1C/llm_normalizer/backend/tests/assistantMcpDiscoveryRespon...

883 lines
46 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 } from "vitest";
import { buildAssistantMcpDiscoveryResponseCandidate } from "../src/services/assistantMcpDiscoveryResponseCandidate";
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: "По данным 1С есть подтвержденная активность; длительность можно оценивать только как вывод.",
confirmed_lines: ["1C activity rows were found for counterparty SVK"],
inference_lines: ["Business activity duration may be inferred from first and latest confirmed 1C activity rows"],
unknown_lines: ["Legal registration date is not proven by this MCP discovery pilot"],
limitation_lines: ["query_documents was skipped internally", "MCP fetch window was limited"],
next_step_line: null
}
},
reason_codes: ["runtime_entry_point_bridge_executed"],
...overrides
} as any;
}
describe("assistant MCP discovery response candidate", () => {
it("builds a Russian guarded candidate from a confirmed discovery draft", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(entryPoint());
expect(candidate.candidate_status).toBe("ready_for_guarded_use");
expect(candidate.hot_runtime_wired).toBe(false);
expect(candidate.reply_type).toBe("partial_coverage");
expect(candidate.eligible_for_future_hot_runtime).toBe(true);
expect(candidate.reply_text).toContain("Коротко:");
expect(candidate.reply_text).toContain("В 1С найдены строки активности по контрагенту SVK.");
expect(candidate.reply_text).toContain("Юридическая дата регистрации этим поиском не подтверждена.");
expect(candidate.reply_text).not.toContain("query_documents");
expect(candidate.reply_text).not.toContain("primitive");
});
it("uses a compact direct answer for business-overview top year questions", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
data_need_graph: {
business_fact_family: "business_overview",
ranking_need: "top_desc",
reason_codes: [
"data_need_graph_family_business_overview",
"data_need_graph_ranking_top_desc",
"data_need_graph_business_overview_direct_money_answer"
]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: null,
incoming_customer_revenue: {
total_amount_human_ru: "157 192 981,43 руб.",
coverage_limited_by_probe_limit: true
},
outgoing_supplier_payout: {
total_amount_human_ru: "35 439 044,74 руб.",
coverage_limited_by_probe_limit: true
},
net_amount_human_ru: "121 753 936,69 руб.",
net_direction: "net_incoming",
top_customers: [],
yearly_breakdown: [
{
year_bucket: "2014",
incoming_total_amount: 11239150.41,
incoming_total_amount_human_ru: "11 239 150,41 руб.",
incoming_rows_with_amount: 4,
net_amount: -634486.17,
net_amount_human_ru: "634 486,17 руб.",
net_direction: "net_outgoing"
},
{
year_bucket: "2015",
incoming_total_amount: 136723459.73,
incoming_total_amount_human_ru: "136 723 459,73 руб.",
incoming_rows_with_amount: 54,
net_amount: 113158051.57,
net_amount_human_ru: "113 158 051,57 руб.",
net_direction: "net_incoming"
}
]
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Ограниченный бизнес-обзор с большим полотном.",
confirmed_lines: ["Профиль операционной активности: лишняя широкая строка."],
inference_lines: ["Сводный LLM-аудит: лишняя широкая строка."],
unknown_lines: ["Прибыль и маржа не подтверждены."],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("в доступном проверенном MCP-срезе");
expect(candidate.reply_text).toContain("лидирует 2015");
expect(candidate.reply_text).toContain("2015");
expect(candidate.reply_text).toContain("136 723 459,73 руб.");
expect(candidate.reply_text).toContain("не полный бухгалтерский рейтинг доходности");
expect(candidate.reply_text).toContain("не как чистую бухгалтерскую прибыль");
expect(candidate.reply_text).toContain("лимит выборки MCP");
expect(candidate.reply_text).not.toContain("Что подтверждено:");
expect(candidate.reply_text).not.toContain("Профиль операционной активности");
});
it("uses a compact direct answer for business-overview earned-money totals", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
data_need_graph: {
business_fact_family: "business_overview",
ranking_need: null,
reason_codes: [
"data_need_graph_family_business_overview",
"data_need_graph_business_overview_direct_money_answer"
]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: "2017",
incoming_customer_revenue: {
total_amount_human_ru: "16 932 063,96 руб.",
coverage_limited_by_probe_limit: false
},
outgoing_supplier_payout: {
total_amount_human_ru: "4 458 027,05 руб.",
coverage_limited_by_probe_limit: true
},
net_amount_human_ru: "12 474 036,91 руб.",
net_direction: "net_incoming",
top_customers: [
{
axis_value: "ГКУ УКРиС",
total_amount_human_ru: "11 536 836,23 руб."
}
],
top_suppliers: [
{
axis_value: "ООО Поставщик",
total_amount_human_ru: "2 200 000 руб."
}
],
yearly_breakdown: []
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Ограниченный бизнес-обзор с большим полотном.",
confirmed_lines: ["Складской срез на дату: лишняя широкая строка."],
inference_lines: ["Риски и контуры внимания: лишняя широкая строка."],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("за 2017");
expect(candidate.reply_text).toContain("получили 16 932 063,96 руб.");
expect(candidate.reply_text).toContain("исходящие платежи/списания 4 458 027,05 руб.");
expect(candidate.reply_text).toContain("12 474 036,91 руб");
expect(candidate.reply_text?.split("\n")[0]).toContain("крупнейший источник входящих денег: ГКУ УКРиС");
expect(candidate.reply_text?.split("\n")[0]).toContain("крупнейший получатель исходящих денег: ООО Поставщик");
expect(candidate.reply_text).toContain("денежный operating-flow proxy");
expect(candidate.reply_text).not.toContain("Что можно сказать только как вывод:");
expect(candidate.reply_text).not.toContain("Складской срез");
});
it("mentions separate counterparty scope in company plus counterparty business summaries", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
explicit_organization_scope: "ООО Альтернатива Плюс",
business_overview_separate_entity_candidates: ["Группа СВК"]
},
data_need_graph: {
business_fact_family: "business_overview",
subject_candidates: [],
ranking_need: null,
reason_codes: ["data_need_graph_family_business_overview"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: null,
incoming_customer_revenue: {
total_amount_human_ru: "157 192 981,43 руб.",
coverage_limited_by_probe_limit: true
},
outgoing_supplier_payout: {
total_amount_human_ru: "35 439 044,74 руб.",
coverage_limited_by_probe_limit: true
},
net_amount_human_ru: "121 753 936,69 руб.",
net_direction: "net_incoming",
top_customers: [],
yearly_breakdown: []
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Company summary.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("по компании ООО Альтернатива Плюс");
expect(candidate.reply_text).toContain("Группа СВК");
expect(candidate.reply_text?.split("\n")[0]).toContain("суммы компании не переношу");
expect(candidate.reply_text).toContain("нельзя делать вывод о выручке, долге или прибыльности");
expect(candidate.reply_text).toContain("без отдельного контрагентского среза");
});
it("adds missing proof boundaries for broad all-time business overviews", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
explicit_organization_scope: "ООО Альтернатива Плюс"
},
data_need_graph: {
business_fact_family: "business_overview",
subject_candidates: [],
ranking_need: null,
reason_codes: ["data_need_graph_family_business_overview"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: null,
incoming_customer_revenue: {
total_amount_human_ru: "157 192 981,43 руб.",
coverage_limited_by_probe_limit: true
},
outgoing_supplier_payout: {
total_amount_human_ru: "35 439 044,74 руб.",
coverage_limited_by_probe_limit: true
},
net_amount_human_ru: "121 753 936,69 руб.",
net_direction: "net_incoming",
top_customers: [],
top_suppliers: [],
yearly_breakdown: []
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Company summary.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("Что не подтверждено в этом срезе");
expect(candidate.reply_text).toContain("НДС");
expect(candidate.reply_text).toContain("долги");
expect(candidate.reply_text).toContain("склад");
expect(candidate.reply_text).not.toContain("capability_id");
});
it("reuses previous counterparty value-flow bundle in company plus counterparty summaries", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
asked_domain_family: "business_overview",
asked_action_family: "broad_evaluation",
explicit_organization_scope: "ООО Альтернатива Плюс",
business_overview_separate_entity_candidates: ["Группа СВК"],
previous_counterparty_value_flow_bundle: {
counterparty: "Группа СВК",
incoming_customer_revenue: {
total_amount_human_ru: "20 653 490 руб.",
rows_with_amount: 26,
rows_matched: 26,
first_movement_date: "2020-07-27",
latest_movement_date: "2021-11-10"
},
outgoing_supplier_payout: {
total_amount_human_ru: "2 129 651 руб.",
rows_with_amount: 1,
rows_matched: 1,
first_movement_date: "2022-01-20",
latest_movement_date: "2022-01-20"
},
net_amount_human_ru: "18 523 839 руб.",
net_direction: "net_incoming"
},
previous_counterparty_document_bundle: {
counterparty: "Группа СВК",
document_count: 19
}
},
data_need_graph: {
business_fact_family: "business_overview",
subject_candidates: [],
ranking_need: null,
reason_codes: ["data_need_graph_family_business_overview"]
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "business_overview_route_template_v1",
derived_business_overview: {
period_scope: null,
incoming_customer_revenue: { total_amount_human_ru: "157 192 981,43 руб." },
outgoing_supplier_payout: { total_amount_human_ru: "35 439 044,74 руб." },
net_amount_human_ru: "121 753 936,69 руб.",
net_direction: "net_incoming",
top_customers: [],
yearly_breakdown: []
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Company summary.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
expect(firstLine).toContain("отдельно по Группа СВК: получили 20 653 490 руб.");
expect(firstLine).toContain("можно утверждать только эти подтвержденные срезы");
expect(firstLine).toContain("нельзя называть это чистой прибылью");
expect(candidate.reply_text).toContain("Отдельно по контрагенту Группа СВК: подтверждено получили 20 653 490 руб.");
expect(candidate.reply_text).toContain("заплатили 2 129 651 руб.");
expect(candidate.reply_text).toContain("Можно утверждать:");
expect(candidate.reply_text).toContain("Нельзя утверждать:");
expect(candidate.reply_text).toContain("документы по цепочке: найдено 19");
expect(candidate.reply_text).toContain("ранее подтвержденный контрагентский срез");
});
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("localizes supplier-payout 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 supplier-payout rows were found for counterparty SVK",
"По найденным строкам исходящих платежей/списаний в 1С по контрагенту SVK за период 2020 сумма исходящих платежей/списаний составляет 5 000 руб."
],
inference_lines: ["Counterparty supplier-payout total was calculated from confirmed 1C outgoing payment rows"],
unknown_lines: [
"Complete requested-period coverage is not proven because the MCP discovery probe row limit was reached",
"Full supplier-payout amount 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("5 000 руб.");
expect(candidate.reply_text).toContain("Полное покрытие запрошенного периода не подтверждено");
expect(candidate.reply_text).toContain("Полный объем исходящих платежей вне проверенного периода этим поиском не подтвержден.");
expect(candidate.reply_text).not.toContain("pilot_");
expect(candidate.reply_text).not.toContain("query_movements");
});
it("localizes bidirectional net 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 bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found",
"По найденным строкам 1С по контрагенту SVK за период 2020: получили 12 500,50 руб. по входящим движениям, заплатили 4 000 руб. по исходящим платежам/списаниям. Расчетное нетто в нашу сторону: 8 500,50 руб."
],
inference_lines: [
"Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"
],
unknown_lines: [
"Complete requested-period coverage for bidirectional value-flow is not proven because at least one MCP discovery probe row limit was reached",
"Full bidirectional value-flow outside the checked period is not proven by this MCP discovery pilot"
],
limitation_lines: ["pilot_bidirectional_value_flow_uses_two_query_movements_and_derives_net"],
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("получили 12 500,50 руб.");
expect(candidate.reply_text).toContain("заплатили 4 000 руб.");
expect(candidate.reply_text).toContain("нетто в нашу сторону: 8 500,50 руб.");
expect(candidate.reply_text).toContain("Полное покрытие запрошенного периода по двустороннему денежному потоку не подтверждено");
expect(candidate.reply_text).not.toContain("pilot_");
expect(candidate.reply_text).not.toContain("query_movements");
});
it("uses a compact direct first line for derived bidirectional value-flow totals", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
pilot: {
pilot_scope: "counterparty_bidirectional_value_flow_query_movements_v1",
derived_bidirectional_value_flow: {
counterparty: "SVK",
period_scope: "2020",
net_amount_human_ru: "8 500,50 руб.",
net_direction: "net_incoming",
coverage_limited_by_probe_limit: false,
incoming_customer_revenue: {
rows_matched: 2,
rows_with_amount: 2,
total_amount_human_ru: "12 500,50 руб.",
first_movement_date: "2020-01-15",
latest_movement_date: "2020-02-20"
},
outgoing_supplier_payout: {
rows_matched: 1,
rows_with_amount: 1,
total_amount_human_ru: "4 000 руб.",
first_movement_date: "2020-03-10",
latest_movement_date: "2020-03-10"
}
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "По данным 1С найдены строки входящих и исходящих денежных движений.",
confirmed_lines: ["1C bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found"],
inference_lines: ["Counterparty net value-flow was calculated as incoming confirmed 1C rows minus outgoing confirmed 1C rows"],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
const firstLine = candidate.reply_text?.split("\n")[0] ?? "";
expect(firstLine).toContain("Коротко:");
expect(firstLine).toContain("SVK");
expect(firstLine).toContain("получили 12 500,50 руб.");
expect(firstLine).toContain("заплатили 4 000 руб.");
expect(firstLine).toContain("нетто в нашу сторону: 8 500,50 руб.");
expect(candidate.reply_text).toContain("Основа:");
expect(candidate.reply_text).not.toContain("Что подтверждено");
expect(candidate.reply_text).not.toContain("pilot_");
expect(candidate.reply_text).not.toContain("query_movements");
});
it("keeps monthly breakdown lines user-facing and localizes monthly inference basis", () => {
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 bidirectional value-flow rows were checked for counterparty SVK: incoming=found, outgoing=found",
"Помесячно: янв 2020 — получили 10 000 руб., заплатили 4 000 руб., нетто в нашу сторону 6 000 руб."
],
inference_lines: [
"Counterparty monthly net value-flow breakdown was grouped by month over confirmed incoming and outgoing 1C rows"
],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("Помесячно: янв 2020");
expect(candidate.reply_text).toContain("Помесячная нетто-раскладка сгруппирована только по подтвержденным входящим и исходящим строкам 1С.");
expect(candidate.reply_text).not.toContain("Counterparty monthly net value-flow breakdown");
});
it("localizes recovered coverage facts without leaking broad-probe wording", () => {
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 supplier-payout rows were found for counterparty SVK"],
inference_lines: [
"Requested period coverage was recovered through monthly 1C value-flow probes after the broad probe hit the row limit"
],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("Покрытие запрошенного периода восстановлено помесячными проверками 1С");
expect(candidate.reply_text).not.toContain("broad probe hit the row limit");
});
it("localizes document evidence without leaking raw English facts", () => {
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 document rows were found for counterparty SVK"],
inference_lines: ["Counterparty document evidence is limited to confirmed 1C document rows in the checked scope"],
unknown_lines: ["Full document history outside the checked period is not proven by this MCP discovery pilot"],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("В 1С найдены строки документов по контрагенту SVK.");
expect(candidate.reply_text).toContain("Срез документов ограничен только подтвержденными строками документов");
expect(candidate.reply_text).toContain("Полный исторический срез документов вне проверенного периода этим поиском не подтвержден.");
expect(candidate.reply_text).not.toContain("1C document rows were found");
});
it("localizes movement evidence without leaking raw English facts", () => {
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 movement rows were found for counterparty SVK"],
inference_lines: ["Counterparty movement evidence is limited to confirmed 1C movement rows in the checked scope"],
unknown_lines: ["Full movement history outside the checked period is not proven by this MCP discovery pilot"],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("Р1С найдены строки движений по контрагенту SVK.");
expect(candidate.reply_text).toContain("Срез движений ограничен только подтвержденными строками движений");
expect(candidate.reply_text).toContain("Полный исторический срез движений вне проверенного периода этим поиском не подтвержден.");
expect(candidate.reply_text).not.toContain("1C movement rows were found");
});
it("localizes metadata evidence without leaking raw MCP wording", () => {
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С найдена подтвержденная metadata-поверхность.",
confirmed_lines: [
'Confirmed 1C metadata surface for scope "НДС": 7 rows and 3 matching objects',
"Available metadata object sets: accumulation_register, document",
"Selected metadata entity set: Документ",
"Selected metadata objects: Документ.СчетФактураВыданный",
"Available metadata fields/sections: amount, vat_rate, organization"
],
inference_lines: [
"A likely next checked lane may be inferred as document_evidence from the confirmed metadata surface"
],
unknown_lines: [
'No matching 1C metadata objects were confirmed for scope "Прибыль"',
"Exact downstream metadata surface remains ambiguous across: Документ, РегистрНакопления"
],
limitation_lines: ["Detailed metadata fields were not returned by this MCP metadata probe"],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain('В 1С подтверждена metadata-поверхность по области "НДС"');
expect(candidate.reply_text).toContain("Доступные типы metadata-объектов");
expect(candidate.reply_text).toContain("Выбранное семейство metadata-объектов: Документ");
expect(candidate.reply_text).toContain("Выбранные metadata-объекты для следующего шага");
expect(candidate.reply_text).toContain("Доступные metadata-поля/секции");
expect(candidate.reply_text).toContain("контур документов");
expect(candidate.reply_text).toContain('В 1С не подтверждены metadata-объекты по области "Прибыль"');
expect(candidate.reply_text).toContain("неоднозначна между family");
expect(candidate.reply_text).toContain("Эта MCP-проверка metadata не вернула детальный список полей");
expect(candidate.reply_text).not.toContain("Confirmed 1C metadata surface");
});
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",
policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint",
entry_status: "skipped_not_applicable",
hot_runtime_wired: false,
discovery_attempted: false,
turn_input: { adapter_status: "not_applicable" },
bridge: null,
reason_codes: []
} as any);
expect(candidate.candidate_status).toBe("not_applicable");
expect(candidate.reply_text).toBeNull();
expect(candidate.eligible_for_future_hot_runtime).toBe(false);
});
it("creates a clarification candidate from a clarification bridge", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
bridge: {
bridge_status: "needs_clarification",
user_facing_response_allowed: true,
business_fact_answer_allowed: false,
requires_user_clarification: true,
answer_draft: {
answer_mode: "needs_clarification",
headline: "Нужно уточнить контекст перед поиском в 1С.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: "Уточните контрагента, период или организацию."
}
}
})
);
expect(candidate.candidate_status).toBe("clarification_candidate");
expect(candidate.reply_type).toBe("clarification_required");
expect(candidate.reply_text).toContain("Нужно уточнить контекст");
expect(candidate.eligible_for_future_hot_runtime).toBe(true);
});
it("surfaces metadata lane-choice clarification as a user-facing clarification candidate", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
bridge: {
bridge_status: "needs_clarification",
user_facing_response_allowed: true,
business_fact_answer_allowed: false,
requires_user_clarification: true,
answer_draft: {
answer_mode: "needs_clarification",
headline: "По подтвержденной metadata-поверхности видно несколько конкурирующих data-lane, и без явного выбора дальше идти нельзя.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: "Уточните, в какой контур идти дальше: по документам или по движениям/регистрам."
}
}
})
);
expect(candidate.candidate_status).toBe("clarification_candidate");
expect(candidate.reply_type).toBe("clarification_required");
expect(candidate.reply_text).toContain("data-lane");
expect(candidate.reply_text).toContain("по документам");
expect(candidate.reply_text).toContain("по движениям/регистрам");
});
it("does not expose unsupported bridge output as a future hot candidate", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
bridge: {
bridge_status: "unsupported",
user_facing_response_allowed: true,
business_fact_answer_allowed: false,
requires_user_clarification: false,
answer_draft: null
}
})
);
expect(candidate.candidate_status).toBe("unsupported");
expect(candidate.reply_type).toBe("no_grounded_answer");
expect(candidate.reply_text).toBeNull();
expect(candidate.eligible_for_future_hot_runtime).toBe(false);
});
it("localizes open-scope bidirectional comparison scope and probe-limit wording without contour garbage", () => {
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:
"\u041f\u043e \u0434\u0430\u043d\u043d\u044b\u043c 1\u0421 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0441\u0442\u0440\u043e\u043a\u0438 \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0445 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0445 \u0434\u0432\u0438\u0436\u0435\u043d\u0438\u0439; \u043d\u0435\u0442\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0437\u044b\u0432\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u0430\u043a \u0440\u0430\u0441\u0447\u0435\u0442 \u043f\u043e \u043d\u0430\u0439\u0434\u0435\u043d\u043d\u044b\u043c \u0441\u0442\u0440\u043e\u043a\u0430\u043c \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u043c\u0443 \u043f\u0435\u0440\u0438\u043e\u0434\u0443.",
confirmed_lines: [
"1C bidirectional value-flow rows were checked for the requested counterparty scope: incoming=found, outgoing=found"
],
inference_lines: [],
unknown_lines: [],
limitation_lines: [
"Requested period hit the MCP row limit, but the approved monthly recovery probe budget is smaller than the required subperiod count"
],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain(
"\u0412 1\u0421 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d\u044b \u0432\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0438 \u0438\u0441\u0445\u043e\u0434\u044f\u0449\u0438\u0435 \u0434\u0435\u043d\u0435\u0436\u043d\u044b\u0435 \u0441\u0442\u0440\u043e\u043a\u0438 \u0432 \u0437\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u043e\u043c \u0441\u0440\u0435\u0437\u0435"
);
expect(candidate.reply_text).toContain(
"\u0417\u0430\u043f\u0440\u043e\u0448\u0435\u043d\u043d\u044b\u0439 \u043f\u0435\u0440\u0438\u043e\u0434 \u0443\u043f\u0435\u0440\u0441\u044f \u0432 \u043b\u0438\u043c\u0438\u0442 \u0441\u0442\u0440\u043e\u043a MCP"
);
expect(candidate.reply_text).not.toContain(
"\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442\u0441\u043a\u043e\u043c\u0443 \u043a\u043e\u043d\u0442\u0443\u0440\u0443"
);
expect(candidate.reply_text).not.toContain("Requested period hit the MCP row limit");
});
it("filters MCP discovery scope-mechanics from clarification unknown and limitation blocks", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
bridge: {
bridge_status: "needs_clarification",
user_facing_response_allowed: true,
business_fact_answer_allowed: false,
requires_user_clarification: true,
answer_draft: {
answer_mode: "needs_clarification",
headline:
"Могу посчитать общий денежный поток в проверяемом окне, но для проверяемого поиска в 1С нужно проверяемый период и организацию.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: ["MCP discovery pilot needs more scope before execution"],
limitation_lines: ["MCP discovery pilot needs more scope before execution"],
next_step_line:
"Уточните период и организацию, и я продолжу поиск по денежному потоку в 1С."
}
}
})
);
expect(candidate.reply_text).toContain("Уточните период и организацию");
expect(candidate.reply_text).not.toContain("MCP discovery");
expect(candidate.reply_text).not.toContain("needs more scope before execution");
expect(candidate.reply_text).not.toContain("Что не подтверждено:");
expect(candidate.reply_text).not.toContain("Ограничения проверки:");
});
});