diff --git a/docs/ARCH/11 - architecture_turnaround/14 - semantic_dialog_authority_recovery_plan_2026-04-19.md b/docs/ARCH/11 - architecture_turnaround/14 - semantic_dialog_authority_recovery_plan_2026-04-19.md index 951bdf0..a9e5c60 100644 --- a/docs/ARCH/11 - architecture_turnaround/14 - semantic_dialog_authority_recovery_plan_2026-04-19.md +++ b/docs/ARCH/11 - architecture_turnaround/14 - semantic_dialog_authority_recovery_plan_2026-04-19.md @@ -891,6 +891,36 @@ Validation: - `npm test -- assistantMcpDiscoveryRuntimeEntryPoint.test.ts assistantMcpDiscoveryTurnInputAdapter.test.ts assistantMcpDiscoveryPolicy.test.ts assistantMcpCatalogIndex.test.ts assistantMcpDiscoveryPlanner.test.ts assistantMcpDiscoveryRuntimeAdapter.test.ts assistantMcpDiscoveryPilotExecutor.test.ts assistantMcpDiscoveryAnswerAdapter.test.ts assistantMcpDiscoveryRuntimeBridge.test.ts` passed 40/40; - `npm run build` passed. +## Progress Update - 2026-04-20 MCP Discovery Debug Attachment + +The tenth implementation slice of Big Block 5 added a debug/evidence attachment layer: + +- `assistantMcpDiscoveryDebugAttachment.ts` +- `assistantMcpDiscoveryDebugAttachment.test.ts` + +It is now attached to: + +- address-lane response runtime debug; +- deep-analysis debug payloads. + +This still does not execute MCP discovery automatically and still does not change the user-facing answer. + +It gives runtime and replay review a stable observable surface: + +- `assistant_mcp_discovery_entry_point_v1`; +- `mcp_discovery_entry_status`; +- `mcp_discovery_attempted`; +- `mcp_discovery_hot_runtime_wired=false`; +- `mcp_discovery_bridge_status`; +- `mcp_discovery_answer_mode`; +- business-fact/user-facing authorization flags; +- clarification flag. + +Validation: + +- `npm test -- assistantMcpDiscoveryDebugAttachment.test.ts assistantDebugPayloadAssembler.test.ts assistantAddressLaneResponseRuntimeAdapter.test.ts assistantMcpDiscoveryRuntimeEntryPoint.test.ts` passed 13/13; +- `npm run build` passed. + ## Execution Rule Do not implement this plan as: diff --git a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js index 2ce9c63..eaac231 100644 --- a/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantAddressLaneResponseRuntimeAdapter.js @@ -4,6 +4,7 @@ exports.runAssistantAddressLaneResponseRuntime = runAssistantAddressLaneResponse const assistantAddressTurnFinalizeRuntimeAdapter_1 = require("./assistantAddressTurnFinalizeRuntimeAdapter"); const assistantCapabilityBindingResponseGuard_1 = require("./assistantCapabilityBindingResponseGuard"); const assistantCapabilityRuntimeBindingAdapter_1 = require("./assistantCapabilityRuntimeBindingAdapter"); +const assistantMcpDiscoveryDebugAttachment_1 = require("./assistantMcpDiscoveryDebugAttachment"); const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver"); const assistantStateTransitionRuntimeAdapter_1 = require("./assistantStateTransitionRuntimeAdapter"); const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter"); @@ -208,13 +209,16 @@ function runAssistantAddressLaneResponseRuntime(input) { addressRuntimeMeta: input.llmPreDecomposeMeta, replyType: normalizeAddressReplyType(input.addressLane.reply_type) }); + const debugWithMcpDiscovery = (0, assistantMcpDiscoveryDebugAttachment_1.attachAssistantMcpDiscoveryDebug)(debugWithCapabilityBinding, { + addressRuntimeMeta: input.llmPreDecomposeMeta + }); const guardedResponse = (0, assistantCapabilityBindingResponseGuard_1.applyAssistantCapabilityBindingResponseGuard)({ assistantReply: safeAddressReply, replyType: normalizeAddressReplyType(input.addressLane.reply_type), - capabilityBinding: debugWithCapabilityBinding.assistant_capability_binding_v1 + capabilityBinding: debugWithMcpDiscovery.assistant_capability_binding_v1 }); const debugWithResponseGuard = { - ...debugWithCapabilityBinding, + ...debugWithMcpDiscovery, capability_binding_response_guard: guardedResponse.audit }; const finalization = finalizeAddressTurnSafe({ diff --git a/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js b/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js index 7477e65..c4e0249 100644 --- a/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js +++ b/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js @@ -5,6 +5,7 @@ exports.buildAssistantBackendErrorDebugPayload = buildAssistantBackendErrorDebug exports.buildAddressRuntimeDebugPayload = buildAddressRuntimeDebugPayload; exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload; const assistantCapabilityRuntimeBindingAdapter_1 = require("./assistantCapabilityRuntimeBindingAdapter"); +const assistantMcpDiscoveryDebugAttachment_1 = require("./assistantMcpDiscoveryDebugAttachment"); const assistantRuntimeContractResolver_1 = require("./assistantRuntimeContractResolver"); const assistantStateTransitionRuntimeAdapter_1 = require("./assistantStateTransitionRuntimeAdapter"); const assistantTruthAnswerPolicyRuntimeAdapter_1 = require("./assistantTruthAnswerPolicyRuntimeAdapter"); @@ -240,6 +241,7 @@ function buildDeepAnalysisDebugPayload(input) { address_llm_predecompose_contract: input.addressRuntimeMetaForDeep?.predecomposeContract ?? null, address_semantic_extraction_contract: input.addressRuntimeMetaForDeep?.semanticExtractionContract ?? null, orchestration_contract_v1: input.addressRuntimeMetaForDeep?.orchestrationContract ?? null, + assistant_mcp_discovery_entry_point_v1: input.addressRuntimeMetaForDeep?.mcpDiscoveryRuntimeEntryPoint ?? null, assistant_outcome_class_v1: input.outcomeClassV1, assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1, answer_contract_stage4_v1: answerContractStage4Audit, @@ -263,10 +265,13 @@ function buildDeepAnalysisDebugPayload(input) { coverageReport: input.coverageReport, replyType: "deep_analysis" }); - return (0, assistantCapabilityRuntimeBindingAdapter_1.attachAssistantCapabilityRuntimeBinding)(debugWithStateTransition, { + const debugWithCapabilityBinding = (0, assistantCapabilityRuntimeBindingAdapter_1.attachAssistantCapabilityRuntimeBinding)(debugWithStateTransition, { addressRuntimeMeta: input.addressRuntimeMetaForDeep, groundingStatus: input.groundingCheck.status, coverageReport: input.coverageReport, replyType: "deep_analysis" }); + return (0, assistantMcpDiscoveryDebugAttachment_1.attachAssistantMcpDiscoveryDebug)(debugWithCapabilityBinding, { + addressRuntimeMeta: input.addressRuntimeMetaForDeep + }); } diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryDebugAttachment.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryDebugAttachment.js new file mode 100644 index 0000000..7351a56 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryDebugAttachment.js @@ -0,0 +1,53 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantMcpDiscoveryDebugAttachmentFields = buildAssistantMcpDiscoveryDebugAttachmentFields; +exports.attachAssistantMcpDiscoveryDebug = attachAssistantMcpDiscoveryDebug; +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 isMcpDiscoveryEntryPointContract(value) { + const record = toRecordObject(value); + return (record?.schema_version === "assistant_mcp_discovery_runtime_entry_point_v1" && + record?.policy_owner === "assistantMcpDiscoveryRuntimeEntryPoint"); +} +function resolveEntryPoint(input) { + if (isMcpDiscoveryEntryPointContract(input.entryPoint)) { + return input.entryPoint; + } + const runtimeMetaEntryPoint = input.addressRuntimeMeta?.mcpDiscoveryRuntimeEntryPoint ?? + input.addressRuntimeMeta?.assistantMcpDiscoveryRuntimeEntryPoint ?? + input.addressRuntimeMeta?.assistant_mcp_discovery_entry_point_v1; + return isMcpDiscoveryEntryPointContract(runtimeMetaEntryPoint) ? runtimeMetaEntryPoint : null; +} +function buildAssistantMcpDiscoveryDebugAttachmentFields(input) { + const entryPoint = resolveEntryPoint(input); + const bridge = toRecordObject(entryPoint?.bridge); + const answerDraft = toRecordObject(bridge?.answer_draft); + return { + assistant_mcp_discovery_entry_point_v1: entryPoint, + mcp_discovery_entry_status: toNonEmptyString(entryPoint?.entry_status), + mcp_discovery_attempted: Boolean(entryPoint?.discovery_attempted), + mcp_discovery_hot_runtime_wired: false, + mcp_discovery_bridge_status: toNonEmptyString(bridge?.bridge_status), + mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode), + mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true, + mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true, + mcp_discovery_requires_clarification: bridge?.requires_user_clarification === true + }; +} +function attachAssistantMcpDiscoveryDebug(debugPayload, input) { + return { + ...debugPayload, + ...buildAssistantMcpDiscoveryDebugAttachmentFields(input) + }; +} diff --git a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts index a6d1ed6..b925d14 100644 --- a/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantAddressLaneResponseRuntimeAdapter.ts @@ -9,6 +9,7 @@ import { } from "./assistantAddressTurnFinalizeRuntimeAdapter"; import { applyAssistantCapabilityBindingResponseGuard } from "./assistantCapabilityBindingResponseGuard"; import { attachAssistantCapabilityRuntimeBinding } from "./assistantCapabilityRuntimeBindingAdapter"; +import { attachAssistantMcpDiscoveryDebug } from "./assistantMcpDiscoveryDebugAttachment"; import { attachAssistantRuntimeContractShadow } from "./assistantRuntimeContractResolver"; import { attachAssistantStateTransition } from "./assistantStateTransitionRuntimeAdapter"; import { attachAssistantTruthAnswerPolicy } from "./assistantTruthAnswerPolicyRuntimeAdapter"; @@ -267,13 +268,16 @@ export function runAssistantAddressLaneResponseRuntime, replyType: "deep_analysis" }); - return attachAssistantCapabilityRuntimeBinding(debugWithStateTransition, { + const debugWithCapabilityBinding = attachAssistantCapabilityRuntimeBinding(debugWithStateTransition, { addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record | null | undefined, groundingStatus: input.groundingCheck.status, coverageReport: input.coverageReport as unknown as Record, replyType: "deep_analysis" + }); + return attachAssistantMcpDiscoveryDebug(debugWithCapabilityBinding, { + addressRuntimeMeta: input.addressRuntimeMetaForDeep as unknown as Record | null | undefined }) as unknown as AssistantDebugPayload; } diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryDebugAttachment.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryDebugAttachment.ts new file mode 100644 index 0000000..79ea915 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryDebugAttachment.ts @@ -0,0 +1,82 @@ +import type { AssistantMcpDiscoveryRuntimeEntryPointContract } from "./assistantMcpDiscoveryRuntimeEntryPoint"; + +export interface AssistantMcpDiscoveryDebugAttachmentFields { + assistant_mcp_discovery_entry_point_v1: AssistantMcpDiscoveryRuntimeEntryPointContract | null; + mcp_discovery_entry_status: string | null; + mcp_discovery_attempted: boolean; + mcp_discovery_hot_runtime_wired: false; + mcp_discovery_bridge_status: string | null; + mcp_discovery_answer_mode: string | null; + mcp_discovery_business_fact_answer_allowed: boolean; + mcp_discovery_user_facing_response_allowed: boolean; + mcp_discovery_requires_clarification: boolean; +} + +export interface AttachAssistantMcpDiscoveryDebugInput { + entryPoint?: unknown; + addressRuntimeMeta?: Record | null; +} + +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 isMcpDiscoveryEntryPointContract(value: unknown): value is AssistantMcpDiscoveryRuntimeEntryPointContract { + const record = toRecordObject(value); + return ( + record?.schema_version === "assistant_mcp_discovery_runtime_entry_point_v1" && + record?.policy_owner === "assistantMcpDiscoveryRuntimeEntryPoint" + ); +} + +function resolveEntryPoint(input: AttachAssistantMcpDiscoveryDebugInput): AssistantMcpDiscoveryRuntimeEntryPointContract | null { + if (isMcpDiscoveryEntryPointContract(input.entryPoint)) { + return input.entryPoint; + } + const runtimeMetaEntryPoint = + input.addressRuntimeMeta?.mcpDiscoveryRuntimeEntryPoint ?? + input.addressRuntimeMeta?.assistantMcpDiscoveryRuntimeEntryPoint ?? + input.addressRuntimeMeta?.assistant_mcp_discovery_entry_point_v1; + return isMcpDiscoveryEntryPointContract(runtimeMetaEntryPoint) ? runtimeMetaEntryPoint : null; +} + +export function buildAssistantMcpDiscoveryDebugAttachmentFields( + input: AttachAssistantMcpDiscoveryDebugInput +): AssistantMcpDiscoveryDebugAttachmentFields { + const entryPoint = resolveEntryPoint(input); + const bridge = toRecordObject(entryPoint?.bridge); + const answerDraft = toRecordObject(bridge?.answer_draft); + + return { + assistant_mcp_discovery_entry_point_v1: entryPoint, + mcp_discovery_entry_status: toNonEmptyString(entryPoint?.entry_status), + mcp_discovery_attempted: Boolean(entryPoint?.discovery_attempted), + mcp_discovery_hot_runtime_wired: false, + mcp_discovery_bridge_status: toNonEmptyString(bridge?.bridge_status), + mcp_discovery_answer_mode: toNonEmptyString(answerDraft?.answer_mode), + mcp_discovery_business_fact_answer_allowed: bridge?.business_fact_answer_allowed === true, + mcp_discovery_user_facing_response_allowed: bridge?.user_facing_response_allowed === true, + mcp_discovery_requires_clarification: bridge?.requires_user_clarification === true + }; +} + +export function attachAssistantMcpDiscoveryDebug>( + debugPayload: T, + input: AttachAssistantMcpDiscoveryDebugInput +): T & AssistantMcpDiscoveryDebugAttachmentFields { + return { + ...debugPayload, + ...buildAssistantMcpDiscoveryDebugAttachmentFields(input) + }; +} diff --git a/llm_normalizer/backend/src/types/assistant.ts b/llm_normalizer/backend/src/types/assistant.ts index 9a30e9f..0c40065 100644 --- a/llm_normalizer/backend/src/types/assistant.ts +++ b/llm_normalizer/backend/src/types/assistant.ts @@ -86,6 +86,7 @@ export interface AssistantAddressRuntimeMetaForDeep { predecomposeContract?: Record | null; semanticExtractionContract?: Record | null; orchestrationContract?: Record | null; + mcpDiscoveryRuntimeEntryPoint?: Record | null; } export interface AssistantRequirement { diff --git a/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts index 9534cc7..7d6127d 100644 --- a/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts +++ b/llm_normalizer/backend/tests/assistantAddressLaneResponseRuntimeAdapter.test.ts @@ -92,7 +92,72 @@ describe("assistant address lane response runtime adapter", () => { capability_binding_response_guard: expect.objectContaining({ schema_version: "assistant_capability_binding_response_guard_v1", applied: false - }) + }), + assistant_mcp_discovery_entry_point_v1: null, + mcp_discovery_attempted: false, + mcp_discovery_hot_runtime_wired: false + }) + ); + }); + + it("attaches MCP discovery summary from predecompose runtime meta without changing the reply", () => { + const runtime = runAssistantAddressLaneResponseRuntime({ + sessionId: "asst-mcp", + userMessage: "raw", + effectiveAddressUserMessage: "raw", + addressLane: { + handled: true, + reply_text: "answer", + reply_type: "partial_coverage", + debug: {} + }, + llmPreDecomposeMeta: { + mcpDiscoveryRuntimeEntryPoint: { + schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", + policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", + entry_status: "bridge_executed", + hot_runtime_wired: false, + discovery_attempted: true, + turn_input: { adapter_status: "ready" }, + bridge: { + bridge_status: "answer_draft_ready", + user_facing_response_allowed: true, + business_fact_answer_allowed: true, + requires_user_clarification: false, + answer_draft: { answer_mode: "confirmed_with_bounded_inference" } + }, + reason_codes: ["runtime_entry_point_bridge_executed"] + } + }, + knownOrganizations: [], + activeOrganization: null, + sanitizeOutgoingAssistantText: (text) => String(text ?? ""), + buildAddressDebugPayload: () => ({}), + buildAddressFollowupOffer: () => null, + mergeKnownOrganizations: (items) => items, + toNonEmptyString: () => null, + appendItem: () => {}, + getSession: () => ({ session_id: "asst-mcp", updated_at: "", items: [], investigation_state: null } as any), + persistSession: () => {}, + cloneConversation: (items) => items, + logEvent: () => {}, + messageIdFactory: () => "msg-mcp", + finalizeAddressTurn: () => ({ + response: { + ok: true + } + }) + }); + + expect(runtime.response).toEqual({ ok: true }); + expect(runtime.debug).toEqual( + expect.objectContaining({ + mcp_discovery_entry_status: "bridge_executed", + mcp_discovery_attempted: true, + mcp_discovery_bridge_status: "answer_draft_ready", + mcp_discovery_answer_mode: "confirmed_with_bounded_inference", + mcp_discovery_business_fact_answer_allowed: true, + mcp_discovery_hot_runtime_wired: false }) ); }); diff --git a/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts b/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts index a5e06d2..096dae1 100644 --- a/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts +++ b/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts @@ -154,6 +154,9 @@ describe("assistant debug payload assembler", () => { binding_action: "observe_only" }) ); + expect(payload.assistant_mcp_discovery_entry_point_v1).toBeNull(); + expect(payload.mcp_discovery_attempted).toBe(false); + expect(payload.mcp_discovery_hot_runtime_wired).toBe(false); }); it("omits optional fields when they are not provided", () => { @@ -188,4 +191,35 @@ describe("assistant debug payload assembler", () => { expect(payload.answer_contract_stage4_v1?.legacy_blocks_present).toContain("Что сломано"); expect(payload.answer_contract_stage4_v1?.legacy_blocks_present).toContain("Ограничения"); }); + + it("attaches MCP discovery entry point summary when runtime meta provides it", () => { + const input = baseInput(); + input.addressRuntimeMetaForDeep = { + ...input.addressRuntimeMetaForDeep, + mcpDiscoveryRuntimeEntryPoint: { + schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", + policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", + entry_status: "bridge_executed", + hot_runtime_wired: false, + discovery_attempted: true, + turn_input: { adapter_status: "ready" }, + bridge: { + bridge_status: "answer_draft_ready", + user_facing_response_allowed: true, + business_fact_answer_allowed: true, + requires_user_clarification: false, + answer_draft: { answer_mode: "confirmed_with_bounded_inference" } + }, + reason_codes: ["runtime_entry_point_bridge_executed"] + } + }; + + const payload = buildDeepAnalysisDebugPayload(input); + + expect(payload.mcp_discovery_entry_status).toBe("bridge_executed"); + expect(payload.mcp_discovery_attempted).toBe(true); + expect(payload.mcp_discovery_bridge_status).toBe("answer_draft_ready"); + expect(payload.mcp_discovery_answer_mode).toBe("confirmed_with_bounded_inference"); + expect(payload.mcp_discovery_business_fact_answer_allowed).toBe(true); + }); }); diff --git a/llm_normalizer/backend/tests/assistantMcpDiscoveryDebugAttachment.test.ts b/llm_normalizer/backend/tests/assistantMcpDiscoveryDebugAttachment.test.ts new file mode 100644 index 0000000..5eb96fe --- /dev/null +++ b/llm_normalizer/backend/tests/assistantMcpDiscoveryDebugAttachment.test.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from "vitest"; +import { attachAssistantMcpDiscoveryDebug } from "../src/services/assistantMcpDiscoveryDebugAttachment"; + +function entryPointContract(overrides: Record = {}) { + return { + schema_version: "assistant_mcp_discovery_runtime_entry_point_v1", + policy_owner: "assistantMcpDiscoveryRuntimeEntryPoint", + entry_status: "bridge_executed", + hot_runtime_wired: false, + discovery_attempted: true, + turn_input: { adapter_status: "ready" }, + bridge: { + bridge_status: "answer_draft_ready", + user_facing_response_allowed: true, + business_fact_answer_allowed: true, + requires_user_clarification: false, + answer_draft: { + answer_mode: "confirmed_with_bounded_inference" + } + }, + reason_codes: ["runtime_entry_point_bridge_executed"], + ...overrides + }; +} + +describe("assistant MCP discovery debug attachment", () => { + it("attaches a validated entry point contract and exposes summary flags", () => { + const debug = attachAssistantMcpDiscoveryDebug( + { trace_id: "trace-1" }, + { addressRuntimeMeta: { mcpDiscoveryRuntimeEntryPoint: entryPointContract() } } + ); + + expect(debug.assistant_mcp_discovery_entry_point_v1?.schema_version).toBe( + "assistant_mcp_discovery_runtime_entry_point_v1" + ); + expect(debug.mcp_discovery_entry_status).toBe("bridge_executed"); + expect(debug.mcp_discovery_attempted).toBe(true); + expect(debug.mcp_discovery_hot_runtime_wired).toBe(false); + expect(debug.mcp_discovery_bridge_status).toBe("answer_draft_ready"); + expect(debug.mcp_discovery_answer_mode).toBe("confirmed_with_bounded_inference"); + expect(debug.mcp_discovery_business_fact_answer_allowed).toBe(true); + expect(debug.mcp_discovery_user_facing_response_allowed).toBe(true); + expect(debug.mcp_discovery_requires_clarification).toBe(false); + }); + + it("keeps safe null flags when no validated entry point exists", () => { + const debug = attachAssistantMcpDiscoveryDebug( + { trace_id: "trace-1" }, + { addressRuntimeMeta: { mcpDiscoveryRuntimeEntryPoint: { schema_version: "wrong" } } } + ); + + expect(debug.assistant_mcp_discovery_entry_point_v1).toBeNull(); + expect(debug.mcp_discovery_entry_status).toBeNull(); + expect(debug.mcp_discovery_attempted).toBe(false); + expect(debug.mcp_discovery_hot_runtime_wired).toBe(false); + expect(debug.mcp_discovery_bridge_status).toBeNull(); + expect(debug.mcp_discovery_answer_mode).toBeNull(); + expect(debug.mcp_discovery_business_fact_answer_allowed).toBe(false); + expect(debug.mcp_discovery_user_facing_response_allowed).toBe(false); + expect(debug.mcp_discovery_requires_clarification).toBe(false); + }); +});