diff --git a/llm_normalizer/backend/dist/services/assistantRoutePolicy.js b/llm_normalizer/backend/dist/services/assistantRoutePolicy.js index 850d997..8505f3d 100644 --- a/llm_normalizer/backend/dist/services/assistantRoutePolicy.js +++ b/llm_normalizer/backend/dist/services/assistantRoutePolicy.js @@ -656,6 +656,45 @@ function createAssistantRoutePolicy(deps) { } }; } + const unsupportedCurrentTurnMeaningBoundary = Boolean(assistantTurnMeaning?.unsupported_but_understood_family && + assistantTurnMeaning?.stale_replay_forbidden === true && + !turnMeaningIntentCandidate && + !dataScopeMetaQuery && + !capabilityMetaQuery && + !dangerOrCoercionSignal && + !organizationClarificationContinuationDetected); + if (unsupportedCurrentTurnMeaningBoundary) { + return { + runAddressLane: false, + toolGateDecision: "skip_address_lane", + toolGateReason: "unsupported_current_turn_meaning_boundary", + livingMode: "chat", + livingReason: "unsupported_current_turn_meaning_boundary", + orchestrationContract: { + schema_version: "assistant_orchestration_contract_v1", + hard_meta_mode: null, + provider_execution: providerExecution, + assistant_turn_meaning: assistantTurnMeaning, + address_mode: resolvedModeDetection.mode, + address_mode_confidence: resolvedModeDetection.confidence, + address_intent: resolvedIntentResolution.intent, + address_intent_confidence: resolvedIntentResolution.confidence, + strong_data_signal_detected: strongDataSignal, + data_retrieval_signal_detected: dataRetrievalSignal, + followup_context_detected: Boolean(followupContext), + unsupported_current_turn_meaning_boundary: true, + unsupported_current_turn_family: assistantTurnMeaning.unsupported_but_understood_family, + unsupported_address_intent_fallback_to_deep: false, + final_decision: { + run_address_lane: false, + tool_gate_decision: "skip_address_lane", + tool_gate_reason: "unsupported_current_turn_meaning_boundary", + living_mode: "chat", + living_reason: "unsupported_current_turn_meaning_boundary" + } + } + }; + } if (organizationFactLookupDetected || organizationFactBoundaryFollowupDetected) { return { runAddressLane: false, diff --git a/llm_normalizer/backend/dist/services/assistantRuntimeContractResolver.js b/llm_normalizer/backend/dist/services/assistantRuntimeContractResolver.js index c705e9d..c286684 100644 --- a/llm_normalizer/backend/dist/services/assistantRuntimeContractResolver.js +++ b/llm_normalizer/backend/dist/services/assistantRuntimeContractResolver.js @@ -111,6 +111,9 @@ function resolveTransitionId(input) { if (toolGateReason === "memory_recap_followup_detected") { return { transitionId: "T9", reasons: ["memory_recap_tool_gate_reason"] }; } + if (toolGateReason === "unsupported_current_turn_meaning_boundary") { + return { transitionId: "T10", reasons: ["unsupported_current_turn_meaning_boundary"] }; + } if (hardMetaMode === "capability" || toolGateReason === "assistant_capability_query_detected") { return { transitionId: "T8", reasons: ["capability_meta_followup_tool_gate_reason"] }; } diff --git a/llm_normalizer/backend/src/services/assistantRoutePolicy.ts b/llm_normalizer/backend/src/services/assistantRoutePolicy.ts index 5a5af53..f9ac527 100644 --- a/llm_normalizer/backend/src/services/assistantRoutePolicy.ts +++ b/llm_normalizer/backend/src/services/assistantRoutePolicy.ts @@ -739,6 +739,46 @@ export function createAssistantRoutePolicy(deps) { } }; } + const unsupportedCurrentTurnMeaningBoundary = Boolean( + assistantTurnMeaning?.unsupported_but_understood_family && + assistantTurnMeaning?.stale_replay_forbidden === true && + !turnMeaningIntentCandidate && + !dataScopeMetaQuery && + !capabilityMetaQuery && + !dangerOrCoercionSignal && + !organizationClarificationContinuationDetected); + if (unsupportedCurrentTurnMeaningBoundary) { + return { + runAddressLane: false, + toolGateDecision: "skip_address_lane", + toolGateReason: "unsupported_current_turn_meaning_boundary", + livingMode: "chat", + livingReason: "unsupported_current_turn_meaning_boundary", + orchestrationContract: { + schema_version: "assistant_orchestration_contract_v1", + hard_meta_mode: null, + provider_execution: providerExecution, + assistant_turn_meaning: assistantTurnMeaning, + address_mode: resolvedModeDetection.mode, + address_mode_confidence: resolvedModeDetection.confidence, + address_intent: resolvedIntentResolution.intent, + address_intent_confidence: resolvedIntentResolution.confidence, + strong_data_signal_detected: strongDataSignal, + data_retrieval_signal_detected: dataRetrievalSignal, + followup_context_detected: Boolean(followupContext), + unsupported_current_turn_meaning_boundary: true, + unsupported_current_turn_family: assistantTurnMeaning.unsupported_but_understood_family, + unsupported_address_intent_fallback_to_deep: false, + final_decision: { + run_address_lane: false, + tool_gate_decision: "skip_address_lane", + tool_gate_reason: "unsupported_current_turn_meaning_boundary", + living_mode: "chat", + living_reason: "unsupported_current_turn_meaning_boundary" + } + } + }; + } if (organizationFactLookupDetected || organizationFactBoundaryFollowupDetected) { return { runAddressLane: false, diff --git a/llm_normalizer/backend/src/services/assistantRuntimeContractResolver.ts b/llm_normalizer/backend/src/services/assistantRuntimeContractResolver.ts index bef6700..4d6198a 100644 --- a/llm_normalizer/backend/src/services/assistantRuntimeContractResolver.ts +++ b/llm_normalizer/backend/src/services/assistantRuntimeContractResolver.ts @@ -159,6 +159,10 @@ function resolveTransitionId(input: { return { transitionId: "T9", reasons: ["memory_recap_tool_gate_reason"] }; } + if (toolGateReason === "unsupported_current_turn_meaning_boundary") { + return { transitionId: "T10", reasons: ["unsupported_current_turn_meaning_boundary"] }; + } + if (hardMetaMode === "capability" || toolGateReason === "assistant_capability_query_detected") { return { transitionId: "T8", reasons: ["capability_meta_followup_tool_gate_reason"] }; } diff --git a/llm_normalizer/backend/tests/assistantRoutePolicy.test.ts b/llm_normalizer/backend/tests/assistantRoutePolicy.test.ts index c897692..3c2c187 100644 --- a/llm_normalizer/backend/tests/assistantRoutePolicy.test.ts +++ b/llm_normalizer/backend/tests/assistantRoutePolicy.test.ts @@ -700,4 +700,48 @@ describe("assistantRoutePolicy", () => { "assistant_turn_meaning_v1" ); }); + + it("routes unsupported-but-understood current meaning to boundary instead of stale address carryover", () => { + const policy = buildPolicy({ + hasDataRetrievalRequestSignal: () => true, + hasStrongDataIntentSignal: () => true, + resolveAddressToolGateDecision: () => ({ + runAddressLane: true, + decision: "run_address_lane", + reason: "followup_context_detected" + }), + resolveAssistantTurnMeaning: () => ({ + schema_version: "assistant_turn_meaning_v1", + asked_domain_family: "counterparty", + asked_action_family: "counterparty_value_or_turnover", + explicit_intent_candidate: null, + unsupported_but_understood_family: "counterparty_value_or_turnover", + stale_replay_forbidden: true, + reason_codes: ["counterparty_turnover_current_turn_signal"] + }) + }); + + const decision = policy.resolveAssistantOrchestrationDecision({ + rawUserMessage: + "\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a", + effectiveAddressUserMessage: + "\u043a\u0430\u043a\u043e\u0439 \u043e\u0431\u043e\u0440\u043e\u0442 \u0431\u044b\u043b \u0441\u0432\u043a", + followupContext: { + previous_intent: "list_documents_by_counterparty", + previous_filters: { + counterparty: "Previous Counterparty" + } + }, + llmPreDecomposeMeta: null, + useMock: false + }); + + expect(decision.runAddressLane).toBe(false); + expect(decision.toolGateReason).toBe("unsupported_current_turn_meaning_boundary"); + expect(decision.livingMode).toBe("chat"); + expect(decision.orchestrationContract?.unsupported_current_turn_meaning_boundary).toBe(true); + expect(decision.orchestrationContract?.unsupported_current_turn_family).toBe( + "counterparty_value_or_turnover" + ); + }); }); diff --git a/llm_normalizer/backend/tests/assistantRuntimeContractRegistry.test.ts b/llm_normalizer/backend/tests/assistantRuntimeContractRegistry.test.ts index a225c4f..0916fa8 100644 --- a/llm_normalizer/backend/tests/assistantRuntimeContractRegistry.test.ts +++ b/llm_normalizer/backend/tests/assistantRuntimeContractRegistry.test.ts @@ -156,6 +156,23 @@ describe("assistant runtime contract registry", () => { expect(blockedDecision.carryover_eligibility).toBe("none"); }); + it("resolves unsupported current-turn meaning boundary as blocked transition", () => { + const decision = resolveAssistantRuntimeContractShadow({ + addressRuntimeMeta: { + toolGateReason: "unsupported_current_turn_meaning_boundary", + orchestrationContract: { + unsupported_current_turn_meaning_boundary: true, + unsupported_current_turn_family: "counterparty_value_or_turnover", + address_intent: "unknown" + } + } + }); + + expect(decision.transition_contract_id).toBe("T10"); + expect(decision.transition_contract_reason).toEqual(["unsupported_current_turn_meaning_boundary"]); + expect(decision.carryover_eligibility).toBe("none"); + }); + it("classifies temporal limitations as a distinct truth gate status", () => { const decision = resolveAssistantRuntimeContractShadow({ addressDebug: {