Post-F: закрепить metadata scope и обновить live7 canary

This commit is contained in:
2026-04-24 19:11:46 +03:00
parent 796a9a93f2
commit 7262603a26
8 changed files with 687 additions and 53 deletions
@@ -89,8 +89,17 @@ function isValueFlowPredicateEntityCandidate(value) {
}
return !/(?<!\p{L})(?:ооо|ип|ао|пао|зао|llc|inc|corp)(?!\p{L})/iu.test(text);
}
function isActionVerbEntityCandidate(value) {
if (!value) {
return false;
}
return new Set(["провести", "показать", "посмотреть", "смотреть", "найти", "искать", "анализ"]).has((0, addressTextRepair_1.normalizeRussianComparableText)(value));
}
function isInvalidEntityCandidate(value) {
return Boolean(value && (isReferentialEntityPlaceholder(value) || isValueFlowPredicateEntityCandidate(value)));
return Boolean(value &&
(isReferentialEntityPlaceholder(value) ||
isValueFlowPredicateEntityCandidate(value) ||
isActionVerbEntityCandidate(value)));
}
function normalizeFollowupCounterpartyCandidate(value) {
const text = candidateValue(value);
@@ -866,9 +875,20 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const rawOrganizationScope = extractOrganizationScopeFromRawText(rawUserText ?? rawEffectiveText ?? rawSignalSourceText);
const currentTurnFreshOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization;
const currentTurnOrganizationScope = currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
const followupCounterpartyIsMetadataOrganizationScope = Boolean(followupSeed.subjectResolutionOptional &&
followupSeed.counterparty &&
(followupSeed.metadataScopeHint ||
followupSeed.metadataSelectedEntitySet ||
followupSeed.metadataSelectedSurfaceObjects.length > 0) &&
(isInvalidEntityCandidate(followupSeed.counterparty) ||
sameScopedName(followupSeed.counterparty, followupSeed.organization) ||
sameScopedName(followupSeed.counterparty, currentTurnOrganizationScope)));
const effectiveFollowupCounterparty = followupCounterpartyIsMetadataOrganizationScope
? null
: followupSeed.counterparty;
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
const organizationClarificationFollowupApplicable = Boolean(followupSeed.domain === "counterparty_value" &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
currentTurnOrganizationScope &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -912,7 +932,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
!rawLifecycleSignal &&
!rawValueFlowSignal &&
hasMetadataObjectHint(rawText));
const metadataLaneCarryoverAvailable = Boolean(followupSeed.counterparty ||
const metadataLaneCarryoverAvailable = Boolean(effectiveFollowupCounterparty ||
followupSeed.discoveryEntity ||
followupSeed.metadataScopeHint ||
followupSeed.metadataSelectedEntitySet ||
@@ -946,7 +966,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
!rawLifecycleSignal &&
metadataMovementHintSignal);
const entityResolutionGroundedDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "entity_resolution_search_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -959,7 +979,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
!rawMetadataSignal &&
metadataDocumentHintSignal);
const entityResolutionGroundedMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "entity_resolution_search_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -974,14 +994,14 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const groundedValueFlowFollowupApplicable = Boolean(rawValueFlowSignal &&
!rawLifecycleSignal &&
!rawMetadataSignal &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
(followupSeed.pilotScope === "entity_resolution_search_v1" ||
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" ||
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" ||
followupSeed.pilotScope === "counterparty_value_flow_query_movements_v1" ||
followupSeed.pilotScope === "counterparty_supplier_payout_query_movements_v1" ||
followupSeed.pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1"));
const groundedValueFlowEvidenceSourceApplicable = Boolean((followupSeed.counterparty || followupSeed.discoveryEntity) &&
const groundedValueFlowEvidenceSourceApplicable = Boolean((effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -995,27 +1015,27 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const valueFlowGroundedDocumentFollowupApplicable = Boolean(groundedValueFlowEvidenceSourceApplicable && metadataDocumentHintSignal);
const valueFlowGroundedMovementFollowupApplicable = Boolean(groundedValueFlowEvidenceSourceApplicable && metadataMovementHintSignal);
const documentEvidenceGroundedMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
metadataMovementHintSignal);
const movementEvidenceGroundedDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
metadataDocumentHintSignal);
const metadataScopedMovementEvidenceToDocumentFollowupApplicable = Boolean(followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" &&
followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
metadataDocumentHintSignal);
const metadataScopedDocumentEvidenceToMovementFollowupApplicable = Boolean(followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" &&
followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1074,7 +1094,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const metadataGroundedDocumentLaneApplicable = metadataGroundedDocumentFollowupApplicable ||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
(followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1092,7 +1112,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
const metadataGroundedMovementLaneApplicable = metadataGroundedMovementFollowupApplicable ||
metadataAmbiguityResolvedMovementFollowupApplicable ||
(followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1171,8 +1191,8 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
? normalizedPredecomposeCounterparty
: null;
const explicitCurrentCounterpartyOverridesFollowupEntity = Boolean(explicitCurrentCounterpartyCandidate &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
!sameScopedName(explicitCurrentCounterpartyCandidate, followupSeed.counterparty ?? followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!sameScopedName(explicitCurrentCounterpartyCandidate, effectiveFollowupCounterparty ?? followupSeed.discoveryEntity) &&
(valueFlowSignal ||
lifecycleSignal ||
metadataGroundedDocumentLaneApplicable ||
@@ -1187,7 +1207,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
followupSeed.metadataSelectedEntitySet ??
null;
const metadataScopedLaneWithoutSubject = Boolean((metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable);
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
? null
@@ -1195,7 +1215,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
? null
: explicitCurrentCounterpartyOverridesFollowupEntity
? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
: effectiveFollowupCounterparty ?? followupSeed.discoveryEntity;
const entityCandidates = entityResolutionSignal ? [] : [];
if (entityResolutionSignal) {
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
@@ -1209,7 +1229,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
}
if (!rawEntitySearchOverridesStaleScope) {
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
pushNormalizedEntityResolutionCandidate(entityCandidates, effectiveFollowupCounterparty);
}
}
else {
@@ -1221,7 +1241,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
}
pushScopedEntityCandidate(entityCandidates, normalizedPredecomposeCounterparty, groundedFollowupEntity);
if (!groundedFollowupEntity) {
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
pushScopedEntityCandidate(entityCandidates, effectiveFollowupCounterparty, null);
if (!metadataScopedLaneWithoutSubject) {
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
}
@@ -1234,7 +1254,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
pushUnique(entityCandidates, followupSeed.discoveryEntity);
pushUnique(entityCandidates, rawMetadataScopeHint);
}
const openScopeValueFlowWithoutCounterparty = valueFlowSignal && !normalizedPredecomposeCounterparty && !followupSeed.counterparty;
const openScopeValueFlowWithoutCounterparty = valueFlowSignal && !normalizedPredecomposeCounterparty && !effectiveFollowupCounterparty;
const valueFlowOrganizationStaysScope = openScopeValueFlowWithoutCounterparty &&
Boolean(bidirectionalValueFlowSignal ||
hasValueRankingSignal(rawText) ||
@@ -1242,7 +1262,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
explicitOrganizationScopeSignal ||
organizationClarificationFollowupApplicable ||
followupSeed.organization);
const openScopeValueFlowWithoutResolvedCounterparty = Boolean(valueFlowSignal && !normalizedPredecomposeCounterparty && !followupSeed.counterparty);
const openScopeValueFlowWithoutResolvedCounterparty = Boolean(valueFlowSignal && !normalizedPredecomposeCounterparty && !effectiveFollowupCounterparty);
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
pushUnique(entityCandidates, predecomposeEntities.organization);
pushUnique(entityCandidates, followupSeed.organization);
@@ -1263,9 +1283,9 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
if ((metadataLaneScopeHint && sameScopedName(candidate, metadataLaneScopeHint)) ||
(explicitOrganizationScope && sameScopedName(candidate, explicitOrganizationScope)) ||
(followupSeed.organization && sameScopedName(candidate, followupSeed.organization)) ||
(followupSeed.counterparty &&
!sameScopedName(followupSeed.counterparty, explicitCurrentCounterpartyCandidate) &&
sameScopedName(candidate, followupSeed.counterparty))) {
(effectiveFollowupCounterparty &&
!sameScopedName(effectiveFollowupCounterparty, explicitCurrentCounterpartyCandidate) &&
sameScopedName(candidate, effectiveFollowupCounterparty))) {
entityCandidates.splice(index, 1);
}
}
@@ -1590,7 +1610,7 @@ function buildAssistantMcpDiscoveryTurnInput(input) {
normalizedPredecomposeCounterparty) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
}
if (followupSeed.counterparty && !rawEntitySearchOverridesStaleScope) {
if (effectiveFollowupCounterparty && !rawEntitySearchOverridesStaleScope) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
}
if (followupDateScopeApplied) {
@@ -135,8 +135,22 @@ function isValueFlowPredicateEntityCandidate(value: string | null): boolean {
return !/(?<!\p{L})(?:ооо|ип|ао|пао|зао|llc|inc|corp)(?!\p{L})/iu.test(text);
}
function isActionVerbEntityCandidate(value: string | null): boolean {
if (!value) {
return false;
}
return new Set(["провести", "показать", "посмотреть", "смотреть", "найти", "искать", "анализ"]).has(
normalizeRussianComparableText(value)
);
}
function isInvalidEntityCandidate(value: string | null): boolean {
return Boolean(value && (isReferentialEntityPlaceholder(value) || isValueFlowPredicateEntityCandidate(value)));
return Boolean(
value &&
(isReferentialEntityPlaceholder(value) ||
isValueFlowPredicateEntityCandidate(value) ||
isActionVerbEntityCandidate(value))
);
}
function normalizeFollowupCounterpartyCandidate(value: unknown): string | null {
@@ -1163,10 +1177,23 @@ export function buildAssistantMcpDiscoveryTurnInput(
const currentTurnFreshOrganizationScope = rawOrganizationScope ?? predecomposeEntities.organization;
const currentTurnOrganizationScope =
currentTurnFreshOrganizationScope ?? assistantTurnMeaningOrganizationScope;
const followupCounterpartyIsMetadataOrganizationScope = Boolean(
followupSeed.subjectResolutionOptional &&
followupSeed.counterparty &&
(followupSeed.metadataScopeHint ||
followupSeed.metadataSelectedEntitySet ||
followupSeed.metadataSelectedSurfaceObjects.length > 0) &&
(isInvalidEntityCandidate(followupSeed.counterparty) ||
sameScopedName(followupSeed.counterparty, followupSeed.organization) ||
sameScopedName(followupSeed.counterparty, currentTurnOrganizationScope))
);
const effectiveFollowupCounterparty = followupCounterpartyIsMetadataOrganizationScope
? null
: followupSeed.counterparty;
const explicitOrganizationScopeSignal = Boolean(rawOrganizationMentionSignal && currentTurnOrganizationScope);
const organizationClarificationFollowupApplicable = Boolean(
followupSeed.domain === "counterparty_value" &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
currentTurnOrganizationScope &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1225,7 +1252,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
hasMetadataObjectHint(rawText)
);
const metadataLaneCarryoverAvailable = Boolean(
followupSeed.counterparty ||
effectiveFollowupCounterparty ||
followupSeed.discoveryEntity ||
followupSeed.metadataScopeHint ||
followupSeed.metadataSelectedEntitySet ||
@@ -1269,7 +1296,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
);
const entityResolutionGroundedDocumentFollowupApplicable = Boolean(
followupSeed.pilotScope === "entity_resolution_search_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1286,7 +1313,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
);
const entityResolutionGroundedMovementFollowupApplicable = Boolean(
followupSeed.pilotScope === "entity_resolution_search_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1305,7 +1332,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
rawValueFlowSignal &&
!rawLifecycleSignal &&
!rawMetadataSignal &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
(followupSeed.pilotScope === "entity_resolution_search_v1" ||
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" ||
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" ||
@@ -1314,7 +1341,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.pilotScope === "counterparty_bidirectional_value_flow_query_movements_v1")
);
const groundedValueFlowEvidenceSourceApplicable = Boolean(
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1334,7 +1361,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
);
const documentEvidenceGroundedMovementFollowupApplicable = Boolean(
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1342,7 +1369,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
);
const movementEvidenceGroundedDocumentFollowupApplicable = Boolean(
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
!rawMetadataSignal &&
@@ -1351,7 +1378,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
const metadataScopedMovementEvidenceToDocumentFollowupApplicable = Boolean(
followupSeed.pilotScope === "counterparty_movement_evidence_query_movements_v1" &&
followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1360,7 +1387,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
const metadataScopedDocumentEvidenceToMovementFollowupApplicable = Boolean(
followupSeed.pilotScope === "counterparty_document_evidence_query_documents_v1" &&
followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1431,7 +1458,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
metadataGroundedDocumentFollowupApplicable ||
metadataAmbiguityResolvedDocumentFollowupApplicable ||
(followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1450,7 +1477,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
metadataGroundedMovementFollowupApplicable ||
metadataAmbiguityResolvedMovementFollowupApplicable ||
(followupSeed.subjectResolutionOptional &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable &&
!rawLifecycleSignal &&
!rawValueFlowSignal &&
@@ -1537,8 +1564,8 @@ export function buildAssistantMcpDiscoveryTurnInput(
: null;
const explicitCurrentCounterpartyOverridesFollowupEntity = Boolean(
explicitCurrentCounterpartyCandidate &&
(followupSeed.counterparty || followupSeed.discoveryEntity) &&
!sameScopedName(explicitCurrentCounterpartyCandidate, followupSeed.counterparty ?? followupSeed.discoveryEntity) &&
(effectiveFollowupCounterparty || followupSeed.discoveryEntity) &&
!sameScopedName(explicitCurrentCounterpartyCandidate, effectiveFollowupCounterparty ?? followupSeed.discoveryEntity) &&
(valueFlowSignal ||
lifecycleSignal ||
metadataGroundedDocumentLaneApplicable ||
@@ -1556,7 +1583,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
null;
const metadataScopedLaneWithoutSubject = Boolean(
(metadataGroundedMovementLaneApplicable || metadataGroundedDocumentLaneApplicable) &&
!followupSeed.counterparty &&
!effectiveFollowupCounterparty &&
metadataLaneCarryoverAvailable
);
const groundedFollowupEntity = metadataScopedLaneWithoutSubject
@@ -1565,7 +1592,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
? null
: explicitCurrentCounterpartyOverridesFollowupEntity
? null
: followupSeed.counterparty ?? followupSeed.discoveryEntity;
: effectiveFollowupCounterparty ?? followupSeed.discoveryEntity;
const entityCandidates = entityResolutionSignal ? [] : [];
if (entityResolutionSignal) {
pushNormalizedEntityResolutionCandidate(entityCandidates, entityResolutionClarificationCandidate);
@@ -1579,7 +1606,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
pushNormalizedEntityResolutionCandidate(entityCandidates, normalizedPredecomposeCounterparty);
}
if (!rawEntitySearchOverridesStaleScope) {
pushNormalizedEntityResolutionCandidate(entityCandidates, followupSeed.counterparty);
pushNormalizedEntityResolutionCandidate(entityCandidates, effectiveFollowupCounterparty);
}
} else {
if (groundedFollowupEntity) {
@@ -1590,7 +1617,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
}
pushScopedEntityCandidate(entityCandidates, normalizedPredecomposeCounterparty, groundedFollowupEntity);
if (!groundedFollowupEntity) {
pushScopedEntityCandidate(entityCandidates, followupSeed.counterparty, null);
pushScopedEntityCandidate(entityCandidates, effectiveFollowupCounterparty, null);
if (!metadataScopedLaneWithoutSubject) {
pushScopedEntityCandidate(entityCandidates, followupSeed.discoveryEntity, null);
}
@@ -1606,7 +1633,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
pushUnique(entityCandidates, rawMetadataScopeHint);
}
const openScopeValueFlowWithoutCounterparty =
valueFlowSignal && !normalizedPredecomposeCounterparty && !followupSeed.counterparty;
valueFlowSignal && !normalizedPredecomposeCounterparty && !effectiveFollowupCounterparty;
const valueFlowOrganizationStaysScope =
openScopeValueFlowWithoutCounterparty &&
Boolean(
@@ -1618,7 +1645,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
followupSeed.organization
);
const openScopeValueFlowWithoutResolvedCounterparty = Boolean(
valueFlowSignal && !normalizedPredecomposeCounterparty && !followupSeed.counterparty
valueFlowSignal && !normalizedPredecomposeCounterparty && !effectiveFollowupCounterparty
);
if (openScopeValueFlowWithoutCounterparty && !valueFlowOrganizationStaysScope) {
pushUnique(entityCandidates, predecomposeEntities.organization);
@@ -1644,9 +1671,9 @@ export function buildAssistantMcpDiscoveryTurnInput(
(metadataLaneScopeHint && sameScopedName(candidate, metadataLaneScopeHint)) ||
(explicitOrganizationScope && sameScopedName(candidate, explicitOrganizationScope)) ||
(followupSeed.organization && sameScopedName(candidate, followupSeed.organization)) ||
(followupSeed.counterparty &&
!sameScopedName(followupSeed.counterparty, explicitCurrentCounterpartyCandidate) &&
sameScopedName(candidate, followupSeed.counterparty))
(effectiveFollowupCounterparty &&
!sameScopedName(effectiveFollowupCounterparty, explicitCurrentCounterpartyCandidate) &&
sameScopedName(candidate, effectiveFollowupCounterparty))
) {
entityCandidates.splice(index, 1);
}
@@ -2000,7 +2027,7 @@ export function buildAssistantMcpDiscoveryTurnInput(
) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_predecompose");
}
if (followupSeed.counterparty && !rawEntitySearchOverridesStaleScope) {
if (effectiveFollowupCounterparty && !rawEntitySearchOverridesStaleScope) {
pushReason(reasonCodes, "mcp_discovery_counterparty_from_followup_context");
}
if (followupDateScopeApplied) {
@@ -2007,6 +2007,7 @@ describe("assistant MCP discovery turn input adapter", () => {
previous_discovery_ranking_need: "top_desc",
previous_filters: {
organization: orgName,
counterparty: "\u041f\u0440\u043e\u0432\u0435\u0441\u0442\u0438",
period_from: "2020-01-01",
period_to: "2020-12-31"
}
@@ -2290,4 +2291,47 @@ describe("assistant MCP discovery turn input adapter", () => {
});
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
});
it("does not promote organization scope into counterparty subject after a metadata-scoped movement year switch", () => {
const orgName =
"\u041e\u041e\u041e \u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0430 \u041f\u043b\u044e\u0441";
const result = buildAssistantMcpDiscoveryTurnInput({
userMessage: "\u0417\u0430 2020 \u0433\u043e\u0434.",
followupContext: {
previous_discovery_pilot_scope: "counterparty_movement_evidence_query_movements_v1",
previous_discovery_loop_status: "ready_for_next_hop",
previous_discovery_loop_selected_chain_id: "movement_evidence",
previous_discovery_loop_pending_axes: [],
previous_discovery_loop_asked_domain_family: "movements",
previous_discovery_loop_asked_action_family: "list_movements",
previous_discovery_loop_unsupported_family: "movement_evidence",
previous_discovery_loop_metadata_scope_hint: "\u041d\u0414\u0421",
previous_discovery_loop_subject_resolution_optional: true,
previous_discovery_entity_candidates: ["\u041d\u0414\u0421"],
previous_filters: {
organization: orgName,
counterparty: orgName,
period_to: "2026-04-24"
}
}
});
expect(result.adapter_status).toBe("ready");
expect(result.should_run_discovery).toBe(true);
expect(result.semantic_data_need).toBe("movement evidence");
expect(result.turn_meaning_ref).toMatchObject({
asked_domain_family: "movements",
asked_action_family: "list_movements",
metadata_scope_hint: "\u041d\u0414\u0421",
subject_resolution_optional: true,
explicit_organization_scope: orgName,
explicit_date_scope: "2020",
unsupported_but_understood_family: "movement_evidence",
stale_replay_forbidden: true
});
expect(result.turn_meaning_ref?.explicit_entity_candidates).toBeUndefined();
expect(result.data_need_graph?.subject_candidates).toEqual([]);
expect(result.reason_codes).toContain("mcp_discovery_metadata_scoped_lane_without_subject");
expect(result.reason_codes).not.toContain("mcp_discovery_counterparty_from_followup_context");
});
});
@@ -1,4 +1,96 @@
[
{
"generation_id": "gen-ag04241610-84c8bb",
"created_at": "2026-04-24T16:10:22+00:00",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary live7",
"count": 24,
"domain": "address_post_f_cross_stage_canary_agent",
"questions": [
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"За 2020 год.",
"А теперь по документам?",
"А теперь за 2021 год?",
"А теперь за все время?",
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?",
"А теперь тот же смысл за 2021 год."
],
"generated_by": "codex_agent",
"saved_case_set_file": "assistant_autogen_saved_user_sessions_20260424161022_gen-ag04241610-84c8bb.json",
"context": {
"llm_provider": null,
"model": null,
"assistant_prompt_version": null,
"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_20260424161022_gen-ag04241610-84c8bb.json",
"saved_case_set_kind": "agent_semantic_scenario",
"agent_run": true,
"agent_focus": "live7: VAT metadata scoped org/document pivots, ranking year switches, fresh SVK reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
}
},
{
"generation_id": "gen-ag04241406-abe4d8",
"created_at": "2026-04-24T14:06:30+00:00",
@@ -0,0 +1,353 @@
{
"saved_at": "2026-04-24T16:10:22+00:00",
"generation_id": "gen-ag04241610-84c8bb",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary live7",
"agent_run": true,
"questions": [
"Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"За 2020 год.",
"А теперь по документам?",
"А теперь за 2021 год?",
"А теперь за все время?",
"С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"Хорошо, а теперь платежи по нему тоже покажи.",
"А по нему договоры?",
"А по нему документы?",
"А за 2021?",
"С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"По ООО Альтернатива Плюс.",
"Понял, тогда за все время.",
"Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"И кто больше всего принес денег этой организации в 2020 году?",
"А в 2021 году?",
"Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"Сколько получили по нему за 2020 год?",
"А теперь сколько заплатили?",
"А какое нетто?",
"А по документам?",
"А по движениям?",
"А теперь тот же смысл за 2021 год."
],
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "live7: VAT metadata scoped org/document pivots, ranking year switches, fresh SVK reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
},
"source_session_id": null,
"session": {
"session_id": null,
"mode": "agent_semantic_run",
"items": [
{
"message_id": "agent-user-001",
"role": "user",
"text": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-002",
"role": "user",
"text": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-003",
"role": "user",
"text": "За 2020 год.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-004",
"role": "user",
"text": "А теперь по документам?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-005",
"role": "user",
"text": "А теперь за 2021 год?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-006",
"role": "user",
"text": "А теперь за все время?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-007",
"role": "user",
"text": "С НДС закончили. Новая тема: покажи документы по Жуковке 51.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-008",
"role": "user",
"text": "Хорошо, а теперь платежи по нему тоже покажи.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-009",
"role": "user",
"text": "А по нему договоры?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-010",
"role": "user",
"text": "А по нему документы?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-011",
"role": "user",
"text": "А за 2021?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-012",
"role": "user",
"text": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-013",
"role": "user",
"text": "По ООО Альтернатива Плюс.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-014",
"role": "user",
"text": "Понял, тогда за все время.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-015",
"role": "user",
"text": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-016",
"role": "user",
"text": "И кто больше всего принес денег этой организации в 2020 году?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-017",
"role": "user",
"text": "А в 2021 году?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-018",
"role": "user",
"text": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-019",
"role": "user",
"text": "Сколько получили по нему за 2020 год?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-020",
"role": "user",
"text": "А теперь сколько заплатили?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-021",
"role": "user",
"text": "А какое нетто?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-022",
"role": "user",
"text": "А по документам?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-023",
"role": "user",
"text": "А по движениям?",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
},
{
"message_id": "agent-user-024",
"role": "user",
"text": "А теперь тот же смысл за 2021 год.",
"created_at": "2026-04-24T16:10:22+00:00",
"reply_type": null,
"trace_id": null,
"debug": null
}
],
"agent_run": true,
"metadata": {
"assistant_prompt_version": null,
"decomposition_prompt_version": null,
"prompt_fingerprint": null,
"agent_focus": "live7: VAT metadata scoped org/document pivots, ranking year switches, fresh SVK reset",
"architecture_phase": "Post-F Semantic Integrity Hardening",
"source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_post_f_cross_stage_canary_agent_20260424.json",
"scenario_id": "address_truth_harness_post_f_cross_stage_canary_agent_20260424",
"semantic_tags": [
"account_injection_guard",
"all_time_followup",
"bidirectional_value_flow",
"clarification_resume",
"contracts_followup",
"counterparty_pronoun_resolution",
"cross_stage_canary",
"document_lane_continuity",
"document_pivot_after_movement",
"documents_followup",
"documents_pivot",
"grounded_counterparty",
"incoming_value_flow",
"legacy_phase39",
"legacy_phase64",
"legacy_phase67",
"metadata_surface",
"movement_execution",
"movement_lane_after_metadata",
"movements_pivot",
"net_value_flow",
"numeric_counterparty_suffix",
"open_scope_total",
"organization_clarification",
"organization_scope",
"payments_followup",
"payout_value_flow",
"period_clarification_resume",
"post_f",
"repeated_pivot",
"scope_reuse",
"topic_reset",
"value_flow_ranking",
"vat_orientation",
"year_switch",
"year_switch_after_document_pivot",
"year_switch_after_repeated_pivot"
]
}
}
}
@@ -0,0 +1,97 @@
{
"suite_id": "assistant_saved_session_gen-ag04241610-84c8bb",
"suite_version": "0.1.0",
"schema_version": "assistant_saved_session_suite_v0_1",
"generated_at": "2026-04-24T16:10:22+00:00",
"generation_id": "gen-ag04241610-84c8bb",
"mode": "saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary live7",
"domain": "address_post_f_cross_stage_canary_agent",
"scenario_count": 1,
"case_ids": [
"SAVED-001"
],
"cases": [
{
"case_id": "SAVED-001",
"scenario_tag": "agent_saved_user_sessions",
"title": "AGENT | Post-F cross-stage semantic integrity canary live7",
"question_type": "followup",
"broadness_level": "medium",
"turns": [
{
"user_message": "Мне нужно понять, где в 1С по НДС вообще лежат данные. Какие объекты стоит смотреть по НДС?"
},
{
"user_message": "Хорошо, тогда покажи движения по ООО Альтернатива Плюс."
},
{
"user_message": "За 2020 год."
},
{
"user_message": "А теперь по документам?"
},
{
"user_message": "А теперь за 2021 год?"
},
{
"user_message": "А теперь за все время?"
},
{
"user_message": "С НДС закончили. Новая тема: покажи документы по Жуковке 51."
},
{
"user_message": "Хорошо, а теперь платежи по нему тоже покажи."
},
{
"user_message": "А по нему договоры?"
},
{
"user_message": "А по нему документы?"
},
{
"user_message": "А за 2021?"
},
{
"user_message": "С Жуковкой закончили. Теперь другая задача: быстрый денежный срез по одной организации. Если для ответа нужна организация, просто уточни ее. Сколько вообще входящих денег было за 2020 год?"
},
{
"user_message": "По ООО Альтернатива Плюс."
},
{
"user_message": "Понял, тогда за все время."
},
{
"user_message": "Хорошо. А что по ООО Альтернатива Плюс больше в 2020 году: входящие или исходящие деньги?"
},
{
"user_message": "И кто больше всего принес денег этой организации в 2020 году?"
},
{
"user_message": "А в 2021 году?"
},
{
"user_message": "Теперь отдельная тема по конкретному контрагенту. Найди в 1С Группу СВК."
},
{
"user_message": "Сколько получили по нему за 2020 год?"
},
{
"user_message": "А теперь сколько заплатили?"
},
{
"user_message": "А какое нетто?"
},
{
"user_message": "А по документам?"
},
{
"user_message": "А по движениям?"
},
{
"user_message": "А теперь тот же смысл за 2021 год."
}
]
}
]
}