93 lines
3.6 KiB
Python
93 lines
3.6 KiB
Python
from app.schemas.rag import RAGAskRequest
|
|
from app.services.pmflow_ai_answer_verifier_registry import PMFlowAIAnswerVerifierRegistry
|
|
from app.services.pmflow_ai_turn_runtime_service import (
|
|
PMFlowAITurnRuntimeService,
|
|
_verification_failure_allows_retry,
|
|
)
|
|
from app.services.pmflow_ai_turn_state_reviser import PMFlowAITurnStateReviser
|
|
|
|
|
|
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",
|
|
},
|
|
},
|
|
)
|
|
|
|
|
|
def _mismatched_static_payload() -> dict:
|
|
return {
|
|
"_runtime_payload_kind": "final_answer",
|
|
"answer": "我按内部合同回答。",
|
|
"answer_runtime_packet": {"objective": {}, "plan": {}, "draft": {}},
|
|
"customer_evidence_packet": {"source_summary": []},
|
|
}
|
|
|
|
|
|
def test_packet_av_verifier_failure_carries_verification_delta() -> None:
|
|
state = _protected_internal_state()
|
|
|
|
failure = PMFlowAIAnswerVerifierRegistry().verify_payload(
|
|
turn_state=state,
|
|
payload=_mismatched_static_payload(),
|
|
)
|
|
|
|
assert failure is not None
|
|
assert failure.verification_delta["packet_version"] == "pmflow_ai_verification_delta.v1"
|
|
assert failure.verification_delta["verification_ok"] is False
|
|
assert failure.verification_delta["retry_would_change_state"] is False
|
|
assert failure.verification_delta["customer_safe_effect"]["blocked_claims"]
|
|
assert "Static contract" in failure.verification_delta["internal_diagnostic"]
|
|
|
|
|
|
def test_packet_av_retry_gate_blocks_same_state_static_contract_retry() -> None:
|
|
state = _protected_internal_state()
|
|
failure = PMFlowAIAnswerVerifierRegistry().verify_payload(
|
|
turn_state=state,
|
|
payload=_mismatched_static_payload(),
|
|
)
|
|
|
|
assert failure is not None
|
|
assert _verification_failure_allows_retry(failure) is False
|
|
|
|
|
|
def test_packet_av_reviser_does_not_create_synthesize_gap_when_retry_would_not_change_state() -> None:
|
|
state = _protected_internal_state()
|
|
failure = PMFlowAIAnswerVerifierRegistry().verify_payload(
|
|
turn_state=state,
|
|
payload=_mismatched_static_payload(),
|
|
)
|
|
assert failure is not None
|
|
|
|
PMFlowAITurnStateReviser().revise_from_verification(turn_state=state, failure=failure)
|
|
|
|
assert not any(gap.system_action_hint == "synthesize_answer" for gap in state.operation_gaps)
|
|
assert state.state_revisions
|
|
assert "Static contract" not in state.state_revisions[-1].reason
|