Закрепить phase83 carryover и движения ассистента

This commit is contained in:
2026-06-03 16:34:21 +03:00
parent 5ed25bad81
commit 4f078d649e
15 changed files with 665 additions and 61 deletions
@@ -12,6 +12,7 @@ exports.readAssistantMcpDiscoveryLoopProvidedAxes = readAssistantMcpDiscoveryLoo
exports.readAssistantMcpDiscoveryLoopAskedDomainFamily = readAssistantMcpDiscoveryLoopAskedDomainFamily;
exports.readAssistantMcpDiscoveryLoopAskedActionFamily = readAssistantMcpDiscoveryLoopAskedActionFamily;
exports.readAssistantMcpDiscoveryLoopUnsupportedFamily = readAssistantMcpDiscoveryLoopUnsupportedFamily;
exports.readAssistantMcpDiscoveryLoopExplicitDateScope = readAssistantMcpDiscoveryLoopExplicitDateScope;
exports.readAssistantMcpDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint;
exports.readAssistantMcpDiscoveryLoopSubjectResolutionOptional = readAssistantMcpDiscoveryLoopSubjectResolutionOptional;
exports.readAssistantMcpDiscoveryMetadataRouteFamily = readAssistantMcpDiscoveryMetadataRouteFamily;
@@ -197,6 +198,11 @@ function readAssistantMcpDiscoveryLoopAskedActionFamily(debug, toNonEmptyString
function readAssistantMcpDiscoveryLoopUnsupportedFamily(debug, toNonEmptyString = fallbackToNonEmptyString) {
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
}
function readAssistantMcpDiscoveryLoopExplicitDateScope(debug, toNonEmptyString = fallbackToNonEmptyString) {
return (toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.explicit_date_scope) ??
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.explicit_date_scope) ??
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.explicit_date_scope));
}
function readAssistantMcpDiscoveryLoopMetadataScopeHint(debug, toNonEmptyString = fallbackToNonEmptyString) {
return (toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.metadata_scope_hint) ??
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.metadata_scope_hint) ??
@@ -239,6 +239,26 @@ function isOpenScopeValueFlowRanking(entryPoint) {
const subjectCandidates = Array.isArray(graph?.subject_candidates) ? graph.subject_candidates : [];
return businessFactFamily === "value_flow" && subjectCandidates.length === 0 && Boolean(toNonEmptyString(graph?.ranking_need));
}
function readDiscoverySelectedChainId(entryPoint) {
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const routeCandidate = toRecordObject(bridge?.route_candidate);
return (toNonEmptyString(routeCandidate?.selected_chain_id) ??
toNonEmptyString(bridge?.selected_chain_id) ??
toNonEmptyString(pilot?.selected_chain_id));
}
function isValueFlowRankingDiscoveryTurn(entryPoint) {
const graph = readDiscoveryDataNeedGraph(entryPoint);
const bridge = toRecordObject(entryPoint?.bridge);
const routeCandidate = toRecordObject(bridge?.route_candidate);
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
const graphReasonCodes = readStringArray(graph?.reason_codes);
return Boolean(toNonEmptyString(graph?.business_fact_family) === "value_flow" &&
(selectedChainId === "value_flow_ranking" ||
toNonEmptyString(routeCandidate?.nearest_catalog_chain_template) === "value_flow_ranking" ||
toNonEmptyString(graph?.ranking_need) === "top_desc" ||
graphReasonCodes.includes("data_need_graph_ranking_top_desc")));
}
function readTruthAnswerShape(input) {
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
if (directShape) {
@@ -296,6 +316,54 @@ function hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(inpu
capabilityId?.includes("inventory_")));
return asksForMovements && staleInventoryItemClarification;
}
function hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(input, entryPoint, candidate) {
if (!hasCurrentClarificationRequiredAddressReply(input)) {
return false;
}
if (candidate.candidate_status !== "ready_for_guarded_use") {
return false;
}
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
return false;
}
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
return false;
}
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
const graph = readDiscoveryDataNeedGraph(entryPoint);
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const handoff = toRecordObject(bridge?.execution_handoff);
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
const asksForMovements = Boolean(selectedChainId === "movement_evidence" ||
discoveryFamily === "movement_evidence" ||
askedDomain === "movements" ||
askedAction === "list_movements");
const truthAnswerShape = readTruthAnswerShape(input);
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const capabilityId = toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
toNonEmptyString(truthAnswerShape?.capability_contract_id);
const missingFilters = [
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
];
const staleInventoryItemClarification = Boolean(missingFilters.includes("item") &&
(detectedIntent?.startsWith("inventory_") ||
selectedRecipe?.includes("inventory_") ||
capabilityId?.includes("inventory_")));
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
const boundedMovementAnswerAllowed = bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
return asksForMovements && staleInventoryItemClarification && mcpExecutionPerformed && boundedMovementAnswerAllowed;
}
function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, entryPoint, candidate) {
if (!hasCurrentClarificationRequiredAddressReply(input)) {
return false;
@@ -313,10 +381,7 @@ function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, en
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const handoff = toRecordObject(bridge?.execution_handoff);
const routeCandidate = toRecordObject(bridge?.route_candidate);
const selectedChainId = toNonEmptyString(routeCandidate?.selected_chain_id) ??
toNonEmptyString(bridge?.selected_chain_id) ??
toNonEmptyString(pilot?.selected_chain_id);
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
const pilotScope = toNonEmptyString(pilot?.pilot_scope);
const actionFamily = toNonEmptyString(graph?.action_family);
const comparisonNeed = toNonEmptyString(graph?.comparison_need);
@@ -329,6 +394,70 @@ function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, en
(handoff?.can_use_guarded_response === true || toNonEmptyString(bridge?.bridge_status) === "answer_draft_ready");
return valueFlowComparison && mcpExecutionPerformed && guardedBusinessAnswerAllowed;
}
function hasCounterpartyRankingQuestionSignal(entryPoint) {
const graph = readDiscoveryDataNeedGraph(entryPoint);
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const rawMessage = toNonEmptyString(turnMeaning?.raw_message) ?? "";
const effectiveMessage = toNonEmptyString(turnMeaning?.effective_message) ?? "";
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
const text = `${rawMessage}\n${effectiveMessage}`.toLowerCase();
return Boolean(isValueFlowRankingDiscoveryTurn(entryPoint) &&
(askedDomain === "counterparty_value" || askedDomain === "counterparty") &&
(askedAction === "turnover" || askedAction === "counterparty_value_or_turnover") &&
(/\bкто\b/u.test(text) ||
text.includes("контрагент") ||
text.includes("клиент") ||
text.includes("покупател") ||
text.includes("заказчик") ||
text.includes("принес") ||
text.includes("принёс") ||
text.includes("больше всего") ||
toNonEmptyString(graph?.ranking_need) === "top_desc"));
}
function hasWrongPeriodRankingCurrentReply(input) {
const currentReply = String(input.currentReply ?? "").toLowerCase();
return (currentReply.includes("самый доходный год") ||
currentReply.includes("топ-1 лет") ||
currentReply.includes("топ-5 лет") ||
currentReply.includes("топ-10 лет") ||
/топ-\d+\s+лет/u.test(currentReply) ||
currentReply.includes("лет по сумме поступлений") ||
currentReply.includes("год по подтвержденным поступлениям"));
}
function hasConfirmedValueFlowRankingEvidence(entryPoint) {
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const handoff = toRecordObject(bridge?.execution_handoff);
const evidence = toRecordObject(pilot?.evidence);
const confirmedFacts = readStringArray(evidence?.confirmed_facts);
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
const businessAnswerAllowed = bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
const confirmedRankingEvidence = Boolean(toNonEmptyString(evidence?.evidence_status) === "confirmed" ||
toNonEmptyString(evidence?.answer_permission) === "confirmed_answer" ||
confirmedFacts.some((fact) => /ranked by counterparty|рейтинг.*контрагент/iu.test(fact)));
return mcpExecutionPerformed && businessAnswerAllowed && confirmedRankingEvidence;
}
function hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(input, entryPoint, candidate) {
if (candidate.candidate_status !== "ready_for_guarded_use") {
return false;
}
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
return false;
}
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
return false;
}
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
return Boolean(hasCounterpartyRankingQuestionSignal(entryPoint) &&
hasWrongPeriodRankingCurrentReply(input) &&
hasConfirmedValueFlowRankingEvidence(entryPoint));
}
function hasEffectivelyFactualAddressReply(input) {
if (toNonEmptyString(input.currentReplyType) === "factual") {
return true;
@@ -804,9 +933,17 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
const currentClarificationRequiredAddressReply = hasCurrentClarificationRequiredAddressReply(input);
const discoveryClarificationCandidatePriority = hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(input, entryPoint, candidate);
const groundedValueFlowCandidatePriority = hasGroundedValueFlowCandidatePriorityOverCurrentClarification(input, entryPoint, candidate);
const groundedMovementCandidatePriority = hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(input, entryPoint, candidate);
const groundedValueFlowRankingCandidatePriority = hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(input, entryPoint, candidate);
const currentClarificationProtectsCurrentReply = currentClarificationRequiredAddressReply &&
!discoveryClarificationCandidatePriority &&
!groundedValueFlowCandidatePriority;
!groundedValueFlowCandidatePriority &&
!groundedMovementCandidatePriority &&
!groundedValueFlowRankingCandidatePriority;
const alignedFactualAddressReplyProtectsCurrent = alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
const matchedFactualAddressContinuationTargetProtectsCurrent = matchedFactualAddressContinuationTarget && !groundedValueFlowRankingCandidatePriority;
const fullConfirmedFactualAddressReplyProtectsCurrent = fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
const exactMatchedFactualAddressReplyProtectsCurrent = exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
const exactBankOperationsProtectsCurrent = exactBankOperationsAddressReply &&
!semanticConflictWithDiscoveryTurnMeaning &&
!valueFlowActionConflictWithDiscoveryTurnMeaning;
@@ -849,6 +986,12 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
if (groundedValueFlowCandidatePriority) {
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification");
}
if (groundedMovementCandidatePriority) {
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_movement_candidate_priority_over_inventory_item_clarification");
}
if (groundedValueFlowRankingCandidatePriority) {
pushReason(reasonCodes, "mcp_discovery_response_policy_grounded_value_flow_ranking_candidate_priority_over_current_exact_reply");
}
if (openScopeValueFlowDiscoveryPriority) {
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
}
@@ -911,11 +1054,11 @@ function applyAssistantMcpDiscoveryResponsePolicy(input) {
}
const canApply = Boolean(entryPoint) &&
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate || discoveryReadyAddressCandidate) &&
!alignedFactualAddressReply &&
!matchedFactualAddressContinuationTarget &&
!alignedFactualAddressReplyProtectsCurrent &&
!matchedFactualAddressContinuationTargetProtectsCurrent &&
!matchedFactualSuggestedIntentPivotTarget &&
!fullConfirmedFactualAddressReply &&
!exactMatchedFactualAddressReply &&
!fullConfirmedFactualAddressReplyProtectsCurrent &&
!exactMatchedFactualAddressReplyProtectsCurrent &&
!runtimeAdjustedExactReply &&
!runtimeMatchedExactReply &&
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
@@ -541,6 +541,7 @@ function collectFollowupDiscoverySeed(followupContext) {
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
const loopExplicitDateScope = toNonEmptyString(followupContext?.previous_discovery_loop_explicit_date_scope);
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
const loopSubjectResolutionOptional = followupContext?.previous_discovery_loop_subject_resolution_optional === true;
const previousIntent = toNonEmptyString(followupContext?.target_intent) ?? toNonEmptyString(followupContext?.previous_intent);
@@ -592,7 +593,7 @@ function collectFollowupDiscoverySeed(followupContext) {
const loopProvidedAllTimeScope = loopProvidedAxes.includes("all_time_scope");
const dateScope = loopProvidedAllTimeScope
? "all_time_scope"
: collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
: loopExplicitDateScope ?? collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
return {
pilotScope: effectivePilotScope,
domain: mapped.domain,
@@ -1011,6 +1011,7 @@ function createAssistantTransitionPolicy(deps) {
const sourceDiscoveryLoopAskedDomainFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedDomainFamily)(carryoverSourceDebug, deps.toNonEmptyString);
const sourceDiscoveryLoopAskedActionFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopAskedActionFamily)(carryoverSourceDebug, deps.toNonEmptyString);
const sourceDiscoveryLoopUnsupportedFamily = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopUnsupportedFamily)(carryoverSourceDebug, deps.toNonEmptyString);
const sourceDiscoveryLoopExplicitDateScope = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopExplicitDateScope)(carryoverSourceDebug, deps.toNonEmptyString);
const sourceDiscoveryLoopMetadataScopeHint = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopMetadataScopeHint)(carryoverSourceDebug, deps.toNonEmptyString);
const sourceDiscoveryLoopSubjectResolutionOptional = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryLoopSubjectResolutionOptional)(carryoverSourceDebug);
const sourceDiscoveryRankingNeed = (0, assistantContinuityPolicy_1.readAssistantMcpDiscoveryRankingNeed)(carryoverSourceDebug, deps.toNonEmptyString);
@@ -1346,6 +1347,7 @@ function createAssistantTransitionPolicy(deps) {
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
previous_discovery_loop_explicit_date_scope: sourceDiscoveryLoopExplicitDateScope ?? undefined,
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
previous_discovery_loop_subject_resolution_optional: sourceDiscoveryLoopSubjectResolutionOptional || undefined,
previous_discovery_ranking_need: sourceDiscoveryRankingNeed ?? undefined,
@@ -330,6 +330,17 @@ export function readAssistantMcpDiscoveryLoopUnsupportedFamily(
return toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.unsupported_but_understood_family);
}
export function readAssistantMcpDiscoveryLoopExplicitDateScope(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
): string | null {
return (
toNonEmptyString(readAssistantMcpDiscoveryLoopState(debug)?.explicit_date_scope) ??
toNonEmptyString(readAssistantMcpDiscoveryTurnMeaning(debug)?.explicit_date_scope) ??
toNonEmptyString(readAssistantMcpDiscoveryDataNeedGraph(debug)?.explicit_date_scope)
);
}
export function readAssistantMcpDiscoveryLoopMetadataScopeHint(
debug: Record<string, unknown> | null,
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
@@ -358,6 +358,36 @@ function isOpenScopeValueFlowRanking(
return businessFactFamily === "value_flow" && subjectCandidates.length === 0 && Boolean(toNonEmptyString(graph?.ranking_need));
}
function readDiscoverySelectedChainId(
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): string | null {
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const routeCandidate = toRecordObject(bridge?.route_candidate);
return (
toNonEmptyString(routeCandidate?.selected_chain_id) ??
toNonEmptyString(bridge?.selected_chain_id) ??
toNonEmptyString(pilot?.selected_chain_id)
);
}
function isValueFlowRankingDiscoveryTurn(
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
const graph = readDiscoveryDataNeedGraph(entryPoint);
const bridge = toRecordObject(entryPoint?.bridge);
const routeCandidate = toRecordObject(bridge?.route_candidate);
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
const graphReasonCodes = readStringArray(graph?.reason_codes);
return Boolean(
toNonEmptyString(graph?.business_fact_family) === "value_flow" &&
(selectedChainId === "value_flow_ranking" ||
toNonEmptyString(routeCandidate?.nearest_catalog_chain_template) === "value_flow_ranking" ||
toNonEmptyString(graph?.ranking_need) === "top_desc" ||
graphReasonCodes.includes("data_need_graph_ranking_top_desc"))
);
}
function readTruthAnswerShape(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): Record<string, unknown> | null {
const directShape = toRecordObject(input.addressRuntimeMeta?.answer_shape_contract);
if (directShape) {
@@ -433,6 +463,69 @@ function hasDiscoveryClarificationCandidatePriorityOverCurrentClarification(
return asksForMovements && staleInventoryItemClarification;
}
function hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
candidate: AssistantMcpDiscoveryResponseCandidateContract
): boolean {
if (!hasCurrentClarificationRequiredAddressReply(input)) {
return false;
}
if (candidate.candidate_status !== "ready_for_guarded_use") {
return false;
}
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
return false;
}
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
return false;
}
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
const graph = readDiscoveryDataNeedGraph(entryPoint);
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const handoff = toRecordObject(bridge?.execution_handoff);
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
const discoveryFamily = toNonEmptyString(graph?.business_fact_family);
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
const askedAction =
toNonEmptyString(turnMeaning?.asked_action_family) ?? toNonEmptyString(graph?.action_family);
const asksForMovements = Boolean(
selectedChainId === "movement_evidence" ||
discoveryFamily === "movement_evidence" ||
askedDomain === "movements" ||
askedAction === "list_movements"
);
const truthAnswerShape = readTruthAnswerShape(input);
const detectedIntent = toNonEmptyString(input.addressRuntimeMeta?.detected_intent);
const selectedRecipe = toNonEmptyString(input.addressRuntimeMeta?.selected_recipe);
const capabilityId =
toNonEmptyString(input.addressRuntimeMeta?.capability_id) ??
toNonEmptyString(input.addressRuntimeMeta?.capability_contract_id) ??
toNonEmptyString(truthAnswerShape?.capability_contract_id);
const missingFilters = [
...readStringArray(input.addressRuntimeMeta?.missing_required_filters),
...readStringArray(input.addressRuntimeMeta?.missing_anchors),
...readStringArray(toRecordObject(input.addressRuntimeMeta?.capability_binding_contract)?.missing_anchors)
];
const staleInventoryItemClarification = Boolean(
missingFilters.includes("item") &&
(detectedIntent?.startsWith("inventory_") ||
selectedRecipe?.includes("inventory_") ||
capabilityId?.includes("inventory_"))
);
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
const boundedMovementAnswerAllowed =
bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
return asksForMovements && staleInventoryItemClarification && mcpExecutionPerformed && boundedMovementAnswerAllowed;
}
function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
@@ -455,11 +548,7 @@ function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const handoff = toRecordObject(bridge?.execution_handoff);
const routeCandidate = toRecordObject(bridge?.route_candidate);
const selectedChainId =
toNonEmptyString(routeCandidate?.selected_chain_id) ??
toNonEmptyString(bridge?.selected_chain_id) ??
toNonEmptyString(pilot?.selected_chain_id);
const selectedChainId = readDiscoverySelectedChainId(entryPoint);
const pilotScope = toNonEmptyString(pilot?.pilot_scope);
const actionFamily = toNonEmptyString(graph?.action_family);
const comparisonNeed = toNonEmptyString(graph?.comparison_need);
@@ -477,6 +566,91 @@ function hasGroundedValueFlowCandidatePriorityOverCurrentClarification(
return valueFlowComparison && mcpExecutionPerformed && guardedBusinessAnswerAllowed;
}
function hasCounterpartyRankingQuestionSignal(
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
const graph = readDiscoveryDataNeedGraph(entryPoint);
const turnMeaning = readDiscoveryTurnMeaning(entryPoint);
const rawMessage = toNonEmptyString(turnMeaning?.raw_message) ?? "";
const effectiveMessage = toNonEmptyString(turnMeaning?.effective_message) ?? "";
const askedDomain = toNonEmptyString(turnMeaning?.asked_domain_family);
const askedAction = toNonEmptyString(turnMeaning?.asked_action_family);
const text = `${rawMessage}\n${effectiveMessage}`.toLowerCase();
return Boolean(
isValueFlowRankingDiscoveryTurn(entryPoint) &&
(askedDomain === "counterparty_value" || askedDomain === "counterparty") &&
(askedAction === "turnover" || askedAction === "counterparty_value_or_turnover") &&
(/\bкто\b/u.test(text) ||
text.includes("контрагент") ||
text.includes("клиент") ||
text.includes("покупател") ||
text.includes("заказчик") ||
text.includes("принес") ||
text.includes("принёс") ||
text.includes("больше всего") ||
toNonEmptyString(graph?.ranking_need) === "top_desc")
);
}
function hasWrongPeriodRankingCurrentReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
const currentReply = String(input.currentReply ?? "").toLowerCase();
return (
currentReply.includes("самый доходный год") ||
currentReply.includes("топ-1 лет") ||
currentReply.includes("топ-5 лет") ||
currentReply.includes("топ-10 лет") ||
/топ-\d+\s+лет/u.test(currentReply) ||
currentReply.includes("лет по сумме поступлений") ||
currentReply.includes("год по подтвержденным поступлениям")
);
}
function hasConfirmedValueFlowRankingEvidence(
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null
): boolean {
const bridge = toRecordObject(entryPoint?.bridge);
const pilot = toRecordObject(bridge?.pilot);
const handoff = toRecordObject(bridge?.execution_handoff);
const evidence = toRecordObject(pilot?.evidence);
const confirmedFacts = readStringArray(evidence?.confirmed_facts);
const mcpExecutionPerformed = handoff?.mcp_execution_performed === true || pilot?.mcp_execution_performed === true;
const businessAnswerAllowed =
bridge?.user_facing_response_allowed === true && bridge?.business_fact_answer_allowed === true;
const confirmedRankingEvidence = Boolean(
toNonEmptyString(evidence?.evidence_status) === "confirmed" ||
toNonEmptyString(evidence?.answer_permission) === "confirmed_answer" ||
confirmedFacts.some((fact) => /ranked by counterparty|рейтинг.*контрагент/iu.test(fact))
);
return mcpExecutionPerformed && businessAnswerAllowed && confirmedRankingEvidence;
}
function hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(
input: ApplyAssistantMcpDiscoveryResponsePolicyInput,
entryPoint: AssistantMcpDiscoveryRuntimeEntryPointContract | null,
candidate: AssistantMcpDiscoveryResponseCandidateContract
): boolean {
if (candidate.candidate_status !== "ready_for_guarded_use") {
return false;
}
if (!candidate.eligible_for_future_hot_runtime || !toNonEmptyString(candidate.reply_text)) {
return false;
}
if (candidate.reply_text && hasInternalMechanics(candidate.reply_text)) {
return false;
}
if (!isDiscoveryReadyAddressCandidate(input, entryPoint)) {
return false;
}
if (!hasEffectivelyFactualAddressReply(input)) {
return false;
}
return Boolean(
hasCounterpartyRankingQuestionSignal(entryPoint) &&
hasWrongPeriodRankingCurrentReply(input) &&
hasConfirmedValueFlowRankingEvidence(entryPoint)
);
}
function hasEffectivelyFactualAddressReply(input: ApplyAssistantMcpDiscoveryResponsePolicyInput): boolean {
if (toNonEmptyString(input.currentReplyType) === "factual") {
return true;
@@ -1092,10 +1266,24 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
entryPoint,
candidate
);
const groundedMovementCandidatePriority =
hasGroundedMovementCandidatePriorityOverCurrentInventoryItemClarification(input, entryPoint, candidate);
const groundedValueFlowRankingCandidatePriority =
hasGroundedValueFlowRankingCandidatePriorityOverCurrentExactReply(input, entryPoint, candidate);
const currentClarificationProtectsCurrentReply =
currentClarificationRequiredAddressReply &&
!discoveryClarificationCandidatePriority &&
!groundedValueFlowCandidatePriority;
!groundedValueFlowCandidatePriority &&
!groundedMovementCandidatePriority &&
!groundedValueFlowRankingCandidatePriority;
const alignedFactualAddressReplyProtectsCurrent =
alignedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
const matchedFactualAddressContinuationTargetProtectsCurrent =
matchedFactualAddressContinuationTarget && !groundedValueFlowRankingCandidatePriority;
const fullConfirmedFactualAddressReplyProtectsCurrent =
fullConfirmedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
const exactMatchedFactualAddressReplyProtectsCurrent =
exactMatchedFactualAddressReply && !groundedValueFlowRankingCandidatePriority;
const exactBankOperationsProtectsCurrent =
exactBankOperationsAddressReply &&
!semanticConflictWithDiscoveryTurnMeaning &&
@@ -1147,6 +1335,18 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
"mcp_discovery_response_policy_grounded_value_flow_candidate_priority_over_current_clarification"
);
}
if (groundedMovementCandidatePriority) {
pushReason(
reasonCodes,
"mcp_discovery_response_policy_grounded_movement_candidate_priority_over_inventory_item_clarification"
);
}
if (groundedValueFlowRankingCandidatePriority) {
pushReason(
reasonCodes,
"mcp_discovery_response_policy_grounded_value_flow_ranking_candidate_priority_over_current_exact_reply"
);
}
if (openScopeValueFlowDiscoveryPriority) {
pushReason(reasonCodes, "mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
}
@@ -1229,11 +1429,11 @@ export function applyAssistantMcpDiscoveryResponsePolicy(
const canApply =
Boolean(entryPoint) &&
(unsupportedBoundary || discoveryReadyChatCandidate || discoveryReadyDeepCandidate || discoveryReadyAddressCandidate) &&
!alignedFactualAddressReply &&
!matchedFactualAddressContinuationTarget &&
!alignedFactualAddressReplyProtectsCurrent &&
!matchedFactualAddressContinuationTargetProtectsCurrent &&
!matchedFactualSuggestedIntentPivotTarget &&
!fullConfirmedFactualAddressReply &&
!exactMatchedFactualAddressReply &&
!fullConfirmedFactualAddressReplyProtectsCurrent &&
!exactMatchedFactualAddressReplyProtectsCurrent &&
!runtimeAdjustedExactReply &&
!runtimeMatchedExactReply &&
!staleMetadataDiscoveryFallbackAgainstExactAddressReply &&
@@ -699,6 +699,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
const loopAskedDomainFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_domain_family);
const loopAskedActionFamily = toNonEmptyString(followupContext?.previous_discovery_loop_asked_action_family);
const loopUnsupportedFamily = toNonEmptyString(followupContext?.previous_discovery_loop_unsupported_family);
const loopExplicitDateScope = toNonEmptyString(followupContext?.previous_discovery_loop_explicit_date_scope);
const loopMetadataScopeHint = toNonEmptyString(followupContext?.previous_discovery_loop_metadata_scope_hint);
const loopSubjectResolutionOptional =
followupContext?.previous_discovery_loop_subject_resolution_optional === true;
@@ -763,7 +764,7 @@ function collectFollowupDiscoverySeed(followupContext: Record<string, unknown> |
const loopProvidedAllTimeScope = loopProvidedAxes.includes("all_time_scope");
const dateScope = loopProvidedAllTimeScope
? "all_time_scope"
: collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
: loopExplicitDateScope ?? collectDateScopeFromFilters(previousFilters) ?? collectDateScopeFromFilters(rootFilters);
return {
pilotScope: effectivePilotScope,
domain: mapped.domain,
@@ -28,6 +28,7 @@ import {
readAssistantMcpDiscoveryLoopAskedDomainFamily,
readAssistantMcpDiscoveryLoopAskedActionFamily,
readAssistantMcpDiscoveryLoopUnsupportedFamily,
readAssistantMcpDiscoveryLoopExplicitDateScope,
readAssistantMcpDiscoveryLoopMetadataScopeHint,
readAssistantMcpDiscoveryLoopSubjectResolutionOptional,
readAddressDebugTemporalScope,
@@ -1388,6 +1389,10 @@ export function createAssistantTransitionPolicy(deps) {
carryoverSourceDebug,
deps.toNonEmptyString
);
const sourceDiscoveryLoopExplicitDateScope = readAssistantMcpDiscoveryLoopExplicitDateScope(
carryoverSourceDebug,
deps.toNonEmptyString
);
const sourceDiscoveryLoopMetadataScopeHint = readAssistantMcpDiscoveryLoopMetadataScopeHint(
carryoverSourceDebug,
deps.toNonEmptyString
@@ -1859,6 +1864,7 @@ export function createAssistantTransitionPolicy(deps) {
previous_discovery_loop_asked_domain_family: sourceDiscoveryLoopAskedDomainFamily ?? undefined,
previous_discovery_loop_asked_action_family: sourceDiscoveryLoopAskedActionFamily ?? undefined,
previous_discovery_loop_unsupported_family: sourceDiscoveryLoopUnsupportedFamily ?? undefined,
previous_discovery_loop_explicit_date_scope: sourceDiscoveryLoopExplicitDateScope ?? undefined,
previous_discovery_loop_metadata_scope_hint: sourceDiscoveryLoopMetadataScopeHint ?? undefined,
previous_discovery_loop_subject_resolution_optional:
sourceDiscoveryLoopSubjectResolutionOptional || undefined,
@@ -130,6 +130,76 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reason_codes).toContain("mcp_discovery_response_policy_keep_current_clarification_required_reply");
});
it("applies a grounded movement candidate over a stale inventory item clarification", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "Need an item before I can continue.",
currentReplySource: "address_query_runtime_v1",
currentReplyType: "partial_coverage",
addressRuntimeMeta: {
truth_mode: "clarification_required",
answer_shape: "clarification_required",
answer_shape_contract: {
answer_shape: "clarification_required",
reply_type: "partial_coverage",
capability_contract_id: "inventory_inventory_purchase_documents_for_item"
},
detected_intent: "inventory_purchase_documents_for_item",
selected_recipe: "address_inventory_purchase_documents_for_item_v1",
capability_id: "inventory_inventory_purchase_documents_for_item",
missing_required_filters: ["item"],
assistant_mcp_discovery_entry_point_v1: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
turn_meaning_ref: {
asked_domain_family: "movements",
asked_action_family: "list_movements",
explicit_date_scope: "2020"
},
data_need_graph: {
business_fact_family: "movement_evidence",
action_family: "list_movements"
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
route_candidate: {
selected_chain_id: "movement_evidence"
},
pilot: {
selected_chain_id: "movement_evidence",
mcp_execution_performed: true
},
execution_handoff: {
mcp_execution_performed: true,
can_use_guarded_response: false
},
answer_draft: {
answer_mode: "bounded_inference_only",
headline: "Movement evidence for Group SVK in 2020 is bounded.",
confirmed_lines: ["No confirmed movement rows were found in the checked 2020 window."],
inference_lines: [],
unknown_lines: [],
limitation_lines: ["Full movement coverage outside the checked window is not confirmed."],
next_step_line: null
}
}
})
}
});
expect(result.applied).toBe(true);
expect(result.reply_text).toContain("Movement evidence for Group SVK in 2020 is bounded.");
expect(result.reply_text).not.toContain("Need an item");
expect(result.reason_codes).toContain(
"mcp_discovery_response_policy_grounded_movement_candidate_priority_over_inventory_item_clarification"
);
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_applied");
});
it("keeps the current reply when the turn is not an unsupported boundary", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "regular chat",
@@ -1252,6 +1322,107 @@ describe("assistant MCP discovery response policy", () => {
expect(result.reason_codes).toContain("mcp_discovery_response_policy_open_scope_value_flow_candidate_priority");
});
it("overrides a year-ranking exact reply when the user asked who brought the most money", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply:
"Самый доходный год по подтвержденным поступлениям: 2020 (47.628.853,03 ₽ по 44 операциям).\nТоп-1 лет по сумме поступлений:\n1. 2020 | сумма: 47.628.853,03 ₽ | операций: 44 | контрагентов: 13",
currentReplySource: "address_query_runtime_v1",
currentReplyType: "factual",
addressRuntimeMeta: {
detected_intent: "customer_revenue_and_payments",
selected_recipe: "address_customer_revenue_and_payments_v1",
mcp_call_status: "matched_non_empty",
truth_mode: "confirmed",
capability_binding_status: "bound",
capability_binding_violations: [],
dialogContinuationContract: {
target_intent: "customer_revenue_and_payments"
},
truth_gate_contract_status: "full_confirmed",
assistant_truth_answer_policy_v1: {
truth_gate: {
coverage_status: "full",
grounding_status: "grounded",
source_truth_gate_status: "full_confirmed"
}
},
assistant_mcp_discovery_entry_point_v1: entryPoint({
turn_input: {
adapter_status: "ready",
should_run_discovery: true,
data_need_graph: {
business_fact_family: "value_flow",
subject_candidates: ["Группа СВК"],
ranking_need: "top_desc",
reason_codes: ["data_need_graph_built", "data_need_graph_ranking_top_desc"]
},
turn_meaning_ref: {
raw_message: "И кто больше всего принес денег этой организации в 2020 году?",
effective_message: "Определить, какой контрагент принёс наибольшую выручку организации в 2020 году",
asked_domain_family: "counterparty_value",
asked_action_family: "turnover",
explicit_entity_candidates: ["Группа СВК"],
explicit_organization_scope: "ООО Альтернатива Плюс",
explicit_date_scope: "2020"
}
},
bridge: {
bridge_status: "answer_draft_ready",
user_facing_response_allowed: true,
business_fact_answer_allowed: true,
requires_user_clarification: false,
route_candidate: {
selected_chain_id: "value_flow_ranking",
nearest_catalog_chain_template: "value_flow_ranking"
},
execution_handoff: {
selected_chain_id: "value_flow_ranking",
mcp_execution_performed: true,
can_use_guarded_response: false,
must_keep_internal_mechanics_hidden: true
},
pilot: {
pilot_scope: "counterparty_value_flow_query_movements_v1",
mcp_execution_performed: true,
evidence: {
evidence_status: "confirmed",
coverage_status: "full",
answer_permission: "confirmed_answer",
confirmed_facts: [
"1C incoming value-flow rows were ranked by counterparty for the checked scope; leader=Группа СВК, rows_with_amount=13"
]
}
},
answer_draft: {
answer_mode: "confirmed_with_bounded_inference",
headline: "По данным 1С можно построить ограниченный рейтинг по контрагентам.",
confirmed_lines: [
"В проверенных входящих поступлениях найден один контрагент Группа СВК по организации ООО Альтернатива Плюс за период 2020: 12 093 465 руб. по 13 строкам с суммой."
],
inference_lines: [
"Рейтинг по контрагентам по организации ООО Альтернатива Плюс за период 2020 рассчитан только по подтвержденным строкам 1С."
],
unknown_lines: ["Полный рейтинг контрагентов вне периода 2020 этим поиском не подтвержден."],
limitation_lines: [],
next_step_line: null
}
}
})
}
});
expect(result.applied).toBe(true);
expect(result.decision).toBe("apply_candidate");
expect(result.reply_text).toContain("Группа СВК");
expect(result.reply_text).toContain("12 093 465 руб.");
expect(result.reply_text).not.toContain("Самый доходный год");
expect(result.reply_text).not.toContain("Топ-1 лет");
expect(result.reason_codes).toContain(
"mcp_discovery_response_policy_grounded_value_flow_ranking_candidate_priority_over_current_exact_reply"
);
expect(result.reason_codes).toContain("mcp_discovery_response_policy_candidate_applied");
});
it("keeps full-confirmed factual address replies even when discovery has a guarded candidate", () => {
const result = applyAssistantMcpDiscoveryResponsePolicy({
currentReply: "ООО Ромашка | сумма: 128000 | операций: 3",
@@ -838,6 +838,54 @@ describe("assistant MCP discovery turn input adapter", () => {
expect(result.reason_codes).not.toContain("mcp_discovery_not_applicable_for_supported_exact_turn");
});
it("prefers semantic loop date scope over technical current-date filters for net value-flow follow-ups", () => {
const counterpartyName = "\u0413\u0440\u0443\u043f\u043f\u0430 \u0421\u0412\u041a";
const organizationName =
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "\u0430 \u043a\u0430\u043a\u043e\u0435 \u043d\u0435\u0442\u0442\u043e?",
assistantTurnMeaning: {
asked_domain_family: "counterparty",
asked_action_family: "turnover",
explicit_intent_candidate: "customer_revenue_and_payments"
},
followupContext: {
previous_discovery_pilot_scope: "counterparty_supplier_payout_query_movements_v1",
previous_discovery_loop_status: "ready_for_next_hop",
previous_discovery_loop_selected_chain_id: "value_flow",
previous_discovery_loop_asked_domain_family: "counterparty_value",
previous_discovery_loop_asked_action_family: "payout",
previous_discovery_loop_explicit_date_scope: "2020",
previous_discovery_loop_metadata_scope_hint: counterpartyName,
previous_filters: {
counterparty: counterpartyName,
organization: organizationName,
period_from: "2020-01-01",
period_to: "2026-06-03"
},
previous_anchor_type: "counterparty",
previous_anchor_value: counterpartyName
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.semantic_data_need).toBe("counterparty value-flow evidence");
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "counterparty_value",
asked_action_family: "net_value_flow",
explicit_entity_candidates: [counterpartyName],
explicit_organization_scope: organizationName,
explicit_date_scope: "2020",
unsupported_but_understood_family: "counterparty_bidirectional_value_flow_or_netting",
stale_replay_forbidden: true
});
expect(result.data_need_graph?.comparison_need).toBe("incoming_vs_outgoing");
expect(result.reason_codes).toContain("mcp_discovery_grounded_value_flow_followup");
expect(result.reason_codes).toContain("mcp_discovery_date_scope_from_followup_context");
expect(result.reason_codes).toContain("mcp_discovery_bidirectional_value_flow_signal_detected");
});
it("prefers grounded discovery metadata scope over stale conflicting counterparty in a short net follow-up", () => {
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "а какое нетто?",
@@ -1903,6 +1903,7 @@ describe("assistantTransitionPolicy", () => {
expect(carryover?.followupContext?.previous_discovery_loop_unsupported_family).toBe(
"counterparty_value_or_turnover"
);
expect(carryover?.followupContext?.previous_discovery_loop_explicit_date_scope).toBe("2020");
});
it("treats a plain organization reply as continuation of a pending route-candidate organization scope", () => {
@@ -1963,6 +1964,7 @@ describe("assistantTransitionPolicy", () => {
expect(carryover?.followupContext?.previous_discovery_loop_status).toBe("awaiting_clarification");
expect(carryover?.followupContext?.previous_discovery_loop_selected_chain_id).toBe("value_flow_ranking");
expect(carryover?.followupContext?.previous_discovery_loop_pending_axes).toEqual(["organization"]);
expect(carryover?.followupContext?.previous_discovery_loop_explicit_date_scope).toBe("2020");
expect(carryover?.followupContext?.target_intent).toBe("customer_revenue_and_payments");
});