Compare commits
3 Commits
0a423288b9
...
ca91622d62
| Author | SHA1 | Date |
|---|---|---|
|
|
ca91622d62 | |
|
|
851289b9a6 | |
|
|
4f56aa9b2a |
|
|
@ -428,6 +428,22 @@ 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;
|
- 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;
|
- 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.
|
- 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 removes another dense party-anchor owner from the transition hot path:
|
||||||
|
- continuity now owns `applyHistoricalPartyCarryoverFilters(...)`, so `contract/counterparty` backfill for party-driven follow-up families no longer lives as an inline cascade inside `assistantTransitionPolicy`;
|
||||||
|
- continuity now also owns `applyReferencedEntityCarryover(...)`, so displayed entity mentions from the previous grounded answer update `previous_filters`, `previous_anchor_*`, and `followupSelectionMode` through one shared state helper instead of another transition-local mutation block;
|
||||||
|
- this matters because counterparty / contract / selected-entity follow-ups are one of the heaviest remaining sources of local carryover reconstruction, and moving them under the shared continuity layer reduces another chance that route retargeting and anchor state drift apart when new domains are added;
|
||||||
|
- targeted `assistantContinuityPolicy` and `assistantTransitionPolicy` regressions now protect both the helper layer and a real `displayed counterparty -> contracts` follow-up path;
|
||||||
|
- the next proof after this pass should still come from a live replay, but the expected verdict should now only move if a real counterparty carryover path regresses rather than because the state mutation lived in an inline transition branch.
|
||||||
|
- the next continuity-authority pass now removes another selected-item state owner from the transition hot path:
|
||||||
|
- continuity now owns `applySelectedItemCarryover(...)`, so `previous_filters.item` plus `previous_anchor_*` for selected-object inventory follow-ups no longer mutate inline inside `assistantTransitionPolicy`;
|
||||||
|
- item carryover precedence is now explicit in one helper: navigation focus item -> continuity active item -> explicit selected-object label from the current message;
|
||||||
|
- this matters because selected-object follow-ups are one of the most fragile continuity seams in the product, and keeping their anchor mutation in the shared continuity layer reduces another chance that future inventory/domain expansion splits `focus_object` truth from follow-up carryover truth;
|
||||||
|
- targeted continuity and transition regressions now protect both the helper layer and a real short selected-item follow-up path that must keep the `item` anchor without reopening company/date drift.
|
||||||
|
- the next replay-gate honesty pass now removes a false red zone from the flagship saved-session proof:
|
||||||
|
- `address_truth_harness_phase12_wider_saved_session_pool` no longer hardcodes `2026-04-18` inside `today`-scoped steps for inventory root, payables, and receivables mirror follow-up;
|
||||||
|
- those steps now resolve `as_of_date` and visible date patterns through runtime placeholders (`{{runtime.today_iso}}`, `{{runtime.today_dot_regex}}`), so the replay verdict tracks real orchestration regressions instead of calendar drift;
|
||||||
|
- this matters because the flagship phase12 pack is one of the main top-level readiness indicators, and a date-stale spec was creating a false `partial` verdict even while `selected_object_continuity_ok`, `direct_answer_ok`, and the repaired continuity paths stayed green;
|
||||||
|
- the next live rerun after this pass should tell us whether any real runtime seam still exists in phase12 once the date-noise is removed.
|
||||||
- the next continuity-authority pass now centralizes temporal backfill precedence for follow-up filters:
|
- 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`;
|
- 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;
|
- shared continuity now owns that merge via `applyTemporalCarryoverFilters(...)`, while `shouldUseNavigationTemporalCarryover(...)` keeps the intent-family boundary explicit in one place;
|
||||||
|
|
|
||||||
|
|
@ -87,12 +87,12 @@
|
||||||
"inventory_on_hand_as_of_date"
|
"inventory_on_hand_as_of_date"
|
||||||
],
|
],
|
||||||
"required_filters": {
|
"required_filters": {
|
||||||
"as_of_date": "2026-04-18",
|
"as_of_date": "{{runtime.today_iso}}",
|
||||||
"organization": "ООО Альтернатива Плюс"
|
"organization": "ООО Альтернатива Плюс"
|
||||||
},
|
},
|
||||||
"required_direct_answer_patterns_any": [
|
"required_direct_answer_patterns_any": [
|
||||||
"(?i)на складе|остат",
|
"(?i)на складе|остат",
|
||||||
"18\\.04\\.2026"
|
"{{runtime.today_dot_regex}}"
|
||||||
],
|
],
|
||||||
"criticality": "critical",
|
"criticality": "critical",
|
||||||
"semantic_tags": [
|
"semantic_tags": [
|
||||||
|
|
@ -302,11 +302,11 @@
|
||||||
"payables_confirmed_as_of_date"
|
"payables_confirmed_as_of_date"
|
||||||
],
|
],
|
||||||
"required_filters": {
|
"required_filters": {
|
||||||
"as_of_date": "2026-04-18"
|
"as_of_date": "{{runtime.today_iso}}"
|
||||||
},
|
},
|
||||||
"required_direct_answer_patterns_any": [
|
"required_direct_answer_patterns_any": [
|
||||||
"(?i)долг к оплате|обязательств",
|
"(?i)долг к оплате|обязательств",
|
||||||
"18\\.04\\.2026"
|
"{{runtime.today_dot_regex}}"
|
||||||
],
|
],
|
||||||
"criticality": "critical",
|
"criticality": "critical",
|
||||||
"semantic_tags": [
|
"semantic_tags": [
|
||||||
|
|
@ -326,10 +326,10 @@
|
||||||
],
|
],
|
||||||
"required_direct_answer_patterns_any": [
|
"required_direct_answer_patterns_any": [
|
||||||
"(?i)дебитор",
|
"(?i)дебитор",
|
||||||
"18\\.04\\.2026"
|
"{{runtime.today_dot_regex}}"
|
||||||
],
|
],
|
||||||
"required_filters": {
|
"required_filters": {
|
||||||
"as_of_date": "2026-04-18"
|
"as_of_date": "{{runtime.today_iso}}"
|
||||||
},
|
},
|
||||||
"criticality": "critical",
|
"criticality": "critical",
|
||||||
"semantic_tags": [
|
"semantic_tags": [
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,9 @@ exports.buildRootScopedCarryoverFilters = buildRootScopedCarryoverFilters;
|
||||||
exports.shouldUseNavigationTemporalCarryover = shouldUseNavigationTemporalCarryover;
|
exports.shouldUseNavigationTemporalCarryover = shouldUseNavigationTemporalCarryover;
|
||||||
exports.applyTemporalCarryoverFilters = applyTemporalCarryoverFilters;
|
exports.applyTemporalCarryoverFilters = applyTemporalCarryoverFilters;
|
||||||
exports.applyOrganizationCarryoverFilters = applyOrganizationCarryoverFilters;
|
exports.applyOrganizationCarryoverFilters = applyOrganizationCarryoverFilters;
|
||||||
|
exports.applyHistoricalPartyCarryoverFilters = applyHistoricalPartyCarryoverFilters;
|
||||||
|
exports.applyReferencedEntityCarryover = applyReferencedEntityCarryover;
|
||||||
|
exports.applySelectedItemCarryover = applySelectedItemCarryover;
|
||||||
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
|
exports.buildInventoryRootFrameFromAddressDebug = buildInventoryRootFrameFromAddressDebug;
|
||||||
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
exports.isGroundedAddressDebug = isGroundedAddressDebug;
|
||||||
exports.resolveAssistantContinuitySnapshot = resolveAssistantContinuitySnapshot;
|
exports.resolveAssistantContinuitySnapshot = resolveAssistantContinuitySnapshot;
|
||||||
|
|
@ -281,6 +284,102 @@ function applyOrganizationCarryoverFilters(previousFilters, historicalOrganizati
|
||||||
}
|
}
|
||||||
return nextFilters;
|
return nextFilters;
|
||||||
}
|
}
|
||||||
|
function applyHistoricalPartyCarryoverFilters(previousFilters, shouldBackfillHistoricalPartyAnchors, historicalContract, historicalCounterparty, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const nextFilters = previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {};
|
||||||
|
if (!shouldBackfillHistoricalPartyAnchors) {
|
||||||
|
return nextFilters;
|
||||||
|
}
|
||||||
|
if (!toNonEmptyString(nextFilters.contract)) {
|
||||||
|
nextFilters.contract = toNonEmptyString(historicalContract) ?? undefined;
|
||||||
|
}
|
||||||
|
if (!toNonEmptyString(nextFilters.counterparty)) {
|
||||||
|
nextFilters.counterparty = toNonEmptyString(historicalCounterparty) ?? undefined;
|
||||||
|
}
|
||||||
|
return nextFilters;
|
||||||
|
}
|
||||||
|
function applyReferencedEntityCarryover(previousFilters, previousAnchorType, previousAnchorValue, followupSelectionMode, resolvedEntityFromFollowup, rootScopedPivot, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const nextFilters = previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {};
|
||||||
|
if (rootScopedPivot || !resolvedEntityFromFollowup || typeof resolvedEntityFromFollowup !== "object") {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const entityType = toNonEmptyString(resolvedEntityFromFollowup.entityType);
|
||||||
|
const entityValue = toNonEmptyString(resolvedEntityFromFollowup.value);
|
||||||
|
if (!entityType || !entityValue) {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let resolvedCounterpartyFromDisplay = false;
|
||||||
|
if (entityType === "counterparty") {
|
||||||
|
nextFilters.counterparty = entityValue;
|
||||||
|
previousAnchorType = "counterparty";
|
||||||
|
previousAnchorValue = entityValue;
|
||||||
|
resolvedCounterpartyFromDisplay = true;
|
||||||
|
}
|
||||||
|
else if (entityType === "contract") {
|
||||||
|
nextFilters.contract = entityValue;
|
||||||
|
previousAnchorType = "contract";
|
||||||
|
previousAnchorValue = entityValue;
|
||||||
|
}
|
||||||
|
else if (entityType === "item") {
|
||||||
|
nextFilters.item = entityValue;
|
||||||
|
previousAnchorType = "item";
|
||||||
|
previousAnchorValue = entityValue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode: followupSelectionMode !== "switch_to_suggested_intent" ? "carry_referenced_entity" : followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function applySelectedItemCarryover(previousFilters, previousAnchorType, previousAnchorValue, rootScopedPivot, shouldApplySelectedItemCarryover, navigationFocusItemLabel, continuityActiveItem, selectedObjectLabelFromUser, selectedObjectLabelFromAlternate, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
|
const nextFilters = previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {};
|
||||||
|
if (rootScopedPivot || !shouldApplySelectedItemCarryover || toNonEmptyString(nextFilters.item)) {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const selectedObjectLabel = toNonEmptyString(navigationFocusItemLabel) ??
|
||||||
|
toNonEmptyString(continuityActiveItem) ??
|
||||||
|
toNonEmptyString(selectedObjectLabelFromUser) ??
|
||||||
|
toNonEmptyString(selectedObjectLabelFromAlternate);
|
||||||
|
if (!selectedObjectLabel) {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
nextFilters.item = selectedObjectLabel;
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType: "item",
|
||||||
|
previousAnchorValue: selectedObjectLabel
|
||||||
|
};
|
||||||
|
}
|
||||||
function buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
function buildInventoryRootFrameFromAddressDebug(debug, toNonEmptyString = fallbackToNonEmptyString) {
|
||||||
if (!debug || typeof debug !== "object") {
|
if (!debug || typeof debug !== "object") {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -602,18 +602,7 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
sourceIntentHint === "list_documents_by_contract" ||
|
sourceIntentHint === "list_documents_by_contract" ||
|
||||||
sourceIntentHint === "bank_operations_by_contract" ||
|
sourceIntentHint === "bank_operations_by_contract" ||
|
||||||
sourceIntentHint === "open_items_by_counterparty_or_contract";
|
sourceIntentHint === "open_items_by_counterparty_or_contract";
|
||||||
if (shouldBackfillHistoricalPartyAnchors && !deps.toNonEmptyString(previousFilters.contract)) {
|
previousFilters = (0, assistantContinuityPolicy_1.applyHistoricalPartyCarryoverFilters)(previousFilters, shouldBackfillHistoricalPartyAnchors, deps.findRecentAddressFilterValue(items, "contract"), deps.findRecentAddressFilterValue(items, "counterparty"), deps.toNonEmptyString);
|
||||||
const historicalContract = deps.findRecentAddressFilterValue(items, "contract");
|
|
||||||
if (historicalContract) {
|
|
||||||
previousFilters.contract = historicalContract;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldBackfillHistoricalPartyAnchors && !deps.toNonEmptyString(previousFilters.counterparty)) {
|
|
||||||
const historicalCounterparty = deps.findRecentAddressFilterValue(items, "counterparty");
|
|
||||||
if (historicalCounterparty) {
|
|
||||||
previousFilters.counterparty = historicalCounterparty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
||||||
const authorityActiveOrganization = deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
const authorityActiveOrganization = deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
||||||
deps.normalizeOrganizationScopeValue(organizationAuthority.continuityActiveOrganization);
|
deps.normalizeOrganizationScopeValue(organizationAuthority.continuityActiveOrganization);
|
||||||
|
|
@ -670,47 +659,27 @@ function createAssistantTransitionPolicy(deps) {
|
||||||
: null);
|
: null);
|
||||||
if (resolvedEntityFromFollowup && !rootScopedPivot) {
|
if (resolvedEntityFromFollowup && !rootScopedPivot) {
|
||||||
displayedEntityTargetIntent = resolveDisplayedEntityRetargetIntent(userMessage, resolvedEntityFromFollowup.entityType);
|
displayedEntityTargetIntent = resolveDisplayedEntityRetargetIntent(userMessage, resolvedEntityFromFollowup.entityType);
|
||||||
if (resolvedEntityFromFollowup.entityType === "counterparty") {
|
|
||||||
previousFilters.counterparty = resolvedEntityFromFollowup.value;
|
|
||||||
previousAnchorType = "counterparty";
|
|
||||||
previousAnchor = resolvedEntityFromFollowup.value;
|
|
||||||
resolvedCounterpartyFromDisplay = true;
|
|
||||||
}
|
|
||||||
else if (resolvedEntityFromFollowup.entityType === "contract") {
|
|
||||||
previousFilters.contract = resolvedEntityFromFollowup.value;
|
|
||||||
previousAnchorType = "contract";
|
|
||||||
previousAnchor = resolvedEntityFromFollowup.value;
|
|
||||||
}
|
|
||||||
else if (resolvedEntityFromFollowup.entityType === "item") {
|
|
||||||
previousFilters.item = resolvedEntityFromFollowup.value;
|
|
||||||
previousAnchorType = "item";
|
|
||||||
previousAnchor = resolvedEntityFromFollowup.value;
|
|
||||||
}
|
|
||||||
if (followupSelectionMode !== "switch_to_suggested_intent") {
|
|
||||||
followupSelectionMode = "carry_referenced_entity";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!rootScopedPivot &&
|
|
||||||
!deps.toNonEmptyString(previousFilters.item) &&
|
|
||||||
(sourceIntentHint === "inventory_on_hand_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" ||
|
|
||||||
hasSelectedObjectInventorySignalPrimary ||
|
|
||||||
hasSelectedObjectInventorySignalAlternate)) {
|
|
||||||
const selectedObjectLabel = (navigationFocusObjectType === "item" ? navigationFocusObjectLabel : null) ??
|
|
||||||
continuitySnapshot.activeItem ??
|
|
||||||
extractSelectedObjectLabel(userMessage) ??
|
|
||||||
(deps.toNonEmptyString(alternateMessage) ? extractSelectedObjectLabel(String(alternateMessage ?? "")) : null);
|
|
||||||
if (selectedObjectLabel) {
|
|
||||||
previousFilters.item = selectedObjectLabel;
|
|
||||||
previousAnchorType = "item";
|
|
||||||
previousAnchor = selectedObjectLabel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
({
|
||||||
|
previousFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue: previousAnchor,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay
|
||||||
|
} = (0, assistantContinuityPolicy_1.applyReferencedEntityCarryover)(previousFilters, previousAnchorType, previousAnchor, followupSelectionMode, resolvedEntityFromFollowup, rootScopedPivot, deps.toNonEmptyString));
|
||||||
|
({
|
||||||
|
previousFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue: previousAnchor
|
||||||
|
} = (0, assistantContinuityPolicy_1.applySelectedItemCarryover)(previousFilters, previousAnchorType, previousAnchor, rootScopedPivot, sourceIntentHint === "inventory_on_hand_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" ||
|
||||||
|
hasSelectedObjectInventorySignalPrimary ||
|
||||||
|
hasSelectedObjectInventorySignalAlternate, navigationFocusObjectType === "item" ? navigationFocusObjectLabel : null, continuitySnapshot.activeItem, extractSelectedObjectLabel(userMessage), deps.toNonEmptyString(alternateMessage) ? extractSelectedObjectLabel(String(alternateMessage ?? "")) : null, deps.toNonEmptyString));
|
||||||
if (explicitOrganizationClarificationSelection && !previousAnchor) {
|
if (explicitOrganizationClarificationSelection && !previousAnchor) {
|
||||||
previousAnchorType = "organization";
|
previousAnchorType = "organization";
|
||||||
previousAnchor = explicitOrganizationClarificationSelection;
|
previousAnchor = explicitOrganizationClarificationSelection;
|
||||||
|
|
|
||||||
|
|
@ -436,6 +436,153 @@ export function applyOrganizationCarryoverFilters(
|
||||||
return nextFilters;
|
return nextFilters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function applyHistoricalPartyCarryoverFilters(
|
||||||
|
previousFilters: Record<string, unknown> | null,
|
||||||
|
shouldBackfillHistoricalPartyAnchors: boolean,
|
||||||
|
historicalContract: unknown,
|
||||||
|
historicalCounterparty: unknown,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): Record<string, unknown> {
|
||||||
|
const nextFilters =
|
||||||
|
previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {};
|
||||||
|
if (!shouldBackfillHistoricalPartyAnchors) {
|
||||||
|
return nextFilters;
|
||||||
|
}
|
||||||
|
if (!toNonEmptyString(nextFilters.contract)) {
|
||||||
|
nextFilters.contract = toNonEmptyString(historicalContract) ?? undefined;
|
||||||
|
}
|
||||||
|
if (!toNonEmptyString(nextFilters.counterparty)) {
|
||||||
|
nextFilters.counterparty = toNonEmptyString(historicalCounterparty) ?? undefined;
|
||||||
|
}
|
||||||
|
return nextFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyReferencedEntityCarryover(
|
||||||
|
previousFilters: Record<string, unknown> | null,
|
||||||
|
previousAnchorType: string | null,
|
||||||
|
previousAnchorValue: string | null,
|
||||||
|
followupSelectionMode: string | null,
|
||||||
|
resolvedEntityFromFollowup:
|
||||||
|
| {
|
||||||
|
entityType?: unknown;
|
||||||
|
value?: unknown;
|
||||||
|
}
|
||||||
|
| null
|
||||||
|
| undefined,
|
||||||
|
rootScopedPivot: boolean,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): {
|
||||||
|
previousFilters: Record<string, unknown>;
|
||||||
|
previousAnchorType: string | null;
|
||||||
|
previousAnchorValue: string | null;
|
||||||
|
followupSelectionMode: string | null;
|
||||||
|
resolvedCounterpartyFromDisplay: boolean;
|
||||||
|
} {
|
||||||
|
const nextFilters =
|
||||||
|
previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {};
|
||||||
|
if (rootScopedPivot || !resolvedEntityFromFollowup || typeof resolvedEntityFromFollowup !== "object") {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityType = toNonEmptyString(resolvedEntityFromFollowup.entityType);
|
||||||
|
const entityValue = toNonEmptyString(resolvedEntityFromFollowup.value);
|
||||||
|
if (!entityType || !entityValue) {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let resolvedCounterpartyFromDisplay = false;
|
||||||
|
if (entityType === "counterparty") {
|
||||||
|
nextFilters.counterparty = entityValue;
|
||||||
|
previousAnchorType = "counterparty";
|
||||||
|
previousAnchorValue = entityValue;
|
||||||
|
resolvedCounterpartyFromDisplay = true;
|
||||||
|
} else if (entityType === "contract") {
|
||||||
|
nextFilters.contract = entityValue;
|
||||||
|
previousAnchorType = "contract";
|
||||||
|
previousAnchorValue = entityValue;
|
||||||
|
} else if (entityType === "item") {
|
||||||
|
nextFilters.item = entityValue;
|
||||||
|
previousAnchorType = "item";
|
||||||
|
previousAnchorValue = entityValue;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue,
|
||||||
|
followupSelectionMode:
|
||||||
|
followupSelectionMode !== "switch_to_suggested_intent" ? "carry_referenced_entity" : followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applySelectedItemCarryover(
|
||||||
|
previousFilters: Record<string, unknown> | null,
|
||||||
|
previousAnchorType: string | null,
|
||||||
|
previousAnchorValue: string | null,
|
||||||
|
rootScopedPivot: boolean,
|
||||||
|
shouldApplySelectedItemCarryover: boolean,
|
||||||
|
navigationFocusItemLabel: unknown,
|
||||||
|
continuityActiveItem: unknown,
|
||||||
|
selectedObjectLabelFromUser: unknown,
|
||||||
|
selectedObjectLabelFromAlternate: unknown,
|
||||||
|
toNonEmptyString: (value: unknown) => string | null = fallbackToNonEmptyString
|
||||||
|
): {
|
||||||
|
previousFilters: Record<string, unknown>;
|
||||||
|
previousAnchorType: string | null;
|
||||||
|
previousAnchorValue: string | null;
|
||||||
|
} {
|
||||||
|
const nextFilters =
|
||||||
|
previousFilters && typeof previousFilters === "object" ? { ...previousFilters } : {};
|
||||||
|
if (rootScopedPivot || !shouldApplySelectedItemCarryover || toNonEmptyString(nextFilters.item)) {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedObjectLabel =
|
||||||
|
toNonEmptyString(navigationFocusItemLabel) ??
|
||||||
|
toNonEmptyString(continuityActiveItem) ??
|
||||||
|
toNonEmptyString(selectedObjectLabelFromUser) ??
|
||||||
|
toNonEmptyString(selectedObjectLabelFromAlternate);
|
||||||
|
if (!selectedObjectLabel) {
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
nextFilters.item = selectedObjectLabel;
|
||||||
|
return {
|
||||||
|
previousFilters: nextFilters,
|
||||||
|
previousAnchorType: "item",
|
||||||
|
previousAnchorValue: selectedObjectLabel
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import {
|
import {
|
||||||
|
applyHistoricalPartyCarryoverFilters,
|
||||||
applyOrganizationCarryoverFilters,
|
applyOrganizationCarryoverFilters,
|
||||||
|
applyReferencedEntityCarryover,
|
||||||
|
applySelectedItemCarryover,
|
||||||
applyTemporalCarryoverFilters,
|
applyTemporalCarryoverFilters,
|
||||||
buildRootScopedCarryoverFilters,
|
buildRootScopedCarryoverFilters,
|
||||||
buildInventoryRootFrameFromAddressDebug,
|
buildInventoryRootFrameFromAddressDebug,
|
||||||
|
|
@ -759,18 +762,13 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
sourceIntentHint === "list_documents_by_contract" ||
|
sourceIntentHint === "list_documents_by_contract" ||
|
||||||
sourceIntentHint === "bank_operations_by_contract" ||
|
sourceIntentHint === "bank_operations_by_contract" ||
|
||||||
sourceIntentHint === "open_items_by_counterparty_or_contract";
|
sourceIntentHint === "open_items_by_counterparty_or_contract";
|
||||||
if (shouldBackfillHistoricalPartyAnchors && !deps.toNonEmptyString(previousFilters.contract)) {
|
previousFilters = applyHistoricalPartyCarryoverFilters(
|
||||||
const historicalContract = deps.findRecentAddressFilterValue(items, "contract");
|
previousFilters,
|
||||||
if (historicalContract) {
|
shouldBackfillHistoricalPartyAnchors,
|
||||||
previousFilters.contract = historicalContract;
|
deps.findRecentAddressFilterValue(items, "contract"),
|
||||||
}
|
deps.findRecentAddressFilterValue(items, "counterparty"),
|
||||||
}
|
deps.toNonEmptyString
|
||||||
if (shouldBackfillHistoricalPartyAnchors && !deps.toNonEmptyString(previousFilters.counterparty)) {
|
);
|
||||||
const historicalCounterparty = deps.findRecentAddressFilterValue(items, "counterparty");
|
|
||||||
if (historicalCounterparty) {
|
|
||||||
previousFilters.counterparty = historicalCounterparty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
const historicalOrganization = deps.findRecentAddressFilterValue(items, "organization");
|
||||||
const authorityActiveOrganization =
|
const authorityActiveOrganization =
|
||||||
deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
deps.normalizeOrganizationScopeValue(organizationAuthority.activeOrganization) ??
|
||||||
|
|
@ -861,28 +859,32 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
userMessage,
|
userMessage,
|
||||||
resolvedEntityFromFollowup.entityType
|
resolvedEntityFromFollowup.entityType
|
||||||
);
|
);
|
||||||
if (resolvedEntityFromFollowup.entityType === "counterparty") {
|
|
||||||
previousFilters.counterparty = resolvedEntityFromFollowup.value;
|
|
||||||
previousAnchorType = "counterparty";
|
|
||||||
previousAnchor = resolvedEntityFromFollowup.value;
|
|
||||||
resolvedCounterpartyFromDisplay = true;
|
|
||||||
} else if (resolvedEntityFromFollowup.entityType === "contract") {
|
|
||||||
previousFilters.contract = resolvedEntityFromFollowup.value;
|
|
||||||
previousAnchorType = "contract";
|
|
||||||
previousAnchor = resolvedEntityFromFollowup.value;
|
|
||||||
} else if (resolvedEntityFromFollowup.entityType === "item") {
|
|
||||||
previousFilters.item = resolvedEntityFromFollowup.value;
|
|
||||||
previousAnchorType = "item";
|
|
||||||
previousAnchor = resolvedEntityFromFollowup.value;
|
|
||||||
}
|
|
||||||
if (followupSelectionMode !== "switch_to_suggested_intent") {
|
|
||||||
followupSelectionMode = "carry_referenced_entity";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (
|
({
|
||||||
!rootScopedPivot &&
|
previousFilters,
|
||||||
!deps.toNonEmptyString(previousFilters.item) &&
|
previousAnchorType,
|
||||||
(sourceIntentHint === "inventory_on_hand_as_of_date" ||
|
previousAnchorValue: previousAnchor,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedCounterpartyFromDisplay
|
||||||
|
} = applyReferencedEntityCarryover(
|
||||||
|
previousFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchor,
|
||||||
|
followupSelectionMode,
|
||||||
|
resolvedEntityFromFollowup,
|
||||||
|
rootScopedPivot,
|
||||||
|
deps.toNonEmptyString
|
||||||
|
));
|
||||||
|
({
|
||||||
|
previousFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchorValue: previousAnchor
|
||||||
|
} = applySelectedItemCarryover(
|
||||||
|
previousFilters,
|
||||||
|
previousAnchorType,
|
||||||
|
previousAnchor,
|
||||||
|
rootScopedPivot,
|
||||||
|
sourceIntentHint === "inventory_on_hand_as_of_date" ||
|
||||||
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
|
sourceIntentHint === "inventory_purchase_provenance_for_item" ||
|
||||||
sourceIntentHint === "inventory_purchase_documents_for_item" ||
|
sourceIntentHint === "inventory_purchase_documents_for_item" ||
|
||||||
sourceIntentHint === "inventory_sale_trace_for_item" ||
|
sourceIntentHint === "inventory_sale_trace_for_item" ||
|
||||||
|
|
@ -890,19 +892,13 @@ export function createAssistantTransitionPolicy(deps) {
|
||||||
sourceIntentHint === "inventory_purchase_to_sale_chain" ||
|
sourceIntentHint === "inventory_purchase_to_sale_chain" ||
|
||||||
sourceIntentHint === "inventory_aging_by_purchase_date" ||
|
sourceIntentHint === "inventory_aging_by_purchase_date" ||
|
||||||
hasSelectedObjectInventorySignalPrimary ||
|
hasSelectedObjectInventorySignalPrimary ||
|
||||||
hasSelectedObjectInventorySignalAlternate)
|
hasSelectedObjectInventorySignalAlternate,
|
||||||
) {
|
navigationFocusObjectType === "item" ? navigationFocusObjectLabel : null,
|
||||||
const selectedObjectLabel =
|
continuitySnapshot.activeItem,
|
||||||
(navigationFocusObjectType === "item" ? navigationFocusObjectLabel : null) ??
|
extractSelectedObjectLabel(userMessage),
|
||||||
continuitySnapshot.activeItem ??
|
deps.toNonEmptyString(alternateMessage) ? extractSelectedObjectLabel(String(alternateMessage ?? "")) : null,
|
||||||
extractSelectedObjectLabel(userMessage) ??
|
deps.toNonEmptyString
|
||||||
(deps.toNonEmptyString(alternateMessage) ? extractSelectedObjectLabel(String(alternateMessage ?? "")) : null);
|
));
|
||||||
if (selectedObjectLabel) {
|
|
||||||
previousFilters.item = selectedObjectLabel;
|
|
||||||
previousAnchorType = "item";
|
|
||||||
previousAnchor = selectedObjectLabel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (explicitOrganizationClarificationSelection && !previousAnchor) {
|
if (explicitOrganizationClarificationSelection && !previousAnchor) {
|
||||||
previousAnchorType = "organization";
|
previousAnchorType = "organization";
|
||||||
previousAnchor = explicitOrganizationClarificationSelection;
|
previousAnchor = explicitOrganizationClarificationSelection;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
|
applyHistoricalPartyCarryoverFilters,
|
||||||
applyOrganizationCarryoverFilters,
|
applyOrganizationCarryoverFilters,
|
||||||
|
applyReferencedEntityCarryover,
|
||||||
|
applySelectedItemCarryover,
|
||||||
applyTemporalCarryoverFilters,
|
applyTemporalCarryoverFilters,
|
||||||
buildRootScopedCarryoverFilters,
|
buildRootScopedCarryoverFilters,
|
||||||
hydrateInventoryRootFrameState,
|
hydrateInventoryRootFrameState,
|
||||||
|
|
@ -281,4 +284,70 @@ describe("assistantContinuityPolicy organization authority", () => {
|
||||||
organization: "Org Existing"
|
organization: "Org Existing"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("backfills historical contract and counterparty only for party-driven follow-up families", () => {
|
||||||
|
const filters = applyHistoricalPartyCarryoverFilters(
|
||||||
|
{},
|
||||||
|
true,
|
||||||
|
"Contract Legacy",
|
||||||
|
"Counterparty Legacy"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(filters).toEqual({
|
||||||
|
contract: "Contract Legacy",
|
||||||
|
counterparty: "Counterparty Legacy"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies referenced entity carryover into filters, anchor, and selection mode", () => {
|
||||||
|
const carryover = applyReferencedEntityCarryover(
|
||||||
|
{
|
||||||
|
organization: "Org Alt"
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"carry_previous_intent",
|
||||||
|
{
|
||||||
|
entityType: "counterparty",
|
||||||
|
value: "SVK Group"
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover).toEqual({
|
||||||
|
previousFilters: {
|
||||||
|
organization: "Org Alt",
|
||||||
|
counterparty: "SVK Group"
|
||||||
|
},
|
||||||
|
previousAnchorType: "counterparty",
|
||||||
|
previousAnchorValue: "SVK Group",
|
||||||
|
followupSelectionMode: "carry_referenced_entity",
|
||||||
|
resolvedCounterpartyFromDisplay: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("applies selected-item carryover from navigation focus before continuity and explicit labels", () => {
|
||||||
|
const carryover = applySelectedItemCarryover(
|
||||||
|
{
|
||||||
|
organization: "Org Alt"
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
"Workstation Navigation",
|
||||||
|
"Workstation Continuity",
|
||||||
|
"Workstation User",
|
||||||
|
"Workstation Alternate"
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover).toEqual({
|
||||||
|
previousFilters: {
|
||||||
|
organization: "Org Alt",
|
||||||
|
item: "Workstation Navigation"
|
||||||
|
},
|
||||||
|
previousAnchorType: "item",
|
||||||
|
previousAnchorValue: "Workstation Navigation"
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,47 @@ describe("assistantTransitionPolicy", () => {
|
||||||
expect(contract.decision).toBe("continue_previous");
|
expect(contract.decision).toBe("continue_previous");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retargets displayed counterparty follow-up through shared referenced-entity carryover", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => ({
|
||||||
|
text: "1. SVK Group\n2. Gamma",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "customer_revenue_and_payments",
|
||||||
|
extracted_filters: {
|
||||||
|
organization: 'ООО "Альтернатива Плюс"',
|
||||||
|
period_from: "2017-01-01",
|
||||||
|
period_to: "2017-12-31"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
hasAddressFollowupContextSignal: () => true,
|
||||||
|
inferDisplayedEntityTypeFromIntent: () => "counterparty",
|
||||||
|
extractDisplayedAddressEntityCandidates: () => [{ entityType: "counterparty", value: "SVK Group" }],
|
||||||
|
resolveDisplayedAddressEntityMention: () => ({ entityType: "counterparty", value: "SVK Group" }),
|
||||||
|
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"покажи договоры по СВК",
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupSelectionMode).toBe("carry_referenced_entity");
|
||||||
|
expect(carryover?.followupContext?.target_intent).toBe("list_contracts_by_counterparty");
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_type).toBe("counterparty");
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_value).toBe("SVK Group");
|
||||||
|
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||||
|
organization: 'ООО "Альтернатива Плюс"',
|
||||||
|
counterparty: "SVK Group",
|
||||||
|
period_from: "2017-01-01",
|
||||||
|
period_to: "2017-12-31"
|
||||||
|
});
|
||||||
|
expect(carryover?.followupContext?.resolved_counterparty_from_display).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it("retargets same-date inventory follow-up away from receivables intent", () => {
|
it("retargets same-date inventory follow-up away from receivables intent", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => ({
|
findLastAddressAssistantItem: () => ({
|
||||||
|
|
@ -256,6 +297,48 @@ describe("assistantTransitionPolicy", () => {
|
||||||
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
expect(carryover?.followupContext?.root_context_only).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hydrates selected-item carryover through shared continuity helper for short object follow-up", () => {
|
||||||
|
const policy = buildPolicy({
|
||||||
|
findLastAddressAssistantItem: () => ({
|
||||||
|
text: "Подтвержден складской срез по выбранной позиции.",
|
||||||
|
debug: {
|
||||||
|
detected_intent: "inventory_on_hand_as_of_date",
|
||||||
|
extracted_filters: {
|
||||||
|
organization: 'ООО "Альтернатива Плюс"',
|
||||||
|
as_of_date: "2020-03-31"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
hasAddressFollowupContextSignal: () => true,
|
||||||
|
hasShortInventoryObjectFollowupSignal: () => true,
|
||||||
|
findRecentInventoryRootFrame: () => null,
|
||||||
|
resolveAddressIntent: () => ({ intent: "unknown" })
|
||||||
|
});
|
||||||
|
|
||||||
|
const carryover = policy.resolveAddressFollowupCarryoverContext(
|
||||||
|
"по этой позиции покажи документы",
|
||||||
|
[],
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
session_context: {
|
||||||
|
active_focus_object: {
|
||||||
|
object_type: "item",
|
||||||
|
label: "Workstation Focus"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(carryover?.followupContext?.previous_filters).toMatchObject({
|
||||||
|
organization: 'ООО "Альтернатива Плюс"',
|
||||||
|
as_of_date: "2020-03-31",
|
||||||
|
item: "Workstation Focus"
|
||||||
|
});
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_type).toBe("item");
|
||||||
|
expect(carryover?.followupContext?.previous_anchor_value).toBe("Workstation Focus");
|
||||||
|
});
|
||||||
|
|
||||||
it("hydrates follow-up organization from shared assistant authority when local history filters are empty", () => {
|
it("hydrates follow-up organization from shared assistant authority when local history filters are empty", () => {
|
||||||
const policy = buildPolicy({
|
const policy = buildPolicy({
|
||||||
findLastAddressAssistantItem: () => ({
|
findLastAddressAssistantItem: () => ({
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue