АРЧ АП11 - Архитектура после регресса: Архитектура: централизовать organization authority в continuity policy для route, living-chat и data-scope

This commit is contained in:
2026-04-18 16:53:04 +03:00
parent 53e9151d5a
commit 9710d81676
16 changed files with 575 additions and 366 deletions
@@ -1,3 +1,8 @@
import {
mergeKnownOrganizations as mergeKnownOrganizationsFromMatcher,
normalizeOrganizationScopeValue as normalizeOrganizationScopeValueFromMatcher
} from "./assistantOrganizationMatcher";
export interface AssistantContinuitySnapshotInput {
sessionItems?: unknown[];
toNonEmptyString?: (value: unknown) => string | null;
@@ -22,6 +27,29 @@ export interface AssistantContinuitySnapshot {
hasGroundedInventoryContext: boolean;
}
export interface AssistantOrganizationAuthorityInput {
sessionItems?: unknown[];
sessionKnownOrganizations?: unknown[];
sessionSelectedOrganization?: unknown;
sessionActiveOrganization?: unknown;
lastOrganizationClarificationDebug?: Record<string, unknown> | null;
toNonEmptyString?: (value: unknown) => string | null;
normalizeOrganizationScopeValue?: (value: unknown) => string | null;
mergeKnownOrganizations?: (values: unknown[]) => string[];
}
export interface AssistantOrganizationAuthority {
continuitySnapshot: AssistantContinuitySnapshot;
continuityActiveOrganization: string | null;
lastAssistantSelectedOrganization: string | null;
lastAssistantActiveOrganization: string | null;
knownOrganizations: string[];
selectedOrganization: string | null;
activeOrganization: string | null;
organizationClarificationCandidates: string[];
organizationClarificationSelectionFromScope: string | null;
}
function fallbackToNonEmptyString(value: unknown): string | null {
if (value === null || value === undefined) {
return null;
@@ -37,6 +65,14 @@ function toRecordObject(value: unknown): Record<string, unknown> | null {
return value as Record<string, unknown>;
}
function mergeKnownOrganizationsDefault(values: unknown[]): string[] {
return mergeKnownOrganizationsFromMatcher(values);
}
function normalizeOrganizationScopeDefault(value: unknown): string | null {
return normalizeOrganizationScopeValueFromMatcher(value);
}
export function formatIsoDateForReply(value: unknown): string | null {
const source = String(value ?? "").trim();
const match = source.match(/^(\d{4})-(\d{2})-(\d{2})$/);
@@ -228,3 +264,113 @@ export function resolveAssistantContinuitySnapshot(
hasGroundedInventoryContext: Boolean(lastGroundedInventoryAddressDebug)
};
}
function collectAssistantOrganizationSignals(
sessionItems: unknown[],
normalizeOrganizationScopeValue: (value: unknown) => string | null
): {
lastAssistantSelectedOrganization: string | null;
lastAssistantActiveOrganization: string | null;
knownOrganizationsFromAssistantHistory: string[];
} {
let lastAssistantSelectedOrganization: string | null = null;
let lastAssistantActiveOrganization: string | null = null;
const knownOrganizationsFromAssistantHistory: unknown[] = [];
for (let index = sessionItems.length - 1; index >= 0; index -= 1) {
const item = sessionItems[index] as { role?: string; debug?: Record<string, unknown> } | null;
if (!item || item.role !== "assistant" || !item.debug || typeof item.debug !== "object") {
continue;
}
const debug = item.debug;
if (!lastAssistantActiveOrganization) {
lastAssistantActiveOrganization = normalizeOrganizationScopeValue(debug.assistant_active_organization);
}
if (!lastAssistantSelectedOrganization) {
lastAssistantSelectedOrganization = normalizeOrganizationScopeValue(debug.living_chat_selected_organization);
}
const directKnownOrganizations = Array.isArray(debug.assistant_known_organizations)
? debug.assistant_known_organizations
: [];
const probeOrganizations = Array.isArray(debug.living_chat_data_scope_probe_organizations)
? debug.living_chat_data_scope_probe_organizations
: [];
const organizationCandidates = Array.isArray(debug.organization_candidates) ? debug.organization_candidates : [];
const directResolvedOrganizations = [
debug.assistant_active_organization,
debug.living_chat_selected_organization,
readAddressDebugOrganization(debug, normalizeOrganizationScopeValue)
];
knownOrganizationsFromAssistantHistory.push(
...directKnownOrganizations,
...probeOrganizations,
...organizationCandidates,
...directResolvedOrganizations
);
}
return {
lastAssistantSelectedOrganization,
lastAssistantActiveOrganization,
knownOrganizationsFromAssistantHistory: mergeKnownOrganizationsDefault(knownOrganizationsFromAssistantHistory)
};
}
export function resolveAssistantOrganizationAuthority(
input: AssistantOrganizationAuthorityInput
): AssistantOrganizationAuthority {
const normalizeOrganizationScopeValue =
input.normalizeOrganizationScopeValue ?? normalizeOrganizationScopeDefault;
const mergeKnownOrganizations = input.mergeKnownOrganizations ?? mergeKnownOrganizationsDefault;
const sessionItems = Array.isArray(input.sessionItems) ? input.sessionItems : [];
const continuitySnapshot = resolveAssistantContinuitySnapshot({
sessionItems,
toNonEmptyString: input.toNonEmptyString ?? normalizeOrganizationScopeValue
});
const assistantSignals = collectAssistantOrganizationSignals(sessionItems, normalizeOrganizationScopeValue);
const continuityActiveOrganization =
normalizeOrganizationScopeValue(input.sessionActiveOrganization) ??
assistantSignals.lastAssistantActiveOrganization ??
normalizeOrganizationScopeValue(continuitySnapshot.activeOrganization);
const knownOrganizations = mergeKnownOrganizations([
...(Array.isArray(input.sessionKnownOrganizations) ? input.sessionKnownOrganizations : []),
...assistantSignals.knownOrganizationsFromAssistantHistory,
input.sessionSelectedOrganization,
input.sessionActiveOrganization,
continuityActiveOrganization
]);
const selectedOrganization =
normalizeOrganizationScopeValue(input.sessionSelectedOrganization) ??
assistantSignals.lastAssistantSelectedOrganization;
const activeOrganization =
normalizeOrganizationScopeValue(input.sessionActiveOrganization) ??
selectedOrganization ??
assistantSignals.lastAssistantActiveOrganization ??
continuityActiveOrganization ??
(knownOrganizations.length === 1 ? knownOrganizations[0] : null);
const organizationClarificationCandidates = Array.isArray(input.lastOrganizationClarificationDebug?.organization_candidates)
? mergeKnownOrganizations([
...input.lastOrganizationClarificationDebug.organization_candidates,
...knownOrganizations,
selectedOrganization,
activeOrganization,
continuityActiveOrganization
])
: [];
const organizationClarificationSelectionFromScope = selectedOrganization ?? activeOrganization;
return {
continuitySnapshot,
continuityActiveOrganization,
lastAssistantSelectedOrganization: assistantSignals.lastAssistantSelectedOrganization,
lastAssistantActiveOrganization: assistantSignals.lastAssistantActiveOrganization,
knownOrganizations,
selectedOrganization,
activeOrganization,
organizationClarificationCandidates,
organizationClarificationSelectionFromScope
};
}
@@ -4,9 +4,7 @@ import {
normalizeOrganizationScopeValue
} from "./assistantOrganizationMatcher";
import {
resolveAssistantContinuitySnapshot,
isGroundedAddressDebug,
readAddressDebugOrganization
resolveAssistantOrganizationAuthority
} from "./assistantContinuityPolicy";
const DATA_SCOPE_CACHE_TTL_MS = 60_000;
@@ -297,40 +295,13 @@ export function createAssistantDataScopePolicy(deps: AssistantDataScopePolicyDep
}
function findLastAssistantActiveOrganization(items: unknown[]): string | null {
const continuitySnapshot = resolveAssistantContinuitySnapshot({
const authority = resolveAssistantOrganizationAuthority({
sessionItems: items,
toNonEmptyString: normalizeOrganizationScopeValue
toNonEmptyString: normalizeOrganizationScopeValue,
normalizeOrganizationScopeValue,
mergeKnownOrganizations
});
const continuityOrganization = normalizeOrganizationScopeValue(continuitySnapshot.activeOrganization);
for (let index = (Array.isArray(items) ? items.length : 0) - 1; index >= 0; index -= 1) {
const item = Array.isArray(items) ? items[index] : null;
if (!item || typeof item !== "object" || (item as { role?: string }).role !== "assistant") {
continue;
}
const debug = (item as { debug?: Record<string, unknown> }).debug;
if (!debug || typeof debug !== "object") {
continue;
}
const direct = normalizeOrganizationScopeValue(debug.assistant_active_organization);
if (direct) {
return direct;
}
const selected = normalizeOrganizationScopeValue(debug.living_chat_selected_organization);
if (selected) {
return selected;
}
if (isGroundedAddressDebug(debug)) {
const groundedOrganization = normalizeOrganizationScopeValue(readAddressDebugOrganization(debug));
if (groundedOrganization) {
return groundedOrganization;
}
}
}
return continuityOrganization;
return normalizeOrganizationScopeValue(authority.activeOrganization);
}
function extractOrganizationFactsFromRows(rows: unknown[]): AssistantOrganizationFacts {
@@ -3,7 +3,7 @@ import {
buildInventoryHistoryCapabilityFollowupReply as buildInventoryHistoryCapabilityFollowupReplyFromPolicy,
resolveAssistantLivingChatMemoryContext
} from "./assistantMemoryRecapPolicy";
import { resolveAssistantContinuitySnapshot } from "./assistantContinuityPolicy";
import { resolveAssistantOrganizationAuthority } from "./assistantContinuityPolicy";
export interface AssistantLivingChatSessionScopeInput {
knownOrganizations?: unknown[];
@@ -249,10 +249,18 @@ export async function runAssistantLivingChatRuntime(
input: AssistantLivingChatRuntimeInput
): Promise<AssistantLivingChatRuntimeOutput> {
const userMessage = String(input.userMessage ?? "");
const continuitySnapshot = resolveAssistantContinuitySnapshot({
const organizationAuthority = resolveAssistantOrganizationAuthority({
sessionItems: input.sessionItems,
toNonEmptyString: input.toNonEmptyString
sessionKnownOrganizations: Array.isArray(input.sessionScope.knownOrganizations)
? input.sessionScope.knownOrganizations
: [],
sessionSelectedOrganization: input.sessionScope.selectedOrganization,
sessionActiveOrganization: input.sessionScope.activeOrganization,
toNonEmptyString: input.toNonEmptyString,
normalizeOrganizationScopeValue: input.toNonEmptyString,
mergeKnownOrganizations: input.mergeKnownOrganizations
});
const continuitySnapshot = organizationAuthority.continuitySnapshot;
const dataScopeMetaQuery = input.hasAssistantDataScopeMetaQuestionSignal(userMessage);
const capabilityMetaQuery = input.shouldHandleAsAssistantCapabilityMetaQuery(userMessage);
const destructiveSignal = input.hasDestructiveDataActionSignal(userMessage);
@@ -267,13 +275,10 @@ export async function runAssistantLivingChatRuntime(
let livingChatGroundingGuardApplied = false;
let livingChatGroundingGuardReason: string | null = null;
let livingChatProactiveScopeOfferApplied = false;
const continuityActiveOrganization = input.toNonEmptyString(continuitySnapshot.activeOrganization);
let knownOrganizations = input.mergeKnownOrganizations([
...(Array.isArray(input.sessionScope.knownOrganizations) ? input.sessionScope.knownOrganizations : []),
continuityActiveOrganization
]);
let selectedOrganization = input.toNonEmptyString(input.sessionScope.selectedOrganization);
let activeOrganization = input.toNonEmptyString(input.sessionScope.activeOrganization) ?? continuityActiveOrganization;
const continuityActiveOrganization = organizationAuthority.continuityActiveOrganization;
let knownOrganizations = [...organizationAuthority.knownOrganizations];
let selectedOrganization = organizationAuthority.selectedOrganization;
let activeOrganization = organizationAuthority.activeOrganization;
const memoryRecapContext = resolveAssistantLivingChatMemoryContext({
modeDecisionReason: input.modeDecision?.reason ?? null,
sessionItems: input.sessionItems
@@ -1,5 +1,7 @@
// @ts-nocheck
import { resolveAssistantContinuitySnapshot } from "./assistantContinuityPolicy";
import {
resolveAssistantOrganizationAuthority
} from "./assistantContinuityPolicy";
const ADDRESS_INTENTS_KEEP_ADDRESS_LANE = new Set([
"period_coverage_profile",
@@ -109,26 +111,25 @@ export function createAssistantRoutePolicy(deps) {
const sessionOrganizationScope = input?.sessionOrganizationScope && typeof input.sessionOrganizationScope === "object"
? input.sessionOrganizationScope
: null;
const continuitySnapshot = resolveAssistantContinuitySnapshot({
const organizationAuthority = resolveAssistantOrganizationAuthority({
sessionItems,
toNonEmptyString
sessionKnownOrganizations: Array.isArray(sessionOrganizationScope?.knownOrganizations)
? sessionOrganizationScope.knownOrganizations
: [],
sessionSelectedOrganization: sessionOrganizationScope?.selectedOrganization,
sessionActiveOrganization: sessionOrganizationScope?.activeOrganization,
lastOrganizationClarificationDebug: findLastOrganizationClarificationAddressDebug(sessionItems),
toNonEmptyString,
normalizeOrganizationScopeValue,
mergeKnownOrganizations
});
const continuityActiveOrganization = normalizeOrganizationScopeValue(sessionOrganizationScope?.activeOrganization) ??
normalizeOrganizationScopeValue(continuitySnapshot.activeOrganization);
const continuitySnapshot = organizationAuthority.continuitySnapshot;
const continuityActiveOrganization = organizationAuthority.continuityActiveOrganization;
const lastGroundedAddressDebug = findLastGroundedAddressAnswerDebug(sessionItems) ??
continuitySnapshot.lastGroundedAddressDebug;
const lastOrganizationClarificationDebug = findLastOrganizationClarificationAddressDebug(sessionItems);
const organizationClarificationCandidates = Array.isArray(lastOrganizationClarificationDebug?.organization_candidates)
? mergeKnownOrganizations([
...lastOrganizationClarificationDebug.organization_candidates,
...((Array.isArray(sessionOrganizationScope?.knownOrganizations)
? sessionOrganizationScope.knownOrganizations
: [])),
continuityActiveOrganization
])
: [];
const organizationClarificationSelectionFromScope = normalizeOrganizationScopeValue(sessionOrganizationScope?.selectedOrganization) ??
continuityActiveOrganization;
const organizationClarificationCandidates = organizationAuthority.organizationClarificationCandidates;
const organizationClarificationSelectionFromScope = organizationAuthority.organizationClarificationSelectionFromScope;
const organizationClarificationSelection = resolveOrganizationSelectionFromMessage(rawUserMessage, organizationClarificationCandidates) ??
resolveOrganizationSelectionFromMessage(repairedRawUserMessage, organizationClarificationCandidates) ??
resolveOrganizationSelectionFromMessage(effectiveAddressUserMessage, organizationClarificationCandidates) ??