"""S02核对结构化交付;不能用成功回执为任意审校字典背书。""" import asyncio import json import pytest import test_受控模型调用 as 模型测试 from muse.任务运行.接口 import 模型执行器, 证据服务 from muse.共享.错误 import Muse错误 from muse.基础设施.宿主.直接调用 import 直接宿主 from muse.基础设施.模型.HTTP传输 import HTTP传输 pytestmark = pytest.mark.数据库 模型环境 = 模型测试.模型环境 合成HTTP = 模型测试.合成HTTP def 调用(env, tmp_path, http, *, 仅回合=False): 库, 运行, 策略, 请求, 原文, 授权, 预算, ctx = env def 回复(_): 数据 = { "type": "response.completed", "response": { "id": "structured-review", "model": "claude-opus-4-8", "status": "completed", "output": [ { "type": "message", "content": [ { "type": "output_text", "text": json.dumps({"text": "结构化审校结果"}, ensure_ascii=False), } ], } ], "usage": {"input_tokens": 1, "output_tokens": 1}, }, } return ("data: " + json.dumps(数据) + "\n\n").encode() key = tmp_path / "key" key.write_text("synthetic-credential") 执行器 = 模型执行器( 运行, 预算, 原文, 证据服务(库), 策略, 直接宿主(HTTP传输(http(回复), "受控存储", str(key), "responses")), 模型测试.合成计价(), ) fn = 执行器.执行回合 if 仅回合 else 执行器.执行 return asyncio.run(fn(ctx, 请求, 阶段="生成", 原文授权ID=授权)) @pytest.mark.case_id( "NC-w22-22e001", environment="隔离PG与合成HTTP提供方", given="真实S02当前领取与未发送/已完成的模型调用", when="调用登记的结构化交付核对或文学片/报告组装公共入口", then=["只有完整派发和结构化响应才可核对,返回真实尝试与哈希"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_只有完整结构化响应可核对且不改业务内容__22e001(模型环境, tmp_path, 合成HTTP): 库, 运行, _, 请求, _, _, _, ctx = 模型环境 with 库.连接() as 连, 连.transaction(), pytest.raises(Muse错误): 运行.核对结构化交付于( 连, ctx.领取, 请求.调用ID, 请求.用户输入, 请求.输出合同, {"text": "结构化审校结果"} ) 交付 = 调用(模型环境, tmp_path, 合成HTTP) with 库.连接() as 连, 连.transaction(): 依据 = 运行.核对结构化交付于( 连, ctx.领取, 请求.调用ID, 请求.用户输入, 请求.输出合同, 交付.内容 ) assert str(依据["attempt_id"]) == ctx.领取.尝试ID and 依据["call_state"] == "saved" assert 依据["dispatch_hash"] == 依据["input_hash"] assert 依据["response_metadata"]["structured_output_hash"] @pytest.mark.case_id( "NC-w22-22e002", environment="隔离PG与合成HTTP提供方", given="结构化交付后篡改输出/输入/合同/调用或结束尝试", when="调用登记的结构化交付核对或文学片/报告组装公共入口", then=["拒绝不匹配绑定与失效领取,不能为任意产物背书"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) @pytest.mark.parametrize("变动", ["输出", "输入", "合同", "调用", "失败"]) def test_结构化交付拒错绑定及失败后保存__22e002(模型环境, tmp_path, 合成HTTP, 变动): 库, 运行, _, 请求, _, _, _, ctx = 模型环境 交付 = 调用(模型环境, tmp_path, 合成HTTP) 输入, 合同, 输出, call = 请求.用户输入, 请求.输出合同, 交付.内容, 请求.调用ID if 变动 == "输出": 输出 = {"text": "凭空更换结论"} elif 变动 == "输入": 输入 = "不是模型实际收到的输入" elif 变动 == "合同": 合同 = {"type": "object"} elif 变动 == "调用": call = "not-called" else: 运行.失败步骤(ctx.领取, "SYNTHETIC_FAILURE") with 库.连接() as 连, 连.transaction(), pytest.raises(Muse错误): 运行.核对结构化交付于(连, ctx.领取, call, 输入, 合同, 输出) @pytest.mark.case_id( "NC-w22-22e003", environment="隔离PG与合成HTTP提供方", given="只校验模型回合终态,没有最终结构化校验", when="调用登记的结构化交付核对或文学片/报告组装公共入口", then=["普通completed不能冒结构化最终交付"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_只检终态的普通回合不冒结构化最终交付__22e003(模型环境, tmp_path, 合成HTTP): 库, 运行, _, 请求, _, _, _, ctx = 模型环境 调用(模型环境, tmp_path, 合成HTTP, 仅回合=True) with 库.连接() as 连, 连.transaction(), pytest.raises(Muse错误): 运行.核对结构化交付于( 连, ctx.领取, 请求.调用ID, 请求.用户输入, 请求.输出合同, {"text": "结构化审校结果"} ) @pytest.mark.case_id( "NC-w25-25a001", environment="隔离PG及受控合成HTTP", given="同一S02尝试顺序执行两个真实模型回合", when="第二次确认成功或确认前中断后查询首轮及次轮", then=["第一轮仍按逐调用记录核验;当前入口不认旧轮,未确认次轮仍拒绝"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) @pytest.mark.parametrize("模型环境", [2], indirect=True) @pytest.mark.parametrize("second_saved", [True, False]) def test_同尝试多回合历史独立核验但未确认回合仍拒绝__25a001( 模型环境, tmp_path, 合成HTTP, monkeypatch, second_saved ): import hashlib from dataclasses import replace from datetime import UTC, datetime, timedelta from muse.任务运行.接口 import 请求字节 库, 运行, 策略, request, raw, _, budget, ctx = 模型环境 first = 调用(模型环境, tmp_path, 合成HTTP) request2 = replace(request, 调用ID="call-2", 用户输入="第二次独立合成输入") digest = hashlib.sha256(请求字节(request2)).hexdigest() auth2 = raw.批准保留( ctx.任务.任务ID, ctx.任务.作者, "approve-second", 来源版本="synthetic-1", 哈希=(digest,), 内容用途="generation", 方式="persistent", 有效期=datetime.now(UTC) + timedelta(minutes=5), 调用ID=request2.调用ID, 调用请求哈希=digest, ) second_env = (库, 运行, 策略, request2, raw, auth2, budget, ctx) if second_saved: second = 调用(second_env, tmp_path, 合成HTTP) else: def 不确认(self, *_args, **_kwargs): raise Muse错误("合成确认前中断") monkeypatch.setattr(模型执行器, "_确认保存", 不确认) with pytest.raises(Muse错误, match="确认前中断"): 调用(second_env, tmp_path, 合成HTTP) with 库.连接() as conn, conn.transaction(): historical = 运行.核对历史结构化交付于( conn, ctx.任务.作者, ctx.任务.任务ID, ctx.领取.尝试ID, request.调用ID, 用户输入哈希=hashlib.sha256(request.用户输入.encode()).hexdigest(), 输出合同=request.输出合同, 输出=first.内容, ) assert historical["response_metadata"]["call_id"] == request.调用ID assert historical["call_state"] == "saved" with pytest.raises(Muse错误): 运行.核对结构化交付于( conn, ctx.领取, request.调用ID, request.用户输入, request.输出合同, first.内容 ) if second_saved: current = 运行.核对结构化交付于( conn, ctx.领取, request2.调用ID, request2.用户输入, request2.输出合同, second.内容 ) assert current["response_metadata"]["call_id"] == request2.调用ID else: with pytest.raises(Muse错误): 运行.核对历史结构化交付于( conn, ctx.任务.作者, ctx.任务.任务ID, ctx.领取.尝试ID, request2.调用ID, 用户输入哈希=hashlib.sha256(request2.用户输入.encode()).hexdigest(), 输出合同=request2.输出合同, 输出={"text": "结构化审校结果"}, )