Compare commits

..

No commits in common. "fa07bb1f0e5092d328df977f66775b2266d75e92" and "e9f0ef6b4129e1e30d010c8ef9272afd742629fe" have entirely different histories.

22 changed files with 198 additions and 1056 deletions

View File

@ -1,10 +1,10 @@
{
"schema_version": "domain_scenario_pack_v1",
"pack_id": "active_phase108_settlement_pivot_debt_boundary_20260615",
"domain": "address_phase108_settlement_pivot_debt_boundary",
"runtime_domain": "settlement_document_payment_contract_pivots",
"title": "Settlement pivot and debt boundary replay",
"description": "Active slice for dogfooding the autonomous agent loop on a settlement-heavy path: counterparty document root, repeated payments/contracts/documents pivots, year switch carryover, debt due-date boundary, and off-domain recovery.",
"pack_id": "active_phase107_mixed_planner_brain_dogfood_20260615",
"domain": "address_phase107_mixed_planner_brain_dogfood",
"runtime_domain": "planner_autonomy_mixed_boundary",
"title": "Mixed planner-brain dogfood replay",
"description": "Active slice for dogfooding the autonomous agent loop on a compact mixed planner path: living chat guard, counterparty alias grounding, follow-up money flow, net, documents, movement evidence, broad business evaluation, and off-domain recovery.",
"status": "active",
"source_of_truth_policy": {
"purpose": "single mutable domain source for the current orchestration target",
@ -21,27 +21,25 @@
},
"analysis_context": {
"as_of_date": "2026-06-15",
"source": "phase108_after_phase107_mixed_planner_brain_dogfood",
"source": "phase107_after_phase106_profit_cashflow_margin_next_step",
"known_previous_anchors": [
"phase75_contracts_to_payments_year_switch",
"phase79_payments_to_contracts_to_documents_year_switch",
"phase81_payments_contracts_documents_back_to_payments_year_switch",
"phase94_debt_due_date_aging_reviewed_route",
"phase107_mixed_planner_brain_dogfood_20260615_p11_loop"
"phase83_planner_brain_alignment_mix_20260603_live9",
"phase104_generic_role_tail_anchor_hygiene_20260615_p04_loop",
"phase106_profit_cashflow_margin_next_step_20260615_p03_loop"
],
"why_now": "Phase107 hardened counterparty money-flow memory. Phase108 moves to settlement pivots: documents, payments, contracts, repeated pronoun reuse, year-switch carryover, and honest debt due-date boundaries."
"why_now": "Phase83 passed in the legacy truth-harness format; this pack replays the highest-value mixed planner slice through the current autonomous pack-loop."
},
"target_score": 88,
"bindings": {
"main_organization": "ООО Альтернатива Плюс",
"pivot_counterparty": "Жуковка 51",
"control_year": "2021",
"debt_boundary_year_end": "2020-12-31"
"control_year": "2020",
"svk_counterparty": "Группа СВК"
},
"issue_codes_under_test": [
"technical_garbage_in_answer",
"business_direct_answer_missing",
"business_next_step_missing",
"counterparty_value_flow_misrouted_to_company_profit",
"business_utility_gap"
],
"detectors_under_test": [
@ -50,66 +48,62 @@
"first_line_not_direct_answer",
"top_level_scaffold_before_answer",
"limited_answer_without_next_action",
"business_answer_too_verbose_output"
"counterparty_value_flow_required_surface",
"counterparty_value_flow_profit_accounts_forbidden"
],
"agent_audit_expectations": {
"semantic_answer_review_first": true,
"direct_answer_first": true,
"business_utility_required": true,
"selected_counterparty_memory_required": true,
"pivot_action_memory_required": true,
"year_switch_keeps_last_action_required": true,
"debt_due_date_boundary_must_be_honest": true,
"date_carryover_required": true,
"documents_and_movements_are_evidence_drilldowns": true,
"off_domain_living_chat_must_not_replay_business_context": true,
"technical_garbage_forbidden": true,
"do_not_accept_if": [
"a pronoun follow-up over 'по нему' loses Жуковка 51",
"the year switch asks again for a counterparty instead of reusing the active settlement object",
"a payment/contracts/documents pivot answers with a different object family without saying so",
"the debt due-date answer claims proven overdue debt without payment-term evidence",
"the final answer leaks route ids, capability ids, runtime enums, or debug payload",
"a follow-up over 'нему' loses the selected counterparty",
"a payout or net follow-up loses the 2020 period",
"a counterparty received/paid/net question is answered as company profit",
"document or movement follow-up answers with a fresh clarification instead of reusing the selected counterparty",
"broad business evaluation leaks route ids, capability ids, runtime enums, or debug payload",
"off-domain living chat is hijacked by stale 1C business context"
]
},
"scenarios": [
{
"scenario_id": "settlement_pivot_year_switch_and_debt_boundary",
"title": "Documents, payments, contracts, year switch, and debt boundary",
"description": "Validate repeated settlement pivots over the same counterparty and a debt due-date boundary canary in one session.",
"scenario_id": "mixed_planner_counterparty_evidence_and_living_guard",
"title": "Counterparty memory, evidence drilldowns, business overview, and off-domain guard",
"description": "Validate a compact but cross-cutting planner path: chat sanity, loose counterparty alias grounding, follow-up money flow and net, documents/movements evidence, explicit broad company evaluation, and recovery into ordinary living chat.",
"steps": [
{
"step_id": "step_01_documents_by_counterparty",
"title": "Open documents for the counterparty",
"step_id": "step_01_human_smalltalk_sanity",
"title": "Living chat remains human and does not expose discovery internals",
"node_role": "root",
"question": "Покажи документы по Жуковке 51.",
"question": "привет, ты на связи?",
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"documents_by_counterparty",
"counterparty_root",
"pivot_seed",
"integrity_guard"
"human_answer",
"mcp_discovery_gate_sanity",
"meta_smalltalk"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)жуковк",
"(?i)документ|сч[её]т|акт|накладн|строк"
"required_answer_patterns_any": [
"(?i)привет|на связи|готов|помочь"
],
"forbidden_answer_patterns": [
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery",
"(?i)уточните.*контрагент|какого контрагента"
"(?i)mcp|runtime_|query_documents|primitive|route_candidate|capability_id|snapshot_items|answer_object"
],
"criticality": "critical"
"criticality": "info"
},
{
"step_id": "step_02_payments_by_pronoun_followup",
"title": "Pivot from documents to payments",
"question": "А по нему платежи?",
"step_id": "step_02_resolve_counterparty_alias",
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
"question": "найди в 1С контрагента СВК",
"depends_on": [
"step_01_documents_by_counterparty"
"step_01_human_smalltalk_sanity"
],
"allowed_reply_types": [
"factual",
@ -117,116 +111,139 @@
"partial_coverage"
],
"semantic_tags": [
"payments_followup",
"counterparty_pronoun_resolution",
"document_to_payments_pivot",
"integrity_guard"
"entity_resolution",
"alias_grounding",
"followup_anchor",
"planner_catalog_alignment"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)плат[её]ж|операц|банк|поступлен|списан"
"(?i)свк",
"(?i)контрагент"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)каталог|найден|наиболее вероятн"
],
"forbidden_answer_patterns": [
"(?i)уточните.*контрагент|какого контрагента",
"(?i)документы по всем контрагентам",
"(?i)получили|заплатили|нетто|оборот|выручк|сумм(а|ы)",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_03_contracts_after_payments_pivot",
"title": "Pivot from payments to contracts",
"question": "А по нему договоры?",
"step_id": "step_03_incoming_by_resolved_entity",
"title": "Incoming value-flow follow-up reuses the resolved counterparty anchor",
"question": "сколько получили по нему за 2020 год",
"depends_on": [
"step_02_payments_by_pronoun_followup"
"step_02_resolve_counterparty_alias"
],
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"contracts_followup",
"second_pivot",
"counterparty_pronoun_resolution",
"integrity_guard"
"entity_resolution",
"incoming_value_flow",
"followup_reuse",
"date_carryover",
"planner_catalog_alignment"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)договор|контракт|соглаш"
"(?i)2020",
"(?i)получил|входящ|поступ",
"(?i)руб"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк"
],
"forbidden_answer_patterns": [
"(?i)уточните.*контрагент|какого контрагента",
"(?i)не найден контрагент|уточните, какого контрагента|по какому контрагенту",
"(?i)чист.*прибыл|90/91/99|финрезульт",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_04_documents_after_contracts_pivot",
"title": "Pivot back from contracts to documents",
"question": "А по нему документы?",
"step_id": "step_04_payout_switch_by_resolved_entity",
"title": "Outgoing payment follow-up keeps the same grounded counterparty and checked year",
"question": "а теперь сколько заплатили?",
"depends_on": [
"step_03_contracts_after_payments_pivot"
"step_03_incoming_by_resolved_entity"
],
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"documents_followup",
"third_pivot",
"counterparty_pronoun_resolution",
"integrity_guard"
"entity_resolution",
"payout_switch",
"followup_reuse",
"date_carryover",
"planner_catalog_alignment"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)документ|сч[её]т|акт|накладн|строк"
"(?i)2020",
"(?i)заплатил|исходящ|списан|платеж|платёж",
"(?i)руб"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк"
],
"forbidden_answer_patterns": [
"(?i)уточните.*контрагент|какого контрагента",
"(?i)не найден контрагент|уточните, какого контрагента|по какому контрагенту|за какой год",
"(?i)чист.*прибыл|90/91/99|финрезульт",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_05_payments_after_documents_pivot",
"title": "Pivot back again to payments",
"question": "А по нему платежи?",
"step_id": "step_05_net_after_payout",
"title": "Net-flow follow-up reuses the same grounded counterparty and checked year after payout",
"question": "а какое нетто?",
"depends_on": [
"step_04_documents_after_contracts_pivot"
"step_04_payout_switch_by_resolved_entity"
],
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"payments_followup",
"fourth_pivot",
"counterparty_pronoun_resolution",
"integrity_guard"
"entity_resolution",
"net_value_flow",
"followup_reuse",
"date_carryover",
"planner_catalog_alignment"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)жуковк|контрагент",
"(?i)плат[её]ж|операц|банк|поступлен|списан"
"(?i)2020",
"(?i)нетто|сальдо|разниц",
"(?i)получ",
"(?i)заплат",
"(?i)руб"
],
"required_answer_patterns_any": [
"12[ .\\u00a0]?093[ .\\u00a0]?465|12093465",
"(?i)группа\\s+свк|свк"
],
"forbidden_answer_patterns": [
"(?i)уточните.*контрагент|какого контрагента",
"(?i)не найден контрагент|уточните, какого контрагента|по какому контрагенту|за какой год",
"(?i)чист.*прибыл|90/91/99|финрезульт",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_06_year_switch_after_payments",
"title": "Year switch keeps counterparty and payment action",
"question": "А за 2021?",
"step_id": "step_06_documents_after_net",
"title": "Document evidence follow-up keeps the grounded counterparty after the net answer",
"question": "а по документам?",
"depends_on": [
"step_05_payments_after_documents_pivot"
"step_05_net_after_payout"
],
"allowed_reply_types": [
"factual",
@ -234,110 +251,119 @@
"partial_coverage"
],
"semantic_tags": [
"year_switch_after_fourth_pivot",
"payments_followup",
"period_followup_reuse",
"integrity_guard"
"entity_resolution",
"document_evidence",
"value_flow_pivot",
"followup_reuse",
"planner_catalog_alignment"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)2021",
"(?i)жуковк|контрагент",
"(?i)плат[её]ж|операц|банк|поступлен|списан"
"(?i)документ|счет|счёт|накладн|акт"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк",
"(?i)2020"
],
"forbidden_answer_patterns": [
"(?i)уточните.*контрагент|какого контрагента|за какой год",
"(?i)метадан|схем|объект[а-я]* 1с",
"(?i)не найден контрагент|уточните, какого контрагента|по какому контрагенту",
"(?i)сколько получили|сколько заплатили|нетто",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_07_debt_due_date_boundary",
"title": "Debt due-date ask stays honest about overdue evidence",
"question": "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
"step_id": "step_07_movements_after_documents",
"title": "Movement evidence follow-up keeps the grounded counterparty after the document answer",
"question": "а по движениям?",
"depends_on": [
"step_06_documents_after_net"
],
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"business_overview",
"debt_due_date_boundary",
"checked_negative",
"honest_boundary"
"entity_resolution",
"movement_evidence",
"document_pivot",
"followup_reuse",
"planner_catalog_alignment"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)дебитор|долг|открыт.*расчет|остат",
"(?i)2020|2020-12-31|конец 2020",
"(?i)срок.*оплат|due[- ]?date|просроч",
"(?i)не установлен|не подтвержд|не доказ|нет подтвержденной просроч",
"(?i)1С|проверенн|найденн|подтвержд"
"(?i)движени|операц|платеж|платёж|списан|поступ"
],
"required_answer_patterns_any": [
"(?i)группа\\s+свк",
"(?i)свк",
"(?i)2020"
],
"forbidden_answer_patterns": [
"(?i)точно подтверждаю.*просроч",
"(?i)просроч.*подтвержден.*без.*срок",
"(?i)route_candidate|query_movements|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
"(?i)не найден контрагент|уточните, какого контрагента|по какому контрагенту",
"(?i)сколько получили|сколько заплатили|нетто",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_08_debt_boundary_short_followup",
"title": "Short follow-up keeps the debt due-date boundary",
"question": "То есть просрочку доказать нельзя, коротко почему?",
"step_id": "step_08_broad_business_evaluation_explicit_org",
"title": "Broad business evaluation stays grounded and bounded after counterparty drilldowns",
"question": "Как ты оценишь деятельность компании {{bindings.main_organization}} по данным 1С?",
"depends_on": [
"step_07_debt_due_date_boundary"
"step_07_movements_after_documents"
],
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"context_carryover",
"debt_due_date_boundary",
"checked_negative",
"short_followup"
"broad_business_evaluation",
"grounded_summary",
"context_switch_from_counterparty_to_company"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)срок.*оплат|due[- ]?date|договор",
"(?i)не установлен|не хватает|не подтвержд|не доказ",
"(?i)просроч",
"(?i)2020|2020-12-31|конец 2020"
"(?i)альтернатива",
"(?i)ограниченн|проверенн|не\\s+аудит|не\\s+аудитор",
"(?i)1с|подтвержд",
"(?i)денежн|долг|ндс|контрагент|операц"
],
"forbidden_answer_patterns": [
"(?i)уточните организац|какую компанию",
"(?i)точно подтверждаю.*просроч",
"(?i)^\\s*коротко:",
"(?i)чистая\\s+прибыль\\s+подтверждена|аудиторское\\s+заключение",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "critical"
},
{
"step_id": "step_09_off_domain_living_chat_not_hijacked",
"title": "Off-domain chat recovers after settlement and debt context",
"question": "Ок, а пингвины умеют летать?",
"title": "Off-domain living chat is not hijacked by stale business context",
"question": "а чем капибара отличается от утки?",
"depends_on": [
"step_08_broad_business_evaluation_explicit_org"
],
"allowed_reply_types": [
"factual",
"factual_with_explanation",
"partial_coverage"
],
"semantic_tags": [
"off_domain_living_chat",
"context_boundary",
"human_answer"
"stale_replay_forbidden",
"context_boundary"
],
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_any": [
"(?i)пингвин",
"(?i)не умеют летать|не летают|большинство.*не летает"
"(?i)капибар.*утк|утк.*капибар",
"(?i)млекопита|птиц|грызун"
],
"forbidden_answer_patterns": [
"(?i)1С|контрагент|дебитор|договор|плат[её]ж|документ|Жуковк|Альтернатива",
"(?i)альтернатива|свк|контрагент|1с|поступлен|платеж|платёж|нетто",
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery"
],
"criticality": "info"
"criticality": "warning"
}
]
}

View File

@ -4317,10 +4317,11 @@ class AddressQueryService {
rowsMatched: expandedFilteredRows.length
});
const expandedFactual = (0, composeStage_1.composeFactualReply)(intent.intent, expandedFilteredRows, composeRuntimeOptions(expandedLimitFilters));
const expandedPrefix = `Период сохранен. Глубина live-выборки автоматически расширена до ${expandedPlan.limit} строк.`;
const expandedLimitations = [...filters.warnings, "query_limit_auto_expanded_for_anchor_recovery"];
const expandedReasons = [...baseReasons, "query_limit_auto_expanded_for_anchor_recovery"];
return buildFactualExecutionResult({
replyText: expandedFactual.text,
replyText: `${expandedPrefix}\n${expandedFactual.text}`,
responseType: expandedFactual.responseType,
responseSemantics: expandedFactual.semantics,
selectedRecipe: expandedSelection.selected_recipe.recipe_id,

View File

@ -582,8 +582,7 @@ function runAssistantAddressLaneResponseRuntime(input) {
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied),
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, debugWithResponseGuard)
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
};
const finalization = finalizeAddressTurnSafe({
sessionId: input.sessionId,

View File

@ -142,8 +142,7 @@ function runAssistantDeepTurnResponseRuntime(input) {
...packagingRuntime.debug,
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, packagingRuntime.debug)
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
};
const assistantItem = mcpDiscoveryResponsePolicy.applied
? {

View File

@ -362,7 +362,6 @@ async function runAssistantLivingChatRuntime(input) {
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, addressRuntimeMeta),
living_chat_script_guard_applied: livingChatScriptGuardApplied,
living_chat_script_guard_reason: livingChatScriptGuardReason,
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,

View File

@ -73,18 +73,6 @@ function requestsBusinessOverviewNextChecks(turnMeaning, graph) {
const text = normalizedTurnAndGraphText(turnMeaning, graph);
return /(?:что\s+(?:дальше\s+)?провер\w*|что\s+провер\w*\s+дальше|следующ\w*\s+шаг|в\s+первую\s+очередь\s+провер\w*|next\s+checks?|next\s+steps?)/iu.test(text);
}
function requestsShortDebtBoundaryFollowup(turnMeaning, graph) {
const text = normalizeQuestionText([
turnMeaning?.raw_message,
turnMeaning?.effective_message,
graph?.source_message,
graph?.question
].join(" "));
if (!/\u043f\u0440\u043e\u0441\u0440\u043e\u0447/iu.test(text)) {
return false;
}
return /(?:\u043a\u043e\u0440\u043e\u0442\u043a|\u043f\u043e\u0447\u0435\u043c\u0443|\u0434\u043e\u043a\u0430\u0437\p{L}*)/iu.test(text);
}
function requestsCompactCashflowAnswer(turnMeaning, graph) {
const text = normalizeQuestionText([
turnMeaning?.raw_message,
@ -914,7 +902,6 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
const debtDueDateBoundary = actionFamily === "debt_due_date_boundary" || unsupportedFamily === "debt_due_date_boundary";
const vendorRiskBoundary = actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
const inventoryReserveBoundary = actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
const shortDebtBoundaryFollowup = debtDueDateBoundary && requestsShortDebtBoundaryFollowup(turnMeaning, graph);
const compactCashflowRequested = directMoneyOnlyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
@ -1048,20 +1035,19 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) {
const rowsWithPaymentTerms = typeof dueDateAging.rows_with_payment_terms === "number" && Number.isFinite(dueDateAging.rows_with_payment_terms)
? dueDateAging.rows_with_payment_terms
: null;
const rowsWithAmount = typeof dueDateAging.rows_with_amount === "number" && Number.isFinite(dueDateAging.rows_with_amount)
? dueDateAging.rows_with_amount
: null;
const dueDateScopePrefix = organizationScope ? `по компании ${organizationScope} ` : "";
if (status === "confirmed_overdue") {
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} подтвержденная просрочка есть: ${overdueAmount ?? "сумма не распознана"} по ${dueDateAging.overdue_rows ?? "найденным"} строкам.`);
lines.push("Основа ответа: открытые расчеты 60/62/76, договорный срок оплаты и дата расчетного документа; это проверка просрочки по срокам оплаты, а не просто возраст договора.");
}
else if (status === "no_payment_terms_configured") {
if (shortDebtBoundaryFollowup) {
lines.push(`Коротко: да, доказать просрочку ${dueDateScopePrefix}на ${asOfDate} нельзя, потому что в подтвержденных данных не установлен срок оплаты.`);
lines.push(`Открытый долг${grossAmount ? ` на ${grossAmount}` : ""} виден, но без due date это задолженность без доказанной договорной просрочки.`);
return joinBusinessReplyLines(lines);
}
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} подтвержденной просрочки нет: открытые расчеты проверены${grossAmount ? ` на ${grossAmount}` : ""}, но в найденных договорах срок оплаты не установлен.`);
lines.push("Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.");
lines.push("Что проверить дальше: сроки оплаты и даты документов-оснований в договорах/расчетах; без них просрочку доказать нельзя.");
lines.push(rowsWithAmount !== null
? `Проверено строк с суммой: ${rowsWithAmount}. Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.`
: "Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.");
}
else if (status === "insufficient_due_date_basis") {
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} просрочка не подтверждена: по строкам с установленным сроком оплаты не хватило даты расчетного документа.`);

View File

@ -1,111 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_POLICY_SCHEMA_VERSION = void 0;
exports.buildAppliedMcpDiscoveryTruthPatch = buildAppliedMcpDiscoveryTruthPatch;
exports.applyAssistantMcpDiscoveryResponsePolicy = applyAssistantMcpDiscoveryResponsePolicy;
const assistantMcpDiscoveryResponseCandidate_1 = require("./assistantMcpDiscoveryResponseCandidate");
const assistantRuntimeContracts_1 = require("../types/assistantRuntimeContracts");
exports.ASSISTANT_MCP_DISCOVERY_RESPONSE_POLICY_SCHEMA_VERSION = "assistant_mcp_discovery_response_policy_v1";
function uniqueReasonCodes(values) {
const result = [];
values.forEach((value) => {
const text = toNonEmptyString(value);
if (!text) {
return;
}
const normalized = normalizeReasonCode(text);
if (normalized && !result.includes(normalized)) {
result.push(normalized);
}
});
return result;
}
function patchCoverageGateState(value, gate) {
const source = toRecordObject(value);
if (!source) {
return value;
}
const currentGate = toRecordObject(source.coverage_gate_state);
return {
...source,
coverage_gate_state: {
...(currentGate ?? {}),
coverage_status: gate.coverage_status,
evidence_grade: gate.evidence_grade,
grounding_status: gate.grounding_status,
truth_mode: gate.truth_mode,
carryover_eligibility: gate.carryover_eligibility,
reason_codes: gate.reason_codes
}
};
}
function buildAppliedMcpDiscoveryTruthPatch(policy, currentDebug) {
if (!policy.applied || !ALLOWED_CANDIDATE_STATUSES.has(policy.candidate.candidate_status)) {
return {};
}
const reasonCodes = uniqueReasonCodes([
...policy.reason_codes,
"mcp_discovery_response_policy_candidate_applied",
"mcp_discovery_response_candidate_guarded_truth_patch"
]);
const truthGate = {
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
coverage_status: "partial",
evidence_grade: "medium",
grounding_status: "partial",
truth_mode: "limited",
carryover_eligibility: "root_only",
reason_codes: reasonCodes,
source_truth_gate_status: "partial_supported",
blocked_or_limited_explanation: "mcp_discovery_guarded_bounded_answer"
};
const answerShape = {
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
answer_shape: "limited_with_reason",
reply_type: "partial_coverage",
capability_contract_id: null,
transition_contract_id: null,
may_state_confirmed_facts: true,
must_include_limitation: true,
may_power_followup: true,
required_sections: ["direct_answer", "evidence_window", "limitations"],
downgrade_only: true
};
const truthPolicy = {
schema_version: assistantRuntimeContracts_1.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
truth_gate: truthGate,
answer_shape: answerShape
};
const debug = currentDebug ?? {};
const patch = {
assistant_truth_answer_policy_v1: truthPolicy,
coverage_gate_contract: truthGate,
answer_shape_contract: answerShape,
truth_gate_contract_status: "partial_supported",
truth_mode: "limited",
carryover_eligibility: "root_only",
answer_shape: "limited_with_reason"
};
const stateTransition = patchCoverageGateState(debug.assistant_state_transition_v1, truthGate);
if (stateTransition !== debug.assistant_state_transition_v1) {
patch.assistant_state_transition_v1 = stateTransition;
}
const stateTransitionContract = patchCoverageGateState(debug.state_transition_contract, truthGate);
if (stateTransitionContract !== debug.state_transition_contract) {
patch.state_transition_contract = stateTransitionContract;
}
const runtimeContract = toRecordObject(debug.assistant_runtime_contract_v1);
if (runtimeContract) {
patch.assistant_runtime_contract_v1 = {
...runtimeContract,
truth_gate_contract_status: "partial_supported"
};
}
return patch;
}
const ALLOWED_CANDIDATE_STATUSES = new Set([
"ready_for_guarded_use",
"checked_sources_only_candidate",

View File

@ -5278,10 +5278,11 @@ export class AddressQueryService {
expandedFilteredRows,
composeRuntimeOptions(expandedLimitFilters)
);
const expandedPrefix = `Период сохранен. Глубина live-выборки автоматически расширена до ${expandedPlan.limit} строк.`;
const expandedLimitations = [...filters.warnings, "query_limit_auto_expanded_for_anchor_recovery"];
const expandedReasons = [...baseReasons, "query_limit_auto_expanded_for_anchor_recovery"];
return buildFactualExecutionResult({
replyText: expandedFactual.text,
replyText: `${expandedPrefix}\n${expandedFactual.text}`,
responseType: expandedFactual.responseType,
responseSemantics: expandedFactual.semantics,
selectedRecipe: expandedSelection.selected_recipe.recipe_id,

View File

@ -10,10 +10,7 @@ import {
import { applyAssistantCapabilityBindingResponseGuard } from "./assistantCapabilityBindingResponseGuard";
import { attachAssistantCapabilityRuntimeBinding } from "./assistantCapabilityRuntimeBindingAdapter";
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
import {
applyAssistantMcpDiscoveryResponsePolicy,
buildAppliedMcpDiscoveryTruthPatch
} from "./assistantMcpDiscoveryResponsePolicy";
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
import { attachAssistantStateTransition } from "./assistantStateTransitionRuntimeAdapter";
import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter";
@ -715,8 +712,7 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
comparison_scope_response_augmentation_v1: comparisonScopeProofReply?.audit ?? null,
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied),
...buildAppliedMcpDiscoveryTruthPatch(mcpDiscoveryResponsePolicy, debugWithResponseGuard)
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied)
};
const finalization = finalizeAddressTurnSafe({
sessionId: input.sessionId,

View File

@ -20,10 +20,7 @@ import {
finalizeAssistantDeepTurn,
type FinalizeAssistantDeepTurnInput
} from "./assistantDeepTurnFinalizeRuntimeAdapter";
import {
applyAssistantMcpDiscoveryResponsePolicy,
buildAppliedMcpDiscoveryTruthPatch
} from "./assistantMcpDiscoveryResponsePolicy";
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
import type { ClaimBoundAnchorAudit, TargetedEvidenceAcquisitionAudit } from "./assistantClaimBoundEvidence";
import type { CompanyAnchorSet } from "./companyAnchorResolver";
import type {
@ -271,11 +268,7 @@ export function runAssistantDeepTurnResponseRuntime(
...packagingRuntime.debug,
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
...buildAppliedMcpDiscoveryTruthPatch(
mcpDiscoveryResponsePolicy,
packagingRuntime.debug as unknown as Record<string, unknown>
)
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
} as AssistantDebugPayload;
const assistantItem = mcpDiscoveryResponsePolicy.applied
? {

View File

@ -8,10 +8,7 @@ import {
} from "./assistantMemoryRecapPolicy";
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
import {
applyAssistantMcpDiscoveryResponsePolicy,
buildAppliedMcpDiscoveryTruthPatch
} from "./assistantMcpDiscoveryResponsePolicy";
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
export interface AssistantLivingChatSessionScopeInput {
knownOrganizations?: unknown[];
@ -476,7 +473,6 @@ export async function runAssistantLivingChatRuntime(
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
...buildAppliedMcpDiscoveryTruthPatch(mcpDiscoveryResponsePolicy, addressRuntimeMeta),
living_chat_script_guard_applied: livingChatScriptGuardApplied,
living_chat_script_guard_reason: livingChatScriptGuardReason,
living_chat_grounding_guard_applied: livingChatGroundingGuardApplied,

View File

@ -117,22 +117,6 @@ function requestsBusinessOverviewNextChecks(
);
}
function requestsShortDebtBoundaryFollowup(
turnMeaning: Record<string, unknown> | null,
graph: Record<string, unknown> | null
): boolean {
const text = normalizeQuestionText([
turnMeaning?.raw_message,
turnMeaning?.effective_message,
graph?.source_message,
graph?.question
].join(" "));
if (!/\u043f\u0440\u043e\u0441\u0440\u043e\u0447/iu.test(text)) {
return false;
}
return /(?:\u043a\u043e\u0440\u043e\u0442\u043a|\u043f\u043e\u0447\u0435\u043c\u0443|\u0434\u043e\u043a\u0430\u0437\p{L}*)/iu.test(text);
}
function requestsCompactCashflowAnswer(
turnMeaning: Record<string, unknown> | null,
graph: Record<string, unknown> | null
@ -1102,7 +1086,6 @@ function buildCompactBusinessOverviewReply(
actionFamily === "vendor_risk_procurement_boundary" || unsupportedFamily === "vendor_risk_procurement_boundary";
const inventoryReserveBoundary =
actionFamily === "inventory_reserve_boundary" || unsupportedFamily === "inventory_reserve_liquidation_boundary";
const shortDebtBoundaryFollowup = debtDueDateBoundary && requestsShortDebtBoundaryFollowup(turnMeaning, graph);
const compactCashflowRequested = directMoneyOnlyAnswer && requestsCompactCashflowAnswer(turnMeaning, graph);
const cashflowPolarityRequested = compactCashflowRequested && requestsCashflowPolarityAnswer(turnMeaning, graph);
const directAccountingProfitRequested = requestsDirectAccountingProfitAnswer(turnMeaning, graph);
@ -1276,6 +1259,10 @@ function buildCompactBusinessOverviewReply(
typeof dueDateAging.rows_with_payment_terms === "number" && Number.isFinite(dueDateAging.rows_with_payment_terms)
? dueDateAging.rows_with_payment_terms
: null;
const rowsWithAmount =
typeof dueDateAging.rows_with_amount === "number" && Number.isFinite(dueDateAging.rows_with_amount)
? dueDateAging.rows_with_amount
: null;
const dueDateScopePrefix = organizationScope ? `по компании ${organizationScope} ` : "";
if (status === "confirmed_overdue") {
lines.push(
@ -1283,23 +1270,13 @@ function buildCompactBusinessOverviewReply(
);
lines.push("Основа ответа: открытые расчеты 60/62/76, договорный срок оплаты и дата расчетного документа; это проверка просрочки по срокам оплаты, а не просто возраст договора.");
} else if (status === "no_payment_terms_configured") {
if (shortDebtBoundaryFollowup) {
lines.push(
`Коротко: да, доказать просрочку ${dueDateScopePrefix}на ${asOfDate} нельзя, потому что в подтвержденных данных не установлен срок оплаты.`
);
lines.push(
`Открытый долг${grossAmount ? ` на ${grossAmount}` : ""} виден, но без due date это задолженность без доказанной договорной просрочки.`
);
return joinBusinessReplyLines(lines);
}
lines.push(
`Коротко: ${dueDateScopePrefix}на ${asOfDate} подтвержденной просрочки нет: открытые расчеты проверены${grossAmount ? ` на ${grossAmount}` : ""}, но в найденных договорах срок оплаты не установлен.`
);
lines.push(
"Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой."
);
lines.push(
"Что проверить дальше: сроки оплаты и даты документов-оснований в договорах/расчетах; без них просрочку доказать нельзя."
rowsWithAmount !== null
? `Проверено строк с суммой: ${rowsWithAmount}. Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.`
: "Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой."
);
} else if (status === "insufficient_due_date_basis") {
lines.push(

View File

@ -4,11 +4,6 @@ import {
type AssistantMcpDiscoveryResponseCandidateStatus
} from "./assistantMcpDiscoveryResponseCandidate";
import type { AssistantMcpDiscoveryRuntimeEntryPointContract } from "./assistantMcpDiscoveryRuntimeEntryPoint";
import {
ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
type AssistantCoverageGateState,
type AssistantTruthAnswerPolicyRuntimeContract
} from "../types/assistantRuntimeContracts";
export const ASSISTANT_MCP_DISCOVERY_RESPONSE_POLICY_SCHEMA_VERSION =
"assistant_mcp_discovery_response_policy_v1" as const;
@ -36,112 +31,6 @@ export interface AssistantMcpDiscoveryResponsePolicyResult {
reason_codes: string[];
}
function uniqueReasonCodes(values: unknown[]): string[] {
const result: string[] = [];
values.forEach((value) => {
const text = toNonEmptyString(value);
if (!text) {
return;
}
const normalized = normalizeReasonCode(text);
if (normalized && !result.includes(normalized)) {
result.push(normalized);
}
});
return result;
}
function patchCoverageGateState(value: unknown, gate: AssistantCoverageGateState): unknown {
const source = toRecordObject(value);
if (!source) {
return value;
}
const currentGate = toRecordObject(source.coverage_gate_state);
return {
...source,
coverage_gate_state: {
...(currentGate ?? {}),
coverage_status: gate.coverage_status,
evidence_grade: gate.evidence_grade,
grounding_status: gate.grounding_status,
truth_mode: gate.truth_mode,
carryover_eligibility: gate.carryover_eligibility,
reason_codes: gate.reason_codes
}
};
}
export function buildAppliedMcpDiscoveryTruthPatch(
policy: AssistantMcpDiscoveryResponsePolicyResult,
currentDebug?: Record<string, unknown> | null
): Record<string, unknown> {
if (!policy.applied || !ALLOWED_CANDIDATE_STATUSES.has(policy.candidate.candidate_status)) {
return {};
}
const reasonCodes = uniqueReasonCodes([
...policy.reason_codes,
"mcp_discovery_response_policy_candidate_applied",
"mcp_discovery_response_candidate_guarded_truth_patch"
]);
const truthGate: AssistantTruthAnswerPolicyRuntimeContract["truth_gate"] = {
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
coverage_status: "partial",
evidence_grade: "medium",
grounding_status: "partial",
truth_mode: "limited",
carryover_eligibility: "root_only",
reason_codes: reasonCodes,
source_truth_gate_status: "partial_supported",
blocked_or_limited_explanation: "mcp_discovery_guarded_bounded_answer"
};
const answerShape: AssistantTruthAnswerPolicyRuntimeContract["answer_shape"] = {
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
answer_shape: "limited_with_reason",
reply_type: "partial_coverage",
capability_contract_id: null,
transition_contract_id: null,
may_state_confirmed_facts: true,
must_include_limitation: true,
may_power_followup: true,
required_sections: ["direct_answer", "evidence_window", "limitations"],
downgrade_only: true
};
const truthPolicy: AssistantTruthAnswerPolicyRuntimeContract = {
schema_version: ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION,
policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter",
truth_gate: truthGate,
answer_shape: answerShape
};
const debug = currentDebug ?? {};
const patch: Record<string, unknown> = {
assistant_truth_answer_policy_v1: truthPolicy,
coverage_gate_contract: truthGate,
answer_shape_contract: answerShape,
truth_gate_contract_status: "partial_supported",
truth_mode: "limited",
carryover_eligibility: "root_only",
answer_shape: "limited_with_reason"
};
const stateTransition = patchCoverageGateState(debug.assistant_state_transition_v1, truthGate);
if (stateTransition !== debug.assistant_state_transition_v1) {
patch.assistant_state_transition_v1 = stateTransition;
}
const stateTransitionContract = patchCoverageGateState(debug.state_transition_contract, truthGate);
if (stateTransitionContract !== debug.state_transition_contract) {
patch.state_transition_contract = stateTransitionContract;
}
const runtimeContract = toRecordObject(debug.assistant_runtime_contract_v1);
if (runtimeContract) {
patch.assistant_runtime_contract_v1 = {
...runtimeContract,
truth_gate_contract_status: "partial_supported"
};
}
return patch;
}
const ALLOWED_CANDIDATE_STATUSES = new Set<AssistantMcpDiscoveryResponseCandidateStatus>([
"ready_for_guarded_use",
"checked_sources_only_candidate",

View File

@ -235,24 +235,6 @@ describe("assistant deep turn response runtime adapter", () => {
expect(runtime.response.assistant_reply).toContain("Discovery answer");
expect(runtime.debug?.mcp_discovery_response_applied).toBe(true);
expect(runtime.debug?.truth_gate_contract_status).toBe("partial_supported");
expect(runtime.debug?.truth_mode).toBe("limited");
expect(runtime.debug?.coverage_gate_contract).toEqual(
expect.objectContaining({
coverage_status: "partial",
evidence_grade: "medium",
grounding_status: "partial",
truth_mode: "limited",
source_truth_gate_status: "partial_supported"
})
);
expect(runtime.debug?.answer_shape_contract).toEqual(
expect.objectContaining({
answer_shape: "limited_with_reason",
reply_type: "partial_coverage",
may_state_confirmed_facts: true
})
);
expect(runFinalizeDeepTurn).toHaveBeenCalledWith(
expect.objectContaining({
assistantReply: expect.stringContaining("Discovery answer"),

View File

@ -523,116 +523,6 @@ describe("assistant MCP discovery response candidate", () => {
expect(candidate.reply_text).not.toContain("руб..");
});
it("keeps debt due-date boundary answers useful and hides row counters", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
raw_message: "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
effective_message: "Проверить просроченную дебиторку ООО Альтернатива Плюс на конец 2020.",
asked_domain_family: "business_overview",
asked_action_family: "debt_due_date_boundary",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020-12-31"
},
data_need_graph: {
business_fact_family: "business_overview",
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: {
debt_due_date_aging: {
evidence_status: "no_payment_terms_configured",
as_of_date: "2020-12-31",
gross_open_amount_human_ru: "35 472 380,36 руб.",
rows_with_amount: 46
}
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Debt boundary.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text).toContain("35 472 380,36 руб.");
expect(candidate.reply_text).toContain("срок оплаты не установлен");
expect(candidate.reply_text).toContain("нельзя честно назвать эти остатки просрочкой");
expect(candidate.reply_text).toContain("Что проверить дальше");
expect(candidate.reply_text).not.toContain("Проверено строк");
expect(candidate.reply_text).not.toContain("строк с суммой");
});
it("answers short debt boundary follow-ups directly instead of replaying the full debt packet", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({
turn_input: {
adapter_status: "ready",
turn_meaning_ref: {
raw_message: "То есть просрочку доказать нельзя, коротко почему?",
effective_message: "Коротко объяснить, почему просрочку нельзя доказать.",
asked_domain_family: "business_overview",
asked_action_family: "debt_due_date_boundary",
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020-12-31"
},
data_need_graph: {
business_fact_family: "business_overview",
question: "То есть просрочку доказать нельзя, коротко почему?",
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: {
debt_due_date_aging: {
evidence_status: "no_payment_terms_configured",
as_of_date: "2020-12-31",
gross_open_amount_human_ru: "35 472 380,36 руб.",
rows_with_amount: 46
}
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "Debt boundary.",
confirmed_lines: [],
inference_lines: [],
unknown_lines: [],
limitation_lines: [],
next_step_line: null
}
}
})
);
expect(candidate.reply_text?.split("\n")[0]).toContain("доказать просрочку");
expect(candidate.reply_text?.split("\n")[0]).toContain("нельзя");
expect(candidate.reply_text).toContain("due date");
expect(candidate.reply_text).not.toContain("Проверено строк");
expect(candidate.reply_text).not.toContain("строк с суммой");
});
it("uses a compact direct answer for business-overview top year questions", () => {
const candidate = buildAssistantMcpDiscoveryResponseCandidate(
entryPoint({

View File

@ -1,69 +1,4 @@
[
{
"generation_id": "gen-ag06152044-9f689d",
"created_at": "2026-06-15T20:44:38+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Phase108 settlement pivots and debt due-date boundary",
"count": 8,
"domain": "address_phase108_settlement_pivot_debt_boundary",
"questions": [
"Покажи документы по Жуковке 51.",
"А по нему платежи?",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
"То есть просрочку доказать нельзя, коротко почему?",
"Ок, а пингвины умеют летать?"
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260615204438_gen-ag06152044-9f689d.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": "address_query_runtime_v1",
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"autogen_personality_id": null,
"autogen_personality_prompt": null,
"source_session_id": null,
"saved_session_file": "assistant_saved_session_20260615204438_gen-ag06152044-9f689d.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "settlement pivots + debt due-date boundary + agent validation",
"architecture_phase": "assistant-stage1 phase108",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\active_domain_contract.json",
"scenario_id": null,
"semantic_tags": [
"business_overview",
"checked_negative",
"context_boundary",
"context_carryover",
"contracts_followup",
"counterparty_pronoun_resolution",
"counterparty_root",
"debt_due_date_boundary",
"document_to_payments_pivot",
"documents_by_counterparty",
"documents_followup",
"fourth_pivot",
"honest_boundary",
"human_answer",
"integrity_guard",
"off_domain_living_chat",
"payments_followup",
"period_followup_reuse",
"pivot_seed",
"second_pivot",
"short_followup",
"third_pivot",
"year_switch_after_fourth_pivot"
],
"validation_status": "accepted_domain_pack_loop",
"validated_run_dir": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"saved_after_validated_replay": true
}
},
{
"generation_id": "gen-ag06151952-5d8f12",
"created_at": "2026-06-15T19:52:40+00:00",

View File

@ -1,237 +0,0 @@
{
"saved_at": "2026-06-15T20:44:38+00:00",
"generation_id": "gen-ag06152044-9f689d",
"mode": "saved_user_sessions",
"title": "AGENT | Phase108 settlement pivots and debt due-date boundary",
"agent_run": true,
"questions": [
"Покажи документы по Жуковке 51.",
"А по нему платежи?",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
"То есть просрочку доказать нельзя, коротко почему?",
"Ок, а пингвины умеют летать?"
],
"metadata": {
"assistant_prompt_version": "address_query_runtime_v1",
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "settlement pivots + debt due-date boundary + agent validation",
"architecture_phase": "assistant-stage1 phase108",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\active_domain_contract.json",
"scenario_id": null,
"semantic_tags": [
"business_overview",
"checked_negative",
"context_boundary",
"context_carryover",
"contracts_followup",
"counterparty_pronoun_resolution",
"counterparty_root",
"debt_due_date_boundary",
"document_to_payments_pivot",
"documents_by_counterparty",
"documents_followup",
"fourth_pivot",
"honest_boundary",
"human_answer",
"integrity_guard",
"off_domain_living_chat",
"payments_followup",
"period_followup_reuse",
"pivot_seed",
"second_pivot",
"short_followup",
"third_pivot",
"year_switch_after_fourth_pivot"
],
"validation_status": "accepted_domain_pack_loop",
"validated_run_dir": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"saved_after_validated_replay": true,
"save_gate": {
"schema_version": "agent_semantic_save_gate_v1",
"validation_status": "accepted_domain_pack_loop",
"validated_run_dir": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"final_status": "accepted",
"loop_id": "phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"target_score": 88,
"iterations_ran": 1,
"quality_score": 92,
"repair_target_count": 0,
"repair_target_severity_counts": {
"P0": 0,
"P1": 0,
"P2": 0
},
"accepted_gate": true,
"effective_runtime": {
"manifest_path": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop\\effective_runtime.json",
"runner": "domain_case_loop.run-pack-loop",
"git_sha": "e9f0ef6b4129e1e30d010c8ef9272afd742629fe",
"backend_url": "http://127.0.0.1:8787",
"mcp_proxy_url": "http://127.0.0.1:6003",
"llm_provider": "local",
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
"temperature": 0.8,
"max_output_tokens": 900,
"prompt_version": "normalizer_v2_0_2",
"prompt_source": "file",
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
"prompt_registry_status": "pass"
},
"saved_after_validated_replay": true
}
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "Покажи документы по Жуковке 51.",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "А по нему платежи?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "А по нему договоры?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "А по нему документы?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "А за 2021?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "То есть просрочку доказать нельзя, коротко почему?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "Ок, а пингвины умеют летать?",
"created_at": "2026-06-15T20:44:38+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": "address_query_runtime_v1",
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "settlement pivots + debt due-date boundary + agent validation",
"architecture_phase": "assistant-stage1 phase108",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\active_domain_contract.json",
"scenario_id": null,
"semantic_tags": [
"business_overview",
"checked_negative",
"context_boundary",
"context_carryover",
"contracts_followup",
"counterparty_pronoun_resolution",
"counterparty_root",
"debt_due_date_boundary",
"document_to_payments_pivot",
"documents_by_counterparty",
"documents_followup",
"fourth_pivot",
"honest_boundary",
"human_answer",
"integrity_guard",
"off_domain_living_chat",
"payments_followup",
"period_followup_reuse",
"pivot_seed",
"second_pivot",
"short_followup",
"third_pivot",
"year_switch_after_fourth_pivot"
],
"validation_status": "accepted_domain_pack_loop",
"validated_run_dir": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"saved_after_validated_replay": true,
"save_gate": {
"schema_version": "agent_semantic_save_gate_v1",
"validation_status": "accepted_domain_pack_loop",
"validated_run_dir": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"final_status": "accepted",
"loop_id": "phase108_settlement_pivot_debt_boundary_20260615_p05_loop",
"target_score": 88,
"iterations_ran": 1,
"quality_score": 92,
"repair_target_count": 0,
"repair_target_severity_counts": {
"P0": 0,
"P1": 0,
"P2": 0
},
"accepted_gate": true,
"effective_runtime": {
"manifest_path": "artifacts\\domain_runs\\phase108_settlement_pivot_debt_boundary_20260615_p05_loop\\effective_runtime.json",
"runner": "domain_case_loop.run-pack-loop",
"git_sha": "e9f0ef6b4129e1e30d010c8ef9272afd742629fe",
"backend_url": "http://127.0.0.1:8787",
"mcp_proxy_url": "http://127.0.0.1:6003",
"llm_provider": "local",
"llm_model": "unsloth/qwen3-30b-a3b-instruct-2507",
"temperature": 0.8,
"max_output_tokens": 900,
"prompt_version": "normalizer_v2_0_2",
"prompt_source": "file",
"prompt_hash": "f36e36a5e491cd24511b380e0c0059cb01aea5e2af738fb9f01671cd291735c1",
"prompt_registry_status": "pass"
},
"saved_after_validated_replay": true
}
}
}
}

View File

@ -1,49 +0,0 @@
{
"suite_id": "assistant_saved_session_gen-ag06152044-9f689d",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-06-15T20:44:38+00:00",
"generation_id": "gen-ag06152044-9f689d",
"mode": "saved_user_sessions",
"title": "AGENT | Phase108 settlement pivots and debt due-date boundary",
"domain": "address_phase108_settlement_pivot_debt_boundary",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Phase108 settlement pivots and debt due-date boundary",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Покажи документы по Жуковке 51."
},
{
"user_message": "А по нему платежи?"
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "По ООО Альтернатива Плюс на конец 2020 можно точно понять, какая дебиторка просрочена?"
},
{
"user_message": "То есть просрочку доказать нельзя, коротко почему?"
},
{
"user_message": "Ок, а пингвины умеют летать?"
}
]
}
]
}

View File

@ -518,24 +518,12 @@ def evaluate_limited_next_action(
limited_outputs = 0
for output in outputs:
text = current_answer_text_for_output(output)
step_state_path = output.get("step_state_path")
step_state = read_json_object(step_state_path) if isinstance(step_state_path, Path) else {}
review = step_state.get("business_first_review") if isinstance(step_state.get("business_first_review"), dict) else {}
review_limited = review.get("limited_answer_detected")
if review_limited is False:
continue
limited_match = first_pattern_search(limited_patterns, text)
if review_limited is not True and not limited_match:
if not limited_match:
continue
limited_outputs += 1
next_action_present = review.get("next_action_present") is True or bool(first_pattern_search(next_action_patterns, text))
if not next_action_present:
evidence = {"path": output["repo_path"]}
if limited_match:
evidence["limited_match"] = limited_match.group(0)[:240]
else:
evidence["limited_match"] = "business_first_review.limited_answer_detected"
failures.append(evidence)
if not first_pattern_search(next_action_patterns, text):
failures.append({"path": output["repo_path"], "limited_match": limited_match.group(0)[:240]})
status = "fail" if failures else "pass"
if failures:
message = "limited answer has no next action"

View File

@ -260,10 +260,6 @@ GUARDED_INSUFFICIENCY_PRIMARY_MARKERS = (
"\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430",
"\u043d\u0435\u043b\u044c\u0437\u044f \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434",
"\u043d\u0435\u043b\u044c\u0437\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b",
"\u043d\u0435\u043b\u044c\u0437\u044f \u0434\u043e\u043a\u0430\u0437\u0430\u0442\u044c",
"\u043d\u0435 \u0434\u043e\u043a\u0430\u0437\u0430\u0442\u044c",
"\u043d\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u0441\u0440\u043e\u043a \u043e\u043f\u043b\u0430\u0442\u044b",
"\u043d\u0435\u0442 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043d\u043e\u0433\u043e \u0441\u0440\u043e\u043a\u0430 \u043e\u043f\u043b\u0430\u0442\u044b",
"\u0447\u0435\u0441\u0442\u043d\u043e \u043f\u043e\u0441\u0447\u0438\u0442\u0430\u0442\u044c \u043d\u0435\u043b\u044c\u0437\u044f",
"\u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e\u0439 \u0431\u0430\u0437\u044b",
"\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u043e\u0439 \u0441\u0435\u0431\u0435\u0441\u0442\u043e\u0438\u043c",
@ -279,9 +275,6 @@ GUARDED_INSUFFICIENCY_LIMITATION_MARKERS = (
"\u043d\u0435 \u0434\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043e\u0441\u0442\u0430\u0442\u043e\u043a",
"\u043d\u0435 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0435\u0441\u0442\u0440",
"\u0433\u0440\u0430\u043d\u0438\u0446\u0430 \u043e\u0442\u0432\u0435\u0442\u0430",
"due date",
"\u0441\u0440\u043e\u043a \u043e\u043f\u043b\u0430\u0442\u044b",
"\u0434\u043e\u0433\u043e\u0432\u043e\u0440\u043d\u043e\u0439 \u043f\u0440\u043e\u0441\u0440\u043e\u0447",
"\u0440\u0430\u0441\u0447\u0435\u0442\u043d\u0443\u044e \u0431\u0430\u0437\u0443",
"\u0440\u0430\u0441\u0447\u0451\u0442\u043d\u0443\u044e \u0431\u0430\u0437\u0443",
)
@ -2805,26 +2798,6 @@ def has_guarded_insufficiency_language(answer_text: str) -> bool:
return has_unconfirmed_marker and has_limitation_marker
def is_validated_guarded_mcp_discovery_insufficiency(state: dict[str, Any]) -> bool:
if state.get("mcp_discovery_response_applied") is not True:
return False
if str(state.get("mcp_discovery_response_candidate_status") or "").strip() != "ready_for_guarded_use":
return False
if str(state.get("mcp_discovery_route_candidate_status") or "").strip() != "ready_for_reviewed_execution":
return False
if str(state.get("mcp_discovery_execution_handoff_can_use_guarded_response") or "").casefold() not in {"true", "1"}:
return False
if str(state.get("truth_gate_contract_status") or "").strip() != "partial_supported":
return False
if str(state.get("truth_mode") or "").strip() not in GUARDED_INSUFFICIENCY_TRUTH_MODES:
return False
if str(state.get("answer_shape") or "").strip() not in GUARDED_INSUFFICIENCY_ANSWER_SHAPES:
return False
if str(state.get("evidence_grade") or "").strip() not in {"weak", "medium", "strong"}:
return False
return bool(str(state.get("assistant_text") or "").strip())
def is_validated_guarded_insufficiency_answer(
state: dict[str, Any],
execution_status: str,
@ -2835,14 +2808,13 @@ def is_validated_guarded_insufficiency_answer(
return False
if violations:
return False
mcp_discovery_guarded = is_validated_guarded_mcp_discovery_insufficiency(state)
if state.get("mcp_discovery_response_applied") is True and not mcp_discovery_guarded:
if state.get("mcp_discovery_response_applied") is True:
return False
if str(state.get("reply_type") or "").strip() not in {"factual", "factual_with_explanation", "partial_coverage"}:
return False
if not mcp_discovery_guarded and str(state.get("fallback_type") or "").strip() not in {"", "none"}:
if str(state.get("fallback_type") or "").strip() not in {"", "none"}:
return False
if not mcp_discovery_guarded and str(state.get("mcp_call_status") or "").strip() != "matched_non_empty":
if str(state.get("mcp_call_status") or "").strip() != "matched_non_empty":
return False
result_mode = str(state.get("result_mode") or "").strip()
truth_mode = str(state.get("truth_mode") or "").strip()
@ -2853,7 +2825,7 @@ def is_validated_guarded_insufficiency_answer(
or answer_shape in GUARDED_INSUFFICIENCY_ANSWER_SHAPES
or state.get("balance_confirmed") is False
)
if not (has_limited_evidence_mode or mcp_discovery_guarded):
if not has_limited_evidence_mode:
return False
if not has_guarded_insufficiency_language(str(state.get("assistant_text") or "")):
return False

View File

@ -160,65 +160,6 @@ class AgentDetectorRunnerTests(unittest.TestCase):
self.assertEqual(results["summary"]["status"], "pass")
self.assertEqual(results["results"][0]["message"], "limited answer includes a next action")
def test_limited_next_action_ignores_non_limited_off_domain_can_not_phrase(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)
artifact_dir = root / "run"
step_dir = artifact_dir / "scenarios" / "chat" / "steps" / "s01"
write_text(step_dir / "output.md", "Нет, пингвины не могут летать.")
write_json(
step_dir / "step_state.json",
{
"business_first_review": {
"limited_answer_detected": False,
"next_action_present": False,
}
},
)
registry_path = root / "detector_registry.json"
issue_catalog_path = root / "issue_catalog.json"
write_json(
registry_path,
{
"schema_version": "agent_detector_registry_v1",
"detectors": {
"limited_answer_without_next_action": {
"kind": "answer_text_required_when_limited",
"automation_level": "semi_automatic",
"description": "Limited answers need a next action.",
"issue_codes": ["business_next_step_missing"],
"inputs": ["output.md"],
"check": {
"limited_patterns": ["(?i)не могу"],
"required_next_action_patterns_any": ["(?i)что проверить дальше"],
},
}
},
},
)
write_json(
issue_catalog_path,
{
"schema_version": "agent_issue_catalog_v1",
"issues": {
"business_next_step_missing": {
"detectors": ["limited_answer_without_next_action"],
}
},
},
)
results = runner.build_detector_results(
artifact_dir,
issue_codes=["business_next_step_missing"],
registry_path=registry_path,
issue_catalog_path=issue_catalog_path,
include_default_global=False,
)
self.assertEqual(results["summary"]["status"], "pass")
self.assertEqual(results["results"][0]["message"], "answer is not limited; next action requirement not triggered")
def test_candidate_forbidden_regex_is_limited_to_evidence_path(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = Path(tmp)

View File

@ -1617,46 +1617,6 @@ class DomainCaseLoopStepStateTests(unittest.TestCase):
self.assertTrue(step_state["guarded_insufficiency_validated"])
self.assertEqual(step_state["acceptance_status"], "validated")
def test_guarded_mcp_discovery_debt_boundary_followup_validates(self) -> None:
answer_text = (
"Да, доказать просрочку по компании ООО Альтернатива Плюс на 2020-12-31 нельзя, "
"потому что в подтвержденных данных не установлен срок оплаты.\n"
"Открытый долг на 35 472 380,36 руб. виден, но без due date это задолженность "
"без доказанной договорной просрочки."
)
step_state = dcl.validate_step_contract(
{
"execution_status": "partial",
"reply_type": "partial_coverage",
"required_answer_shape": "direct_answer_first",
"required_answer_patterns_all": [
"(?i)срок.*оплат|due[- ]?date|договор",
"(?i)не установлен|не хватает|не подтвержд|не доказ",
"(?i)просроч",
"(?i)2020|2020-12-31|конец 2020",
],
"fallback_type": "partial",
"truth_gate_contract_status": "partial_supported",
"truth_mode": "limited",
"answer_shape": "limited_with_reason",
"evidence_grade": "medium",
"mcp_discovery_response_applied": True,
"mcp_discovery_response_candidate_status": "ready_for_guarded_use",
"mcp_discovery_route_candidate_status": "ready_for_reviewed_execution",
"mcp_discovery_execution_handoff_can_use_guarded_response": True,
"assistant_text": answer_text,
"actual_direct_answer": (
"Да, доказать просрочку по компании ООО Альтернатива Плюс на 2020-12-31 нельзя, "
"потому что в подтвержденных данных не установлен срок оплаты."
),
"top_non_empty_lines": answer_text.splitlines(),
}
)
self.assertNotIn("required_answer_patterns_all_missing", step_state["violated_invariants"])
self.assertTrue(step_state["guarded_insufficiency_validated"])
self.assertEqual(step_state["acceptance_status"], "validated")
def test_expected_clarification_partial_answer_validates(self) -> None:
answer_text = (
"Для рейтинга прибыльности номенклатуры нужен период.\n\n"