import { describe, expect, it } from "vitest"; import { composeAssistantAnswer } from "../src/services/answerComposer"; import type { UnifiedRetrievalResult } from "../src/types/assistant"; function buildRouteSummary() { return { mode: "deterministic_v2" as const, message_in_scope: true, scope_confidence: "high" as const, planner: { total_fragments: 1, in_scope_fragments: 1, out_of_scope_fragments: 0, discarded_fragments: 0, contains_multiple_tasks: false }, decisions: [], fallback: { type: "none" as const, message: null } }; } describe("assistant answer leakage guard", () => { it("removes raw technical refs from assistant reply but keeps structured refs in answer structure", () => { const retrieval: UnifiedRetrievalResult = { fragment_id: "F1", requirement_ids: ["R1"], route: "store_feature_risk", status: "ok", result_type: "list", items: [ { source_entity: "Document", source_id: "c921c08a-c117-11ea-a2e2-00155d012600", risk_score: 4 } ], summary: { broad_query_detected: false, broad_result_flag: false, minimum_evidence_failed: false, narrowing_strength: "strong" }, evidence: [ { evidence_id: "ev-1", claim_ref: "requirement:R1", source_type: "retrieval_item", source_ref: { schema_version: "evidence_source_ref_v1", namespace: "snapshot_2020", entity: "document", id: "c921c08a-c117-11ea-a2e2-00155d012600", period: "2020-06", canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|c921c08a-c117-11ea-a2e2-00155d012600|2020-06" }, pointer: { fragment_id: "F1", route: "store_feature_risk", source: { namespace: "snapshot_2020", entity: "document", id: "c921c08a-c117-11ea-a2e2-00155d012600", period: "2020-06" }, locator: { field_path: "risk_score", item_index: 0 } }, evidence_kind: "anomaly_signal", mechanism_note: null, confidence: "medium", limitation: { reason_code: "weak_source_mapping", note: null }, payload: { risk_score: 4 } } ], why_included: ["synthetic-test"], selection_reason: ["synthetic-test"], risk_factors: ["document_conflict"], business_interpretation: ["synthetic-test"], confidence: "medium", limitations: ["Weak source mapping evidence."], errors: [] }; const output = composeAssistantAnswer({ userMessage: "Проверь документный риск по счету 60 за 2020-06.", routeSummary: buildRouteSummary(), retrievalResults: [retrieval], requirements: [ { requirement_id: "R1", source_fragment_id: "F1", requirement_text: "Проверить документный риск", subject_tokens: ["account_60", "document", "period_2020_06"], status: "covered", route: "store_feature_risk" } ], coverageReport: { requirements_total: 1, requirements_covered: 1, requirements_uncovered: [], requirements_partially_covered: [], clarification_needed_for: [], out_of_scope_requirements: [] }, groundingCheck: { status: "grounded", route_subject_match: true, missing_requirements: [], reasons: [], why_included_summary: ["synthetic-test"], selection_reason_summary: ["synthetic-test"] }, enableAnswerPolicyV11: true, enableProblemCentricAnswerV1: true }); expect(output.assistant_reply).not.toMatch(/source_ref|canonical_ref|fragment_id|entity_id|guid|uuid/i); expect(output.assistant_reply).not.toMatch(/\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i); expect(output.assistant_reply).not.toContain("evidence_source_ref_v1|"); expect(output.assistant_reply).toMatch(/опор|документ|проводк|проблем/i); expect(output.answer_structure_v11?.evidence_block.source_refs?.length).toBeGreaterThan(0); }); });