ГЛОБАЛЬНЫЙ РЕФАКТОРИНГ АРХИТЕКТУРЫ - Архитектурный фундамент: capability-guard, navigation state и трассируемый route-контракт
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
import {
|
||||
FEATURE_ASSISTANT_CAPABILITY_ROUTE_GUARD_V1,
|
||||
FEATURE_ASSISTANT_ADDRESS_QUERY_V1,
|
||||
FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1
|
||||
} from "../config";
|
||||
import type {
|
||||
AddressCapabilityLayer,
|
||||
AddressCapabilityRouteMode,
|
||||
AddressShadowRouteStatus,
|
||||
AddressAsOfDateBasis,
|
||||
AddressEvidenceStrength,
|
||||
AddressExecutionResult,
|
||||
@@ -25,6 +29,11 @@ import { executeAddressMcpQuery } from "./addressMcpClient";
|
||||
import { runAddressDecomposeStage, type AddressFollowupContext } from "./address_runtime/decomposeStage";
|
||||
import { resolvePrimaryAnchor, refineAnchorFromRows, type AnchorResolutionDebug } from "./address_runtime/resolveStage";
|
||||
import { composeFactualReply, inferReplyType, type ComposeReplySemantics } from "./address_runtime/composeStage";
|
||||
import {
|
||||
isCapabilityRouteBlocked,
|
||||
resolveAddressCapabilityRouteDecision,
|
||||
resolveShadowRouteIntent
|
||||
} from "./addressCapabilityPolicy";
|
||||
|
||||
interface NormalizedAddressRow {
|
||||
period: string | null;
|
||||
@@ -40,6 +49,20 @@ interface AddressTryHandleOptions {
|
||||
analysisDateHint?: string | null;
|
||||
}
|
||||
|
||||
interface AddressCapabilityAudit {
|
||||
capabilityId: string;
|
||||
layer: AddressCapabilityLayer;
|
||||
routeMode: AddressCapabilityRouteMode;
|
||||
enabled: boolean;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
interface AddressShadowRouteAudit {
|
||||
intent: AddressIntent | null;
|
||||
selectedRecipe: string | null;
|
||||
status: AddressShadowRouteStatus;
|
||||
}
|
||||
|
||||
const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"] as const;
|
||||
const ACCOUNT_SCOPE_MATCH_STRATEGY = "account_code_regex_plus_alias_map_v1" as const;
|
||||
const ADDRESS_ANCHOR_RECOVERY_LIMIT = 1000;
|
||||
@@ -904,6 +927,45 @@ function withConfirmedBalanceFallbackReason(
|
||||
return [...reasons, "confirmed_balance_unavailable_fallback_to_heuristic_candidates"];
|
||||
}
|
||||
|
||||
function buildCapabilityAudit(intent: AddressIntent): AddressCapabilityAudit {
|
||||
const decision = resolveAddressCapabilityRouteDecision(intent);
|
||||
return {
|
||||
capabilityId: decision.capability_id,
|
||||
layer: decision.capability_layer,
|
||||
routeMode: decision.capability_route_mode,
|
||||
enabled: decision.capability_route_enabled,
|
||||
reason: decision.capability_route_reason
|
||||
};
|
||||
}
|
||||
|
||||
function buildShadowRouteAudit(input: {
|
||||
intent: AddressIntent;
|
||||
requestedResultMode: AddressResultMode | undefined;
|
||||
filters: AddressFilterSet;
|
||||
}): AddressShadowRouteAudit {
|
||||
const shadowIntent = resolveShadowRouteIntent(input.intent, input.requestedResultMode);
|
||||
if (!shadowIntent) {
|
||||
return {
|
||||
intent: null,
|
||||
selectedRecipe: null,
|
||||
status: "skipped"
|
||||
};
|
||||
}
|
||||
const shadowRecipeSelection = selectAddressRecipe(shadowIntent, input.filters);
|
||||
if (!shadowRecipeSelection.selected_recipe) {
|
||||
return {
|
||||
intent: shadowIntent,
|
||||
selectedRecipe: null,
|
||||
status: "unavailable"
|
||||
};
|
||||
}
|
||||
return {
|
||||
intent: shadowIntent,
|
||||
selectedRecipe: shadowRecipeSelection.selected_recipe.recipe_id,
|
||||
status: "planned"
|
||||
};
|
||||
}
|
||||
|
||||
function enforceStrictAccountScopeForIntent(
|
||||
plan: AddressRecipeExecutionPlan,
|
||||
intent: AddressIntent
|
||||
@@ -1701,6 +1763,8 @@ function buildLimitedExecutionResult(input: {
|
||||
reasonText: string;
|
||||
nextStep?: string;
|
||||
category: AddressLimitedReasonCategory;
|
||||
capabilityAudit?: AddressCapabilityAudit;
|
||||
shadowRouteAudit?: AddressShadowRouteAudit;
|
||||
}): AddressExecutionResult {
|
||||
const accountScopeAudit = input.accountScopeAudit ?? buildDefaultAccountScopeAudit(input.filters);
|
||||
const resultSemantics = deriveAddressResultSemantics({
|
||||
@@ -1772,6 +1836,14 @@ function buildLimitedExecutionResult(input: {
|
||||
runtime_readiness: runtimeReadinessForLimitedCategory(input.category),
|
||||
limited_reason_category: input.category,
|
||||
response_type: "LIMITED_WITH_REASON",
|
||||
capability_id: input.capabilityAudit?.capabilityId ?? null,
|
||||
capability_layer: input.capabilityAudit?.layer ?? null,
|
||||
capability_route_mode: input.capabilityAudit?.routeMode ?? null,
|
||||
capability_route_enabled: input.capabilityAudit?.enabled ?? true,
|
||||
capability_route_reason: input.capabilityAudit?.reason ?? null,
|
||||
shadow_route_intent: input.shadowRouteAudit?.intent ?? null,
|
||||
shadow_route_selected_recipe: input.shadowRouteAudit?.selectedRecipe ?? null,
|
||||
shadow_route_status: input.shadowRouteAudit?.status ?? "skipped",
|
||||
...resultSemantics,
|
||||
limitations: input.limitations,
|
||||
reasons
|
||||
@@ -1826,6 +1898,36 @@ export class AddressQueryService {
|
||||
baseReasons.push("as_of_date_derived_for_confirmed_payables");
|
||||
}
|
||||
}
|
||||
const capabilityDecision = resolveAddressCapabilityRouteDecision(intent.intent);
|
||||
const capabilityAudit = buildCapabilityAudit(intent.intent);
|
||||
const shadowRouteAudit = buildShadowRouteAudit({
|
||||
intent: intent.intent,
|
||||
requestedResultMode,
|
||||
filters: executionFilters
|
||||
});
|
||||
if (isCapabilityRouteBlocked(capabilityDecision)) {
|
||||
return buildLimitedExecutionResult({
|
||||
mode,
|
||||
shape,
|
||||
intent,
|
||||
filters: executionFilters,
|
||||
missingRequiredFilters: [],
|
||||
selectedRecipe: null,
|
||||
mcpCallStatus: "skipped",
|
||||
rowsFetched: 0,
|
||||
rowsMatched: 0,
|
||||
category: "unsupported",
|
||||
reasonText: "маршрут capability временно отключен feature-флагом",
|
||||
nextStep: "включите capability route или используйте соседний поддерживаемый сценарий",
|
||||
limitations: ["capability_route_disabled_by_flag"],
|
||||
reasons: [
|
||||
...baseReasons,
|
||||
FEATURE_ASSISTANT_CAPABILITY_ROUTE_GUARD_V1 ? "capability_route_guard_blocked" : "capability_route_guard_skipped"
|
||||
],
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
const composeOptionsFromFilters = (filterSet: AddressFilterSet) => ({
|
||||
userMessage,
|
||||
periodFrom: typeof filterSet.period_from === "string" ? filterSet.period_from : undefined,
|
||||
@@ -1889,7 +1991,9 @@ export class AddressQueryService {
|
||||
reasonText: "сценарий пока вне поддерживаемого контура текущего адресного режима",
|
||||
nextStep: "могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету",
|
||||
limitations: ["intent_not_supported_in_v1"],
|
||||
reasons: baseReasons
|
||||
reasons: baseReasons,
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1909,7 +2013,9 @@ export class AddressQueryService {
|
||||
reasonText: "для этого сценария пока нет готового шаблона выборки в текущем режиме",
|
||||
nextStep: "можно выбрать близкий поддерживаемый сценарий или переключить запрос в режим расширенной проверки",
|
||||
limitations: ["recipe_not_available"],
|
||||
reasons: [...baseReasons, ...recipeSelection.selection_reason]
|
||||
reasons: [...baseReasons, ...recipeSelection.selection_reason],
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1929,7 +2035,9 @@ export class AddressQueryService {
|
||||
reasonText: "не хватает обязательных фильтров",
|
||||
nextStep: `уточните: ${recipeSelection.missing_required_filters.join(", ")}`,
|
||||
limitations: ["missing_required_filters"],
|
||||
reasons: [...baseReasons, ...recipeSelection.selection_reason]
|
||||
reasons: [...baseReasons, ...recipeSelection.selection_reason],
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1949,7 +2057,9 @@ export class AddressQueryService {
|
||||
reasonText: "live address lane выключен feature-флагом",
|
||||
nextStep: "включите FEATURE_ASSISTANT_ADDRESS_QUERY_LIVE_V1",
|
||||
limitations: ["address_live_lane_disabled"],
|
||||
reasons: baseReasons
|
||||
reasons: baseReasons,
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2087,7 +2197,9 @@ export class AddressQueryService {
|
||||
reasonText: "live MCP вызов завершился ошибкой",
|
||||
nextStep: mcp.error,
|
||||
limitations: ["mcp_call_failed"],
|
||||
reasons: [...baseReasons, mcp.error]
|
||||
reasons: [...baseReasons, mcp.error],
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2862,7 +2974,9 @@ export class AddressQueryService {
|
||||
reasonText,
|
||||
nextStep,
|
||||
limitations,
|
||||
reasons: baseReasons
|
||||
reasons: baseReasons,
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2904,7 +3018,9 @@ export class AddressQueryService {
|
||||
reasonText: "exact payables mode: confirmed balance was not proven for the requested as-of slice",
|
||||
nextStep: "specify as_of_date/counterparty or enable detailed settlement registers for exact confirmed balance",
|
||||
limitations: ["exact_payables_mode_unconfirmed_output_blocked"],
|
||||
reasons: [...baseReasons, "exact_payables_mode_unconfirmed_output_blocked"]
|
||||
reasons: [...baseReasons, "exact_payables_mode_unconfirmed_output_blocked"],
|
||||
capabilityAudit,
|
||||
shadowRouteAudit
|
||||
});
|
||||
}
|
||||
return {
|
||||
@@ -2949,6 +3065,14 @@ export class AddressQueryService {
|
||||
runtime_readiness: "LIVE_QUERYABLE_WITH_LIMITS",
|
||||
limited_reason_category: null,
|
||||
response_type: factual.responseType,
|
||||
capability_id: capabilityAudit.capabilityId,
|
||||
capability_layer: capabilityAudit.layer,
|
||||
capability_route_mode: capabilityAudit.routeMode,
|
||||
capability_route_enabled: capabilityAudit.enabled,
|
||||
capability_route_reason: capabilityAudit.reason,
|
||||
shadow_route_intent: shadowRouteAudit.intent,
|
||||
shadow_route_selected_recipe: shadowRouteAudit.selectedRecipe,
|
||||
shadow_route_status: shadowRouteAudit.status,
|
||||
...factualResultSemantics,
|
||||
limitations: filters.warnings,
|
||||
reasons: withConfirmedBalanceFallbackReason(
|
||||
|
||||
Reference in New Issue
Block a user