Архитектура: централизовать root-frame-aware carryover filters в continuity policy и transition glue
This commit is contained in:
parent
a71e1352be
commit
c9730c986a
|
|
@ -415,6 +415,13 @@ Still open after the accepted phase12 replay:
|
||||||
- this matters because follow-up carryover in the top-level service now reads the same root-frame authority that already owns `root_filters / root_anchor / current_frame_kind`, instead of keeping a service-local fallback that could silently prefer drilldown `extracted_filters` over the real `address_root_frame_context`;
|
- this matters because follow-up carryover in the top-level service now reads the same root-frame authority that already owns `root_filters / root_anchor / current_frame_kind`, instead of keeping a service-local fallback that could silently prefer drilldown `extracted_filters` over the real `address_root_frame_context`;
|
||||||
- targeted `assistantAddressFollowupContext` and `addressInventoryRootFrameRegression` suites are green after the move, including a new regression that explicitly proves `root_filters` come from `address_root_frame_context.root_filters` rather than from stale drilldown `extracted_filters`;
|
- targeted `assistantAddressFollowupContext` and `addressInventoryRootFrameRegression` suites are green after the move, including a new regression that explicitly proves `root_filters` come from `address_root_frame_context.root_filters` rather than from stale drilldown `extracted_filters`;
|
||||||
- this pass strengthens continuity convergence in the top-level orchestration glue without introducing a new case-specific branch.
|
- this pass strengthens continuity convergence in the top-level orchestration glue without introducing a new case-specific branch.
|
||||||
|
- the next continuity-authority pass now removes one more duplicate carryover owner from `assistantTransitionPolicy`:
|
||||||
|
- transition no longer seeds `previous_filters` from raw `previousAddressDebug.extracted_filters` as an isolated local truth source;
|
||||||
|
- shared continuity now owns that merge through `resolveAddressDebugCarryoverFilters(...)`, which overlays inventory `address_root_frame_context.root_filters` onto stale drilldown filters before the follow-up policy starts composing pivots;
|
||||||
|
- this matters because the top-level transition glue can now inherit the same root-frame date and warehouse authority that already exists in continuity, instead of silently carrying a stale drilldown `as_of_date` into `root_context_only` pivots;
|
||||||
|
- targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move, including explicit regression coverage for `inventory_purchase_documents_for_item -> inventory_on_hand_as_of_date` carryover where `root_filters` must override a stale drilldown date;
|
||||||
|
- this pass reduces one more hidden state-reconstruction fork between the continuity layer and transition glue without introducing case-specific routing;
|
||||||
|
- a fresh live rerun of `address_truth_harness_phase12_wider_saved_session_pool` on `2026-04-19` stayed semantically clean on the repaired carryover path and failed only on the already-known time-unstable `today` expectations (`2026-04-18` vs `2026-04-19`) in `inventory_root_today`, `payables_today`, and `receivables_mirror_today`.
|
||||||
|
|
||||||
## Next Execution Slice (2026-04-18)
|
## Next Execution Slice (2026-04-18)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ exports.readAddressDebugScopedDate = readAddressDebugScopedDate;
|
||||||
exports.readAddressDebugTemporalScope = readAddressDebugTemporalScope;
|
exports.readAddressDebugTemporalScope = readAddressDebugTemporalScope;
|
||||||
exports.resolveAddressDebugAnchorContext = resolveAddressDebugAnchorContext;
|
exports.resolveAddressDebugAnchorContext = resolveAddressDebugAnchorContext;
|
||||||
exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
|
exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts;
|
||||||
|
exports.resolveAddressDebugCarryoverFilters = resolveAddressDebugCarryoverFilters;
|
||||||
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
|
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
|
||||||
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
||||||
exports.resolveAssistantContinuitySnapshot = resolveAssistantContinuitySnapshot;
|
exports.resolveAssistantContinuitySnapshot = resolveAssistantContinuitySnapshot;
|
||||||
|
|
@ -121,6 +122,37 @@ function resolveAddressDebugContextFacts(debug, toNonEmptyString = fallbackToNon
|
||||||
scopedDate: readAddressDebugScopedDate(debug)
|
scopedDate: readAddressDebugScopedDate(debug)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function resolveAddressDebugCarryoverFilters(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const extractedFilters = readAddressDebugFilters(debug);
|
||||||
|
const nextFilters = extractedFilters ? { ...extractedFilters } : {};
|
||||||
|
const inventoryRootFrame = buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString);
|
||||||
|
const rootFilters = inventoryRootFrame?.filters && typeof inventoryRootFrame.filters === "object"
|
||||||
|
? inventoryRootFrame.filters
|
||||||
|
: null;
|
||||||
|
if (rootFilters) {
|
||||||
|
const organization = toNonEmptyString(rootFilters.organization);
|
||||||
|
const warehouse = toNonEmptyString(rootFilters.warehouse);
|
||||||
|
const asOfDate = toNonEmptyString(rootFilters.as_of_date);
|
||||||
|
const periodFrom = toNonEmptyString(rootFilters.period_from);
|
||||||
|
const periodTo = toNonEmptyString(rootFilters.period_to);
|
||||||
|
if (organization) {
|
||||||
|
nextFilters.organization = organization;
|
||||||
|
}
|
||||||
|
if (warehouse) {
|
||||||
|
nextFilters.warehouse = warehouse;
|
||||||
|
}
|
||||||
|
if (asOfDate) {
|
||||||
|
nextFilters.as_of_date = asOfDate;
|
||||||
|
}
|
||||||
|
if (periodFrom) {
|
||||||
|
nextFilters.period_from = periodFrom;
|
||||||
|
}
|
||||||
|
if (periodTo) {
|
||||||
|
nextFilters.period_to = periodTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextFilters;
|
||||||
|
}
|
||||||
function buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
if (!debug || typeof debug !== "object") {
|
if (!debug || typeof debug !== "object") {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -627,8 +627,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
: null;
|
: null;
|
||||||
let resolvedCounterpartyFromDisplay = false;
|
let resolvedCounterpartyFromDisplay = false;
|
||||||
let displayedEntityTargetIntent = null;
|
let displayedEntityTargetIntent = null;
|
||||||
const previousFiltersRaw = previousAddressDebug.extracted_filters;
|
let previousFilters = (0, assistantContinuityPolicy_1.resolveAddressDebugCarryoverFilters)(previousAddressDebug, deps.toNonEmptyString);
|
||||||
let previousFilters = previousFiltersRaw && typeof previousFiltersRaw === "object" ? { ...previousFiltersRaw } : {};
|
|
||||||
const shouldBackfillHistoricalPartyAnchors = sourceIntentHint === "list_contracts_by_counterparty" ||
|
const shouldBackfillHistoricalPartyAnchors = sourceIntentHint === "list_contracts_by_counterparty" ||
|
||||||
sourceIntentHint === "list_documents_by_counterparty" ||
|
sourceIntentHint === "list_documents_by_counterparty" ||
|
||||||
sourceIntentHint === "bank_operations_by_counterparty" ||
|
sourceIntentHint === "bank_operations_by_counterparty" ||
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,42 @@ export function resolveAddressDebugContextFacts(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveAddressDebugCarryoverFilters(
|
||||||
|
debug: Record<string, unknown> | null,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const extractedFilters = readAddressDebugFilters(debug);
|
||||||
|
const nextFilters = extractedFilters ? { ...extractedFilters } : {};
|
||||||
|
const inventoryRootFrame = buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString);
|
||||||
|
const rootFilters =
|
||||||
|
inventoryRootFrame?.filters && typeof inventoryRootFrame.filters === "object"
|
||||||
|
? inventoryRootFrame.filters
|
||||||
|
: null;
|
||||||
|
if (rootFilters) {
|
||||||
|
const organization = toNonEmptyString(rootFilters.organization);
|
||||||
|
const warehouse = toNonEmptyString(rootFilters.warehouse);
|
||||||
|
const asOfDate = toNonEmptyString(rootFilters.as_of_date);
|
||||||
|
const periodFrom = toNonEmptyString(rootFilters.period_from);
|
||||||
|
const periodTo = toNonEmptyString(rootFilters.period_to);
|
||||||
|
if (organization) {
|
||||||
|
nextFilters.organization = organization;
|
||||||
|
}
|
||||||
|
if (warehouse) {
|
||||||
|
nextFilters.warehouse = warehouse;
|
||||||
|
}
|
||||||
|
if (asOfDate) {
|
||||||
|
nextFilters.as_of_date = asOfDate;
|
||||||
|
}
|
||||||
|
if (periodFrom) {
|
||||||
|
nextFilters.period_from = periodFrom;
|
||||||
|
}
|
||||||
|
if (periodTo) {
|
||||||
|
nextFilters.period_to = periodTo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextFilters;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildInventoryRootFrameFromAddressDebug(
|
export function buildInventoryRootFrameFromAddressDebug(
|
||||||
debug: Record<string, unknown> | null,
|
debug: Record<string, unknown> | null,
|
||||||
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
readAddressDebugFilters,
|
readAddressDebugFilters,
|
||||||
readAddressDebugItem,
|
readAddressDebugItem,
|
||||||
readAddressDebugTemporalScope,
|
readAddressDebugTemporalScope,
|
||||||
|
resolveAddressDebugCarryoverFilters,
|
||||||
resolveAddressDebugAnchorContext,
|
resolveAddressDebugAnchorContext,
|
||||||
resolveAssistantOrganizationAuthority
|
resolveAssistantOrganizationAuthority
|
||||||
} from "./assistantContinuityPolicy";
|
} from "./assistantContinuityPolicy";
|
||||||
|
|
@ -773,9 +774,7 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
: null;
|
: null;
|
||||||
let resolvedCounterpartyFromDisplay = false;
|
let resolvedCounterpartyFromDisplay = false;
|
||||||
let displayedEntityTargetIntent = null;
|
let displayedEntityTargetIntent = null;
|
||||||
const previousFiltersRaw = previousAddressDebug.extracted_filters;
|
let previousFilters = resolveAddressDebugCarryoverFilters(previousAddressDebug, deps.toNonEmptyString);
|
||||||
let previousFilters =
|
|
||||||
previousFiltersRaw && typeof previousFiltersRaw === "object" ? { ...previousFiltersRaw } : {};
|
|
||||||
const shouldBackfillHistoricalPartyAnchors =
|
const shouldBackfillHistoricalPartyAnchors =
|
||||||
sourceIntentHint === "list_contracts_by_counterparty" ||
|
sourceIntentHint === "list_contracts_by_counterparty" ||
|
||||||
sourceIntentHint === "list_documents_by_counterparty" ||
|
sourceIntentHint === "list_documents_by_counterparty" ||
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
readAddressDebugTemporalScope,
|
readAddressDebugTemporalScope,
|
||||||
|
resolveAddressDebugCarryoverFilters,
|
||||||
resolveAddressDebugContextFacts,
|
resolveAddressDebugContextFacts,
|
||||||
resolveAddressDebugAnchorContext,
|
resolveAddressDebugAnchorContext,
|
||||||
resolveAssistantOrganizationAuthority
|
resolveAssistantOrganizationAuthority
|
||||||
|
|
@ -100,4 +101,36 @@ describe("assistantContinuityPolicy organization authority", () => {
|
||||||
anchorValue: "Рабочая станция"
|
anchorValue: "Рабочая станция"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it("prefers inventory root-frame filters over stale drilldown date scope in carryover filters", () => {
|
||||||
|
const filters = resolveAddressDebugCarryoverFilters({
|
||||||
|
detected_intent: "inventory_purchase_documents_for_item",
|
||||||
|
extracted_filters: {
|
||||||
|
item: "Workstation",
|
||||||
|
organization: "Org Alt",
|
||||||
|
as_of_date: "2021-04-15"
|
||||||
|
},
|
||||||
|
address_root_frame_context: {
|
||||||
|
root_intent: "inventory_on_hand_as_of_date",
|
||||||
|
root_filters: {
|
||||||
|
organization: "Org Alt",
|
||||||
|
warehouse: "Main Warehouse",
|
||||||
|
as_of_date: "2021-03-31",
|
||||||
|
period_from: "2021-03-01",
|
||||||
|
period_to: "2021-03-31"
|
||||||
|
},
|
||||||
|
root_anchor_type: "organization",
|
||||||
|
root_anchor_value: "Org Alt",
|
||||||
|
current_frame_kind: "inventory_drilldown"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(filters).toEqual({
|
||||||
|
item: "Workstation",
|
||||||
|
organization: "Org Alt",
|
||||||
|
warehouse: "Main Warehouse",
|
||||||
|
as_of_date: "2021-03-31",
|
||||||
|
period_from: "2021-03-01",
|
||||||
|
period_to: "2021-03-31"
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -754,4 +754,88 @@ describe("assistantTransitionPolicy", () => {
|
||||||
});
|
});
|
||||||
expect(carryover?.followupContext?.previous_anchor_type).toBe("item");
|
expect(carryover?.followupContext?.previous_anchor_type).toBe("item");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("prefers root-frame dates over stale drilldown filters when hydrating previous filters", () => {
|
||||||
|
const organization = "Org Alt";
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: (_items: unknown[]) => ({
|
||||||
|
text: "Workstation drilldown",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "inventory_purchase_documents_for_item",
|
||||||
|
extracted_filters: {
|
||||||
|
item: "Workstation",
|
||||||
|
organization,
|
||||||
|
as_of_date: "2021-04-15"
|
||||||
|
},
|
||||||
|
anchor_type: "item",
|
||||||
|
anchor_value_resolved: "Workstation",
|
||||||
|
address_root_frame_context: {
|
||||||
|
root_intent: "inventory_on_hand_as_of_date",
|
||||||
|
root_filters: {
|
||||||
|
organization,
|
||||||
|
warehouse: "Main Warehouse",
|
||||||
|
as_of_date: "2021-03-31",
|
||||||
|
period_from: "2021-03-01",
|
||||||
|
period_to: "2021-03-31"
|
||||||
|
},
|
||||||
|
root_anchor_type: "organization",
|
||||||
|
root_anchor_value: organization,
|
||||||
|
current_frame_kind: "inventory_drilldown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
findRecentInventoryRootFrame: () => null,
|
||||||
|
hasInventoryRootTemporalFollowupSignal: (message: string) => /эту же дату/i.test(message)
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
role: "assistant",
|
||||||
|
text: "Workstation drilldown",
|
||||||
|
debug: {
|
||||||
|
execution_lane: "address_query",
|
||||||
|
answer_grounding_check: { status: "grounded" },
|
||||||
|
detected_intent: "inventory_purchase_documents_for_item",
|
||||||
|
extracted_filters: {
|
||||||
|
item: "Workstation",
|
||||||
|
organization,
|
||||||
|
as_of_date: "2021-04-15"
|
||||||
|
},
|
||||||
|
anchor_type: "item",
|
||||||
|
anchor_value_resolved: "Workstation",
|
||||||
|
address_root_frame_context: {
|
||||||
|
root_intent: "inventory_on_hand_as_of_date",
|
||||||
|
root_filters: {
|
||||||
|
organization,
|
||||||
|
warehouse: "Main Warehouse",
|
||||||
|
as_of_date: "2021-03-31",
|
||||||
|
period_from: "2021-03-01",
|
||||||
|
period_to: "2021-03-31"
|
||||||
|
},
|
||||||
|
root_anchor_type: "organization",
|
||||||
|
root_anchor_value: organization,
|
||||||
|
current_frame_kind: "inventory_drilldown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"остатки на эту же дату",
|
||||||
|
items as any,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.root_context_only).toBe(true);
|
||||||
|
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||||
|
organization,
|
||||||
|
warehouse: "Main Warehouse",
|
||||||
|
as_of_date: "2021-03-31",
|
||||||
|
period_from: "2021-03-01",
|
||||||
|
period_to: "2021-03-31"
|
||||||
|
});
|
||||||
|
expect(carryover?.followupContext?.previous_filters?.as_of_date).not.toBe("2021-04-15");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue