实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
481 lines
19 KiB
Python
481 lines
19 KiB
Python
"""应用读取前拒绝、阶段用量恢复和版本化回放的合成行为检查。"""
|
|
|
|
from contextlib import nullcontext
|
|
from dataclasses import replace
|
|
from datetime import UTC, datetime
|
|
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from muse.上下文.任务范围 import 上下文服务
|
|
from muse.上下文.分页 import 解码游标
|
|
from muse.上下文.存储 import 上下文存储
|
|
from muse.上下文.来源读取 import _片段游标, _片段起点, 读取历史正文
|
|
from muse.上下文.模型 import 上下文错误, 依赖状态
|
|
from muse.任务运行.上下文预算 import (
|
|
上下文预算错误,
|
|
核对请求预算,
|
|
读取用量,
|
|
默认读取预算,
|
|
)
|
|
from muse.任务运行.工具调用 import 只读工具集, 工具定义, 工具来源, 工具结果, 工具范围
|
|
from muse.任务运行.执行合同 import 工具请求
|
|
from muse.共享.调用身份 import 用途
|
|
from muse.正文写作.存储 import 正文存储
|
|
|
|
|
|
def 策略(**变化):
|
|
return {"context_policy_version": 2, "context_read_budget": {**默认读取预算, **变化}}
|
|
|
|
|
|
def 来源范围(**变化):
|
|
return {
|
|
**策略(),
|
|
"authorized_by": "author",
|
|
"work_id": "work",
|
|
"directory_revision": 1,
|
|
"run_purpose": "production",
|
|
"content_purpose": "generation",
|
|
"source_ids": ["doc"],
|
|
"documents": [
|
|
{
|
|
"chapter_id": "ch",
|
|
"branch_id": "main",
|
|
"revision": 1,
|
|
"position": 1,
|
|
"document_id": "doc",
|
|
"document_hash": "hash",
|
|
}
|
|
],
|
|
**变化,
|
|
}
|
|
|
|
|
|
def 工具域(用量=None):
|
|
return 工具范围("task", "work", ("doc",), 1, "production", "generation", 用量)
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-001")
|
|
def test_超长来源在取得正文前拒绝():
|
|
连 = MagicMock()
|
|
连.transaction.return_value = nullcontext()
|
|
数据库 = SimpleNamespace(连接=lambda **_: nullcontext(连))
|
|
with (
|
|
patch(
|
|
"muse.上下文.来源读取._核对目录",
|
|
return_value={"chapters": [{"chapter_id": "ch", "position": 1}]},
|
|
),
|
|
patch(
|
|
"muse.上下文.来源读取.读取正文元数据",
|
|
return_value={"current_revision": 1, "document_hash": "hash", "payload_bytes": 262145},
|
|
),
|
|
patch("muse.上下文.来源读取.读取正文依据") as 正文,
|
|
):
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
读取历史正文(数据库, 来源范围(), 工具域(), {"chapter_id": "ch", "revision": 1})
|
|
assert 错.value.上下文["requested"] == 262145
|
|
正文.assert_not_called()
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-002")
|
|
def test_重启从持久消费量继续而非清零():
|
|
事件 = []
|
|
一 = 读取用量(策略(source_reads=1), 记录=事件.append)
|
|
一.预留来源(25)
|
|
二 = 读取用量(策略(source_reads=1), 事件[-1])
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
二.预留来源(25)
|
|
assert 错.value.上下文["consumed"]["source_bytes"] == 25
|
|
assert 错.value.上下文["dimension"] == "source_reads"
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-003")
|
|
def test_预算预留未落盘时禁止来源读取():
|
|
def 拒写(_):
|
|
raise RuntimeError("event unavailable")
|
|
|
|
量 = 读取用量(策略(), 记录=拒写)
|
|
with pytest.raises(RuntimeError):
|
|
量.预留来源(32)
|
|
assert 量.已用["source_reads"] == 0
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-004")
|
|
def test_单工具和累计历史在返回前分别拒绝():
|
|
单 = 读取用量(策略(single_tool_bytes=10))
|
|
with pytest.raises(上下文预算错误):
|
|
单.接收工具(11)
|
|
累 = 读取用量(策略(tool_bytes=30))
|
|
for _ in range(3):
|
|
累.接收工具(10)
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
累.接收工具(1)
|
|
assert 错.value.上下文["consumed"]["tool_bytes"] == 30
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-005")
|
|
def test_单次与累计耗时独立限制():
|
|
量 = 读取用量(策略(single_read_milliseconds=20, read_milliseconds=25))
|
|
with patch("muse.任务运行.上下文预算.monotonic", return_value=0.015):
|
|
量.完成读取(0)
|
|
with patch("muse.任务运行.上下文预算.monotonic", return_value=0.015):
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
量.完成读取(0)
|
|
assert 错.value.上下文["dimension"] == "read_milliseconds"
|
|
with patch("muse.任务运行.上下文预算.monotonic", return_value=0.03):
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
读取用量(策略(single_read_milliseconds=20)).完成读取(0)
|
|
assert 错.value.上下文["dimension"] == "single_read_milliseconds"
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-006")
|
|
def test_旧任务超部署上界要求新授权且不改原冻结输入():
|
|
旧 = {"legacy": True}
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
核对请求预算(旧, b"x" * 262145)
|
|
assert 错.value.错误码 == "CONTEXT_POLICY_REAUTH_REQUIRED"
|
|
assert 旧 == {"legacy": True}
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
核对请求预算(策略(request_bytes=32), b"x" * 33)
|
|
assert 错.value.错误码 == "CONTEXT_BUDGET_EXCEEDED"
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-007")
|
|
def test_正文游标不可跨任务版本或投影():
|
|
冻结 = 来源范围()
|
|
键 = ("ch", "main", 1)
|
|
域 = 工具域()
|
|
游标 = _片段游标(域, 冻结, 键, 2048)
|
|
assert _片段起点(域, 冻结, 键, {"cursor": 游标}) == 2048
|
|
for 域2, 冻结2, 键2 in [
|
|
(replace(域, 任务ID="other"), 冻结, 键),
|
|
(域, {**冻结, "directory_revision": 2}, 键),
|
|
(域, 冻结, ("ch", "main", 2)),
|
|
(replace(域, 内容用途="planning"), 冻结, 键),
|
|
]:
|
|
with pytest.raises(上下文错误):
|
|
_片段起点(域2, 冻结2, 键2, {"cursor": 游标})
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-008")
|
|
def test_窄依据只查版本哈希与数据库侧大小():
|
|
连 = MagicMock()
|
|
正文存储(连).读取元数据("doc", 1)
|
|
sql, params = 连.cursor.return_value.execute.call_args.args
|
|
assert "v.document," not in sql and "v.document " not in sql
|
|
assert "octet_length(v.document::text) AS payload_bytes" in sql
|
|
assert params == (1, "doc")
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-009")
|
|
def test_快照摘要页不载入装配正文且请求有上界():
|
|
存储 = 上下文存储(object(), 用途.生产)
|
|
with patch.object(存储, "查询") as 查询:
|
|
存储.按任务读取摘要页("task", 20, None)
|
|
sql, params = 查询.call_args.args
|
|
assert "SELECT *" not in sql and "assembly->'文本'" not in sql
|
|
assert params[-1] == 21
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-010")
|
|
def test_快照页同范围只做一遍依赖核验并保留下一页位置():
|
|
行 = {
|
|
"snapshot_id": "00000000-0000-0000-0000-000000000001",
|
|
"task_id": "task",
|
|
"work_id": "work",
|
|
"input_hash": "hash",
|
|
"frozen_scope": 来源范围(),
|
|
"source_bindings": [],
|
|
"assembly": {},
|
|
"created_at": datetime(2026, 1, 1, tzinfo=UTC),
|
|
}
|
|
连 = MagicMock()
|
|
连.transaction.return_value = nullcontext()
|
|
服务 = 上下文服务(SimpleNamespace(用途=用途.生产, 连接=lambda **_: nullcontext(连)), None)
|
|
with (
|
|
patch("muse.上下文.存储.上下文存储.按任务读取摘要页", return_value=[行] * 21),
|
|
patch("muse.上下文.依赖校验.核对依赖", return_value=依赖状态(True, (), ())) as 核对,
|
|
):
|
|
页 = 服务.读取任务快照页("task")
|
|
assert len(页["items"]) == 20 and 页["has_more"]
|
|
核对.assert_called_once()
|
|
assert (
|
|
解码游标(页["next_cursor"], {"task_id": "task", "purpose": "production"})[1]
|
|
== 行["snapshot_id"]
|
|
)
|
|
with pytest.raises(上下文错误):
|
|
解码游标(页["next_cursor"], {"task_id": "other", "purpose": "production"})
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-011")
|
|
def test_工具结果超限不进入历史也不产生成功读取记录():
|
|
记录 = []
|
|
量 = 读取用量(策略(single_tool_bytes=20))
|
|
定义 = 工具定义(
|
|
"read",
|
|
"读",
|
|
{"type": "object"},
|
|
lambda *_: 工具结果("x" * 50, (工具来源("doc", "1", "hash", "v1"),)),
|
|
)
|
|
集 = 只读工具集((定义,), ("read",), 工具域(量), 记录.append)
|
|
with pytest.raises(上下文预算错误):
|
|
集.调用(工具请求("call", "read", {}))
|
|
assert len(记录) == 1 and 记录[0]["outcome"] == "blocked"
|
|
assert 记录[0]["reason_code"] == "CONTEXT_BUDGET_EXCEEDED"
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-012")
|
|
def test_多文稿依赖复检只读元数据并保留版本失效():
|
|
from muse.上下文.依赖校验 import 核对依赖
|
|
|
|
文稿 = [
|
|
{
|
|
"chapter_id": f"ch{i}",
|
|
"document_id": f"d{i}",
|
|
"revision": 1,
|
|
"position": i + 1,
|
|
"document_hash": "hash",
|
|
"branch_id": "main",
|
|
}
|
|
for i in range(16)
|
|
]
|
|
冻结 = 来源范围(documents=文稿)
|
|
目录 = {
|
|
"revision": 1,
|
|
"chapters": [{"chapter_id": d["chapter_id"], "position": d["position"]} for d in 文稿],
|
|
}
|
|
|
|
def 元数据(_连, _作者, 章, _版, **_):
|
|
return {
|
|
"document_id": "d" + 章[2:],
|
|
"document_hash": "hash",
|
|
"current_revision": 2 if 章 == "ch15" else 1,
|
|
}
|
|
|
|
with (
|
|
patch("muse.上下文.依赖校验.读取作品范围", return_value=目录),
|
|
patch("muse.上下文.依赖校验.读取正文元数据", side_effect=元数据) as 元,
|
|
patch("muse.正文写作.存储.正文存储.恢复草稿", side_effect=AssertionError("no payload")),
|
|
):
|
|
状态 = 核对依赖(object(), 冻结)
|
|
assert 元.call_count == 16 and not 状态.有效
|
|
assert len(状态.锁定依据) == 16 # 目录和十五个未漂移正文
|
|
assert "ch15" in 状态.变化项[0]
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-013")
|
|
def test_v2默认正文片段明确续页而v1回放内容不变():
|
|
连 = MagicMock()
|
|
连.transaction.return_value = nullcontext()
|
|
数据库 = SimpleNamespace(连接=lambda **_: nullcontext(连))
|
|
依据 = {"document": object(), "document_id": "doc", "document_hash": "hash"}
|
|
with (
|
|
patch("muse.上下文.来源读取._核对并读取", return_value=依据),
|
|
patch("muse.上下文.来源读取.可见文本", return_value="a" * 3000),
|
|
patch("muse.上下文.来源读取.可见文本哈希", return_value="hash"),
|
|
):
|
|
新 = 读取历史正文(数据库, 来源范围(), 工具域(), {"chapter_id": "ch", "revision": 1})
|
|
旧 = 来源范围()
|
|
del 旧["context_policy_version"], 旧["context_read_budget"]
|
|
老 = 读取历史正文(数据库, 旧, 工具域(), {"chapter_id": "ch", "revision": 1})
|
|
assert len(新.内容["text"]) == 2048 and 新.内容["has_more"]
|
|
assert 新.内容["next_cursor"] and 新.内容["omitted_reason"] == "bounded_fragment"
|
|
assert len(老.内容["text"]) == 3000 and "has_more" not in 老.内容
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-014")
|
|
def test_事实全量超限在时点投影前拒绝():
|
|
from muse.上下文.来源读取 import 读取时点事实
|
|
|
|
连 = MagicMock()
|
|
连.transaction.return_value = nullcontext()
|
|
数据库 = SimpleNamespace(连接=lambda **_: nullcontext(连))
|
|
with (
|
|
patch("muse.上下文.来源读取._核对目录"),
|
|
patch("muse.上下文.来源读取.读取当前事实系统版本", return_value=1),
|
|
patch(
|
|
"muse.上下文.来源读取.读取事实预算元数据",
|
|
return_value={"source_headers": 5, "payload_bytes": 262145},
|
|
),
|
|
patch("muse.上下文.来源读取.读取时点事实投影") as 投影,
|
|
):
|
|
with pytest.raises(上下文预算错误):
|
|
读取时点事实(数据库, 来源范围(facts={"system_revision": 1}), 工具域(), {})
|
|
投影.assert_not_called()
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-015")
|
|
def test_细纲超限在正文投影前拒绝():
|
|
from muse.上下文.来源读取 import 读取目标细纲
|
|
|
|
连 = MagicMock()
|
|
连.transaction.return_value = nullcontext()
|
|
数据库 = SimpleNamespace(连接=lambda **_: nullcontext(连))
|
|
with (
|
|
patch("muse.上下文.来源读取._核对目录"),
|
|
patch("muse.上下文.来源读取.读取细纲元数据", return_value={"payload_bytes": 262145}),
|
|
patch("muse.上下文.来源读取.读取细纲投影") as 投影,
|
|
):
|
|
with pytest.raises(上下文预算错误):
|
|
读取目标细纲(数据库, 来源范围(target_plan={"chapter_id": "ch"}), 工具域(), {})
|
|
投影.assert_not_called()
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-016")
|
|
def test_事实点名集合越出冻结范围建联前拒绝():
|
|
from muse.上下文.来源读取 import 读取时点事实
|
|
|
|
数据库 = MagicMock()
|
|
with pytest.raises(上下文错误) as 错:
|
|
读取时点事实(
|
|
数据库, 来源范围(facts={"system_revision": 1}), 工具域(), {"object_ids": ["secret"]}
|
|
)
|
|
assert 错.value.错误码 == "SCOPE_DENIED"
|
|
数据库.连接.assert_not_called()
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-017")
|
|
def test_篡改v2预算不能放大部署上界():
|
|
for 冻结 in [
|
|
{"context_policy_version": 2},
|
|
策略(request_bytes=262145),
|
|
策略(source_reads=True),
|
|
]:
|
|
with pytest.raises(上下文预算错误):
|
|
读取用量(冻结)
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-018")
|
|
def test_单阶段失败预检耗时也保存消费量():
|
|
from muse.上下文.来源读取 import _读取阶段
|
|
|
|
事件 = []
|
|
量 = 读取用量(策略(), 记录=事件.append)
|
|
数据库 = MagicMock()
|
|
数据库.连接.side_effect = RuntimeError("connection failed")
|
|
with (
|
|
pytest.raises(RuntimeError),
|
|
patch("muse.上下文.来源读取.monotonic", return_value=1),
|
|
patch("muse.任务运行.上下文预算.monotonic", return_value=1.01),
|
|
):
|
|
with _读取阶段(数据库, 来源范围(), 工具域(量)):
|
|
raise AssertionError("unreachable")
|
|
assert 事件[-1]["read_milliseconds"] >= 9
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-019")
|
|
def test_快照部分失效不将声音偏好方法误报为事实状态():
|
|
from muse.上下文.模型 import 上下文快照, 依赖锁定, 来源绑定
|
|
from muse.接入.http.路由.上下文 import 呈现快照
|
|
|
|
冻结 = 来源范围(
|
|
source_ids=["doc", "voice", "pref", "method", "fact"],
|
|
voice={"version_id": "voice"},
|
|
preferences=[{"preference_id": "pref"}],
|
|
methods=[{"binding_id": "method"}],
|
|
facts={"system_revision": 1},
|
|
)
|
|
快照 = 上下文快照(
|
|
"snap",
|
|
"task",
|
|
"work",
|
|
"hash",
|
|
冻结,
|
|
{},
|
|
tuple(来源绑定(k, "1", "hash", "v1") for k in ["voice", "pref", "method", "fact"]),
|
|
"SOURCE_STALE",
|
|
"voice changed",
|
|
)
|
|
状态 = 依赖状态(
|
|
False,
|
|
(依赖锁定("work:事实系统", "facts", "1", ""), 依赖锁定("method", "method", "1", "")),
|
|
("voice changed",),
|
|
)
|
|
视图 = 呈现快照(快照, 状态)
|
|
assert {r.source_id: r.current_state for r in 视图.sources} == {
|
|
"voice": "SOURCE_STALE",
|
|
"pref": "SOURCE_STALE",
|
|
"method": "有效",
|
|
"fact": "有效",
|
|
}
|
|
状态 = 依赖状态(False, (依赖锁定("voice", "voice", "1", ""),), ("fact changed",))
|
|
assert 呈现快照(快照, 状态).sources[0].current_state == "有效"
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-020")
|
|
def test_HTTP快照页先核任务作者再取摘要():
|
|
from muse.接入.http.路由.上下文 import 列出任务快照页
|
|
|
|
请求 = MagicMock()
|
|
with patch(
|
|
"muse.接入.http.路由.上下文.读取作者任务",
|
|
side_effect=上下文错误("SCOPE_DENIED", "wrong author"),
|
|
):
|
|
with pytest.raises(上下文错误):
|
|
列出任务快照页("task", 请求, "author", limit=20, cursor=None)
|
|
请求.app.state.装配.要求上下文.assert_not_called()
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-021")
|
|
def test_实验臂纯组装不改变原文码点且必读超限失败():
|
|
from muse.上下文.回放材料 import _组装实验臂, _选取足额补充
|
|
|
|
原 = [{"text": "甲🙂乙", "document_id": "doc"}]
|
|
片段 = _选取足额补充(原, 2)
|
|
assert 片段[0]["text"] == "甲🙂" and 原[0]["text"] == "甲🙂乙"
|
|
with pytest.raises(上下文错误):
|
|
_选取足额补充(原, 4)
|
|
with pytest.raises(上下文错误):
|
|
_组装实验臂({"content": {"必读": "保留"}}, 原, [], [], [], 1)
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-022")
|
|
def test_并发工具实例不能覆盖已消费累计量():
|
|
from muse.任务运行.接口 import 任务服务
|
|
from muse.任务运行.模型 import 状态冲突
|
|
|
|
最新 = {}
|
|
存储 = MagicMock()
|
|
存储.读取上下文用量.side_effect = lambda *_: dict(最新)
|
|
存储.追加事件.side_effect = lambda _任务, _类型, payload, **_: 最新.update(
|
|
payload["context_usage"]
|
|
)
|
|
服务 = 任务服务.__new__(任务服务)
|
|
服务._存储 = lambda: nullcontext(存储)
|
|
领取 = SimpleNamespace(任务ID="task", 步骤ID="step", 尝试ID="try")
|
|
一 = 读取用量(策略())
|
|
二 = 读取用量(策略())
|
|
一.记录 = lambda 新: 服务.登记上下文用量(领取, 一.已用, 新, "a")
|
|
二.记录 = lambda 新: 服务.登记上下文用量(领取, 二.已用, 新, "b")
|
|
with patch("muse.任务运行.接口.校验领取") as 锁:
|
|
一.预留来源(10)
|
|
with pytest.raises(状态冲突):
|
|
二.预留来源(11)
|
|
assert 最新["source_bytes"] == 10 and 存储.追加事件.call_count == 1
|
|
assert 锁.call_count == 2 and 二.已用["source_reads"] == 0
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-023")
|
|
def test_旧任务工具描述不被v2参数扩展改写():
|
|
from muse.上下文.来源读取 import 事实工具, 历史正文工具
|
|
|
|
新 = 来源范围(facts={"system_revision": 1})
|
|
旧 = {k: v for k, v in 新.items() if k not in {"context_policy_version", "context_read_budget"}}
|
|
assert "cursor" in 历史正文工具(None, 新).参数合同["properties"]
|
|
assert "cursor" not in 历史正文工具(None, 旧).参数合同["properties"]
|
|
assert "object_ids" in 事实工具(None, 新).参数合同["properties"]
|
|
assert "object_ids" not in 事实工具(None, 旧).参数合同["properties"]
|
|
assert "context_policy_version" not in 旧
|
|
|
|
|
|
@pytest.mark.case_id("NC-O07-024")
|
|
def test_循环读取在下一载荷前检查剩余耗时():
|
|
量 = 读取用量(策略(single_read_milliseconds=10))
|
|
量.当前开始 = 0
|
|
with patch("muse.任务运行.上下文预算.monotonic", return_value=0.011):
|
|
with pytest.raises(上下文预算错误) as 错:
|
|
量.预留来源(8)
|
|
assert 错.value.上下文["dimension"] == "single_read_milliseconds"
|
|
assert 量.已用["source_reads"] == 0
|