diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js index 9d99df9..1d1637a 100644 --- a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js @@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime; const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter"); const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver"); +const assistantStateTransitionRuntimeAdapter_1 = require("./assistantStateTransitionRuntimeAdapter"); const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter"); function toRecordObject(value) { if (!value || typeof value !== "object") { @@ -197,6 +198,10 @@ function runAssistantAddressLaneResponseRuntime(input) { addressRuntimeMeta: input.llmPreDecomposeMeta, replyType: normalizeAddressReplyType(input.addressLane.reply_type) }); + const debugWithStateTransition = (0, assistantStateTransitionRuntimeAdapter_1.attachAssistantStateTransition)(debugWithTruthAnswerPolicy, { + addressRuntimeMeta: input.llmPreDecomposeMeta, + replyType: normalizeAddressReplyType(input.addressLane.reply_type) + }); const finalization = finalizeAddressTurnSafe({ sessionId: input.sessionId, userMessage: input.userMessage, @@ -204,7 +209,7 @@ function runAssistantAddressLaneResponseRuntime(input) { assistantReply: safeAddressReply, replyType: normalizeAddressReplyType(input.addressLane.reply_type), addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug), - debug: debugWithTruthAnswerPolicy, + debug: debugWithStateTransition, carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta), llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta), appendItem: input.appendItem, @@ -216,6 +221,6 @@ function runAssistantAddressLaneResponseRuntime(input) { }); return { response: finalization.response, - debug: debugWithTruthAnswerPolicy + debug: debugWithStateTransition }; } diff --git a/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js b/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js index cdc6704..52b40a3 100644 --- a/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js +++ b/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js @@ -2,6 +2,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload; const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver"); +const assistantStateTransitionRuntimeAdapter_1 = require("./assistantStateTransitionRuntimeAdapter"); const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter"); const assistantStage4AnswerContractAudit_1 = require("./assistantStage4AnswerContractAudit"); function toAnalysisContext(input) { @@ -99,7 +100,13 @@ function buildDeepAnalysisDebugPayload(input) { addressRuntimeMeta: input.addressRuntimeMetaForDeep, groundingStatus: input.groundingCheck.status }); - return (0, assistantTruthAnswerPolicyRuntimeAdapter_1.attachAssistantTruthAnswerPolicy)(debugWithRuntimeContracts, { + const debugWithTruthAnswerPolicy = (0, assistantTruthAnswerPolicyRuntimeAdapter_1.attachAssistantTruthAnswerPolicy)(debugWithRuntimeContracts, { + addressRuntimeMeta: input.addressRuntimeMetaForDeep, + groundingStatus: input.groundingCheck.status, + coverageReport: input.coverageReport, + replyType: "deep_analysis" + }); + return (0, assistantStateTransitionRuntimeAdapter_1.attachAssistantStateTransition)(debugWithTruthAnswerPolicy, { addressRuntimeMeta: input.addressRuntimeMetaForDeep, groundingStatus: input.groundingCheck.status, coverageReport: input.coverageReport, diff --git a/llm_normalizer/backend/dist/services/assistantStateTransitionRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantStateTransitionRuntimeAdapter.js new file mode 100644 index 0000000..704252c --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantStateTransitionRuntimeAdapter.js @@ -0,0 +1,265 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.resolveAssistantStateTransitionRuntime = resolveAssistantStateTransitionRuntime; +exports.buildAssistantStateTransitionRuntimeFields = buildAssistantStateTransitionRuntimeFields; +exports.attachAssistantStateTransition = attachAssistantStateTransition; +const assistantRuntimeContracts_1 = require("../types/assistantRuntimeContracts"); +const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver"); +const assistantRuntimeContractRegistry_1 = require("./assistantRuntimeContractRegistry"); +const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter"); +function toRecordObject(value) { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + return value; +} +function toNonEmptyString(value) { + if (value === null || value === undefined) { + return null; + } + const text = String(value).trim(); + return text.length > 0 ? text : null; +} +function uniqueStrings(values) { + return Array.from(new Set(values.filter((item) => item.trim().length > 0))); +} +function baseStateActions() { + return { + living_mode_state: "none", + root_frame_state: "none", + selected_object_frame_state: "none", + meta_frame_state: "none", + clarification_state: "none", + coverage_gate_state: "none", + answer_context_state: "none" + }; +} +function stateActionsFor(transitionId) { + const actions = baseStateActions(); + if (!transitionId) { + return actions; + } + actions.living_mode_state = "update"; + actions.coverage_gate_state = "create"; + if (transitionId === "T1") { + return { + ...actions, + root_frame_state: "create", + selected_object_frame_state: "clear", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T2") { + return { + ...actions, + root_frame_state: "update", + selected_object_frame_state: "clear", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T3") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "create", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T4" || transitionId === "T5") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "reuse", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T6") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "clear", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T7") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "preserve", + meta_frame_state: "none", + clarification_state: "update", + answer_context_state: "none" + }; + } + if (transitionId === "T8") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "preserve", + meta_frame_state: "create", + clarification_state: "none", + answer_context_state: "reuse" + }; + } + if (transitionId === "T9") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "preserve", + meta_frame_state: "create", + clarification_state: "none", + coverage_gate_state: "preserve", + answer_context_state: "reuse" + }; + } + return { + ...actions, + root_frame_state: "none", + selected_object_frame_state: "clear", + meta_frame_state: "none", + clarification_state: "none", + coverage_gate_state: "block", + answer_context_state: "none" + }; +} +function applicationStatusFor(input) { + if (!input.transitionId) { + return "unresolved"; + } + if (input.coverageGateState.truth_mode === "clarification_required") { + return "clarification_required"; + } + if (input.transitionId === "T10" || input.coverageGateState.coverage_status === "blocked" || input.coverageGateState.truth_mode === "unsupported") { + return "blocked"; + } + return "applied"; +} +function effectiveCarryoverDepthFor(input) { + if (input.status === "unresolved" || input.status === "blocked") { + return "none"; + } + if (!input.declared) { + return input.truthGate; + } + if (input.status === "clarification_required") { + return input.declared === "full" ? "full" : input.truthGate; + } + if (input.truthGate === "none") { + return "none"; + } + if (input.declared === "full") { + return input.truthGate; + } + if (input.truthGate === "full") { + return input.declared; + } + return input.declared === input.truthGate ? input.declared : "none"; +} +function resolveShadow(input, debug) { + return (input.runtimeContractShadow ?? + toRecordObject(debug.assistant_runtime_contract_v1) ?? + (0, assistantRuntimeContractResolver_1.resolveAssistantRuntimeContractShadow)({ + addressDebug: debug, + addressRuntimeMeta: input.addressRuntimeMeta, + groundingStatus: input.groundingStatus + })); +} +function resolveTruthPolicy(input, debug) { + return (input.truthAnswerPolicy ?? + toRecordObject(debug.assistant_truth_answer_policy_v1) ?? + (0, assistantTruthAnswerPolicyRuntimeAdapter_1.resolveAssistantTruthAnswerPolicyRuntime)({ + addressDebug: debug, + addressRuntimeMeta: input.addressRuntimeMeta, + groundingStatus: input.groundingStatus, + coverageReport: input.coverageReport, + replyType: input.replyType + })); +} +function reasonCodesFor(input) { + return uniqueStrings([ + `transition_status_${input.applicationStatus}`, + ...(input.transitionId ? [`transition_${input.transitionId}`] : ["transition_unresolved"]), + ...input.shadow.transition_contract_reason, + ...input.shadow.capability_contract_reason, + ...input.truthPolicy.truth_gate.reason_codes + ]).slice(0, 40); +} +function resolveAssistantStateTransitionRuntime(input) { + const debug = toRecordObject(input.addressDebug) ?? {}; + const shadow = resolveShadow(input, debug); + const truthPolicy = resolveTruthPolicy(input, debug); + const transitionId = shadow.transition_contract_id; + const transition = transitionId ? (0, assistantRuntimeContractRegistry_1.getAssistantTransitionContract)(transitionId) : null; + const status = applicationStatusFor({ + transitionId, + coverageGateState: truthPolicy.truth_gate + }); + const effectiveCarryoverDepth = effectiveCarryoverDepthFor({ + declared: transition?.allowed_carryover_depth ?? null, + truthGate: truthPolicy.truth_gate.carryover_eligibility, + status + }); + const forbiddenCarryover = uniqueStrings([ + ...(transition?.forbidden_carryover ?? []), + ...(status === "blocked" ? ["blocked_as_confirmed_factual_answer"] : []), + ...(effectiveCarryoverDepth === "none" ? ["implicit_followup_reuse"] : []) + ]); + return { + schema_version: assistantRuntimeContracts_1.ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION, + state_owner: "assistantStateTransitionRuntimeAdapter", + transition_id: transitionId, + transition_title: transition?.title ?? shadow.transition_contract_title, + application_status: status, + declared_carryover_depth: transition?.allowed_carryover_depth ?? null, + truth_gate_carryover_depth: truthPolicy.truth_gate.carryover_eligibility, + effective_carryover_depth: effectiveCarryoverDepth, + required_prior_state: transition?.required_prior_state ?? [], + expected_answer_mode: transition?.expected_answer_mode ?? null, + state_mutations: transition?.state_mutations ?? [], + forbidden_carryover: forbiddenCarryover, + state_actions: stateActionsFor(transitionId), + coverage_gate_state: { + coverage_status: truthPolicy.truth_gate.coverage_status, + evidence_grade: truthPolicy.truth_gate.evidence_grade, + grounding_status: truthPolicy.truth_gate.grounding_status, + truth_mode: truthPolicy.truth_gate.truth_mode, + carryover_eligibility: truthPolicy.truth_gate.carryover_eligibility, + reason_codes: truthPolicy.truth_gate.reason_codes + }, + reason_codes: reasonCodesFor({ + shadow, + truthPolicy, + transitionId, + applicationStatus: status + }) + }; +} +function buildAssistantStateTransitionRuntimeFields(input) { + const transition = resolveAssistantStateTransitionRuntime(input); + return { + assistant_state_transition_v1: transition, + state_transition_contract: transition, + state_transition_id: transition.transition_id, + state_transition_status: transition.application_status, + effective_carryover_depth: transition.effective_carryover_depth + }; +} +function attachAssistantStateTransition(debugPayload, input) { + return { + ...debugPayload, + ...buildAssistantStateTransitionRuntimeFields({ + ...input, + addressDebug: debugPayload + }) + }; +} diff --git a/llm_normalizer/backend/dist/types/assistantRuntimeContracts.js b/llm_normalizer/backend/dist/types/assistantRuntimeContracts.js index 3574b11..9a42065 100644 --- a/llm_normalizer/backend/dist/types/assistantRuntimeContracts.js +++ b/llm_normalizer/backend/dist/types/assistantRuntimeContracts.js @@ -1,5 +1,6 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = void 0; +exports.ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION = exports.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = void 0; exports.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1"; exports.ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = "assistant_truth_answer_policy_runtime_v1"; +exports.ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION = "assistant_state_transition_runtime_v1"; diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts index 17849a1..4a2adf2 100644 --- a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts @@ -8,6 +8,7 @@ import { type FinalizeAssistantAddressTurnInput } from "./assistantAddressTurnFinalizeRuntimeAdapter"; import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver"; +import { attachAssistantStateTransition } from "./assistantStateTransitionRuntimeAdapter"; import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter"; export interface RunAssistantAddressLaneResponseRuntimeInput { @@ -256,6 +257,10 @@ export function runAssistantAddressLaneResponseRuntime | null | undefined, groundingStatus: input.groundingCheck.status }); - return attachAssistantTruthAnswerPolicy(debugWithRuntimeContracts, { + const debugWithTruthAnswerPolicy = attachAssistantTruthAnswerPolicy(debugWithRuntimeContracts, { + addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record | null | undefined, + groundingStatus: input.groundingCheck.status, + coverageReport: input.coverageReport as unknown as Record, + replyType: "deep_analysis" + }); + return attachAssistantStateTransition(debugWithTruthAnswerPolicy, { addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record | null | undefined, groundingStatus: input.groundingCheck.status, coverageReport: input.coverageReport as unknown as Record, diff --git a/llm_normalizer/backend/src/services/assistantStateTransitionRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantStateTransitionRuntimeAdapter.ts new file mode 100644 index 0000000..4aa7df8 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantStateTransitionRuntimeAdapter.ts @@ -0,0 +1,331 @@ +import { + ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION, + type AssistantCarryoverDepth, + type AssistantCoverageGateState, + type AssistantRuntimeContractShadowDecision, + type AssistantStateTransitionApplicationStatus, + type AssistantStateTransitionRuntimeContract, + type AssistantTruthAnswerPolicyRuntimeContract, + type AssistantTransitionClassId +} from "../types/assistantRuntimeContracts"; +import { resolveAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver"; +import { getAssistantTransitionContract } from "./assistantRuntimeContractRegistry"; +import { resolveAssistantTruthAnswerPolicyRuntime } from "./assistantTruthAnswerPolicyRuntimeAdapter"; + +export interface ResolveAssistantStateTransitionRuntimeInput { + addressDebug?: Record | null; + addressRuntimeMeta?: Record | null; + groundingStatus?: unknown; + coverageReport?: Record | null; + replyType?: unknown; + runtimeContractShadow?: AssistantRuntimeContractShadowDecision | null; + truthAnswerPolicy?: AssistantTruthAnswerPolicyRuntimeContract | null; +} + +export interface AssistantStateTransitionRuntimeFields { + assistant_state_transition_v1: AssistantStateTransitionRuntimeContract; + state_transition_contract: AssistantStateTransitionRuntimeContract; + state_transition_id: AssistantTransitionClassId | null; + state_transition_status: AssistantStateTransitionApplicationStatus; + effective_carryover_depth: AssistantCarryoverDepth; +} + +function toRecordObject(value: unknown): Record | null { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + return value as Record; +} + +function toNonEmptyString(value: unknown): string | null { + if (value === null || value === undefined) { + return null; + } + const text = String(value).trim(); + return text.length > 0 ? text : null; +} + +function uniqueStrings(values: string[]): string[] { + return Array.from(new Set(values.filter((item) => item.trim().length > 0))); +} + +function baseStateActions(): AssistantStateTransitionRuntimeContract["state_actions"] { + return { + living_mode_state: "none", + root_frame_state: "none", + selected_object_frame_state: "none", + meta_frame_state: "none", + clarification_state: "none", + coverage_gate_state: "none", + answer_context_state: "none" + }; +} + +function stateActionsFor(transitionId: AssistantTransitionClassId | null): AssistantStateTransitionRuntimeContract["state_actions"] { + const actions = baseStateActions(); + if (!transitionId) { + return actions; + } + actions.living_mode_state = "update"; + actions.coverage_gate_state = "create"; + + if (transitionId === "T1") { + return { + ...actions, + root_frame_state: "create", + selected_object_frame_state: "clear", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T2") { + return { + ...actions, + root_frame_state: "update", + selected_object_frame_state: "clear", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T3") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "create", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T4" || transitionId === "T5") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "reuse", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T6") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "clear", + meta_frame_state: "clear", + clarification_state: "clear", + answer_context_state: "create" + }; + } + if (transitionId === "T7") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "preserve", + meta_frame_state: "none", + clarification_state: "update", + answer_context_state: "none" + }; + } + if (transitionId === "T8") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "preserve", + meta_frame_state: "create", + clarification_state: "none", + answer_context_state: "reuse" + }; + } + if (transitionId === "T9") { + return { + ...actions, + root_frame_state: "preserve", + selected_object_frame_state: "preserve", + meta_frame_state: "create", + clarification_state: "none", + coverage_gate_state: "preserve", + answer_context_state: "reuse" + }; + } + return { + ...actions, + root_frame_state: "none", + selected_object_frame_state: "clear", + meta_frame_state: "none", + clarification_state: "none", + coverage_gate_state: "block", + answer_context_state: "none" + }; +} + +function applicationStatusFor(input: { + transitionId: AssistantTransitionClassId | null; + coverageGateState: AssistantCoverageGateState; +}): AssistantStateTransitionApplicationStatus { + if (!input.transitionId) { + return "unresolved"; + } + if (input.coverageGateState.truth_mode === "clarification_required") { + return "clarification_required"; + } + if (input.transitionId === "T10" || input.coverageGateState.coverage_status === "blocked" || input.coverageGateState.truth_mode === "unsupported") { + return "blocked"; + } + return "applied"; +} + +function effectiveCarryoverDepthFor(input: { + declared: AssistantCarryoverDepth | null; + truthGate: AssistantCarryoverDepth; + status: AssistantStateTransitionApplicationStatus; +}): AssistantCarryoverDepth { + if (input.status === "unresolved" || input.status === "blocked") { + return "none"; + } + if (!input.declared) { + return input.truthGate; + } + if (input.status === "clarification_required") { + return input.declared === "full" ? "full" : input.truthGate; + } + if (input.truthGate === "none") { + return "none"; + } + if (input.declared === "full") { + return input.truthGate; + } + if (input.truthGate === "full") { + return input.declared; + } + return input.declared === input.truthGate ? input.declared : "none"; +} + +function resolveShadow( + input: ResolveAssistantStateTransitionRuntimeInput, + debug: Record +): AssistantRuntimeContractShadowDecision { + return ( + input.runtimeContractShadow ?? + (toRecordObject(debug.assistant_runtime_contract_v1) as AssistantRuntimeContractShadowDecision | null) ?? + resolveAssistantRuntimeContractShadow({ + addressDebug: debug, + addressRuntimeMeta: input.addressRuntimeMeta, + groundingStatus: input.groundingStatus + }) + ); +} + +function resolveTruthPolicy( + input: ResolveAssistantStateTransitionRuntimeInput, + debug: Record +): AssistantTruthAnswerPolicyRuntimeContract { + return ( + input.truthAnswerPolicy ?? + (toRecordObject(debug.assistant_truth_answer_policy_v1) as AssistantTruthAnswerPolicyRuntimeContract | null) ?? + resolveAssistantTruthAnswerPolicyRuntime({ + addressDebug: debug, + addressRuntimeMeta: input.addressRuntimeMeta, + groundingStatus: input.groundingStatus, + coverageReport: input.coverageReport, + replyType: input.replyType + }) + ); +} + +function reasonCodesFor(input: { + shadow: AssistantRuntimeContractShadowDecision; + truthPolicy: AssistantTruthAnswerPolicyRuntimeContract; + transitionId: AssistantTransitionClassId | null; + applicationStatus: AssistantStateTransitionApplicationStatus; +}): string[] { + return uniqueStrings([ + `transition_status_${input.applicationStatus}`, + ...(input.transitionId ? [`transition_${input.transitionId}`] : ["transition_unresolved"]), + ...input.shadow.transition_contract_reason, + ...input.shadow.capability_contract_reason, + ...input.truthPolicy.truth_gate.reason_codes + ]).slice(0, 40); +} + +export function resolveAssistantStateTransitionRuntime( + input: ResolveAssistantStateTransitionRuntimeInput +): AssistantStateTransitionRuntimeContract { + const debug = toRecordObject(input.addressDebug) ?? {}; + const shadow = resolveShadow(input, debug); + const truthPolicy = resolveTruthPolicy(input, debug); + const transitionId = shadow.transition_contract_id; + const transition = transitionId ? getAssistantTransitionContract(transitionId) : null; + const status = applicationStatusFor({ + transitionId, + coverageGateState: truthPolicy.truth_gate + }); + const effectiveCarryoverDepth = effectiveCarryoverDepthFor({ + declared: transition?.allowed_carryover_depth ?? null, + truthGate: truthPolicy.truth_gate.carryover_eligibility, + status + }); + const forbiddenCarryover = uniqueStrings([ + ...(transition?.forbidden_carryover ?? []), + ...(status === "blocked" ? ["blocked_as_confirmed_factual_answer"] : []), + ...(effectiveCarryoverDepth === "none" ? ["implicit_followup_reuse"] : []) + ]); + + return { + schema_version: ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION, + state_owner: "assistantStateTransitionRuntimeAdapter", + transition_id: transitionId, + transition_title: transition?.title ?? shadow.transition_contract_title, + application_status: status, + declared_carryover_depth: transition?.allowed_carryover_depth ?? null, + truth_gate_carryover_depth: truthPolicy.truth_gate.carryover_eligibility, + effective_carryover_depth: effectiveCarryoverDepth, + required_prior_state: transition?.required_prior_state ?? [], + expected_answer_mode: transition?.expected_answer_mode ?? null, + state_mutations: transition?.state_mutations ?? [], + forbidden_carryover: forbiddenCarryover, + state_actions: stateActionsFor(transitionId), + coverage_gate_state: { + coverage_status: truthPolicy.truth_gate.coverage_status, + evidence_grade: truthPolicy.truth_gate.evidence_grade, + grounding_status: truthPolicy.truth_gate.grounding_status, + truth_mode: truthPolicy.truth_gate.truth_mode, + carryover_eligibility: truthPolicy.truth_gate.carryover_eligibility, + reason_codes: truthPolicy.truth_gate.reason_codes + }, + reason_codes: reasonCodesFor({ + shadow, + truthPolicy, + transitionId, + applicationStatus: status + }) + }; +} + +export function buildAssistantStateTransitionRuntimeFields( + input: ResolveAssistantStateTransitionRuntimeInput +): AssistantStateTransitionRuntimeFields { + const transition = resolveAssistantStateTransitionRuntime(input); + return { + assistant_state_transition_v1: transition, + state_transition_contract: transition, + state_transition_id: transition.transition_id, + state_transition_status: transition.application_status, + effective_carryover_depth: transition.effective_carryover_depth + }; +} + +export function attachAssistantStateTransition>( + debugPayload: T, + input: Omit +): T & AssistantStateTransitionRuntimeFields { + return { + ...debugPayload, + ...buildAssistantStateTransitionRuntimeFields({ + ...input, + addressDebug: debugPayload + }) + }; +} diff --git a/llm_normalizer/backend/src/types/assistant.ts b/llm_normalizer/backend/src/types/assistant.ts index 2f5b8f2..5b272dd 100644 --- a/llm_normalizer/backend/src/types/assistant.ts +++ b/llm_normalizer/backend/src/types/assistant.ts @@ -20,6 +20,8 @@ import type { AddressNavigationState } from "./addressNavigation"; import type { AssistantAnswerShapeKind, AssistantRuntimeContractShadowDecision, + AssistantStateTransitionApplicationStatus, + AssistantStateTransitionRuntimeContract, AssistantTruthAnswerPolicyRuntimeContract, AssistantTruthMode, AssistantTransitionClassId @@ -463,6 +465,11 @@ export interface AssistantDebugPayload { truth_mode?: AssistantTruthMode; carryover_eligibility?: AssistantTruthAnswerPolicyRuntimeContract["truth_gate"]["carryover_eligibility"]; answer_shape?: AssistantAnswerShapeKind; + assistant_state_transition_v1?: AssistantStateTransitionRuntimeContract; + state_transition_contract?: AssistantStateTransitionRuntimeContract; + state_transition_id?: AssistantTransitionClassId | null; + state_transition_status?: AssistantStateTransitionApplicationStatus; + effective_carryover_depth?: AssistantStateTransitionRuntimeContract["effective_carryover_depth"]; execution_lane?: "address_query" | "deep_analysis"; llm_decomposition_applied?: boolean; llm_decomposition_attempted?: boolean; diff --git a/llm_normalizer/backend/src/types/assistantRuntimeContracts.ts b/llm_normalizer/backend/src/types/assistantRuntimeContracts.ts index 53b71b5..c500ee9 100644 --- a/llm_normalizer/backend/src/types/assistantRuntimeContracts.ts +++ b/llm_normalizer/backend/src/types/assistantRuntimeContracts.ts @@ -2,6 +2,7 @@ import type { AddressIntent } from "./addressQuery"; export const ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION = "assistant_runtime_contracts_v1" as const; export const ASSISTANT_TRUTH_ANSWER_POLICY_RUNTIME_SCHEMA_VERSION = "assistant_truth_answer_policy_runtime_v1" as const; +export const ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION = "assistant_state_transition_runtime_v1" as const; export type AssistantLivingMode = "address_data" | "assistant_data_scope" | "chat" | "meta_followup" | "clarification"; export type AssistantFrameStatus = "active" | "suspended" | "closed" | "blocked"; @@ -36,6 +37,8 @@ export type AssistantAnswerShapeKind = | "blocked_no_answer" | "unknown"; export type AssistantAnswerShapeReplyType = "factual" | "partial_coverage" | "deep_analysis" | "unknown"; +export type AssistantStateTransitionApplicationStatus = "applied" | "clarification_required" | "blocked" | "unresolved"; +export type AssistantStateFrameAction = "create" | "update" | "preserve" | "reuse" | "clear" | "block" | "none"; export interface AssistantDateScopeState { as_of_date: string | null; @@ -115,6 +118,32 @@ export interface AssistantTruthAnswerPolicyRuntimeContract { answer_shape: AssistantAnswerShapeRuntimeContract; } +export interface AssistantStateTransitionRuntimeContract { + schema_version: typeof ASSISTANT_STATE_TRANSITION_RUNTIME_SCHEMA_VERSION; + state_owner: "assistantStateTransitionRuntimeAdapter"; + transition_id: AssistantTransitionClassId | null; + transition_title: string | null; + application_status: AssistantStateTransitionApplicationStatus; + declared_carryover_depth: AssistantCarryoverDepth | null; + truth_gate_carryover_depth: AssistantCarryoverDepth; + effective_carryover_depth: AssistantCarryoverDepth; + required_prior_state: AssistantStateSlice[]; + expected_answer_mode: AssistantAnswerMode | null; + state_mutations: string[]; + forbidden_carryover: string[]; + state_actions: { + living_mode_state: AssistantStateFrameAction; + root_frame_state: AssistantStateFrameAction; + selected_object_frame_state: AssistantStateFrameAction; + meta_frame_state: AssistantStateFrameAction; + clarification_state: AssistantStateFrameAction; + coverage_gate_state: AssistantStateFrameAction; + answer_context_state: AssistantStateFrameAction; + }; + coverage_gate_state: AssistantCoverageGateState; + reason_codes: string[]; +} + export interface AssistantSessionAggregateState { schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION; living_mode_state: { diff --git a/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts index 8ac8cdc..e733cba 100644 --- a/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts +++ b/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts @@ -74,6 +74,13 @@ describe("assistant address lane response runtime adapter", () => { }), answer_shape_contract: expect.objectContaining({ reply_type: "factual" + }), + assistant_state_transition_v1: expect.objectContaining({ + schema_version: "assistant_state_transition_runtime_v1", + state_owner: "assistantStateTransitionRuntimeAdapter" + }), + state_transition_contract: expect.objectContaining({ + schema_version: "assistant_state_transition_runtime_v1" }) }) ); @@ -128,10 +135,17 @@ describe("assistant address lane response runtime adapter", () => { answer_shape: "blocked_no_answer", reply_type: "partial_coverage" }), + assistant_state_transition_v1: expect.objectContaining({ + application_status: "unresolved", + effective_carryover_depth: "none" + }), transition_contract_id: null, capability_contract_id: null, truth_gate_contract_status: "unknown", - carryover_eligibility: "none" + carryover_eligibility: "none", + state_transition_id: null, + state_transition_status: "unresolved", + effective_carryover_depth: "none" }) ); expect(runtime.response).toEqual({ ok: true }); diff --git a/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts b/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts index 078c130..f0bbb76 100644 --- a/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts +++ b/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts @@ -134,6 +134,18 @@ describe("assistant debug payload assembler", () => { reply_type: "deep_analysis" }) ); + expect(payload.assistant_state_transition_v1).toEqual( + expect.objectContaining({ + schema_version: "assistant_state_transition_runtime_v1", + state_owner: "assistantStateTransitionRuntimeAdapter", + application_status: "unresolved" + }) + ); + expect(payload.state_transition_contract).toEqual( + expect.objectContaining({ + effective_carryover_depth: "none" + }) + ); }); it("omits optional fields when they are not provided", () => { diff --git a/llm_normalizer/backend/tests/assistantStateTransitionRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantStateTransitionRuntimeAdapter.test.ts new file mode 100644 index 0000000..c589fce --- /dev/null +++ b/llm_normalizer/backend/tests/assistantStateTransitionRuntimeAdapter.test.ts @@ -0,0 +1,162 @@ +import { describe, expect, it } from "vitest"; +import { + attachAssistantStateTransition, + resolveAssistantStateTransitionRuntime +} from "../src/services/assistantStateTransitionRuntimeAdapter"; + +describe("assistant state transition runtime adapter", () => { + it("applies root entry as a root-frame create and selected-object clear", () => { + const transition = resolveAssistantStateTransitionRuntime({ + addressDebug: { + capability_id: "confirmed_inventory_on_hand_as_of_date", + detected_intent: "inventory_on_hand_as_of_date", + rows_matched: 4, + route_expectation_status: "matched" + }, + groundingStatus: "grounded", + replyType: "factual" + }); + + expect(transition).toEqual( + expect.objectContaining({ + schema_version: "assistant_state_transition_runtime_v1", + state_owner: "assistantStateTransitionRuntimeAdapter", + transition_id: "T1", + transition_title: "Root Query Entry", + application_status: "applied", + declared_carryover_depth: "none", + truth_gate_carryover_depth: "root_only", + effective_carryover_depth: "none", + expected_answer_mode: "confirmed" + }) + ); + expect(transition.state_actions).toEqual( + expect.objectContaining({ + root_frame_state: "create", + selected_object_frame_state: "clear", + coverage_gate_state: "create", + answer_context_state: "create" + }) + ); + expect(transition.coverage_gate_state.truth_mode).toBe("confirmed"); + }); + + it("keeps selected-object short follow-ups object-scoped", () => { + const transition = resolveAssistantStateTransitionRuntime({ + addressDebug: { + capability_id: "inventory_inventory_purchase_provenance_for_item", + detected_intent: "inventory_purchase_provenance_for_item", + rows_matched: 1, + route_expectation_status: "matched" + }, + addressRuntimeMeta: { + dialogContinuationContract: { + decision: "continue_previous", + target_intent: "inventory_purchase_provenance_for_item" + } + }, + groundingStatus: "grounded", + replyType: "factual" + }); + + expect(transition.transition_id).toBe("T4"); + expect(transition.application_status).toBe("applied"); + expect(transition.effective_carryover_depth).toBe("object_only"); + expect(transition.state_actions.selected_object_frame_state).toBe("reuse"); + expect(transition.state_actions.root_frame_state).toBe("preserve"); + expect(transition.forbidden_carryover).toEqual( + expect.arrayContaining(["generic_chat_fallback", "data_scope_selection_fallback", "object_focus_reset"]) + ); + }); + + it("turns missing-anchor cases into clarification state transitions", () => { + const transition = resolveAssistantStateTransitionRuntime({ + addressDebug: { + capability_id: "inventory_inventory_purchase_provenance_for_item", + missing_required_filters: ["item"], + limited_reason_category: "missing_anchor" + }, + replyType: "partial_coverage" + }); + + expect(transition.transition_id).toBe("T7"); + expect(transition.application_status).toBe("clarification_required"); + expect(transition.effective_carryover_depth).toBe("full"); + expect(transition.state_actions.clarification_state).toBe("update"); + expect(transition.coverage_gate_state.truth_mode).toBe("clarification_required"); + }); + + it("blocks route expectation failures and forbids implicit follow-up reuse", () => { + const transition = resolveAssistantStateTransitionRuntime({ + addressDebug: { + capability_id: "confirmed_inventory_on_hand_as_of_date", + route_expectation_status: "mismatch" + }, + groundingStatus: "route_mismatch_blocked", + replyType: "partial_coverage" + }); + + expect(transition.transition_id).toBe("T10"); + expect(transition.application_status).toBe("blocked"); + expect(transition.effective_carryover_depth).toBe("none"); + expect(transition.state_actions.coverage_gate_state).toBe("block"); + expect(transition.forbidden_carryover).toEqual( + expect.arrayContaining(["blocked_as_confirmed_factual_answer", "implicit_followup_reuse"]) + ); + }); + + it("attaches compact debug fields and preserves the nested transition contract", () => { + const debug = attachAssistantStateTransition( + { + assistant_runtime_contract_v1: { + schema_version: "assistant_runtime_contracts_v1", + transition_contract_id: "T8", + transition_contract_title: "Meta Follow-Up Over Answer Object", + transition_contract_reason: ["capability_meta_followup_tool_gate_reason"], + capability_contract_id: "confirmed_inventory_on_hand_as_of_date", + capability_contract_reason: ["intent_matched_capability_contract"], + truth_gate_contract_status: "partial_supported", + carryover_eligibility: "meta_only" + }, + assistant_truth_answer_policy_v1: { + schema_version: "assistant_truth_answer_policy_runtime_v1", + policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter", + truth_gate: { + schema_version: "assistant_truth_answer_policy_runtime_v1", + policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter", + coverage_status: "partial", + evidence_grade: "weak", + grounding_status: "partial", + truth_mode: "limited", + carryover_eligibility: "meta_only", + reason_codes: ["truth_gate_partial_supported"], + source_truth_gate_status: "partial_supported", + blocked_or_limited_explanation: "evidence_or_coverage_is_partial" + }, + answer_shape: { + schema_version: "assistant_truth_answer_policy_runtime_v1", + policy_owner: "assistantTruthAnswerPolicyRuntimeAdapter", + answer_shape: "limited_with_reason", + reply_type: "deep_analysis", + capability_contract_id: "confirmed_inventory_on_hand_as_of_date", + transition_contract_id: "T8", + may_state_confirmed_facts: true, + must_include_limitation: true, + may_power_followup: true, + required_sections: ["direct_answer", "evidence_window", "limitations"], + downgrade_only: true + } + } + }, + { + replyType: "deep_analysis" + } + ); + + expect(debug.state_transition_id).toBe("T8"); + expect(debug.state_transition_status).toBe("applied"); + expect(debug.effective_carryover_depth).toBe("meta_only"); + expect(debug.assistant_state_transition_v1.state_actions.meta_frame_state).toBe("create"); + expect(debug.state_transition_contract.state_actions.answer_context_state).toBe("reuse"); + }); +});