АРЧ АП11 - Подключить shadow-контракты переходов и truth gate в debug payload
This commit is contained in:
parent
93ad18daa3
commit
1d95ed9b60
|
|
@ -2,6 +2,7 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponseRuntime;
|
||||
const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter");
|
||||
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
||||
function toRecordObject(value) {
|
||||
if (!value || typeof value !== "object") {
|
||||
return null;
|
||||
|
|
@ -187,6 +188,10 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
|||
period_to: input.toNonEmptyString(rootFilters?.period_to)
|
||||
};
|
||||
}
|
||||
const debugWithRuntimeContracts = (0, assistantRuntimeContractResolver_1.attachAssistantRuntimeContractShadow)(debug, {
|
||||
userMessage: input.userMessage,
|
||||
addressRuntimeMeta: input.llmPreDecomposeMeta
|
||||
});
|
||||
const finalization = finalizeAddressTurnSafe({
|
||||
sessionId: input.sessionId,
|
||||
userMessage: input.userMessage,
|
||||
|
|
@ -194,7 +199,7 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
|||
assistantReply: safeAddressReply,
|
||||
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
||||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||||
debug,
|
||||
debug: debugWithRuntimeContracts,
|
||||
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
||||
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
||||
appendItem: input.appendItem,
|
||||
|
|
@ -206,6 +211,6 @@ function runAssistantAddressLaneResponseRuntime(input) {
|
|||
});
|
||||
return {
|
||||
response: finalization.response,
|
||||
debug
|
||||
debug: debugWithRuntimeContracts
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload;
|
||||
const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver");
|
||||
const assistantStage4AnswerContractAudit_1 = require("./assistantStage4AnswerContractAudit");
|
||||
function toAnalysisContext(input) {
|
||||
if (!input.active) {
|
||||
|
|
@ -17,7 +18,7 @@ function toAnalysisContext(input) {
|
|||
function buildDeepAnalysisDebugPayload(input) {
|
||||
const analysisContext = toAnalysisContext(input.runtimeAnalysisContext);
|
||||
const answerContractStage4Audit = (0, assistantStage4AnswerContractAudit_1.buildStage4AnswerContractAuditV1)(input.assistantReply);
|
||||
return {
|
||||
const debugPayload = {
|
||||
trace_id: input.traceId,
|
||||
prompt_version: input.promptVersion,
|
||||
schema_version: input.schemaVersion,
|
||||
|
|
@ -93,4 +94,8 @@ function buildDeepAnalysisDebugPayload(input) {
|
|||
investigation_state_snapshot: input.investigationStateSnapshot,
|
||||
normalized: input.normalizedPayload
|
||||
};
|
||||
return (0, assistantRuntimeContractResolver_1.attachAssistantRuntimeContractShadow)(debugPayload, {
|
||||
addressRuntimeMeta: input.addressRuntimeMetaForDeep,
|
||||
groundingStatus: input.groundingCheck.status
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,220 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveAssistantRuntimeContractShadow = resolveAssistantRuntimeContractShadow;
|
||||
exports.buildAssistantRuntimeContractShadowFields = buildAssistantRuntimeContractShadowFields;
|
||||
exports.attachAssistantRuntimeContractShadow = attachAssistantRuntimeContractShadow;
|
||||
const assistantRuntimeContracts_1 = require("../types/assistantRuntimeContracts");
|
||||
const assistantRuntimeContractRegistry_1 = require("./assistantRuntimeContractRegistry");
|
||||
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 isAddressIntent(value) {
|
||||
return Boolean(value) && value !== "unknown";
|
||||
}
|
||||
function asNumber(value) {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function runtimeMetaFrom(input) {
|
||||
const direct = toRecordObject(input.addressRuntimeMeta);
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
const debug = toRecordObject(input.addressDebug);
|
||||
if (!debug) {
|
||||
return null;
|
||||
}
|
||||
const dialogContinuationContract = toRecordObject(debug.dialog_continuation_contract_v2);
|
||||
const orchestrationContract = toRecordObject(debug.orchestration_contract_v1);
|
||||
if (!dialogContinuationContract && !orchestrationContract) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
dialogContinuationContract,
|
||||
orchestrationContract,
|
||||
toolGateDecision: debug.tool_gate_decision,
|
||||
toolGateReason: debug.tool_gate_reason
|
||||
};
|
||||
}
|
||||
function resolveCapabilityContractId(debug, meta) {
|
||||
const explicitCapabilityId = toNonEmptyString(debug.capability_id);
|
||||
if (explicitCapabilityId && (0, assistantRuntimeContractRegistry_1.getAssistantCapabilityContract)(explicitCapabilityId)) {
|
||||
return {
|
||||
capabilityId: explicitCapabilityId,
|
||||
reasons: ["debug_capability_id_matched_contract"]
|
||||
};
|
||||
}
|
||||
const orchestrationContract = toRecordObject(meta?.orchestrationContract) ?? toRecordObject(debug.orchestration_contract_v1);
|
||||
const intent = toNonEmptyString(debug.detected_intent) ??
|
||||
toNonEmptyString(orchestrationContract?.address_intent);
|
||||
if (isAddressIntent(intent)) {
|
||||
const contract = (0, assistantRuntimeContractRegistry_1.getAssistantCapabilityContractByIntent)(intent);
|
||||
if (contract) {
|
||||
return {
|
||||
capabilityId: contract.capability_id,
|
||||
reasons: ["intent_matched_capability_contract"]
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
capabilityId: null,
|
||||
reasons: explicitCapabilityId ? ["debug_capability_id_has_no_contract"] : ["capability_contract_not_resolved"]
|
||||
};
|
||||
}
|
||||
function isSelectedObjectCapability(capabilityId) {
|
||||
if (!capabilityId) {
|
||||
return false;
|
||||
}
|
||||
const contract = (0, assistantRuntimeContractRegistry_1.getAssistantCapabilityContract)(capabilityId);
|
||||
return Boolean(contract?.requires_focus_object);
|
||||
}
|
||||
function resolveTransitionId(input) {
|
||||
const dialogContinuationContract = toRecordObject(input.meta?.dialogContinuationContract) ??
|
||||
toRecordObject(input.debug.dialog_continuation_contract_v2);
|
||||
const orchestrationContract = toRecordObject(input.meta?.orchestrationContract) ??
|
||||
toRecordObject(input.debug.orchestration_contract_v1);
|
||||
const finalDecision = toRecordObject(orchestrationContract?.final_decision);
|
||||
const hardMetaMode = toNonEmptyString(orchestrationContract?.hard_meta_mode);
|
||||
const toolGateReason = toNonEmptyString(input.meta?.toolGateReason) ??
|
||||
toNonEmptyString(input.debug.tool_gate_reason) ??
|
||||
toNonEmptyString(finalDecision?.tool_gate_reason);
|
||||
const continuationDecision = toNonEmptyString(dialogContinuationContract?.decision);
|
||||
const targetIntent = toNonEmptyString(dialogContinuationContract?.target_intent) ??
|
||||
toNonEmptyString(input.debug.detected_intent);
|
||||
const currentFrameKind = toNonEmptyString(toRecordObject(input.debug.address_root_frame_context)?.current_frame_kind);
|
||||
const missingFilters = Array.isArray(input.debug.missing_required_filters) ? input.debug.missing_required_filters.length : 0;
|
||||
if (input.groundingStatus === "route_mismatch_blocked" ||
|
||||
input.debug.route_expectation_status === "mismatch" ||
|
||||
input.debug.limited_reason_category === "execution_error") {
|
||||
return { transitionId: "T10", reasons: ["blocked_or_route_mismatch_debug_status"] };
|
||||
}
|
||||
if (missingFilters > 0 || input.debug.limited_reason_category === "missing_anchor") {
|
||||
return { transitionId: "T7", reasons: ["missing_anchor_or_filter_requires_clarification"] };
|
||||
}
|
||||
if (toolGateReason === "memory_recap_followup_detected") {
|
||||
return { transitionId: "T9", reasons: ["memory_recap_tool_gate_reason"] };
|
||||
}
|
||||
if (hardMetaMode === "capability" || toolGateReason === "assistant_capability_query_detected") {
|
||||
return { transitionId: "T8", reasons: ["capability_meta_followup_tool_gate_reason"] };
|
||||
}
|
||||
if (isSelectedObjectCapability(input.capabilityId)) {
|
||||
if (continuationDecision === "continue_previous") {
|
||||
return { transitionId: "T4", reasons: ["selected_object_capability_continue_previous"] };
|
||||
}
|
||||
if (continuationDecision === "new_topic") {
|
||||
return { transitionId: "T3", reasons: ["selected_object_capability_explicit_entry"] };
|
||||
}
|
||||
return { transitionId: "T5", reasons: ["selected_object_capability_without_explicit_continuation_decision"] };
|
||||
}
|
||||
if (currentFrameKind === "inventory_drilldown" && targetIntent && targetIntent !== "inventory_on_hand_as_of_date") {
|
||||
return { transitionId: "T6", reasons: ["root_context_reused_after_drilldown_context"] };
|
||||
}
|
||||
if (continuationDecision === "continue_previous") {
|
||||
return { transitionId: "T2", reasons: ["root_followup_continue_previous"] };
|
||||
}
|
||||
if (input.capabilityId) {
|
||||
return { transitionId: "T1", reasons: ["capability_contract_root_or_new_entry"] };
|
||||
}
|
||||
return { transitionId: null, reasons: ["transition_contract_not_resolved"] };
|
||||
}
|
||||
function resolveTruthGateStatus(input) {
|
||||
if (input.groundingStatus === "route_mismatch_blocked" || input.debug.route_expectation_status === "mismatch") {
|
||||
return "blocked_route_expectation_failure";
|
||||
}
|
||||
if (input.debug.limited_reason_category === "execution_error") {
|
||||
return "blocked_execution_error";
|
||||
}
|
||||
if (input.debug.limited_reason_category === "missing_anchor") {
|
||||
return "blocked_missing_anchor";
|
||||
}
|
||||
if (input.debug.temporal_guard_outcome === "ambiguous_limited" ||
|
||||
input.debug.temporal_alignment_status === "conflicting") {
|
||||
return "limited_temporal_or_contextual";
|
||||
}
|
||||
if (input.debug.limited_reason_category || input.groundingStatus === "partial") {
|
||||
return "partial_supported";
|
||||
}
|
||||
const rowsMatched = asNumber(input.debug.rows_matched);
|
||||
const routeExpectationStatus = toNonEmptyString(input.debug.route_expectation_status);
|
||||
if ((input.groundingStatus === "grounded" || (rowsMatched !== null && rowsMatched > 0)) && (!routeExpectationStatus || routeExpectationStatus === "matched")) {
|
||||
return "full_confirmed";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
function carryoverEligibilityFor(transitionId, truthGateStatus) {
|
||||
if (truthGateStatus.startsWith("blocked")) {
|
||||
return "none";
|
||||
}
|
||||
if (transitionId === "T3" || transitionId === "T4" || transitionId === "T5") {
|
||||
return "object_only";
|
||||
}
|
||||
if (transitionId === "T8" || transitionId === "T9") {
|
||||
return "meta_only";
|
||||
}
|
||||
if (transitionId === "T1" || transitionId === "T2" || transitionId === "T6") {
|
||||
return "root_only";
|
||||
}
|
||||
if (transitionId === "T7") {
|
||||
return "full";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
function resolveAssistantRuntimeContractShadow(input) {
|
||||
const debug = toRecordObject(input.addressDebug) ?? {};
|
||||
const meta = runtimeMetaFrom(input);
|
||||
const groundingStatus = toNonEmptyString(input.groundingStatus) ??
|
||||
toNonEmptyString(toRecordObject(debug.answer_grounding_check)?.status);
|
||||
const capability = resolveCapabilityContractId(debug, meta);
|
||||
const transition = resolveTransitionId({
|
||||
debug,
|
||||
meta,
|
||||
capabilityId: capability.capabilityId,
|
||||
groundingStatus
|
||||
});
|
||||
const transitionContract = transition.transitionId ? (0, assistantRuntimeContractRegistry_1.getAssistantTransitionContract)(transition.transitionId) : null;
|
||||
const truthGateStatus = resolveTruthGateStatus({ debug, groundingStatus });
|
||||
return {
|
||||
schema_version: assistantRuntimeContracts_1.ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_contract_id: transition.transitionId,
|
||||
transition_contract_title: transitionContract?.title ?? null,
|
||||
transition_contract_reason: transition.reasons,
|
||||
capability_contract_id: capability.capabilityId,
|
||||
capability_contract_reason: capability.reasons,
|
||||
truth_gate_contract_status: truthGateStatus,
|
||||
carryover_eligibility: carryoverEligibilityFor(transition.transitionId, truthGateStatus)
|
||||
};
|
||||
}
|
||||
function buildAssistantRuntimeContractShadowFields(input) {
|
||||
const decision = resolveAssistantRuntimeContractShadow(input);
|
||||
return {
|
||||
assistant_runtime_contract_v1: decision,
|
||||
transition_contract_id: decision.transition_contract_id,
|
||||
capability_contract_id: decision.capability_contract_id,
|
||||
truth_gate_contract_status: decision.truth_gate_contract_status
|
||||
};
|
||||
}
|
||||
function attachAssistantRuntimeContractShadow(debugPayload, input) {
|
||||
return {
|
||||
...debugPayload,
|
||||
...buildAssistantRuntimeContractShadowFields({
|
||||
...input,
|
||||
addressDebug: debugPayload
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
type AddressLlmPreDecomposeMetaLogInput,
|
||||
type FinalizeAssistantAddressTurnInput
|
||||
} from "./assistantAddressTurnFinalizeRuntimeAdapter";
|
||||
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||
|
||||
export interface RunAssistantAddressLaneResponseRuntimeInput<ResponseType = AssistantMessageResponsePayload> {
|
||||
sessionId: string;
|
||||
|
|
@ -246,6 +247,10 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
|||
period_to: input.toNonEmptyString(rootFilters?.period_to)
|
||||
};
|
||||
}
|
||||
const debugWithRuntimeContracts = attachAssistantRuntimeContractShadow(debug, {
|
||||
userMessage: input.userMessage,
|
||||
addressRuntimeMeta: input.llmPreDecomposeMeta
|
||||
});
|
||||
const finalization = finalizeAddressTurnSafe({
|
||||
sessionId: input.sessionId,
|
||||
userMessage: input.userMessage,
|
||||
|
|
@ -253,7 +258,7 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
|||
assistantReply: safeAddressReply,
|
||||
replyType: normalizeAddressReplyType(input.addressLane.reply_type),
|
||||
addressLaneDebug: normalizeAddressLaneDebug(input.addressLane.debug),
|
||||
debug,
|
||||
debug: debugWithRuntimeContracts,
|
||||
carryoverMeta: normalizeCarryoverMeta(input.carryoverMeta),
|
||||
llmPreDecomposeMeta: normalizeLlmPreDecomposeMeta(input.llmPreDecomposeMeta),
|
||||
appendItem: input.appendItem,
|
||||
|
|
@ -266,6 +271,6 @@ export function runAssistantAddressLaneResponseRuntime<ResponseType = AssistantM
|
|||
|
||||
return {
|
||||
response: finalization.response as ResponseType,
|
||||
debug
|
||||
debug: debugWithRuntimeContracts
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import type {
|
|||
GroundedAnswerEligibilityAudit,
|
||||
TemporalGuardAudit
|
||||
} from "./assistantRuntimeGuards";
|
||||
import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver";
|
||||
import { buildStage4AnswerContractAuditV1 } from "./assistantStage4AnswerContractAudit";
|
||||
|
||||
type RetrievalStatusItem = AssistantDebugPayload["retrieval_status"][number];
|
||||
|
|
@ -97,7 +98,7 @@ function toAnalysisContext(input: DeepAnalysisDebugPayloadInput["runtimeAnalysis
|
|||
export function buildDeepAnalysisDebugPayload(input: DeepAnalysisDebugPayloadInput): AssistantDebugPayload {
|
||||
const analysisContext = toAnalysisContext(input.runtimeAnalysisContext);
|
||||
const answerContractStage4Audit = buildStage4AnswerContractAuditV1(input.assistantReply);
|
||||
return {
|
||||
const debugPayload = {
|
||||
trace_id: input.traceId,
|
||||
prompt_version: input.promptVersion,
|
||||
schema_version: input.schemaVersion,
|
||||
|
|
@ -173,4 +174,8 @@ export function buildDeepAnalysisDebugPayload(input: DeepAnalysisDebugPayloadInp
|
|||
investigation_state_snapshot: input.investigationStateSnapshot,
|
||||
normalized: input.normalizedPayload
|
||||
} as unknown as AssistantDebugPayload;
|
||||
return attachAssistantRuntimeContractShadow(debugPayload as unknown as Record<string, unknown>, {
|
||||
addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record<string, unknown> | null | undefined,
|
||||
groundingStatus: input.groundingCheck.status
|
||||
}) as unknown as AssistantDebugPayload;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,294 @@
|
|||
import {
|
||||
ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
type AssistantCarryoverDepth,
|
||||
type AssistantRuntimeContractShadowDecision,
|
||||
type AssistantTransitionClassId
|
||||
} from "../types/assistantRuntimeContracts";
|
||||
import type { AddressIntent } from "../types/addressQuery";
|
||||
import {
|
||||
getAssistantCapabilityContract,
|
||||
getAssistantCapabilityContractByIntent,
|
||||
getAssistantTransitionContract
|
||||
} from "./assistantRuntimeContractRegistry";
|
||||
|
||||
export interface ResolveAssistantRuntimeContractShadowInput {
|
||||
userMessage?: unknown;
|
||||
addressDebug?: Record<string, unknown> | null;
|
||||
addressRuntimeMeta?: Record<string, unknown> | null;
|
||||
groundingStatus?: unknown;
|
||||
}
|
||||
|
||||
export interface AssistantRuntimeContractShadowFields {
|
||||
assistant_runtime_contract_v1: AssistantRuntimeContractShadowDecision;
|
||||
transition_contract_id: AssistantTransitionClassId | null;
|
||||
capability_contract_id: string | null;
|
||||
truth_gate_contract_status: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"];
|
||||
}
|
||||
|
||||
function toRecordObject(value: unknown): Record<string, unknown> | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
return value as Record<string, unknown>;
|
||||
}
|
||||
|
||||
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 isAddressIntent(value: string | null): value is AddressIntent {
|
||||
return Boolean(value) && value !== "unknown";
|
||||
}
|
||||
|
||||
function asNumber(value: unknown): number | null {
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function runtimeMetaFrom(input: ResolveAssistantRuntimeContractShadowInput): Record<string, unknown> | null {
|
||||
const direct = toRecordObject(input.addressRuntimeMeta);
|
||||
if (direct) {
|
||||
return direct;
|
||||
}
|
||||
const debug = toRecordObject(input.addressDebug);
|
||||
if (!debug) {
|
||||
return null;
|
||||
}
|
||||
const dialogContinuationContract = toRecordObject(debug.dialog_continuation_contract_v2);
|
||||
const orchestrationContract = toRecordObject(debug.orchestration_contract_v1);
|
||||
if (!dialogContinuationContract && !orchestrationContract) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
dialogContinuationContract,
|
||||
orchestrationContract,
|
||||
toolGateDecision: debug.tool_gate_decision,
|
||||
toolGateReason: debug.tool_gate_reason
|
||||
};
|
||||
}
|
||||
|
||||
function resolveCapabilityContractId(debug: Record<string, unknown>, meta: Record<string, unknown> | null): {
|
||||
capabilityId: string | null;
|
||||
reasons: string[];
|
||||
} {
|
||||
const explicitCapabilityId = toNonEmptyString(debug.capability_id);
|
||||
if (explicitCapabilityId && getAssistantCapabilityContract(explicitCapabilityId)) {
|
||||
return {
|
||||
capabilityId: explicitCapabilityId,
|
||||
reasons: ["debug_capability_id_matched_contract"]
|
||||
};
|
||||
}
|
||||
|
||||
const orchestrationContract = toRecordObject(meta?.orchestrationContract) ?? toRecordObject(debug.orchestration_contract_v1);
|
||||
const intent =
|
||||
toNonEmptyString(debug.detected_intent) ??
|
||||
toNonEmptyString(orchestrationContract?.address_intent);
|
||||
if (isAddressIntent(intent)) {
|
||||
const contract = getAssistantCapabilityContractByIntent(intent);
|
||||
if (contract) {
|
||||
return {
|
||||
capabilityId: contract.capability_id,
|
||||
reasons: ["intent_matched_capability_contract"]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
capabilityId: null,
|
||||
reasons: explicitCapabilityId ? ["debug_capability_id_has_no_contract"] : ["capability_contract_not_resolved"]
|
||||
};
|
||||
}
|
||||
|
||||
function isSelectedObjectCapability(capabilityId: string | null): boolean {
|
||||
if (!capabilityId) {
|
||||
return false;
|
||||
}
|
||||
const contract = getAssistantCapabilityContract(capabilityId);
|
||||
return Boolean(contract?.requires_focus_object);
|
||||
}
|
||||
|
||||
function resolveTransitionId(input: {
|
||||
debug: Record<string, unknown>;
|
||||
meta: Record<string, unknown> | null;
|
||||
capabilityId: string | null;
|
||||
groundingStatus: string | null;
|
||||
}): { transitionId: AssistantTransitionClassId | null; reasons: string[] } {
|
||||
const dialogContinuationContract =
|
||||
toRecordObject(input.meta?.dialogContinuationContract) ??
|
||||
toRecordObject(input.debug.dialog_continuation_contract_v2);
|
||||
const orchestrationContract =
|
||||
toRecordObject(input.meta?.orchestrationContract) ??
|
||||
toRecordObject(input.debug.orchestration_contract_v1);
|
||||
const finalDecision = toRecordObject(orchestrationContract?.final_decision);
|
||||
const hardMetaMode = toNonEmptyString(orchestrationContract?.hard_meta_mode);
|
||||
const toolGateReason =
|
||||
toNonEmptyString(input.meta?.toolGateReason) ??
|
||||
toNonEmptyString(input.debug.tool_gate_reason) ??
|
||||
toNonEmptyString(finalDecision?.tool_gate_reason);
|
||||
const continuationDecision = toNonEmptyString(dialogContinuationContract?.decision);
|
||||
const targetIntent =
|
||||
toNonEmptyString(dialogContinuationContract?.target_intent) ??
|
||||
toNonEmptyString(input.debug.detected_intent);
|
||||
const currentFrameKind = toNonEmptyString(toRecordObject(input.debug.address_root_frame_context)?.current_frame_kind);
|
||||
const missingFilters = Array.isArray(input.debug.missing_required_filters) ? input.debug.missing_required_filters.length : 0;
|
||||
|
||||
if (
|
||||
input.groundingStatus === "route_mismatch_blocked" ||
|
||||
input.debug.route_expectation_status === "mismatch" ||
|
||||
input.debug.limited_reason_category === "execution_error"
|
||||
) {
|
||||
return { transitionId: "T10", reasons: ["blocked_or_route_mismatch_debug_status"] };
|
||||
}
|
||||
|
||||
if (missingFilters > 0 || input.debug.limited_reason_category === "missing_anchor") {
|
||||
return { transitionId: "T7", reasons: ["missing_anchor_or_filter_requires_clarification"] };
|
||||
}
|
||||
|
||||
if (toolGateReason === "memory_recap_followup_detected") {
|
||||
return { transitionId: "T9", reasons: ["memory_recap_tool_gate_reason"] };
|
||||
}
|
||||
|
||||
if (hardMetaMode === "capability" || toolGateReason === "assistant_capability_query_detected") {
|
||||
return { transitionId: "T8", reasons: ["capability_meta_followup_tool_gate_reason"] };
|
||||
}
|
||||
|
||||
if (isSelectedObjectCapability(input.capabilityId)) {
|
||||
if (continuationDecision === "continue_previous") {
|
||||
return { transitionId: "T4", reasons: ["selected_object_capability_continue_previous"] };
|
||||
}
|
||||
if (continuationDecision === "new_topic") {
|
||||
return { transitionId: "T3", reasons: ["selected_object_capability_explicit_entry"] };
|
||||
}
|
||||
return { transitionId: "T5", reasons: ["selected_object_capability_without_explicit_continuation_decision"] };
|
||||
}
|
||||
|
||||
if (currentFrameKind === "inventory_drilldown" && targetIntent && targetIntent !== "inventory_on_hand_as_of_date") {
|
||||
return { transitionId: "T6", reasons: ["root_context_reused_after_drilldown_context"] };
|
||||
}
|
||||
|
||||
if (continuationDecision === "continue_previous") {
|
||||
return { transitionId: "T2", reasons: ["root_followup_continue_previous"] };
|
||||
}
|
||||
|
||||
if (input.capabilityId) {
|
||||
return { transitionId: "T1", reasons: ["capability_contract_root_or_new_entry"] };
|
||||
}
|
||||
|
||||
return { transitionId: null, reasons: ["transition_contract_not_resolved"] };
|
||||
}
|
||||
|
||||
function resolveTruthGateStatus(input: {
|
||||
debug: Record<string, unknown>;
|
||||
groundingStatus: string | null;
|
||||
}): AssistantRuntimeContractShadowDecision["truth_gate_contract_status"] {
|
||||
if (input.groundingStatus === "route_mismatch_blocked" || input.debug.route_expectation_status === "mismatch") {
|
||||
return "blocked_route_expectation_failure";
|
||||
}
|
||||
if (input.debug.limited_reason_category === "execution_error") {
|
||||
return "blocked_execution_error";
|
||||
}
|
||||
if (input.debug.limited_reason_category === "missing_anchor") {
|
||||
return "blocked_missing_anchor";
|
||||
}
|
||||
if (
|
||||
input.debug.temporal_guard_outcome === "ambiguous_limited" ||
|
||||
input.debug.temporal_alignment_status === "conflicting"
|
||||
) {
|
||||
return "limited_temporal_or_contextual";
|
||||
}
|
||||
if (input.debug.limited_reason_category || input.groundingStatus === "partial") {
|
||||
return "partial_supported";
|
||||
}
|
||||
const rowsMatched = asNumber(input.debug.rows_matched);
|
||||
const routeExpectationStatus = toNonEmptyString(input.debug.route_expectation_status);
|
||||
if ((input.groundingStatus === "grounded" || (rowsMatched !== null && rowsMatched > 0)) && (!routeExpectationStatus || routeExpectationStatus === "matched")) {
|
||||
return "full_confirmed";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function carryoverEligibilityFor(
|
||||
transitionId: AssistantTransitionClassId | null,
|
||||
truthGateStatus: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"]
|
||||
): AssistantCarryoverDepth {
|
||||
if (truthGateStatus.startsWith("blocked")) {
|
||||
return "none";
|
||||
}
|
||||
if (transitionId === "T3" || transitionId === "T4" || transitionId === "T5") {
|
||||
return "object_only";
|
||||
}
|
||||
if (transitionId === "T8" || transitionId === "T9") {
|
||||
return "meta_only";
|
||||
}
|
||||
if (transitionId === "T1" || transitionId === "T2" || transitionId === "T6") {
|
||||
return "root_only";
|
||||
}
|
||||
if (transitionId === "T7") {
|
||||
return "full";
|
||||
}
|
||||
return "none";
|
||||
}
|
||||
|
||||
export function resolveAssistantRuntimeContractShadow(
|
||||
input: ResolveAssistantRuntimeContractShadowInput
|
||||
): AssistantRuntimeContractShadowDecision {
|
||||
const debug = toRecordObject(input.addressDebug) ?? {};
|
||||
const meta = runtimeMetaFrom(input);
|
||||
const groundingStatus =
|
||||
toNonEmptyString(input.groundingStatus) ??
|
||||
toNonEmptyString(toRecordObject(debug.answer_grounding_check)?.status);
|
||||
const capability = resolveCapabilityContractId(debug, meta);
|
||||
const transition = resolveTransitionId({
|
||||
debug,
|
||||
meta,
|
||||
capabilityId: capability.capabilityId,
|
||||
groundingStatus
|
||||
});
|
||||
const transitionContract = transition.transitionId ? getAssistantTransitionContract(transition.transitionId) : null;
|
||||
const truthGateStatus = resolveTruthGateStatus({ debug, groundingStatus });
|
||||
return {
|
||||
schema_version: ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION,
|
||||
transition_contract_id: transition.transitionId,
|
||||
transition_contract_title: transitionContract?.title ?? null,
|
||||
transition_contract_reason: transition.reasons,
|
||||
capability_contract_id: capability.capabilityId,
|
||||
capability_contract_reason: capability.reasons,
|
||||
truth_gate_contract_status: truthGateStatus,
|
||||
carryover_eligibility: carryoverEligibilityFor(transition.transitionId, truthGateStatus)
|
||||
};
|
||||
}
|
||||
|
||||
export function buildAssistantRuntimeContractShadowFields(
|
||||
input: ResolveAssistantRuntimeContractShadowInput
|
||||
): AssistantRuntimeContractShadowFields {
|
||||
const decision = resolveAssistantRuntimeContractShadow(input);
|
||||
return {
|
||||
assistant_runtime_contract_v1: decision,
|
||||
transition_contract_id: decision.transition_contract_id,
|
||||
capability_contract_id: decision.capability_contract_id,
|
||||
truth_gate_contract_status: decision.truth_gate_contract_status
|
||||
};
|
||||
}
|
||||
|
||||
export function attachAssistantRuntimeContractShadow<T extends Record<string, unknown>>(
|
||||
debugPayload: T,
|
||||
input: Omit<ResolveAssistantRuntimeContractShadowInput, "addressDebug">
|
||||
): T & AssistantRuntimeContractShadowFields {
|
||||
return {
|
||||
...debugPayload,
|
||||
...buildAssistantRuntimeContractShadowFields({
|
||||
...input,
|
||||
addressDebug: debugPayload
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
@ -17,6 +17,10 @@ import type {
|
|||
} from "./stage2ProblemUnits";
|
||||
import type { AccountingGraphBuildResult } from "./stage4Graph";
|
||||
import type { AddressNavigationState } from "./addressNavigation";
|
||||
import type {
|
||||
AssistantRuntimeContractShadowDecision,
|
||||
AssistantTransitionClassId
|
||||
} from "./assistantRuntimeContracts";
|
||||
|
||||
export type AssistantFallbackType = "none" | "out_of_scope" | "clarification" | "partial" | "unknown";
|
||||
export type AssistantReplyType =
|
||||
|
|
@ -446,6 +450,10 @@ export interface AssistantDebugPayload {
|
|||
route_expectation_expected_selected_recipes?: string[];
|
||||
route_expectation_expected_requested_result_modes?: Array<"heuristic_candidates" | "confirmed_balance">;
|
||||
route_expectation_expected_result_modes?: Array<"heuristic_candidates" | "confirmed_balance">;
|
||||
assistant_runtime_contract_v1?: AssistantRuntimeContractShadowDecision;
|
||||
transition_contract_id?: AssistantTransitionClassId | null;
|
||||
capability_contract_id?: string | null;
|
||||
truth_gate_contract_status?: AssistantRuntimeContractShadowDecision["truth_gate_contract_status"];
|
||||
execution_lane?: "address_query" | "deep_analysis";
|
||||
llm_decomposition_applied?: boolean;
|
||||
llm_decomposition_attempted?: boolean;
|
||||
|
|
|
|||
|
|
@ -152,3 +152,21 @@ export interface AssistantCapabilityContract {
|
|||
required_transition_tests: string[];
|
||||
required_scenario_families: string[];
|
||||
}
|
||||
|
||||
export interface AssistantRuntimeContractShadowDecision {
|
||||
schema_version: typeof ASSISTANT_RUNTIME_CONTRACTS_SCHEMA_VERSION;
|
||||
transition_contract_id: AssistantTransitionClassId | null;
|
||||
transition_contract_title: string | null;
|
||||
transition_contract_reason: string[];
|
||||
capability_contract_id: string | null;
|
||||
capability_contract_reason: string[];
|
||||
truth_gate_contract_status:
|
||||
| "full_confirmed"
|
||||
| "partial_supported"
|
||||
| "blocked_missing_anchor"
|
||||
| "blocked_route_expectation_failure"
|
||||
| "blocked_execution_error"
|
||||
| "limited_temporal_or_contextual"
|
||||
| "unknown";
|
||||
carryover_eligibility: AssistantCarryoverDepth;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,12 +61,15 @@ describe("assistant address lane response runtime adapter", () => {
|
|||
expect.objectContaining({
|
||||
assistant_known_organizations: ["ООО Ромашка", "ООО Лютик"],
|
||||
assistant_active_organization: "ООО Ромашка",
|
||||
address_followup_offer: { suggestion: "continue_previous" }
|
||||
address_followup_offer: { suggestion: "continue_previous" },
|
||||
assistant_runtime_contract_v1: expect.objectContaining({
|
||||
schema_version: "assistant_runtime_contracts_v1"
|
||||
})
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps debug minimal when optional enrichment is absent", () => {
|
||||
it("keeps debug bounded to shadow contracts when optional enrichment is absent", () => {
|
||||
const runtime = runAssistantAddressLaneResponseRuntime({
|
||||
sessionId: "asst-2",
|
||||
userMessage: "raw",
|
||||
|
|
@ -97,7 +100,18 @@ describe("assistant address lane response runtime adapter", () => {
|
|||
})
|
||||
});
|
||||
|
||||
expect(runtime.debug).toEqual({});
|
||||
expect(runtime.debug).toEqual(
|
||||
expect.objectContaining({
|
||||
assistant_runtime_contract_v1: expect.objectContaining({
|
||||
transition_contract_id: null,
|
||||
capability_contract_id: null,
|
||||
truth_gate_contract_status: "unknown"
|
||||
}),
|
||||
transition_contract_id: null,
|
||||
capability_contract_id: null,
|
||||
truth_gate_contract_status: "unknown"
|
||||
})
|
||||
);
|
||||
expect(runtime.response).toEqual({ ok: true });
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
listAssistantTransitionContracts,
|
||||
listInventoryCapabilityContracts
|
||||
} from "../src/services/assistantRuntimeContractRegistry";
|
||||
import { resolveAssistantRuntimeContractShadow } from "../src/services/assistantRuntimeContractResolver";
|
||||
|
||||
describe("assistant runtime contract registry", () => {
|
||||
it("declares the architecture turnaround transition set T1-T10", () => {
|
||||
|
|
@ -81,4 +82,65 @@ describe("assistant runtime contract registry", () => {
|
|||
expect(contract.execution_error_behavior).toBe("blocked_execution_error");
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves shadow contract ids for selected-object provenance debug", () => {
|
||||
const decision = resolveAssistantRuntimeContractShadow({
|
||||
addressDebug: {
|
||||
detected_intent: "inventory_purchase_provenance_for_item",
|
||||
capability_id: "inventory_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"
|
||||
});
|
||||
|
||||
expect(decision.transition_contract_id).toBe("T4");
|
||||
expect(decision.capability_contract_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||||
expect(decision.truth_gate_contract_status).toBe("full_confirmed");
|
||||
expect(decision.carryover_eligibility).toBe("object_only");
|
||||
});
|
||||
|
||||
it("resolves meta follow-up and blocked route expectation in shadow mode", () => {
|
||||
const metaDecision = resolveAssistantRuntimeContractShadow({
|
||||
addressRuntimeMeta: {
|
||||
toolGateReason: "assistant_capability_query_detected",
|
||||
orchestrationContract: {
|
||||
hard_meta_mode: "capability",
|
||||
address_intent: "inventory_on_hand_as_of_date"
|
||||
}
|
||||
}
|
||||
});
|
||||
expect(metaDecision.transition_contract_id).toBe("T8");
|
||||
expect(metaDecision.capability_contract_id).toBe("confirmed_inventory_on_hand_as_of_date");
|
||||
expect(metaDecision.carryover_eligibility).toBe("meta_only");
|
||||
|
||||
const blockedDecision = resolveAssistantRuntimeContractShadow({
|
||||
addressDebug: {
|
||||
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||
route_expectation_status: "mismatch"
|
||||
},
|
||||
groundingStatus: "route_mismatch_blocked"
|
||||
});
|
||||
expect(blockedDecision.transition_contract_id).toBe("T10");
|
||||
expect(blockedDecision.truth_gate_contract_status).toBe("blocked_route_expectation_failure");
|
||||
expect(blockedDecision.carryover_eligibility).toBe("none");
|
||||
});
|
||||
|
||||
it("classifies temporal limitations as a distinct truth gate status", () => {
|
||||
const decision = resolveAssistantRuntimeContractShadow({
|
||||
addressDebug: {
|
||||
capability_id: "confirmed_inventory_on_hand_as_of_date",
|
||||
temporal_guard_outcome: "ambiguous_limited"
|
||||
},
|
||||
groundingStatus: "partial"
|
||||
});
|
||||
|
||||
expect(decision.truth_gate_contract_status).toBe("limited_temporal_or_contextual");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue