import copy import inspect import time from types import SimpleNamespace import pytest from app.schemas.rag import RAGAskRequest from app.services.customer_answer_presentation_service import CustomerAnswerPresentationService from app.services.pmflow_ai_answer_verifier_registry import PMFlowAIAnswerVerifierRegistry from app.services.pmflow_ai_static_contract_registry import PMFlowAIStaticContractRegistry from app.services.pmflow_ai_turn_runtime_service import ( PMFlowAITurnRuntimeService, _render_source_authority_answer_from_turn_state, ) def _service() -> PMFlowAITurnRuntimeService: return PMFlowAITurnRuntimeService(object(), tenant_id=1) def _protected_internal_state(): return _service().build_initial_turn_state( request=RAGAskRequest(question="你的技术架构是什么"), route_plan={}, turn_hypothesis={ "normalized_goal": "request_pmflow_internal_architecture", "desired_outcome": "说明是否可以披露 PMFlow 内部实现", "answer_shape": "general_answer", "goal_frames": [ { "frame_id": "internal-disclosure-boundary", "operation_kind": "explain_assistant", "target_scope": {"scope_type": "current_space", "binding_strength": "surface"}, "target_object_requirement": "none", "source_policy": "none", "answer_shape": "general_answer", "confidence": 0.9, } ], "operation_gaps": [], "disclosure_policy": { "visibility_class": "protected_pmflow_internal", "protected_categories": ["internal_architecture", "source_code", "model_provider"], "allowed_response_mode": "protected_refusal", "contract_key": "protected_internal_refusal.v1", }, }, ) @pytest.mark.asyncio async def test_packet_an_static_contract_is_answer_runtime_material_not_exact_answer() -> None: state = _protected_internal_state() contract = PMFlowAIStaticContractRegistry().resolve_for_turn_state(state) payload = await _service()._run_state_answer_synthesis( request=RAGAskRequest(question="你的技术架构是什么"), current_user=SimpleNamespace(id=1, tenant_id=1), route_plan={}, turn_state=state, presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1), started_at=time.perf_counter(), ) assert contract is not None assert not hasattr(contract, "answer") assert payload["answer_runtime_packet"]["plan"]["draft_sources"] == ["产品能力或披露边界合同"] assert payload["answer_runtime_packet"]["draft"]["source_renderer"] == "产品能力或披露边界合同" assert "不能披露" in payload["answer"] assert ".answer" not in inspect.getsource(_render_source_authority_answer_from_turn_state) @pytest.mark.asyncio async def test_packet_an_verifier_accepts_contract_guided_rewording() -> None: state = _protected_internal_state() payload = await _service()._run_state_answer_synthesis( request=RAGAskRequest(question="你的技术架构是什么"), current_user=SimpleNamespace(id=1, tenant_id=1), route_plan={}, turn_state=state, presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1), started_at=time.perf_counter(), ) reworded = copy.deepcopy(payload) reworded["answer"] = ( "这类内容属于 PMFlow 的内部实现与机密信息,用户侧不能披露。" "我可以改为说明产品里可见、可使用的能力边界。" ) reworded["reply_text"] = reworded["answer"] failure = PMFlowAIAnswerVerifierRegistry().verify_payload(turn_state=state, payload=reworded) assert failure is None def test_packet_an_verifier_rejects_static_contract_without_answer_runtime_packet() -> None: state = _protected_internal_state() payload = { "_runtime_payload_kind": "final_answer", "answer": "PMFlow 的后端在 backend/app/services/pmflow_ai_turn_runtime_service.py 里组织运行。", "customer_evidence_packet": {"source_summary": ["PMFlow 内部信息保护边界"]}, } failure = PMFlowAIAnswerVerifierRegistry().verify_payload(turn_state=state, payload=payload) assert failure is not None assert failure.failure_type == "static_contract_mismatch" assert failure.next_action_hint == "synthesize_answer"