From ca713354998b9c996eb5e7737870c67001c15373 Mon Sep 17 00:00:00 2001 From: dctouch Date: Sun, 19 Apr 2026 10:13:24 +0300 Subject: [PATCH] =?UTF-8?q?=D0=90=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D0=B0:=20=D1=86=D0=B5=D0=BD=D1=82=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20tempor?= =?UTF-8?q?al=20carryover=20precedence=20=D0=B2=20continuity=20policy=20?= =?UTF-8?q?=D0=B8=20transition=20glue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ontinuity_stabilization_plan_2026-04-17.md | 6 ++ .../services/assistantContinuityPolicy.js | 34 +++++++++++ .../services/assistantTransitionPolicy.js | 41 +------------ .../src/services/assistantContinuityPolicy.ts | 44 +++++++++++++ .../src/services/assistantTransitionPolicy.ts | 61 +++---------------- .../tests/assistantContinuityPolicy.test.ts | 49 +++++++++++++++ 6 files changed, 142 insertions(+), 93 deletions(-) diff --git a/docs/ARCH/11 - architecture_turnaround/11 - continuity_stabilization_plan_2026-04-17.md b/docs/ARCH/11 - architecture_turnaround/11 - continuity_stabilization_plan_2026-04-17.md index 32d8065..53c4956 100644 --- a/docs/ARCH/11 - architecture_turnaround/11 - continuity_stabilization_plan_2026-04-17.md +++ b/docs/ARCH/11 - architecture_turnaround/11 - continuity_stabilization_plan_2026-04-17.md @@ -428,6 +428,12 @@ Still open after the accepted phase12 replay: - this matters because `inventoryRootFrame`, `current_frame_kind`, and `root-scoped` filter precedence now converge through one authority layer before `root_context_only` pivots are decided, which reduces another hidden chance for state drift when new domains or new follow-up families are added; - targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move, with explicit coverage for root-frame hydration from navigation scope and for previous-date precedence over a stale inventory root frame; - a fresh live rerun of `address_truth_harness_phase12_wider_saved_session_pool` on `2026-04-19` remained semantically stable on all repaired continuity paths and again failed only on the already-known date-sensitive `today` expectations, not on the new shared root-frame state owner. +- the next continuity-authority pass now centralizes temporal backfill precedence for follow-up filters: + - transition no longer holds a service-local block of `shouldBackfillPreviousDateScopeFromNavigation + six field-level ifs` for `as_of_date / period_from / period_to`; + - shared continuity now owns that merge via `applyTemporalCarryoverFilters(...)`, while `shouldUseNavigationTemporalCarryover(...)` keeps the intent-family boundary explicit in one place; + - this matters because navigation date scope and continuity temporal scope are now merged through one owner before transition decides pivots, instead of being backfilled ad hoc inside the hot path; + - targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` suites are green after the move, with direct helper coverage for navigation-first temporal precedence and for non-applicable intent families staying untouched; + - a fresh live rerun of `address_truth_harness_phase12_wider_saved_session_pool` on `2026-04-19` stayed semantically stable and again failed only on the already-known date-sensitive `today` expectations, not on the new shared temporal carryover authority. ## Next Execution Slice (2026-04-18) diff --git a/llm_normalizer/backend/dist/services/assistantContinuityPolicy.js b/llm_normalizer/backend/dist/services/assistantContinuityPolicy.js index 64d5ab6..b2e228d 100644 --- a/llm_normalizer/backend/dist/services/assistantContinuityPolicy.js +++ b/llm_normalizer/backend/dist/services/assistantContinuityPolicy.js @@ -11,6 +11,8 @@ exports.resolveAddressDebugContextFacts = resolveAddressDebugContextFacts; exports.resolveAddressDebugCarryoverFilters = resolveAddressDebugCarryoverFilters; exports.hydrateInventoryRootFrameState = hydrateInventoryRootFrameState; exports.buildRootScopedCarryoverFilters = buildRootScopedCarryoverFilters; +exports.shouldUseNavigationTemporalCarryover = shouldUseNavigationTemporalCarryover; +exports.applyTemporalCarryoverFilters = applyTemporalCarryoverFilters; exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug; exports.isGroundedAddressDebug = isGroundedAddressDebug; exports.resolveAssistantContinuitySnapshot = resolveAssistantContinuitySnapshot; @@ -233,6 +235,38 @@ function buildRootScopedCarryoverFilters(previousFilters, inventoryRootFrame, to } return nextFilters; } +function shouldUseNavigationTemporalCarryover(sourceIntentHint) { + const normalizedIntent = fallbackToNonEmptyString(sourceIntentHint); + return (normalizedIntent === "inventory_on_hand_as_of_date" || + normalizedIntent === "inventory_supplier_stock_overlap_as_of_date" || + normalizedIntent === "inventory_purchase_provenance_for_item" || + normalizedIntent === "inventory_purchase_documents_for_item" || + normalizedIntent === "inventory_sale_trace_for_item" || + normalizedIntent === "inventory_profitability_for_item" || + normalizedIntent === "inventory_purchase_to_sale_chain" || + normalizedIntent === "inventory_aging_by_purchase_date" || + normalizedIntent === "account_balance_snapshot" || + normalizedIntent === "documents_forming_balance"); +} +function applyTemporalCarryoverFilters(previousFilters, navigationDateScope, continuityTemporalScope, sourceIntentHint, toNonEmptyString = fallbackToNonEmptyString) { + const nextFilters = previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {}; + if (!shouldUseNavigationTemporalCarryover(sourceIntentHint)) { + return nextFilters; + } + if (!toNonEmptyString(nextFilters.as_of_date)) { + nextFilters.as_of_date = + toNonEmptyString(navigationDateScope?.as_of_date) ?? continuityTemporalScope?.asOfDate ?? undefined; + } + if (!toNonEmptyString(nextFilters.period_from)) { + nextFilters.period_from = + toNonEmptyString(navigationDateScope?.period_from) ?? continuityTemporalScope?.periodFrom ?? undefined; + } + if (!toNonEmptyString(nextFilters.period_to)) { + nextFilters.period_to = + toNonEmptyString(navigationDateScope?.period_to) ?? continuityTemporalScope?.periodTo ?? undefined; + } + return nextFilters; +} function buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString = fallbackToNonEmptyString) { if (!debug || typeof debug !== "object") { return null; diff --git a/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js b/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js index 08675f8..b2c36af 100644 --- a/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js +++ b/llm_normalizer/backend/dist/services/assistantTransitionPolicy.js @@ -647,46 +647,7 @@ function createAssistantTransitionPolicy(deps) { previousFilters.period_to = purchaseBridgeWindow.period_to; } } - const shouldBackfillPreviousDateScopeFromNavigation = sourceIntentHint === "inventory_on_hand_as_of_date" || - sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" || - sourceIntentHint === "inventory_purchase_provenance_for_item" || - sourceIntentHint === "inventory_purchase_documents_for_item" || - sourceIntentHint === "inventory_sale_trace_for_item" || - sourceIntentHint === "inventory_profitability_for_item" || - sourceIntentHint === "inventory_purchase_to_sale_chain" || - sourceIntentHint === "inventory_aging_by_purchase_date" || - sourceIntentHint === "account_balance_snapshot" || - sourceIntentHint === "documents_forming_balance"; - if (shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.as_of_date) && - deps.toNonEmptyString(navigationDateScope?.as_of_date)) { - previousFilters.as_of_date = deps.toNonEmptyString(navigationDateScope?.as_of_date); - } - if (shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.as_of_date) && - continuityTemporalScope.asOfDate) { - previousFilters.as_of_date = continuityTemporalScope.asOfDate; - } - if (shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_from) && - deps.toNonEmptyString(navigationDateScope?.period_from)) { - previousFilters.period_from = deps.toNonEmptyString(navigationDateScope?.period_from); - } - if (shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_from) && - continuityTemporalScope.periodFrom) { - previousFilters.period_from = continuityTemporalScope.periodFrom; - } - if (shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_to) && - deps.toNonEmptyString(navigationDateScope?.period_to)) { - previousFilters.period_to = deps.toNonEmptyString(navigationDateScope?.period_to); - } - if (shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_to) && - continuityTemporalScope.periodTo) { - previousFilters.period_to = continuityTemporalScope.periodTo; - } + 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); diff --git a/llm_normalizer/backend/src/services/assistantContinuityPolicy.ts b/llm_normalizer/backend/src/services/assistantContinuityPolicy.ts index 5a9c4c0..049a385 100644 --- a/llm_normalizer/backend/src/services/assistantContinuityPolicy.ts +++ b/llm_normalizer/backend/src/services/assistantContinuityPolicy.ts @@ -369,6 +369,50 @@ export function buildRootScopedCarryoverFilters( return nextFilters; } +export function shouldUseNavigationTemporalCarryover(sourceIntentHint: unknown): boolean { + const normalizedIntent = fallbackToNonEmptyString(sourceIntentHint); + return ( + normalizedIntent === "inventory_on_hand_as_of_date" || + normalizedIntent === "inventory_supplier_stock_overlap_as_of_date" || + normalizedIntent === "inventory_purchase_provenance_for_item" || + normalizedIntent === "inventory_purchase_documents_for_item" || + normalizedIntent === "inventory_sale_trace_for_item" || + normalizedIntent === "inventory_profitability_for_item" || + normalizedIntent === "inventory_purchase_to_sale_chain" || + normalizedIntent === "inventory_aging_by_purchase_date" || + normalizedIntent === "account_balance_snapshot" || + normalizedIntent === "documents_forming_balance" + ); +} + +export function applyTemporalCarryoverFilters( + previousFilters: Record | null, + navigationDateScope: AssistantNavigationDateScope | null, + continuityTemporalScope: AssistantAddressDebugTemporalScope | null, + sourceIntentHint: unknown, + toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString +): Record { + const nextFilters = + previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {}; + if (!shouldUseNavigationTemporalCarryover(sourceIntentHint)) { + return nextFilters; + } + + if (!toNonEmptyString(nextFilters.as_of_date)) { + nextFilters.as_of_date = + toNonEmptyString(navigationDateScope?.as_of_date) ?? continuityTemporalScope?.asOfDate ?? undefined; + } + if (!toNonEmptyString(nextFilters.period_from)) { + nextFilters.period_from = + toNonEmptyString(navigationDateScope?.period_from) ?? continuityTemporalScope?.periodFrom ?? undefined; + } + if (!toNonEmptyString(nextFilters.period_to)) { + nextFilters.period_to = + toNonEmptyString(navigationDateScope?.period_to) ?? continuityTemporalScope?.periodTo ?? undefined; + } + return nextFilters; +} + export function buildInventoryRootFrameFromAddressDebug( debug: Record | null, toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString diff --git a/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts b/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts index adab710..d32d5a9 100644 --- a/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts +++ b/llm_normalizer/backend/src/services/assistantTransitionPolicy.ts @@ -1,5 +1,6 @@ // @ts-nocheck import { + applyTemporalCarryoverFilters, buildRootScopedCarryoverFilters, buildInventoryRootFrameFromAddressDebug, hydrateInventoryRootFrameState, @@ -807,59 +808,13 @@ export function createAssistantTransitionPolicy(deps) { previousFilters.period_to = purchaseBridgeWindow.period_to; } } - const shouldBackfillPreviousDateScopeFromNavigation = - sourceIntentHint === "inventory_on_hand_as_of_date" || - sourceIntentHint === "inventory_supplier_stock_overlap_as_of_date" || - sourceIntentHint === "inventory_purchase_provenance_for_item" || - sourceIntentHint === "inventory_purchase_documents_for_item" || - sourceIntentHint === "inventory_sale_trace_for_item" || - sourceIntentHint === "inventory_profitability_for_item" || - sourceIntentHint === "inventory_purchase_to_sale_chain" || - sourceIntentHint === "inventory_aging_by_purchase_date" || - sourceIntentHint === "account_balance_snapshot" || - sourceIntentHint === "documents_forming_balance"; - if ( - shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.as_of_date) && - deps.toNonEmptyString(navigationDateScope?.as_of_date) - ) { - previousFilters.as_of_date = deps.toNonEmptyString(navigationDateScope?.as_of_date); - } - if ( - shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.as_of_date) && - continuityTemporalScope.asOfDate - ) { - previousFilters.as_of_date = continuityTemporalScope.asOfDate; - } - if ( - shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_from) && - deps.toNonEmptyString(navigationDateScope?.period_from) - ) { - previousFilters.period_from = deps.toNonEmptyString(navigationDateScope?.period_from); - } - if ( - shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_from) && - continuityTemporalScope.periodFrom - ) { - previousFilters.period_from = continuityTemporalScope.periodFrom; - } - if ( - shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_to) && - deps.toNonEmptyString(navigationDateScope?.period_to) - ) { - previousFilters.period_to = deps.toNonEmptyString(navigationDateScope?.period_to); - } - if ( - shouldBackfillPreviousDateScopeFromNavigation && - !deps.toNonEmptyString(previousFilters.period_to) && - continuityTemporalScope.periodTo - ) { - previousFilters.period_to = continuityTemporalScope.periodTo; - } + previousFilters = applyTemporalCarryoverFilters( + previousFilters, + navigationDateScope, + continuityTemporalScope, + sourceIntentHint, + deps.toNonEmptyString + ); const rootContextOnlyPivot = Boolean( (deps.isInventorySelectedObjectIntent(sourceIntentHint) || currentFrameKind === "inventory_drilldown") && deps.hasForeignAccountingPivotOverInventoryMessage(userMessage, alternateMessage) && diff --git a/llm_normalizer/backend/tests/assistantContinuityPolicy.test.ts b/llm_normalizer/backend/tests/assistantContinuityPolicy.test.ts index 29fc1f9..d538731 100644 --- a/llm_normalizer/backend/tests/assistantContinuityPolicy.test.ts +++ b/llm_normalizer/backend/tests/assistantContinuityPolicy.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + applyTemporalCarryoverFilters, buildRootScopedCarryoverFilters, hydrateInventoryRootFrameState, readAddressDebugTemporalScope, @@ -199,4 +200,52 @@ describe("assistantContinuityPolicy organization authority", () => { period_to: "2020-03-31" }); }); + + it("applies temporal carryover from navigation scope first and falls back to continuity scope", () => { + const filters = applyTemporalCarryoverFilters( + { + organization: "Org Alt" + }, + { + as_of_date: "2021-03-31", + period_from: "2021-03-01" + }, + { + asOfDate: "2020-12-31", + periodFrom: "2020-12-01", + periodTo: "2020-12-31" + }, + "inventory_purchase_documents_for_item" + ); + + expect(filters).toEqual({ + organization: "Org Alt", + as_of_date: "2021-03-31", + period_from: "2021-03-01", + period_to: "2020-12-31" + }); + }); + + it("does not inject temporal carryover for unrelated intent families", () => { + const filters = applyTemporalCarryoverFilters( + { + organization: "Org Alt" + }, + { + as_of_date: "2021-03-31", + period_from: "2021-03-01", + period_to: "2021-03-31" + }, + { + asOfDate: "2020-12-31", + periodFrom: "2020-12-01", + periodTo: "2020-12-31" + }, + "list_documents_by_counterparty" + ); + + expect(filters).toEqual({ + organization: "Org Alt" + }); + }); });