Compare commits
4 Commits
e9f0ef6b41
...
fa07bb1f0e
| Author | SHA1 | Date |
|---|---|---|
|
|
fa07bb1f0e | |
|
|
2873758579 | |
|
|
fa1731a5b4 | |
|
|
ee98b04706 |
|
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"schema_version": "domain_scenario_pack_v1",
|
||||
"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.",
|
||||
"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.",
|
||||
"status": "active",
|
||||
"source_of_truth_policy": {
|
||||
"purpose": "single mutable domain source for the current orchestration target",
|
||||
|
|
@ -21,25 +21,27 @@
|
|||
},
|
||||
"analysis_context": {
|
||||
"as_of_date": "2026-06-15",
|
||||
"source": "phase107_after_phase106_profit_cashflow_margin_next_step",
|
||||
"source": "phase108_after_phase107_mixed_planner_brain_dogfood",
|
||||
"known_previous_anchors": [
|
||||
"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"
|
||||
"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"
|
||||
],
|
||||
"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."
|
||||
"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."
|
||||
},
|
||||
"target_score": 88,
|
||||
"bindings": {
|
||||
"main_organization": "ООО Альтернатива Плюс",
|
||||
"control_year": "2020",
|
||||
"svk_counterparty": "Группа СВК"
|
||||
"pivot_counterparty": "Жуковка 51",
|
||||
"control_year": "2021",
|
||||
"debt_boundary_year_end": "2020-12-31"
|
||||
},
|
||||
"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": [
|
||||
|
|
@ -48,62 +50,66 @@
|
|||
"first_line_not_direct_answer",
|
||||
"top_level_scaffold_before_answer",
|
||||
"limited_answer_without_next_action",
|
||||
"counterparty_value_flow_required_surface",
|
||||
"counterparty_value_flow_profit_accounts_forbidden"
|
||||
"business_answer_too_verbose_output"
|
||||
],
|
||||
"agent_audit_expectations": {
|
||||
"semantic_answer_review_first": true,
|
||||
"direct_answer_first": true,
|
||||
"business_utility_required": true,
|
||||
"selected_counterparty_memory_required": true,
|
||||
"date_carryover_required": true,
|
||||
"documents_and_movements_are_evidence_drilldowns": true,
|
||||
"pivot_action_memory_required": true,
|
||||
"year_switch_keeps_last_action_required": true,
|
||||
"debt_due_date_boundary_must_be_honest": true,
|
||||
"off_domain_living_chat_must_not_replay_business_context": true,
|
||||
"technical_garbage_forbidden": true,
|
||||
"do_not_accept_if": [
|
||||
"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",
|
||||
"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",
|
||||
"off-domain living chat is hijacked by stale 1C business context"
|
||||
]
|
||||
},
|
||||
"scenarios": [
|
||||
{
|
||||
"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.",
|
||||
"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.",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_human_smalltalk_sanity",
|
||||
"title": "Living chat remains human and does not expose discovery internals",
|
||||
"step_id": "step_01_documents_by_counterparty",
|
||||
"title": "Open documents for the counterparty",
|
||||
"node_role": "root",
|
||||
"question": "привет, ты на связи?",
|
||||
"question": "Покажи документы по Жуковке 51.",
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"human_answer",
|
||||
"mcp_discovery_gate_sanity",
|
||||
"meta_smalltalk"
|
||||
"documents_by_counterparty",
|
||||
"counterparty_root",
|
||||
"pivot_seed",
|
||||
"integrity_guard"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)привет|на связи|готов|помочь"
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)жуковк",
|
||||
"(?i)документ|сч[её]т|акт|накладн|строк"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)mcp|runtime_|query_documents|primitive|route_candidate|capability_id|snapshot_items|answer_object"
|
||||
"(?i)route_candidate|primitive|planner_|catalog_|snapshot_items|answer_object|capability_id|mcp_discovery",
|
||||
"(?i)уточните.*контрагент|какого контрагента"
|
||||
],
|
||||
"criticality": "info"
|
||||
"criticality": "critical"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_resolve_counterparty_alias",
|
||||
"title": "Entity resolution grounds the checked 1C counterparty from a loose alias",
|
||||
"question": "найди в 1С контрагента СВК",
|
||||
"step_id": "step_02_payments_by_pronoun_followup",
|
||||
"title": "Pivot from documents to payments",
|
||||
"question": "А по нему платежи?",
|
||||
"depends_on": [
|
||||
"step_01_human_smalltalk_sanity"
|
||||
"step_01_documents_by_counterparty"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
|
|
@ -111,139 +117,29 @@
|
|||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"entity_resolution",
|
||||
"alias_grounding",
|
||||
"followup_anchor",
|
||||
"planner_catalog_alignment"
|
||||
"payments_followup",
|
||||
"counterparty_pronoun_resolution",
|
||||
"document_to_payments_pivot",
|
||||
"integrity_guard"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)свк",
|
||||
"(?i)контрагент"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)группа\\s+свк",
|
||||
"(?i)каталог|найден|наиболее вероятн"
|
||||
"(?i)жуковк|контрагент",
|
||||
"(?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_incoming_by_resolved_entity",
|
||||
"title": "Incoming value-flow follow-up reuses the resolved counterparty anchor",
|
||||
"question": "сколько получили по нему за 2020 год",
|
||||
"step_id": "step_03_contracts_after_payments_pivot",
|
||||
"title": "Pivot from payments to contracts",
|
||||
"question": "А по нему договоры?",
|
||||
"depends_on": [
|
||||
"step_02_resolve_counterparty_alias"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"entity_resolution",
|
||||
"incoming_value_flow",
|
||||
"followup_reuse",
|
||||
"date_carryover",
|
||||
"planner_catalog_alignment"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2020",
|
||||
"(?i)получил|входящ|поступ",
|
||||
"(?i)руб"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)группа\\s+свк",
|
||||
"(?i)свк"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?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_payout_switch_by_resolved_entity",
|
||||
"title": "Outgoing payment follow-up keeps the same grounded counterparty and checked year",
|
||||
"question": "а теперь сколько заплатили?",
|
||||
"depends_on": [
|
||||
"step_03_incoming_by_resolved_entity"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"entity_resolution",
|
||||
"payout_switch",
|
||||
"followup_reuse",
|
||||
"date_carryover",
|
||||
"planner_catalog_alignment"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2020",
|
||||
"(?i)заплатил|исходящ|списан|платеж|платёж",
|
||||
"(?i)руб"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)группа\\s+свк",
|
||||
"(?i)свк"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?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_net_after_payout",
|
||||
"title": "Net-flow follow-up reuses the same grounded counterparty and checked year after payout",
|
||||
"question": "а какое нетто?",
|
||||
"depends_on": [
|
||||
"step_04_payout_switch_by_resolved_entity"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"entity_resolution",
|
||||
"net_value_flow",
|
||||
"followup_reuse",
|
||||
"date_carryover",
|
||||
"planner_catalog_alignment"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2020",
|
||||
"(?i)нетто|сальдо|разниц",
|
||||
"(?i)получ",
|
||||
"(?i)заплат",
|
||||
"(?i)руб"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"12[ .\\u00a0]?093[ .\\u00a0]?465|12093465",
|
||||
"(?i)группа\\s+свк|свк"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?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_documents_after_net",
|
||||
"title": "Document evidence follow-up keeps the grounded counterparty after the net answer",
|
||||
"question": "а по документам?",
|
||||
"depends_on": [
|
||||
"step_05_net_after_payout"
|
||||
"step_02_payments_by_pronoun_followup"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
|
|
@ -251,34 +147,28 @@
|
|||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"entity_resolution",
|
||||
"document_evidence",
|
||||
"value_flow_pivot",
|
||||
"followup_reuse",
|
||||
"planner_catalog_alignment"
|
||||
"contracts_followup",
|
||||
"second_pivot",
|
||||
"counterparty_pronoun_resolution",
|
||||
"integrity_guard"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)документ|счет|счёт|накладн|акт"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)группа\\s+свк",
|
||||
"(?i)свк",
|
||||
"(?i)2020"
|
||||
"(?i)жуковк|контрагент",
|
||||
"(?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_07_movements_after_documents",
|
||||
"title": "Movement evidence follow-up keeps the grounded counterparty after the document answer",
|
||||
"question": "а по движениям?",
|
||||
"step_id": "step_04_documents_after_contracts_pivot",
|
||||
"title": "Pivot back from contracts to documents",
|
||||
"question": "А по нему документы?",
|
||||
"depends_on": [
|
||||
"step_06_documents_after_net"
|
||||
"step_03_contracts_after_payments_pivot"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
|
|
@ -286,84 +176,168 @@
|
|||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"entity_resolution",
|
||||
"movement_evidence",
|
||||
"document_pivot",
|
||||
"followup_reuse",
|
||||
"planner_catalog_alignment"
|
||||
"documents_followup",
|
||||
"third_pivot",
|
||||
"counterparty_pronoun_resolution",
|
||||
"integrity_guard"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)движени|операц|платеж|платёж|списан|поступ"
|
||||
],
|
||||
"required_answer_patterns_any": [
|
||||
"(?i)группа\\s+свк",
|
||||
"(?i)свк",
|
||||
"(?i)2020"
|
||||
"(?i)жуковк|контрагент",
|
||||
"(?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_08_broad_business_evaluation_explicit_org",
|
||||
"title": "Broad business evaluation stays grounded and bounded after counterparty drilldowns",
|
||||
"question": "Как ты оценишь деятельность компании {{bindings.main_organization}} по данным 1С?",
|
||||
"step_id": "step_05_payments_after_documents_pivot",
|
||||
"title": "Pivot back again to payments",
|
||||
"question": "А по нему платежи?",
|
||||
"depends_on": [
|
||||
"step_07_movements_after_documents"
|
||||
"step_04_documents_after_contracts_pivot"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"broad_business_evaluation",
|
||||
"grounded_summary",
|
||||
"context_switch_from_counterparty_to_company"
|
||||
"payments_followup",
|
||||
"fourth_pivot",
|
||||
"counterparty_pronoun_resolution",
|
||||
"integrity_guard"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)альтернатива",
|
||||
"(?i)ограниченн|проверенн|не\\s+аудит|не\\s+аудитор",
|
||||
"(?i)1с|подтвержд",
|
||||
"(?i)денежн|долг|ндс|контрагент|операц"
|
||||
"(?i)жуковк|контрагент",
|
||||
"(?i)плат[её]ж|операц|банк|поступлен|списан"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)^\\s*коротко:",
|
||||
"(?i)чистая\\s+прибыль\\s+подтверждена|аудиторское\\s+заключение",
|
||||
"(?i)уточните.*контрагент|какого контрагента",
|
||||
"(?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?",
|
||||
"depends_on": [
|
||||
"step_05_payments_after_documents_pivot"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"year_switch_after_fourth_pivot",
|
||||
"payments_followup",
|
||||
"period_followup_reuse",
|
||||
"integrity_guard"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)2021",
|
||||
"(?i)жуковк|контрагент",
|
||||
"(?i)плат[её]ж|операц|банк|поступлен|списан"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните.*контрагент|какого контрагента|за какой год",
|
||||
"(?i)метадан|схем|объект[а-я]* 1с",
|
||||
"(?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 можно точно понять, какая дебиторка просрочена?",
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"business_overview",
|
||||
"debt_due_date_boundary",
|
||||
"checked_negative",
|
||||
"honest_boundary"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)дебитор|долг|открыт.*расчет|остат",
|
||||
"(?i)2020|2020-12-31|конец 2020",
|
||||
"(?i)срок.*оплат|due[- ]?date|просроч",
|
||||
"(?i)не установлен|не подтвержд|не доказ|нет подтвержденной просроч",
|
||||
"(?i)1С|проверенн|найденн|подтвержд"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)точно подтверждаю.*просроч",
|
||||
"(?i)просроч.*подтвержден.*без.*срок",
|
||||
"(?i)route_candidate|query_movements|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": "То есть просрочку доказать нельзя, коротко почему?",
|
||||
"depends_on": [
|
||||
"step_07_debt_due_date_boundary"
|
||||
],
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"context_carryover",
|
||||
"debt_due_date_boundary",
|
||||
"checked_negative",
|
||||
"short_followup"
|
||||
],
|
||||
"required_answer_shape": "direct_answer_first",
|
||||
"required_answer_patterns_all": [
|
||||
"(?i)срок.*оплат|due[- ]?date|договор",
|
||||
"(?i)не установлен|не хватает|не подтвержд|не доказ",
|
||||
"(?i)просроч",
|
||||
"(?i)2020|2020-12-31|конец 2020"
|
||||
],
|
||||
"forbidden_answer_patterns": [
|
||||
"(?i)уточните организац|какую компанию",
|
||||
"(?i)точно подтверждаю.*просроч",
|
||||
"(?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 living chat is not hijacked by stale business context",
|
||||
"question": "а чем капибара отличается от утки?",
|
||||
"depends_on": [
|
||||
"step_08_broad_business_evaluation_explicit_org"
|
||||
],
|
||||
"title": "Off-domain chat recovers after settlement and debt context",
|
||||
"question": "Ок, а пингвины умеют летать?",
|
||||
"allowed_reply_types": [
|
||||
"factual",
|
||||
"factual_with_explanation",
|
||||
"partial_coverage"
|
||||
],
|
||||
"semantic_tags": [
|
||||
"off_domain_living_chat",
|
||||
"stale_replay_forbidden",
|
||||
"context_boundary"
|
||||
"context_boundary",
|
||||
"human_answer"
|
||||
],
|
||||
"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": "warning"
|
||||
"criticality": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4317,11 +4317,10 @@ 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: `${expandedPrefix}\n${expandedFactual.text}`,
|
||||
replyText: expandedFactual.text,
|
||||
responseType: expandedFactual.responseType,
|
||||
responseSemantics: expandedFactual.semantics,
|
||||
selectedRecipe: expandedSelection.selected_recipe.recipe_id,
|
||||
|
|
|
|||
|
|
@ -582,7 +582,8 @@ 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)
|
||||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied),
|
||||
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, debugWithResponseGuard)
|
||||
};
|
||||
const finalization = finalizeAddressTurnSafe({
|
||||
sessionId: input.sessionId,
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ function runAssistantDeepTurnResponseRuntime(input) {
|
|||
...packagingRuntime.debug,
|
||||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
...(0, assistantMcpDiscoveryResponsePolicy_1.buildAppliedMcpDiscoveryTruthPatch)(mcpDiscoveryResponsePolicy, packagingRuntime.debug)
|
||||
};
|
||||
const assistantItem = mcpDiscoveryResponsePolicy.applied
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -362,6 +362,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -73,6 +73,18 @@ 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,
|
||||
|
|
@ -902,6 +914,7 @@ 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);
|
||||
|
|
@ -1035,19 +1048,20 @@ 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(rowsWithAmount !== null
|
||||
? `Проверено строк с суммой: ${rowsWithAmount}. Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.`
|
||||
: "Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.");
|
||||
lines.push("Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.");
|
||||
lines.push("Что проверить дальше: сроки оплаты и даты документов-оснований в договорах/расчетах; без них просрочку доказать нельзя.");
|
||||
}
|
||||
else if (status === "insufficient_due_date_basis") {
|
||||
lines.push(`Коротко: ${dueDateScopePrefix}на ${asOfDate} просрочка не подтверждена: по строкам с установленным сроком оплаты не хватило даты расчетного документа.`);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,111 @@
|
|||
"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",
|
||||
|
|
|
|||
|
|
@ -5278,11 +5278,10 @@ 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: `${expandedPrefix}\n${expandedFactual.text}`,
|
||||
replyText: expandedFactual.text,
|
||||
responseType: expandedFactual.responseType,
|
||||
responseSemantics: expandedFactual.semantics,
|
||||
selectedRecipe: expandedSelection.selected_recipe.recipe_id,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ import {
|
|||
import { applyAssistantCapabilityBindingResponseGuard } from "./assistantCapabilityBindingResponseGuard";
|
||||
import { attachAssistantCapabilityRuntimeBinding } from "./assistantCapabilityRuntimeBindingAdapter";
|
||||
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import {
|
||||
applyAssistantMcpDiscoveryResponsePolicy,
|
||||
buildAppliedMcpDiscoveryTruthPatch
|
||||
} from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||
import { attachAssistantStateTransition } from "./assistantStateTransitionRuntimeAdapter";
|
||||
import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter";
|
||||
|
|
@ -712,7 +715,8 @@ 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)
|
||||
...buildAppliedMcpDiscoveryRoutePatch(debugWithResponseGuard, mcpDiscoveryResponsePolicy.applied),
|
||||
...buildAppliedMcpDiscoveryTruthPatch(mcpDiscoveryResponsePolicy, debugWithResponseGuard)
|
||||
};
|
||||
const finalization = finalizeAddressTurnSafe({
|
||||
sessionId: input.sessionId,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,10 @@ import {
|
|||
finalizeAssistantDeepTurn,
|
||||
type FinalizeAssistantDeepTurnInput
|
||||
} from "./assistantDeepTurnFinalizeRuntimeAdapter";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import {
|
||||
applyAssistantMcpDiscoveryResponsePolicy,
|
||||
buildAppliedMcpDiscoveryTruthPatch
|
||||
} from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import type { ClaimBoundAnchorAudit, TargetedEvidenceAcquisitionAudit } from "./assistantClaimBoundEvidence";
|
||||
import type { CompanyAnchorSet } from "./companyAnchorResolver";
|
||||
import type {
|
||||
|
|
@ -268,7 +271,11 @@ export function runAssistantDeepTurnResponseRuntime(
|
|||
...packagingRuntime.debug,
|
||||
mcp_discovery_response_policy_v1: mcpDiscoveryResponsePolicy,
|
||||
mcp_discovery_response_candidate_v1: mcpDiscoveryResponsePolicy.candidate,
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied
|
||||
mcp_discovery_response_applied: mcpDiscoveryResponsePolicy.applied,
|
||||
...buildAppliedMcpDiscoveryTruthPatch(
|
||||
mcpDiscoveryResponsePolicy,
|
||||
packagingRuntime.debug as unknown as Record<string, unknown>
|
||||
)
|
||||
} as AssistantDebugPayload;
|
||||
const assistantItem = mcpDiscoveryResponsePolicy.applied
|
||||
? {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import {
|
|||
} from "./assistantMemoryRecapPolicy";
|
||||
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
|
||||
import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment";
|
||||
import { applyAssistantMcpDiscoveryResponsePolicy } from "./assistantMcpDiscoveryResponsePolicy";
|
||||
import {
|
||||
applyAssistantMcpDiscoveryResponsePolicy,
|
||||
buildAppliedMcpDiscoveryTruthPatch
|
||||
} from "./assistantMcpDiscoveryResponsePolicy";
|
||||
|
||||
export interface AssistantLivingChatSessionScopeInput {
|
||||
knownOrganizations?: unknown[];
|
||||
|
|
@ -473,6 +476,7 @@ 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,
|
||||
|
|
|
|||
|
|
@ -117,6 +117,22 @@ 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
|
||||
|
|
@ -1086,6 +1102,7 @@ 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);
|
||||
|
|
@ -1259,10 +1276,6 @@ 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(
|
||||
|
|
@ -1270,13 +1283,23 @@ 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(
|
||||
rowsWithAmount !== null
|
||||
? `Проверено строк с суммой: ${rowsWithAmount}. Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой.`
|
||||
: "Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой."
|
||||
"Без установленного срока оплаты нельзя честно назвать эти остатки просрочкой."
|
||||
);
|
||||
lines.push(
|
||||
"Что проверить дальше: сроки оплаты и даты документов-оснований в договорах/расчетах; без них просрочку доказать нельзя."
|
||||
);
|
||||
} else if (status === "insufficient_due_date_basis") {
|
||||
lines.push(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@ 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;
|
||||
|
|
@ -31,6 +36,112 @@ 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",
|
||||
|
|
|
|||
|
|
@ -235,6 +235,24 @@ 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"),
|
||||
|
|
|
|||
|
|
@ -523,6 +523,116 @@ 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({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,69 @@
|
|||
[
|
||||
{
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"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": "Ок, а пингвины умеют летать?"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -518,12 +518,24 @@ 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 not limited_match:
|
||||
if review_limited is not True and not limited_match:
|
||||
continue
|
||||
limited_outputs += 1
|
||||
if not first_pattern_search(next_action_patterns, text):
|
||||
failures.append({"path": output["repo_path"], "limited_match": limited_match.group(0)[:240]})
|
||||
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)
|
||||
status = "fail" if failures else "pass"
|
||||
if failures:
|
||||
message = "limited answer has no next action"
|
||||
|
|
|
|||
|
|
@ -260,6 +260,10 @@ 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",
|
||||
|
|
@ -275,6 +279,9 @@ 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",
|
||||
)
|
||||
|
|
@ -2798,6 +2805,26 @@ 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,
|
||||
|
|
@ -2808,13 +2835,14 @@ def is_validated_guarded_insufficiency_answer(
|
|||
return False
|
||||
if violations:
|
||||
return False
|
||||
if state.get("mcp_discovery_response_applied") is True:
|
||||
mcp_discovery_guarded = is_validated_guarded_mcp_discovery_insufficiency(state)
|
||||
if state.get("mcp_discovery_response_applied") is True and not mcp_discovery_guarded:
|
||||
return False
|
||||
if str(state.get("reply_type") or "").strip() not in {"factual", "factual_with_explanation", "partial_coverage"}:
|
||||
return False
|
||||
if str(state.get("fallback_type") or "").strip() not in {"", "none"}:
|
||||
if not mcp_discovery_guarded and str(state.get("fallback_type") or "").strip() not in {"", "none"}:
|
||||
return False
|
||||
if str(state.get("mcp_call_status") or "").strip() != "matched_non_empty":
|
||||
if not mcp_discovery_guarded and 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()
|
||||
|
|
@ -2825,7 +2853,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:
|
||||
if not (has_limited_evidence_mode or mcp_discovery_guarded):
|
||||
return False
|
||||
if not has_guarded_insufficiency_language(str(state.get("assistant_text") or "")):
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -160,6 +160,65 @@ 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)
|
||||
|
|
|
|||
|
|
@ -1617,6 +1617,46 @@ 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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue