"""运行配置角色探针:证据结构、身份核对与失败关闭;宿主为替身,不证明真实链路。""" from pathlib import Path import pytest import yaml from muse.任务运行.接口 import ( 凭据引用, 提供方配置, 模型用量, 模型结果, 角色策略目录, 运行配置内容, ) from muse.共享.错误 import Muse错误 from muse.基础设施.模型.角色探针 import 角色运行探针 仓库根 = Path(__file__).resolve().parents[2] 发布身份 = "探针资源-1" def _策略() -> 角色策略目录: 定义 = yaml.safe_load((仓库根 / "配置/角色策略.yaml").read_text(encoding="utf-8")) return 角色策略目录(定义, 资源发布身份=发布身份) def _配置(角色="extractor", model="claude-opus-4-8") -> 运行配置内容: 策略 = _策略() return 运行配置内容( "direct", "1", 策略.定义["version"], 发布身份, "账户-1", {角色: {"provider": "网关", "model": model, "thinking": "off"}}, (凭据引用("key", "受控存储", "/不存在/凭据.txt"),), (提供方配置("网关", "chat-completions", "http://127.0.0.1:1/v1/chat/completions", "key"),), "费率-1", ) class _假已备: def __init__(self, 宿主: "_假宿主") -> None: self.宿主 = 宿主 async def 调用(self, 收到片段=None) -> 模型结果: return self.宿主.结果 class _假宿主: def __init__(self, 结果: 模型结果) -> None: self.结果 = 结果 self.请求 = None def 名称(self) -> str: return "direct" def 准备(self, 请求): self.请求 = 请求 return _假已备(self) def _成功结果(实际模型="claude-opus-4-8", 文本='{"ok": true}') -> 模型结果: return 模型结果( "completed", 文本, 实际模型, 模型用量(5, 7), "探针响应-1", 回放协议="chat-completions", 回放内容=(), ) @pytest.mark.case_id( "NC-r2-da0001", environment="离线单元(注入宿主)", given="运行配置声明合成单角色探针与受控凭据", when="执行运行配置验证(固定探针,冻结请求,不外发)", then=["逐角色仅组装并判定固定探针请求,费用与证据走统一治理账;不因配置草案验证取得业务执行权"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_合成单角色探针冻结请求且禁止绕过治理__da0001() -> None: 宿主 = _假宿主(_成功结果()) 探针 = 角色运行探针(_策略(), 宿主工厂=lambda 提供方, 凭据: 宿主) 请求 = 探针.请求("task-1", "extractor", _配置()) assert 请求 == 探针.请求("task-1", "extractor", _配置()) assert 请求.调用ID != 探针.请求("task-2", "extractor", _配置()).调用ID assert 请求.工具 == () and 请求.最大输出token == 64 and 请求.总期限秒 == 60 assert 探针.核对(请求, _成功结果()) == {"ok": True} with pytest.raises(Muse错误, match="具名命令"): 探针.验证(_配置(), _用途()) assert 宿主.请求 is None @pytest.mark.case_id( "NC-r2-da0002", environment="离线单元(注入宿主)", given="模型不在角色策略或探针输出不确认或未完成", when="执行运行配置验证", then=["拒绝生成验证证据"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_探针在模型不在策略或输出不确认时拒绝__da0002() -> None: 策略 = _策略() 不在策略 = 角色运行探针(策略, 宿主工厂=lambda 提供方, 凭据: _假宿主(_成功结果("other-model"))) with pytest.raises(Muse错误): 不在策略.请求("task", "extractor", _配置(model="glm-9.9")) 不确认 = 角色运行探针( 策略, 宿主工厂=lambda 提供方, 凭据: _假宿主(_成功结果(文本='{"ok": false}')) ) with pytest.raises(Muse错误, match="未返回可用确认"): 不确认.核对(不确认.请求("task", "extractor", _配置()), _成功结果(文本='{"ok": false}')) 未完成 = 角色运行探针( 策略, 宿主工厂=lambda 提供方, 凭据: _假宿主( 模型结果("incomplete", "", "claude-opus-4-8", 模型用量(5, 0), None) ), ) with pytest.raises(Muse错误): 未完成.核对( 未完成.请求("task", "extractor", _配置()), 模型结果("incomplete", "", "claude-opus-4-8", None), ) @pytest.mark.case_id( "NC-r2-da0004", environment="离线单元(注入宿主)", given="运行配置声明 thinking=off,提供方回包仍带推理内容", when="执行运行配置验证", then=["在验证阶段拒绝,不把无法关闭推理的协议当成通过"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_探针拒绝声明关闭推理却仍返回推理的提供方__da0004() -> None: 推理结果 = 模型结果( "completed", '{"ok": true}', "claude-opus-4-8", 模型用量(5, 9), "探针响应-2", 回放协议="chat-completions", 回放内容=(), 推理字符数=128, ) 探针 = 角色运行探针(_策略(), 宿主工厂=lambda 提供方, 凭据: _假宿主(推理结果)) with pytest.raises(Muse错误, match="thinking=off"): 探针.核对(探针.请求("task", "extractor", _配置()), 推理结果) @pytest.mark.case_id( "NC-r2-da0003", environment="离线单元(注入宿主)", given="配置固定的资源发布身份与当前安装不一致", when="执行运行配置验证", then=["按资源发布身份拒绝"], contract="docs/系统架构/新版设计/模块设计/S02-任务运行.md", ) def test_探针核对资源与角色策略版本__da0003() -> None: 策略 = _策略() 探针 = 角色运行探针(策略, 宿主工厂=lambda 提供方, 凭据: _假宿主(_成功结果())) 其他资源 = 运行配置内容( "direct", "1", 策略.定义["version"], "别的发布身份", "账户-1", {"extractor": {"provider": "网关", "model": "claude-opus-4-8", "thinking": "off"}}, (凭据引用("key", "受控存储", "/不存在/凭据.txt"),), (提供方配置("网关", "chat-completions", "http://127.0.0.1:1/v1/chat/completions", "key"),), "费率-1", ) with pytest.raises(Muse错误, match="资源或角色策略"): 探针.请求("task", "extractor", 其他资源) def _用途(): from muse.共享.调用身份 import 用途 return 用途.生产 @pytest.mark.case_id("NC-O02-PROBE-CAPABILITY") def test_验证任务只接受固定草案且拒绝工具与业务输入(monkeypatch): from contextlib import nullcontext from dataclasses import replace from types import SimpleNamespace import muse.任务运行.任务领取 as 领取模块 import muse.任务运行.配置探针 as 探针模块 from muse.任务运行.接口 import 内容哈希 from muse.任务运行.配置版本 import 配置快照 内容 = _配置() 配置 = 配置快照("draft", "1", 内容哈希(内容.冻结()), 内容) 任务 = SimpleNamespace(任务ID="task") 领取 = SimpleNamespace(任务ID="task", 步骤ID="extractor") 连 = SimpleNamespace(transaction=lambda: nullcontext()) 数据库 = SimpleNamespace(用途=_用途(), 连接=lambda: nullcontext(连)) 运行 = SimpleNamespace( 数据库=数据库, 重验执行范围=lambda _: None, 读取任务=lambda _: 任务, ) 执行器 = SimpleNamespace( 运行=运行, 角色=_策略(), 固定配置=配置, 计价=SimpleNamespace(版本=内容.计价版本) ) 上下文 = SimpleNamespace(任务=任务, 领取=领取) monkeypatch.setattr(领取模块, "校验领取", lambda *_: None) monkeypatch.setattr(探针模块, "读取探针绑定于", lambda *_: 配置) 请求 = 探针模块.探针请求("task", "extractor", 内容, _策略()) assert 探针模块.核对探针请求(执行器, 上下文, 请求)[0] == 请求 for 篡改 in ( replace(请求, 用户输入="读取作者正文"), replace(请求, provider="other-endpoint"), replace(请求, 工具=({"name": "write_canonical"},)), replace(请求, 最大输出token=65), ): with pytest.raises(Muse错误, match="零工具请求"): 探针模块.核对探针请求(执行器, 上下文, 篡改) 执行器.固定配置 = replace(配置, 版本="other") with pytest.raises(Muse错误, match="草案端点"): 探针模块.核对探针请求(执行器, 上下文, 请求)