muse-agent-example/tests/契约/test_探索回放合同.py

206 lines
8.6 KiB
Python

"""探索清单与确定性回放的合同行为:未知工具、越界参数、缺来源拒绝;顺序、范围、理由与消费哈希可核对。
离线纯函数与合成读取器;不连库、不代表真实来源授权。旧 TC 分配不承身份,按功能价值新写。
"""
import pytest
from jsonschema import Draft202012Validator
from muse.上下文.接口 import (
上下文错误,
探索条目,
来源绑定,
校验清单,
记录探索,
逐项比对,
)
from muse.任务运行.接口 import (
内容哈希,
只读工具集,
工具定义,
工具来源,
工具结果,
工具范围,
工具请求,
模型协议错误,
)
范围 = {
"work_id": "work-a",
"as_of": 2,
"source_ids": ["doc-1", "plan-1", "fact-lin"],
"documents": [
{
"chapter_id": "ch-1",
"branch_id": "main",
"revision": 1,
"document_id": "doc-1",
"document_hash": "hash-doc-1",
"position": 1,
}
],
"directory_revision": 3,
"authorized_by": "author-a",
"run_purpose": "production",
"content_purpose": "generation",
"target_plan": {
"chapter_id": "ch-3",
"plan_id": "plan-1",
"revision": 1,
"content_hash": "hash-plan-1",
},
"facts": {"system_revision": 5},
}
投影 = "visible-text-codepoints/v1:production:generation"
文档来源 = 工具来源("doc-1", "1", "hash-doc-1", 投影)
文档绑定 = (来源绑定("doc-1", "1", "hash-doc-1", 投影),)
文档结果 = 工具结果({"chapter_id": "ch-1", "text": "林深停在码头,雨还没停。"}, (文档来源,))
def 带参条目(工具名: str, 参数: dict, 顺序: int = 1, 调用ID: str = "call-x") -> 探索条目:
"""以固定来源绑定与消费哈希构造任意参数条目;参数合法性正是被测对象。"""
return 探索条目(
调用ID=调用ID,
工具名=工具名,
顺序=顺序,
参数=参数,
来源引用=文档绑定,
消费哈希=内容哈希(文档结果.内容),
)
def test_未知工具与未授权工具分别明确拒绝__b7e0a1() -> None:
with pytest.raises(上下文错误) as 异常:
校验清单((带参条目("read_anything", {"chapter_id": "ch-1"}),), 范围)
assert 异常.value.错误码 == "CONTEXT_INPUT_INVALID"
assert "未知工具" in 异常.value.说明 and "read_anything" in 异常.value.说明
# 工具真实存在但任务范围未冻结对应来源:拒绝的是授权,不是字符串存在性
无细纲 = {**范围, "target_plan": None}
with pytest.raises(上下文错误) as 异常:
校验清单((带参条目("query_work_plan", {}),), 无细纲)
assert 异常.value.错误码 == "SCOPE_DENIED"
assert "query_work_plan" in 异常.value.说明 and "冻结范围" in 异常.value.说明
无事实 = {**范围, "facts": None}
with pytest.raises(上下文错误) as 异常:
校验清单((带参条目("query_facts_as_of", {}),), 无事实)
assert 异常.value.错误码 == "SCOPE_DENIED"
def test_参数越出冻结范围与扩权字段拒绝__b7e0a2() -> None:
未冻结版本 = 带参条目("read_history_document", {"chapter_id": "ch-1", "revision": 2})
with pytest.raises(上下文错误) as 异常:
校验清单((未冻结版本,), 范围)
assert 异常.value.错误码 == "SCOPE_DENIED"
assert "不在冻结范围内" in 异常.value.说明 and "版本 2" in 异常.value.说明
未冻结文稿 = 带参条目(
"read_history_document", {"chapter_id": "ch-2", "revision": 1, "branch_id": "draft"}
)
with pytest.raises(上下文错误) as 异常:
校验清单((未冻结文稿,), 范围)
assert 异常.value.错误码 == "SCOPE_DENIED"
扩权 = 带参条目("read_history_document", {"chapter_id": "ch-1", "revision": 1, "as_of": 9})
with pytest.raises(上下文错误) as 异常:
校验清单((扩权,), 范围)
assert 异常.value.错误码 == "SCOPE_DENIED"
assert "扩权" in 异常.value.说明 or "不得" in 异常.value.说明
# 角色视角必须携带知情主体,其他视角不得携带:与工具读取同一规则
无知情主体 = 带参条目("query_facts_as_of", {"view": "character"})
with pytest.raises(上下文错误) as 异常:
校验清单((无知情主体,), 范围)
assert 异常.value.错误码 == "CONTEXT_INPUT_INVALID"
assert "知情主体" in 异常.value.说明
非法视角 = 带参条目("query_facts_as_of", {"view": "narrator"})
with pytest.raises(上下文错误) as 异常:
校验清单((非法视角,), 范围)
assert 异常.value.错误码 == "CONTEXT_INPUT_INVALID"
def test_回放比对指明版本哈希投影与内容漂移__b7e0a3() -> None:
基准 = 带参条目(
"read_history_document", {"chapter_id": "ch-1", "revision": 1}, 调用ID="call-hist"
)
assert 逐项比对(基准, 文档结果) == ()
版本漂移 = 逐项比对(
基准, 工具结果(文档结果.内容, (工具来源("doc-1", "2", "hash-doc-1", 投影),))
)
assert len(版本漂移) == 1 and "数据版本" in 版本漂移[0] and "call-hist" in 版本漂移[0]
哈希漂移 = 逐项比对(基准, 工具结果(文档结果.内容, (工具来源("doc-1", "1", "hash-改", 投影),)))
assert len(哈希漂移) == 1 and "结构哈希" in 哈希漂移[0]
投影漂移 = 逐项比对(
基准, 工具结果(文档结果.内容, (工具来源("doc-1", "1", "hash-doc-1", "proj/v2"),))
)
assert len(投影漂移) == 1 and "投影版本" in 投影漂移[0]
内容漂移 = 逐项比对(基准, 工具结果({"chapter_id": "ch-1", "text": "改过的正文"}, (文档来源,)))
assert any("消费哈希" in 项 for 项 in 内容漂移)
来源消失 = 逐项比对(基准, 工具结果(文档结果.内容, ()))
assert any("已不在" in 项 for 项 in 来源消失)
清单外来源 = 逐项比对(
基准, 工具结果(文档结果.内容, (文档来源, 工具来源("doc-9", "1", "hash-doc-1", 投影)))
)
assert any("清单外来源 doc-9" in 项 for 项 in 清单外来源)
def test_记录探索消费哈希与来源绑定同键__b7e0a4() -> None:
记录 = []
def 读取(_, 参数):
assert 参数 == {"chapter_id": "ch-1", "revision": 1}
return 文档结果
定义 = 工具定义(
"read_history_document",
"读取历史正文",
{
"type": "object",
"additionalProperties": False,
"required": ["chapter_id", "revision"],
"properties": {
"chapter_id": {"type": "string", "minLength": 1},
"revision": {"type": "integer", "minimum": 1},
},
},
读取,
)
Draft202012Validator.check_schema(定义.参数合同)
集合 = 只读工具集(
(定义,),
("read_history_document",),
工具范围("task-r", "work-a", ("doc-1",), 2, "production", "generation"),
记录.append,
)
参数 = {"chapter_id": "ch-1", "revision": 1}
结果 = 集合.调用(工具请求("call-hist", "read_history_document", 参数))
条目 = 记录探索((), "call-hist", "read_history_document", 参数, 结果)[0]
# 清单条目与 S02 工具读取事件留痕同键:来源四元组逐字段一致
留痕 = 记录[0]["source_refs"][0]
绑定 = 条目.来源引用[0]
assert (绑定.来源ID, 绑定.数据版本, 绑定.结构哈希, 绑定.投影版本) == (
留痕["source_id"],
留痕["revision"],
留痕["schema_hash"],
留痕["projection_version"],
)
assert 条目.消费哈希 == 内容哈希(结果.内容)
assert 条目.顺序 == 1
校验清单((条目,), 范围)
# 工具读取被 S02 拒绝时不产生清单可承接的结果
with pytest.raises(模型协议错误):
集合.调用(
工具请求("call-bad", "read_history_document", {"chapter_id": "ch-1", "work_id": "x"})
)
assert len(记录) == 1
def test_三类工具清单按序通过且顺序可核对__b7e0a5() -> None:
清单: tuple[探索条目, ...] = ()
for 调用ID, 工具名, 参数 in [
("call-plan", "query_work_plan", {}),
("call-facts", "query_facts_as_of", {}),
("call-hist", "read_history_document", {"chapter_id": "ch-1", "revision": 1, "end": 6}),
]:
清单 = 记录探索(清单, 调用ID, 工具名, 参数, 文档结果)
校验清单(清单, 范围) # 不抛异常即通过
assert [项.调用ID for 项 in 清单] == ["call-plan", "call-facts", "call-hist"]
assert [项.顺序 for 项 in 清单] == [1, 2, 3]