Архитектура: централизовать inventory pivot flags и carryover target intent в continuity policy

This commit is contained in:
dctouch 2026-04-19 12:53:15 +03:00
parent 15662cfe4f
commit d1ad8e2c1b
6 changed files with 207 additions and 76 deletions

View File

@ -461,6 +461,11 @@ Still open after the accepted phase12 replay:
- the shared retarget contract is now explicitly UTF-8-safe, which matters because a broken regex encoding in the first extraction attempt silently collapsed short phrases like `покажи договоры по СВК` back into stale previous-intent carryover;
- `assistantTransitionPolicy` no longer keeps a second local `resolveDisplayedEntityRetargetIntent(...)` owner beside the shared continuity helper, which reduces another chance that future follow-up fixes land in dead code or diverge between route retarget and anchor hydration;
- targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move, and a fresh live rerun of `address_truth_harness_phase11_manual_followup_meta_quality` on `2026-04-19` is accepted `10/10`, including the short displayed-counterparty retarget step.
- the next continuity-authority pass now centralizes one more dense decision block from the transition hot path:
- continuity now owns `resolveInventoryFollowupPivotFlags(...)`, so inventory root pivots and explicit same-date pivots are no longer computed by a local boolean cascade inside `assistantTransitionPolicy`;
- continuity now also owns `resolveFollowupTargetIntent(...)`, so `carryover target intent` precedence for purchase-date VAT bridge, selected-object retarget, root-context carryover, same-date pivot, displayed-entity retarget, and plain previous-intent fallback is expressed in one shared helper instead of an inline ternary tower;
- this matters because root-pivot semantics and target-intent precedence are among the heaviest remaining orchestration decisions in the follow-up path, and keeping them under one shared continuity layer reduces another chance that future domain expansion reintroduces drift between carryover state and target route selection;
- targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move, and a fresh live rerun of `address_truth_harness_phase12_wider_saved_session_pool` on `2026-04-19` is accepted `20/20`, which is the critical proof that the flagship mixed replay still survives after the decision-block extraction.
## Next Execution Slice (2026-04-18)

View File

@ -11,6 +11,8 @@ exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
exports.resolveAddressDebugCarryoverFilters = resolveAddressDebugCarryoverFilters;
exports.hydrateInventoryRootFrameState = hydrateInventoryRootFrameState;
exports.buildRootScopedCarryoverFilters = buildRootScopedCarryoverFilters;
exports.resolveInventoryFollowupPivotFlags = resolveInventoryFollowupPivotFlags;
exports.resolveFollowupTargetIntent = resolveFollowupTargetIntent;
exports.shouldUseNavigationTemporalCarryover = shouldUseNavigationTemporalCarryover;
exports.applyTemporalCarryoverFilters = applyTemporalCarryoverFilters;
exports.applyOrganizationCarryoverFilters = applyOrganizationCarryoverFilters;
@ -241,6 +243,49 @@ function buildRootScopedCarryoverFilters(previousFilters, inventoryRootFrame, to
}
return nextFilters;
}
function resolveInventoryFollowupPivotFlags(inventoryRootFrame, sourceIntentHint, currentFrameKind, hasForeignAccountingPivotMessage, inventoryPurchaseDateVatBridge, hasInventoryRootTemporalFollowupPrimary, hasInventoryRootTemporalFollowupAlternate, hasInventoryRootRestatementPrimary, hasInventoryRootRestatementAlternate, hasExplicitInventorySameDatePivotPrimary, hasExplicitInventorySameDatePivotAlternate, isInventorySelectedObjectIntent, isInventoryRootFrameIntent) {
const rootContextOnlyPivot = Boolean((isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
hasForeignAccountingPivotMessage &&
!inventoryPurchaseDateVatBridge);
const inventoryRootTemporalPivot = Boolean(inventoryRootFrame &&
(isInventorySelectedObjectIntent(sourceIntentHint) ||
isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root") &&
(hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate) &&
!hasForeignAccountingPivotMessage);
const inventoryRootRestatementPivot = Boolean(inventoryRootFrame &&
(isInventorySelectedObjectIntent(sourceIntentHint) ||
isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root" ||
currentFrameKind === "generic") &&
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
!hasForeignAccountingPivotMessage);
const explicitInventorySameDatePivot = Boolean(!inventoryRootFrame &&
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
!hasForeignAccountingPivotMessage);
return {
rootScopedPivot: rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot,
explicitInventorySameDatePivot
};
}
function resolveFollowupTargetIntent(inventoryPurchaseDateVatBridge, selectedObjectRetargetIntent, explicitIntent, sourceIntent, followupSelectionMode, inventoryRootFrameIntent, displayedEntityTargetIntent, previousIntent, explicitInventorySameDatePivot) {
if (inventoryPurchaseDateVatBridge) {
return "vat_liability_confirmed_for_tax_period";
}
if (selectedObjectRetargetIntent &&
(explicitIntent === null || explicitIntent === "inventory_on_hand_as_of_date" || explicitIntent === sourceIntent)) {
return selectedObjectRetargetIntent;
}
if (followupSelectionMode === "carry_root_context") {
return inventoryRootFrameIntent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
}
if (explicitInventorySameDatePivot) {
return "inventory_on_hand_as_of_date";
}
return displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
}
function shouldUseNavigationTemporalCarryover(sourceIntentHint) {
const normalizedIntent = fallbackToNonEmptyString(sourceIntentHint);
return (normalizedIntent === "inventory_on_hand_as_of_date" ||

View File

@ -591,28 +591,7 @@ function createAssistantTransitionPolicy(deps) {
}
}
previousFilters = (0, assistantContinuityPolicy_1.applyTemporalCarryoverFilters)(previousFilters, navigationDateScope, continuityTemporalScope, sourceIntentHint, deps.toNonEmptyString);
const rootContextOnlyPivot = Boolean((deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
!inventoryPurchaseDateVatBridge);
const inventoryRootTemporalPivot = Boolean(inventoryRootFrame &&
(deps.isInventorySelectedObjectIntent(sourceIntentHint) ||
deps.isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root") &&
(hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate) &&
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
const inventoryRootRestatementPivot = Boolean(inventoryRootFrame &&
(deps.isInventorySelectedObjectIntent(sourceIntentHint) ||
deps.isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root" ||
currentFrameKind === "generic") &&
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
const explicitInventorySameDatePivot = Boolean(!inventoryRootFrame &&
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage));
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
const { rootScopedPivot, explicitInventorySameDatePivot } = (0, assistantContinuityPolicy_1.resolveInventoryFollowupPivotFlags)(inventoryRootFrame, sourceIntentHint, currentFrameKind, deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage), inventoryPurchaseDateVatBridge, hasInventoryRootTemporalFollowupPrimary, hasInventoryRootTemporalFollowupAlternate, hasInventoryRootRestatementPrimary, hasInventoryRootRestatementAlternate, hasExplicitInventorySameDatePivotPrimary, hasExplicitInventorySameDatePivotAlternate, deps.isInventorySelectedObjectIntent, deps.isInventoryRootFrameIntent);
if (rootScopedPivot) {
previousIntent = null;
previousAnchorType = null;
@ -679,18 +658,7 @@ function createAssistantTransitionPolicy(deps) {
hasInventoryRootRestatementAlternate ||
hasSelectedObjectInventorySignalPrimary ||
hasSelectedObjectInventorySignalAlternate));
const carryoverTargetIntent = inventoryPurchaseDateVatBridge
? "vat_liability_confirmed_for_tax_period"
: selectedObjectRetargetIntent &&
(explicitIntent === null ||
explicitIntent === "inventory_on_hand_as_of_date" ||
explicitIntent === sourceIntent)
? selectedObjectRetargetIntent
: followupSelectionMode === "carry_root_context"
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
: explicitInventorySameDatePivot
? "inventory_on_hand_as_of_date"
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
const carryoverTargetIntent = (0, assistantContinuityPolicy_1.resolveFollowupTargetIntent)(inventoryPurchaseDateVatBridge, selectedObjectRetargetIntent, explicitIntent, sourceIntent, followupSelectionMode, deps.toNonEmptyString(inventoryRootFrame?.intent), displayedEntityTargetIntent, previousIntent, explicitInventorySameDatePivot);
return {
followupContext: {
previous_intent: previousIntent ?? undefined,

View File

@ -369,6 +369,93 @@ export function buildRootScopedCarryoverFilters(
return nextFilters;
}
export function resolveInventoryFollowupPivotFlags(
inventoryRootFrame:
| {
intent?: unknown;
}
| null
| undefined,
sourceIntentHint: string | null,
currentFrameKind: string | null,
hasForeignAccountingPivotMessage: boolean,
inventoryPurchaseDateVatBridge: boolean,
hasInventoryRootTemporalFollowupPrimary: boolean,
hasInventoryRootTemporalFollowupAlternate: boolean,
hasInventoryRootRestatementPrimary: boolean,
hasInventoryRootRestatementAlternate: boolean,
hasExplicitInventorySameDatePivotPrimary: boolean,
hasExplicitInventorySameDatePivotAlternate: boolean,
isInventorySelectedObjectIntent: (intent: unknown) => boolean,
isInventoryRootFrameIntent: (intent: unknown) => boolean
): {
rootScopedPivot: boolean;
explicitInventorySameDatePivot: boolean;
} {
const rootContextOnlyPivot = Boolean(
(isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
hasForeignAccountingPivotMessage &&
!inventoryPurchaseDateVatBridge
);
const inventoryRootTemporalPivot = Boolean(
inventoryRootFrame &&
(isInventorySelectedObjectIntent(sourceIntentHint) ||
isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root") &&
(hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate) &&
!hasForeignAccountingPivotMessage
);
const inventoryRootRestatementPivot = Boolean(
inventoryRootFrame &&
(isInventorySelectedObjectIntent(sourceIntentHint) ||
isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root" ||
currentFrameKind === "generic") &&
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
!hasForeignAccountingPivotMessage
);
const explicitInventorySameDatePivot = Boolean(
!inventoryRootFrame &&
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
!hasForeignAccountingPivotMessage
);
return {
rootScopedPivot: rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot,
explicitInventorySameDatePivot
};
}
export function resolveFollowupTargetIntent(
inventoryPurchaseDateVatBridge: boolean,
selectedObjectRetargetIntent: string | null,
explicitIntent: string | null,
sourceIntent: string | null,
followupSelectionMode: string | null,
inventoryRootFrameIntent: string | null,
displayedEntityTargetIntent: string | null,
previousIntent: string | null,
explicitInventorySameDatePivot: boolean
): string | undefined {
if (inventoryPurchaseDateVatBridge) {
return "vat_liability_confirmed_for_tax_period";
}
if (
selectedObjectRetargetIntent &&
(explicitIntent === null || explicitIntent === "inventory_on_hand_as_of_date" || explicitIntent === sourceIntent)
) {
return selectedObjectRetargetIntent;
}
if (followupSelectionMode === "carry_root_context") {
return inventoryRootFrameIntent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
}
if (explicitInventorySameDatePivot) {
return "inventory_on_hand_as_of_date";
}
return displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
}
export function shouldUseNavigationTemporalCarryover(sourceIntentHint: unknown): boolean {
const normalizedIntent = fallbackToNonEmptyString(sourceIntentHint);
return (

View File

@ -13,6 +13,8 @@ import {
resolveAddressDebugCarryoverFilters,
resolveAddressDebugAnchorContext,
resolveDisplayedEntityFollowupRetarget,
resolveFollowupTargetIntent,
resolveInventoryFollowupPivotFlags,
resolveAssistantOrganizationAuthority
} from "./assistantContinuityPolicy";
@ -773,36 +775,21 @@ export function createAssistantTransitionPolicy(deps) {
sourceIntentHint,
deps.toNonEmptyString
);
const rootContextOnlyPivot = Boolean(
(deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") &&
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) &&
!inventoryPurchaseDateVatBridge
const { rootScopedPivot, explicitInventorySameDatePivot } = resolveInventoryFollowupPivotFlags(
inventoryRootFrame,
sourceIntentHint,
currentFrameKind,
deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage),
inventoryPurchaseDateVatBridge,
hasInventoryRootTemporalFollowupPrimary,
hasInventoryRootTemporalFollowupAlternate,
hasInventoryRootRestatementPrimary,
hasInventoryRootRestatementAlternate,
hasExplicitInventorySameDatePivotPrimary,
hasExplicitInventorySameDatePivotAlternate,
deps.isInventorySelectedObjectIntent,
deps.isInventoryRootFrameIntent
);
const inventoryRootTemporalPivot = Boolean(
inventoryRootFrame &&
(deps.isInventorySelectedObjectIntent(sourceIntentHint) ||
deps.isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root") &&
(hasInventoryRootTemporalFollowupPrimary || hasInventoryRootTemporalFollowupAlternate) &&
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
);
const inventoryRootRestatementPivot = Boolean(
inventoryRootFrame &&
(deps.isInventorySelectedObjectIntent(sourceIntentHint) ||
deps.isInventoryRootFrameIntent(sourceIntentHint) ||
currentFrameKind === "inventory_drilldown" ||
currentFrameKind === "inventory_root" ||
currentFrameKind === "generic") &&
(hasInventoryRootRestatementPrimary || hasInventoryRootRestatementAlternate) &&
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
);
const explicitInventorySameDatePivot = Boolean(
!inventoryRootFrame &&
(hasExplicitInventorySameDatePivotPrimary || hasExplicitInventorySameDatePivotAlternate) &&
!deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage)
);
const rootScopedPivot = rootContextOnlyPivot || inventoryRootTemporalPivot || inventoryRootRestatementPivot;
if (rootScopedPivot) {
previousIntent = null;
previousAnchorType = null;
@ -899,19 +886,17 @@ export function createAssistantTransitionPolicy(deps) {
hasSelectedObjectInventorySignalPrimary ||
hasSelectedObjectInventorySignalAlternate)
);
const carryoverTargetIntent =
inventoryPurchaseDateVatBridge
? "vat_liability_confirmed_for_tax_period"
: selectedObjectRetargetIntent &&
(explicitIntent === null ||
explicitIntent === "inventory_on_hand_as_of_date" ||
explicitIntent === sourceIntent)
? selectedObjectRetargetIntent
: followupSelectionMode === "carry_root_context"
? inventoryRootFrame?.intent ?? displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined
: explicitInventorySameDatePivot
? "inventory_on_hand_as_of_date"
: displayedEntityTargetIntent ?? explicitIntent ?? previousIntent ?? undefined;
const carryoverTargetIntent = resolveFollowupTargetIntent(
inventoryPurchaseDateVatBridge,
selectedObjectRetargetIntent,
explicitIntent,
sourceIntent,
followupSelectionMode,
deps.toNonEmptyString(inventoryRootFrame?.intent),
displayedEntityTargetIntent,
previousIntent,
explicitInventorySameDatePivot
);
return {
followupContext: {
previous_intent: previousIntent ?? undefined,

View File

@ -12,6 +12,8 @@ import {
resolveAddressDebugContextFacts,
resolveAddressDebugAnchorContext,
resolveDisplayedEntityFollowupRetarget,
resolveFollowupTargetIntent,
resolveInventoryFollowupPivotFlags,
resolveAssistantOrganizationAuthority
} from "../src/services/assistantContinuityPolicy";
@ -254,6 +256,45 @@ describe("assistantContinuityPolicy organization authority", () => {
});
});
it("resolves inventory root pivots through one shared helper", () => {
const flags = resolveInventoryFollowupPivotFlags(
{ intent: "inventory_on_hand_as_of_date" },
"inventory_purchase_documents_for_item",
"inventory_drilldown",
false,
false,
true,
false,
false,
false,
false,
false,
(intent) => String(intent ?? "") === "inventory_purchase_documents_for_item",
(intent) => String(intent ?? "") === "inventory_on_hand_as_of_date"
);
expect(flags).toEqual({
rootScopedPivot: true,
explicitInventorySameDatePivot: false
});
});
it("resolves follow-up target intent precedence through one shared helper", () => {
const targetIntent = resolveFollowupTargetIntent(
false,
null,
null,
"customer_revenue_and_payments",
"carry_root_context",
"inventory_on_hand_as_of_date",
"list_contracts_by_counterparty",
"customer_revenue_and_payments",
false
);
expect(targetIntent).toBe("inventory_on_hand_as_of_date");
});
it("applies organization carryover precedence from historical to shared authority to navigation and clarification", () => {
const filters = applyOrganizationCarryoverFilters(
{},