import { describe, expect, it } from "vitest"; import { applyAssistantCapabilityBindingResponseGuard } from "../src/services/assistantCapabilityBindingResponseGuard"; import type { AssistantCapabilityRuntimeBindingContract } from "../src/types/assistantRuntimeContracts"; function binding(overrides: Partial): AssistantCapabilityRuntimeBindingContract { return { schema_version: "assistant_capability_runtime_binding_v1", binding_owner: "assistantCapabilityRuntimeBindingAdapter", capability_id: "inventory_inventory_purchase_provenance_for_item", capability_contract_id: "inventory_inventory_purchase_provenance_for_item", binding_status: "bound", binding_action: "allow", runtime_lane_expected: "address_exact", runtime_lane_observed: "address_exact", execution_adapter: "AddressQueryService", transition_id: "T4", transition_allowed: true, required_anchors: ["item"], provided_anchors: ["item"], missing_anchors: [], requires_focus_object: true, focus_object_binding_status: "inferred_from_anchor", result_shape: "supplier_purchase_provenance_trace", answer_object_shape: "inventory_provenance_bundle", truth_gate_behavior: "partial_or_blocked_if_evidence_insufficient", truth_fallback_allowed: true, answer_shape_compatible: true, violations: [], reason_codes: ["binding_status_bound"], ...overrides }; } describe("assistant capability binding response guard", () => { it("leaves allowed answers unchanged", () => { const output = applyAssistantCapabilityBindingResponseGuard({ assistantReply: "answer", replyType: "factual", capabilityBinding: binding({}) }); expect(output.assistantReply).toBe("answer"); expect(output.replyType).toBe("factual"); expect(output.audit.applied).toBe(false); expect(output.audit.guarded_reply_type).toBe("factual"); }); it("turns missing required anchors into clarification replies", () => { const output = applyAssistantCapabilityBindingResponseGuard({ assistantReply: "unsafe answer", replyType: "factual", capabilityBinding: binding({ binding_status: "blocked", binding_action: "clarify", missing_anchors: ["item"], focus_object_binding_status: "missing", violations: ["required_anchor_missing", "focus_object_required_but_unbound"], reason_codes: ["required_anchor_missing"] }) }); expect(output.replyType).toBe("partial_coverage"); expect(output.assistantReply).toContain("Нужно уточнение"); expect(output.assistantReply).toContain("item"); expect(output.audit.applied).toBe(true); expect(output.audit.reason_codes).toContain("capability_binding_guard_clarification_reply"); }); it("turns blocked incompatible transitions into bounded replies", () => { const output = applyAssistantCapabilityBindingResponseGuard({ assistantReply: "unsafe answer", replyType: "factual", capabilityBinding: binding({ binding_status: "blocked", binding_action: "block", violations: ["transition_not_supported_by_capability"], reason_codes: ["transition_not_supported_by_capability"] }) }); expect(output.replyType).toBe("partial_coverage"); expect(output.assistantReply).toContain("Не могу надежно подтвердить"); expect(output.assistantReply).toContain("transition_not_supported_by_capability"); expect(output.audit.applied).toBe(true); expect(output.audit.reason_codes).toContain("capability_binding_guard_blocked_reply"); }); it("downgrades factual reply type for limited bound answers without rewriting text", () => { const output = applyAssistantCapabilityBindingResponseGuard({ assistantReply: "limited answer", replyType: "factual", capabilityBinding: binding({ binding_status: "bound_with_limits", binding_action: "limit", reason_codes: ["truth_mode_limited"] }) }); expect(output.assistantReply).toBe("limited answer"); expect(output.replyType).toBe("partial_coverage"); expect(output.audit.applied).toBe(true); expect(output.audit.reason_codes).toContain("capability_binding_guard_limited_reply_type"); }); });