104 lines
4.6 KiB
Python
104 lines
4.6 KiB
Python
"""Guard the prompt evidence contract; no model or device is invoked here."""
|
||
|
||
from unittest.mock import AsyncMock, MagicMock, patch
|
||
|
||
import pytest
|
||
|
||
from artemis.agents.checker.checker import (
|
||
CheckReport,
|
||
assemble_checker_prompt_segments,
|
||
run_final_check,
|
||
)
|
||
from artemis.context import ArtemisContext, ExecutionSetup
|
||
|
||
|
||
@pytest.mark.parametrize("entry", ["checkpoint", "final"])
|
||
def test_bound_persistent_readout_can_supply_precision_beyond_pixel_ticks(entry):
|
||
prompt = assemble_checker_prompt_segments(entry, [], probe_tool_registered=False)
|
||
|
||
# Native origin alone is insufficient: the rule must bind the displayed value
|
||
# to the selected state and require stability plus the visible pointer check.
|
||
for requirement in (
|
||
"标签、节点角色/层级",
|
||
"目标控件当前所选值或固定指针所选时间",
|
||
"不能仅因原生节点含有数值或执行者称其可信",
|
||
"在被验收状态中持续显示",
|
||
"引用已停稳或暂停的证据",
|
||
"与同一控件的指针及可辨刻度一致",
|
||
"结合读数精度与用户容差",
|
||
"粗刻度无法独立区分秒数,不等于",
|
||
"无需再由粗像素刻度独立插值证明同一精确值",
|
||
):
|
||
assert requirement in prompt
|
||
|
||
assert "拖动浮层或原生时间读数与固定指针刻度冲突时" not in prompt
|
||
|
||
|
||
@pytest.mark.parametrize("entry", ["checkpoint", "final"])
|
||
def test_native_origin_does_not_promote_unbound_or_conflicting_evidence(entry):
|
||
prompt = assemble_checker_prompt_segments(entry, [], probe_tool_registered=False)
|
||
|
||
for excluded_source in ("事件卡片时间", "视频画面水印", "录制起止时间", "拖动/松手浮层"):
|
||
assert excluded_source in prompt
|
||
assert "即使它们也能通过原生节点读取,仍须先证明上述绑定与持久性" in prompt
|
||
assert "可辨精度上存在实际冲突" in prompt
|
||
assert "读数仍在变化、绑定不明或无法确认已停稳/暂停" in prompt
|
||
assert "应判证据不足(inconclusive)" in prompt
|
||
assert "不得挑选有利读数或忽略矛盾" in prompt
|
||
assert "可信读数明确超出用户容差时仍按通用规则判定" in prompt
|
||
|
||
|
||
@pytest.mark.parametrize("entry", ["checkpoint", "final"])
|
||
def test_pixel_fallback_remains_quantitative_and_requires_post_action_evidence(entry):
|
||
prompt = assemble_checker_prompt_segments(entry, [], probe_tool_registered=False)
|
||
fallback = prompt.split("没有可信持久读数时,", 1)[1]
|
||
|
||
assert "动作后已停稳截图的两处真实数值刻度与固定指针位置作线性插值" in fallback
|
||
assert "单位换算、估读不确定性及目标误差" in fallback
|
||
assert "目标误差加估读不确定性不超过用户容差" in fallback
|
||
assert "刻度非线性或无法确定已停稳,仍应判证据不足" in fallback
|
||
assert "操作前的校准计划及回执误差不能替代动作后测量" in fallback
|
||
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_final_review_receives_live_readout_and_rules_separately_from_report_claims():
|
||
ctx = MagicMock(spec=ArtemisContext)
|
||
ctx.execution_setup = ExecutionSetup()
|
||
ctx.data_engine = MagicMock()
|
||
ctx.data_engine.base_dir = "unused"
|
||
ctx.data_engine.get_agent_friendly_steps.return_value = []
|
||
ctx.data_engine.get_step_number.return_value = 7
|
||
elements = "[1] 当前所选值: 23.5; [2] 已暂停"
|
||
review = AsyncMock(return_value=CheckReport(verdicts=[]))
|
||
|
||
with (
|
||
patch("artemis.agents.checker.checker._run_check_loop", new=review),
|
||
patch(
|
||
"artemis.agents.checker.checker._capture_final_screen",
|
||
new=AsyncMock(return_value=("captured-image", elements)),
|
||
),
|
||
):
|
||
await run_final_check(
|
||
ctx,
|
||
goal="核对当前所选值及容差",
|
||
plan_text="",
|
||
ledger=[],
|
||
check_items=[],
|
||
execution_report="执行者声称当前所选值为 88",
|
||
)
|
||
|
||
review.assert_awaited_once()
|
||
messages = review.await_args.args[1]
|
||
assert "无需再由粗像素刻度独立插值证明同一精确值" in messages[0].content
|
||
assert "不得挑选有利读数或忽略矛盾" in messages[0].content
|
||
content = messages[1].content
|
||
text = content[0]["text"]
|
||
assert "Executor's Final Report (claims to verify, not independent evidence)" in text
|
||
assert "执行者声称当前所选值为 88" in text
|
||
assert f"# Final Screen Elements\n{elements}" in text
|
||
assert any(
|
||
block.get("type") == "image_url"
|
||
and block["image_url"]["url"] == "data:image/jpeg;base64,captured-image"
|
||
for block in content
|
||
)
|