实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
190 lines
8.4 KiB
Python
190 lines
8.4 KiB
Python
"""合成依赖证明编排前置拒绝与固定预算;不运行模型、网络或数据库。"""
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
import yaml
|
|
from pydantic import ValidationError
|
|
|
|
from muse.任务运行.接口 import 步骤处理器, 步骤结果, 预算状态冲突
|
|
from muse.共享.错误 import 配置错误
|
|
from muse.编排 import 研究拆书, 章后处理
|
|
from muse.编排.接口 import 流程登记
|
|
from muse.编排.槽位约束 import 流程校验错误
|
|
from muse.编排.步骤工具 import 前步检查点
|
|
from muse.编排.载入模板 import 解析流程文本
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-O02-ORCH-001", environment="离线合成依赖", then=["同一任务预算截止固定", "冲突向调用者传播"]
|
|
)
|
|
@pytest.mark.parametrize(
|
|
"模块,处理器,小时", [(章后处理, "post.config", 6), (研究拆书, "rs.config", 12)]
|
|
)
|
|
@pytest.mark.parametrize("冲突", [False, True])
|
|
def test_预算重试使用持久创建时间且不吞冲突(模块, 处理器, 小时, 冲突, monkeypatch):
|
|
from muse.任务运行 import 接口
|
|
|
|
固定时刻 = datetime(2026, 9, 17, tzinfo=UTC)
|
|
数据库 = object()
|
|
装配 = SimpleNamespace(要求数据库=lambda: 数据库, 要求故事世界=lambda: object())
|
|
配置管理 = Mock()
|
|
配置管理.冻结到任务.return_value = SimpleNamespace(内容=SimpleNamespace(预算策略引用="account"))
|
|
monkeypatch.setattr(接口, "配置版本管理", lambda 工厂: 配置管理)
|
|
预算 = Mock()
|
|
if 冲突:
|
|
预算.登记任务预算.side_effect = 预算状态冲突("原计划哈希不同")
|
|
monkeypatch.setattr(模块, "预算管理", lambda *a: 预算)
|
|
登记 = 流程登记()
|
|
if 模块 is 章后处理:
|
|
模块.登记章后处理(登记, 装配, None)
|
|
else:
|
|
模块.登记研究拆书(登记, 装配, None)
|
|
上下文 = SimpleNamespace(
|
|
任务=SimpleNamespace(
|
|
任务ID="task",
|
|
创建时间=固定时刻,
|
|
冻结输入={"输入": {"配置ID": "cfg", "窗计划": [{"窗ID": "w1"}]}},
|
|
)
|
|
)
|
|
动作 = 登记.获取(处理器, "1").执行
|
|
for _ in range(2):
|
|
if 冲突:
|
|
with pytest.raises(预算状态冲突, match="原计划哈希不同"):
|
|
动作(上下文)
|
|
else:
|
|
assert 动作(上下文).输出 == {"配置ID": "cfg"}
|
|
计划 = [c.args[1] for c in 预算.登记任务预算.call_args_list]
|
|
assert len(计划) == 2 and 计划[0].冻结() == 计划[1].冻结()
|
|
assert 计划[0].截止时间 == 固定时刻 + timedelta(hours=小时)
|
|
assert 计划[0].总金额 > 0
|
|
|
|
|
|
@pytest.mark.case_id("NC-O02-ORCH-002", environment="离线", then=["无配置不得读源或创建任务"])
|
|
@pytest.mark.parametrize("配置", ["", " ", None])
|
|
def test_拆书无配置在装配或读源前拒绝(配置):
|
|
with pytest.raises(配置错误, match="必须明确指定运行配置ID"):
|
|
研究拆书.发起研究拆书(None, None, "command", source_id="source", 配置ID=配置)
|
|
|
|
|
|
@pytest.mark.case_id("NC-O02-ORCH-003", environment="离线", then=["HTTP和CLI均要求非空config_id"])
|
|
@pytest.mark.parametrize("请求", [{}, {"config_id": None}, {"config_id": ""}])
|
|
def test_HTTP和CLI拆书配置字段必填(请求):
|
|
from muse.接入.cli.研究命令 import 拆解请求
|
|
from muse.接入.http.路由.资料研究 import 发起拆书请求
|
|
|
|
for 模型, 附加 in ((拆解请求, {"source_id": "source"}), (发起拆书请求, {})):
|
|
with pytest.raises(ValidationError) as 错:
|
|
模型.model_validate({"command_id": "command", **附加, **请求})
|
|
assert [e["loc"] for e in 错.value.errors()] == [("config_id",)]
|
|
assert (
|
|
模型.model_validate({"command_id": "command", **附加, "config_id": "cfg"}).config_id
|
|
== "cfg"
|
|
)
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-O02-ORCH-004", environment="离线", then=["模板无消费字段拒绝", "保护仍以处理器为准"]
|
|
)
|
|
def test_模板不能靠声明保护绕过真实处理器():
|
|
模板 = {
|
|
"flow_id": "fixed",
|
|
"version": "1",
|
|
"steps": [{"step_id": "only", "processor": "work", "processor_version": "1"}],
|
|
}
|
|
登记 = 流程登记()
|
|
登记.登记类型("fixed", 必需保护=("输入校验",))
|
|
登记.登记处理器(步骤处理器("work", "1", lambda _: 步骤结果({}), "1", "1"))
|
|
with pytest.raises(流程校验错误):
|
|
登记.冻结(解析流程文本(yaml.safe_dump(模板)))
|
|
模板["steps"][0]["protection"] = "输入校验"
|
|
with pytest.raises(流程校验错误, match="未登记字段"):
|
|
解析流程文本(yaml.safe_dump(模板))
|
|
模板["steps"][0].pop("protection")
|
|
真登记 = 流程登记()
|
|
真登记.登记类型("fixed", 必需保护=("输入校验",))
|
|
真登记.登记处理器(
|
|
步骤处理器("work", "1", lambda _: 步骤结果({}), "1", "1", 保护职责="输入校验")
|
|
)
|
|
assert 真登记.冻结(解析流程文本(yaml.safe_dump(模板))).步骤[0].处理器ID == "work"
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-O02-ORCH-005", environment="离线", then=["前步成功读回", "缺失与空检查点使用旅程错误"]
|
|
)
|
|
@pytest.mark.parametrize("错误", [章后处理.章后处理错误, 研究拆书.拆书错误])
|
|
def test_共享检查点保留各旅程错误合同(错误):
|
|
上下文 = SimpleNamespace(
|
|
任务=SimpleNamespace(步骤=[{"step_id": "one", "checkpoint": {"value": 7}}])
|
|
)
|
|
assert 前步检查点(上下文, "one", 错误类型=错误) == {"value": 7}
|
|
with pytest.raises(错误, match="尚未执行"):
|
|
前步检查点(上下文, "absent", 错误类型=错误)
|
|
上下文.任务.步骤[0]["checkpoint"] = {}
|
|
with pytest.raises(错误, match="没有可用检查点"):
|
|
前步检查点(上下文, "one", 错误类型=错误)
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-O02-ORCH-006", environment="离线源模板", then=["全部流程模板使用同一解析合同"]
|
|
)
|
|
def test_实际模板可解析且没有保护影子字段():
|
|
文件 = sorted((Path(__file__).parents[2] / "配置/流程模板").glob("*.yaml"))
|
|
assert 文件
|
|
for 路径 in 文件:
|
|
文本 = 路径.read_text()
|
|
定义 = 解析流程文本(文本)
|
|
assert 定义.流程ID and 定义.步骤
|
|
assert all("protection" not in 步 for 步 in yaml.safe_load(文本)["steps"])
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-O02-ORCH-007",
|
|
environment="离线来源替身",
|
|
then=["任务与配置只走受控创建", "受控拒绝无旁路创建"],
|
|
)
|
|
@pytest.mark.parametrize("拒绝", [False, True])
|
|
def test_拆书发起只使用原子受控创建(拒绝, monkeypatch):
|
|
from contextlib import nullcontext
|
|
|
|
from muse.任务运行 import 接口 as 运行接口
|
|
from muse.共享.调用身份 import 用途
|
|
from muse.资料研究 import 接口 as 资料接口
|
|
|
|
monkeypatch.setattr(资料接口, "核对来源授权", lambda *a: None)
|
|
来源服务 = SimpleNamespace(
|
|
读取版本=lambda *a: {"revision": 1, "content_hash": "frozen", "content": "合成资料"}
|
|
)
|
|
monkeypatch.setattr(研究拆书, "资料服务", lambda: 来源服务)
|
|
monkeypatch.setattr(
|
|
运行接口.角色策略目录,
|
|
"从发布包",
|
|
lambda: SimpleNamespace(定义={"version": "policy"}, 资源发布身份="release"),
|
|
)
|
|
创建 = Mock(return_value="task-id", side_effect=配置错误("配置未启用") if 拒绝 else None)
|
|
装配 = SimpleNamespace(
|
|
要求数据库=lambda: SimpleNamespace(连接=lambda **k: nullcontext(object())),
|
|
任务运行=SimpleNamespace(创建受控任务=创建),
|
|
配置=SimpleNamespace(运行用途=用途.生产),
|
|
)
|
|
if 拒绝:
|
|
with pytest.raises(配置错误, match="配置未启用"):
|
|
研究拆书.发起研究拆书(
|
|
装配, SimpleNamespace(作者="author"), "command", source_id="source", 配置ID="cfg"
|
|
)
|
|
else:
|
|
assert (
|
|
研究拆书.发起研究拆书(
|
|
装配, SimpleNamespace(作者="author"), "command", source_id="source", 配置ID="cfg"
|
|
)["task_id"]
|
|
== "task-id"
|
|
)
|
|
创建.assert_called_once()
|
|
请求 = 创建.call_args.args[0]
|
|
assert 请求.作者 == "author" and 请求.来源ID == "source" and 请求.输入["配置ID"] == "cfg"
|
|
assert 创建.call_args.args[1:] == ("研究拆书", "1")
|
|
assert 创建.call_args.kwargs == {"配置ID": "cfg"}
|