实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
870 lines
40 KiB
Python
870 lines
40 KiB
Python
"""知识方法整链:提案确认、启用回执、固定绑定、三接缝消费、索引与来源回查。
|
||
|
||
验收口径:资料→分析→方法确认→固定绑定→生成消费可沿来源回查;
|
||
同哈希不同 owner、陈旧向量与不允许来源拒绝;参数上限来自版本策略。
|
||
"""
|
||
|
||
import pytest
|
||
|
||
from muse.作品规划.接口 import 档案保存, 节点目录保存
|
||
from muse.元数据.接口 import 元数据服务
|
||
from muse.共享.调用身份 import 内容用途, 用途, 调用身份
|
||
from muse.共享.错误 import Muse错误
|
||
from muse.启动 import 构建
|
||
from muse.正式变更.接口 import 作者动作
|
||
from muse.知识方法.模型 import (
|
||
方法定位,
|
||
方法提案请求,
|
||
方法结构,
|
||
消费输入,
|
||
状态命令,
|
||
绑定命令,
|
||
证据引用,
|
||
)
|
||
from muse.资料研究.接口 import 资料服务
|
||
from muse.资料研究.模型 import 导入请求
|
||
from muse.配置 import 应用配置
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
|
||
|
||
@pytest.fixture
|
||
def 方法环境(内置结构测试库):
|
||
应用测试库 = 内置结构测试库
|
||
装配 = 构建(应用配置(应用测试库[用途.生产].引用, "synthetic"))
|
||
with 装配.生命周期():
|
||
作者 = 调用身份("method-author", None, 用途.生产, 内容用途.规划)
|
||
服务 = 装配.要求知识方法()
|
||
作品 = 装配.要求作品()
|
||
结构 = 作品.新档案结构(作者, "method-work", "work_core", 1)
|
||
作品.保存档案(作者, "create-mw", 档案保存("method-work", 0, {"名称": "方法链作品"}, 结构))
|
||
作品.添加章节(作者, "mw-1", "method-work", "mw-1", "第一章", 预期目录版本=0)
|
||
with 应用测试库[用途.生产].连接() as 连:
|
||
资料 = 资料服务()
|
||
来源 = 资料.导入(
|
||
连,
|
||
"method-author",
|
||
导入请求(
|
||
kind="reference",
|
||
title="参考书",
|
||
origin="seed://method-source",
|
||
content="第一章 埋下了枪。第二章 枪响了。",
|
||
authorized_uses=("analysis",),
|
||
),
|
||
)
|
||
无权来源 = 资料.导入(
|
||
连,
|
||
"method-author",
|
||
导入请求(
|
||
kind="note",
|
||
title="笔记",
|
||
origin="seed://no-use",
|
||
content="只有作者自己看的笔记。",
|
||
authorized_uses=(),
|
||
),
|
||
)
|
||
yield 装配, 作者, 服务, 来源, 无权来源, 应用测试库
|
||
|
||
|
||
def _方法身份(标签: str) -> str:
|
||
from uuid import NAMESPACE_URL, uuid5
|
||
|
||
return str(uuid5(NAMESPACE_URL, "muse:B04:method:" + 标签))
|
||
|
||
|
||
def _提案(标签="craft-1", 标题="契诃夫之枪", source_id=None, revision=1, owner=None, 方法ID=None):
|
||
return 方法提案请求(
|
||
method_id=方法ID or _方法身份(标签),
|
||
owner=owner or "method-author",
|
||
type_id="craft",
|
||
title=标题,
|
||
schema=方法结构("craft", 1),
|
||
content={
|
||
"装置形态": "结构装置",
|
||
"原理": "当场景需要回收时引爆前文物件,因为读者会期待承诺兑现",
|
||
"埋设手法": "挂在日常动线上",
|
||
"回收点": "危机时刻顺手引爆",
|
||
"记忆维持": "中点一次轻提",
|
||
"间隔纪律": "至少隔两章",
|
||
"滥用反例": "每章都埋新枪即廉价",
|
||
"例证出处": "参考书 第1-2章 埋枪与响枪",
|
||
},
|
||
证据=(
|
||
证据引用(kind="source", source_id=source_id, revision=revision, 定位="第1-2章"),
|
||
证据引用(kind="counter", note="每章都埋即烂大街"),
|
||
),
|
||
参数上限={"单章上限": 1},
|
||
)
|
||
|
||
|
||
def _确认(服务, 作者, proposal_id):
|
||
提案 = 服务.读取方法提案(作者, proposal_id)
|
||
定位 = 方法定位(
|
||
proposal_id,
|
||
提案["method_id"],
|
||
提案["revision"],
|
||
提案["proposal_hash"],
|
||
提案["current_version"],
|
||
)
|
||
审阅 = 服务.打开方法审阅(作者, 定位)
|
||
return 服务.决定方法(
|
||
作者,
|
||
f"decide-{proposal_id}",
|
||
定位,
|
||
作者动作.采纳,
|
||
审阅["review_id"],
|
||
审阅["review_hash"],
|
||
("method",),
|
||
)
|
||
|
||
|
||
def 准备已确认消费方法(装配, 作者, work_id: str, *, 标题: str, 原理: str):
|
||
"""来源、提案、确认和绑定走真实服务;只用直接 SQL 准备历史启用状态。"""
|
||
from dataclasses import replace
|
||
from uuid import uuid4
|
||
|
||
服务 = 装配.要求知识方法()
|
||
with 服务.数据库.连接() as 连:
|
||
来源 = 资料服务().导入(
|
||
连,
|
||
作者.作者,
|
||
导入请求(
|
||
kind="reference",
|
||
title="合成方法来源",
|
||
origin="seed://model-method-evidence",
|
||
content="用动作表现人物的迟疑;提前埋下的物件在危机中发挥作用。",
|
||
authorized_uses=("analysis",),
|
||
),
|
||
)
|
||
请求 = _提案(str(uuid4()), 标题, str(来源.source_id), owner=作者.作者)
|
||
请求 = replace(
|
||
请求,
|
||
content={
|
||
**请求.content,
|
||
"原理": 原理,
|
||
"例证出处": "方法字段仅供规划定位,不应发给写手。",
|
||
},
|
||
)
|
||
提案 = 服务.提出方法(作者, str(uuid4()), 请求)
|
||
_确认(服务, 作者, 提案["target_ref"])
|
||
版本 = 服务.读取方法详情(作者, 请求.method_id)["versions"][0]
|
||
with 服务.数据库.连接() as 连:
|
||
连.execute("UPDATE muse_method SET state='enabled' WHERE method_id=%s", (请求.method_id,))
|
||
服务.绑定方法(
|
||
作者,
|
||
str(uuid4()),
|
||
绑定命令(
|
||
work_id,
|
||
"work",
|
||
"work",
|
||
请求.method_id,
|
||
str(版本["version_id"]),
|
||
),
|
||
)
|
||
return 请求.method_id, str(版本["version_id"])
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b01a",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=["提案→作者确认→版本台账与证据分组"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_提出确认与版本台账__20b01a(方法环境):
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
回执 = 服务.提出方法(作者, "prop-1", _提案(source_id=str(来源.source_id)))
|
||
提案 = 服务.读取方法提案(作者, 回执["target_ref"])
|
||
assert 提案["state"] == "proposed"
|
||
_确认(服务, 作者, 提案["proposal_id"])
|
||
详情 = 服务.读取方法详情(作者, 提案["method_id"])
|
||
assert 详情["state"] == "confirmed" and len(详情["versions"]) == 1
|
||
assert 详情["evidence"]["sources"] and 详情["evidence"]["counters"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b02b",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=["同 owner 幂等、同哈希不同 owner 拒绝"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_同哈希去重与owner冲突__20b02b(方法环境):
|
||
from dataclasses import replace
|
||
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
请求 = _提案(source_id=str(来源.source_id))
|
||
首 = 服务.提出方法(作者, "prop-2", 请求)
|
||
再 = 服务.提出方法(作者, "prop-2", 请求)
|
||
assert 再 == 首
|
||
首提案 = 服务.读取方法提案(作者, 首["target_ref"])
|
||
assert 首提案["revision"] == 1
|
||
详情 = 服务.读取方法详情(作者, 请求.method_id)
|
||
assert [str(条["proposal_id"]) for 条 in 详情["proposals"]] == [首["target_ref"]]
|
||
assert {str(条["method_id"]) for 条 in 服务.列出方法(作者)} == {请求.method_id}
|
||
# 不同命令可形成独立提案;它不应另立第二张同内容方法卡。
|
||
另 = 服务.提出方法(作者, "prop-2b", 请求)
|
||
另提案 = 服务.读取方法提案(作者, 另["target_ref"])
|
||
assert 另提案["method_id"] == 首提案["method_id"]
|
||
assert 另提案["proposal_id"] != 首提案["proposal_id"]
|
||
assert 另提案["revision"] == 2
|
||
assert {str(条["method_id"]) for 条 in 服务.列出方法(作者)} == {请求.method_id}
|
||
他人 = 调用身份("other-author", None, 用途.生产, 内容用途.规划)
|
||
with pytest.raises(Muse错误) as 错:
|
||
服务.提出方法(
|
||
他人, "prop-2c", replace(请求, owner=他人.作者, method_id=_方法身份("other-owner"))
|
||
)
|
||
assert 错.value.错误码 == "METHOD_OWNER_CONFLICT"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b03c",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=["来源未授权 analysis 用途拒绝立卡"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_未授权来源拒绝__20b03c(方法环境):
|
||
装配, 作者, 服务, 来源, 无权, 库 = 方法环境
|
||
with pytest.raises(Muse错误, match="analysis 用途"):
|
||
服务.提出方法(作者, "prop-3", _提案(source_id=str(无权.source_id)))
|
||
with pytest.raises(Muse错误, match="来源版本"):
|
||
服务.提出方法(作者, "prop-3-missing", _提案(source_id=str(来源.source_id), revision=999))
|
||
|
||
|
||
def _准备历史启用状态(库, 方法ID):
|
||
"""只为停用、绑定、索引回归准备历史数据,不作为正式启用的验收证据。"""
|
||
with 库[用途.生产].连接() as 连:
|
||
连.execute("UPDATE muse_method SET state='enabled' WHERE method_id=%s", (方法ID,))
|
||
|
||
|
||
def _准备历史消费(库, 作者, 输入):
|
||
"""仅构造历史回执以测试拒绝启用和回查;不作为真实模型消费证明。"""
|
||
from uuid import uuid4
|
||
|
||
from muse.知识方法.存储 import 方法存储
|
||
|
||
身份 = str(uuid4())
|
||
with 库[用途.生产].连接() as 连:
|
||
方法存储(连).写消费(身份, 作者.作者, 输入)
|
||
return {"usage_id": 身份}
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b04d",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=[
|
||
(
|
||
"缺独立评测判据时即使有同版本消费也拒绝正式启用;HTTP 禁止自报消费;历史已启用方法停"
|
||
"用后不再返回材料"
|
||
)
|
||
],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_启用需真实回执与停用不消费__20b04d(方法环境):
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
服务.提出方法(作者, "prop-4", _提案(source_id=str(来源.source_id)))
|
||
提案 = 服务.读取方法提案(作者, _提案身份(服务, 作者, "prop-4"))
|
||
_确认(服务, 作者, 提案["proposal_id"])
|
||
方法ID = 提案["method_id"]
|
||
with pytest.raises(Muse错误, match="回执"):
|
||
服务.变更方法状态(
|
||
作者, "st-1", 状态命令(方法ID, "enable", "not-a-usage", 提案["current_version"])
|
||
)
|
||
版本 = 服务.读取方法详情(作者, 方法ID)["versions"][0]
|
||
自报 = 消费输入(
|
||
method_version_id=str(版本["version_id"]),
|
||
kind="generation",
|
||
context_ref="snap-seed",
|
||
fragment_hash="h1",
|
||
)
|
||
with pytest.raises(Muse错误) as 错:
|
||
服务.记录消费(作者, 自报)
|
||
assert 错.value.错误码 == "USAGE_EVIDENCE_REQUIRED"
|
||
assert 服务.列出消费(作者, str(版本["version_id"])) == []
|
||
用法 = _准备历史消费(库, 作者, 自报)
|
||
回读 = 服务.列出消费(作者, str(版本["version_id"]))
|
||
assert [str(条["usage_id"]) for 条 in 回读] == [用法["usage_id"]]
|
||
assert 回读[0]["kind"] == "generation" and 回读[0]["fragment_hash"] == "h1"
|
||
# 即使同版本已有消费,缺少独立评测判据仍不能正式启用。
|
||
with pytest.raises(Muse错误) as 错:
|
||
服务.变更方法状态(作者, "st-2", 状态命令(方法ID, "enable", 用法["usage_id"], 1))
|
||
assert 错.value.错误码 == "METHOD_ACTIVATION_UNAVAILABLE"
|
||
assert 服务.读取方法详情(作者, 方法ID)["state"] == "confirmed"
|
||
from fastapi import FastAPI
|
||
from fastapi.testclient import TestClient
|
||
|
||
from muse.接入.http.路由.知识方法 import 路由
|
||
|
||
应用 = FastAPI()
|
||
应用.include_router(路由)
|
||
with TestClient(应用) as 客户端:
|
||
响应 = 客户端.post(
|
||
f"/api/v1/methods/{方法ID}/versions/{版本['version_id']}/usages",
|
||
json={"kind": "generation", "context_ref": "fabricated"},
|
||
)
|
||
assert 响应.status_code == 405, "不得暴露自报模型消费的 HTTP 写入口"
|
||
_准备历史启用状态(库, 方法ID)
|
||
# 绑定后停用:材料不再返回,绑定保留可回查
|
||
服务.绑定方法(
|
||
作者,
|
||
"bind-1",
|
||
绑定命令("method-work", "work", "work", 方法ID, str(版本["version_id"])),
|
||
)
|
||
with 库[用途.生产].连接() as 连:
|
||
from muse.知识方法.方法绑定 import 读取绑定材料
|
||
|
||
assert (
|
||
len(
|
||
读取绑定材料(
|
||
连,
|
||
"method-work",
|
||
内容用途=作者.内容用途.value,
|
||
运行用途=作者.用途.value,
|
||
)
|
||
)
|
||
== 1
|
||
)
|
||
详情 = 服务.读取方法详情(作者, 方法ID)
|
||
服务.变更方法状态(作者, "st-3", 状态命令(方法ID, "disable", "", 详情["current_version"]))
|
||
with 库[用途.生产].连接() as 连:
|
||
from muse.知识方法.方法绑定 import 读取绑定材料
|
||
|
||
assert (
|
||
读取绑定材料(
|
||
连,
|
||
"method-work",
|
||
内容用途=作者.内容用途.value,
|
||
运行用途=作者.用途.value,
|
||
)
|
||
== []
|
||
)
|
||
assert len(服务.列出绑定(作者, "method-work")) == 1
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b05e",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=["绑定固定版本不追随修订;重绑影响只标记"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_绑定不追随修订与影响清单__20b05e(方法环境):
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
服务.提出方法(作者, "prop-5", _提案(source_id=str(来源.source_id)))
|
||
提案 = 服务.读取方法提案(作者, _提案身份(服务, 作者, "prop-5"))
|
||
_确认(服务, 作者, 提案["proposal_id"])
|
||
方法ID = 提案["method_id"]
|
||
v1 = 服务.读取方法详情(作者, 方法ID)["versions"][0]
|
||
用法 = _准备历史消费(
|
||
库,
|
||
作者,
|
||
消费输入(
|
||
method_version_id=str(v1["version_id"]),
|
||
kind="generation",
|
||
context_ref="snap-a",
|
||
fragment_hash="h1",
|
||
),
|
||
)
|
||
_准备历史启用状态(库, 方法ID)
|
||
服务.绑定方法(
|
||
作者, "bind-2", 绑定命令("method-work", "work", "work", 方法ID, str(v1["version_id"]))
|
||
)
|
||
from muse.上下文.接口 import 核对依赖
|
||
|
||
冻结范围 = 装配.要求上下文().授权任务范围(作者, "method-work", 0, 含事实=True, 含方法=True)
|
||
assert 冻结范围["methods"][0]["version_id"] == str(v1["version_id"])
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert 核对依赖(连, 冻结范围).有效
|
||
# 修订:改内容再提案再确认 → v2
|
||
服务.提出方法(
|
||
作者,
|
||
"prop-5b",
|
||
方法提案请求(
|
||
method_id=方法ID,
|
||
owner="method-author",
|
||
type_id="craft",
|
||
title="契诃夫之枪·修订",
|
||
schema=方法结构("craft", 1),
|
||
content={
|
||
"装置形态": "结构装置",
|
||
"原理": "修订后的因果链:当冲突收紧时引爆前文承诺",
|
||
"埋设手法": "挂在情绪节点上",
|
||
"回收点": "高潮前置半拍引爆",
|
||
"记忆维持": "四分之一处轻提",
|
||
"间隔纪律": "至少隔三章",
|
||
"滥用反例": "无埋设直接引爆",
|
||
"例证出处": "参考书 第1-2章",
|
||
},
|
||
证据=(
|
||
证据引用(kind="source", source_id=str(来源.source_id), revision=1, 定位="第1-2章"),
|
||
),
|
||
参数上限={"单章上限": 1},
|
||
修订自版本=1,
|
||
),
|
||
)
|
||
修订提案 = 服务.读取方法提案(作者, _提案身份(服务, 作者, "prop-5b"))
|
||
assert 服务.读取方法详情(作者, 方法ID)["title"] == "契诃夫之枪", "提案不能改正式标题"
|
||
assert 服务.读取版本(作者, str(v1["version_id"]))["title"] == "契诃夫之枪"
|
||
_确认(服务, 作者, 修订提案["proposal_id"])
|
||
详情 = 服务.读取方法详情(作者, 方法ID)
|
||
assert len(详情["versions"]) == 2
|
||
# 同一方法的旧版消费不能证明新版本已经验证。
|
||
with pytest.raises(Muse错误, match="版本"):
|
||
服务.变更方法状态(
|
||
作者, "reject-old-receipt", 状态命令(方法ID, "enable", 用法["usage_id"], 2)
|
||
)
|
||
assert 服务.读取方法详情(作者, 方法ID)["state"] == "confirmed"
|
||
# 绑定仍指向 v1:不追随
|
||
绑定 = 服务.列出绑定(作者, "method-work")[0]
|
||
assert str(绑定["version_id"]) == str(v1["version_id"]) and 绑定["version"] == 1
|
||
assert 绑定["title"] == "契诃夫之枪", "绑定标题也必须对应固定版本"
|
||
assert 服务.读取方法详情(作者, 方法ID)["title"] == "契诃夫之枪·修订"
|
||
旧链 = 服务.读取来源回查链(作者, str(v1["version_id"]))
|
||
新链 = 服务.读取来源回查链(作者, str(详情["versions"][1]["version_id"]))
|
||
assert len(旧链["证据"]) == 2 and len(新链["证据"]) == 1
|
||
assert 旧链["title"] == "契诃夫之枪"
|
||
# 重绑影响评估:旧版本消费清单可见(只标记)
|
||
影响 = 服务.评估绑定变更(作者, "method-work", 方法ID, str(详情["versions"][1]["version_id"]))
|
||
assert 影响["影响消费数"] >= 1 and any(u["kind"] == "generation" for u in 影响["影响清单"])
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert not 核对依赖(连, 冻结范围).有效, "确认新版本后未启用,旧任务不能继续使用"
|
||
_准备历史启用状态(库, 方法ID)
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert 核对依赖(连, 冻结范围).有效, "固定绑定不能自动追随当前版本"
|
||
v2 = next(v for v in 详情["versions"] if v["version"] == 2)
|
||
重绑命令 = 绑定命令("method-work", "work", "work", 方法ID, str(v2["version_id"]))
|
||
首次 = 服务.绑定方法(作者, "bind-v2", 重绑命令)
|
||
assert 服务.绑定方法(作者, "bind-v2", 重绑命令) == 首次, "命令重试不得再次释放或绑定"
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
失效 = 核对依赖(连, 冻结范围)
|
||
assert not 失效.有效 and any("方法" in 项 for 项 in 失效.变化项)
|
||
新范围 = 装配.要求上下文().授权任务范围(作者, "method-work", 0, 含事实=True, 含方法=True)
|
||
with 库[用途.生产].连接() as 连:
|
||
连.execute(
|
||
"UPDATE muse_source SET authorized_uses='[]' WHERE source_id=%s", (来源.source_id,)
|
||
)
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
撤权 = 核对依赖(连, 新范围)
|
||
assert not 撤权.有效 and any("授权" in 项 for 项 in 撤权.变化项)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b06f",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=["保存目录不得自动登记方法消费;既有版本证据可回查来源。此用例不证明规划实际使用方法"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_规划接缝消费与回查链__20b06f(方法环境):
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
服务.提出方法(作者, "prop-6", _提案(source_id=str(来源.source_id)))
|
||
提案 = 服务.读取方法提案(作者, _提案身份(服务, 作者, "prop-6"))
|
||
_确认(服务, 作者, 提案["proposal_id"])
|
||
方法ID = 提案["method_id"]
|
||
v1 = 服务.读取方法详情(作者, 方法ID)["versions"][0]
|
||
_准备历史启用状态(库, 方法ID)
|
||
服务.绑定方法(
|
||
作者, "bind-6", 绑定命令("method-work", "work", "work", 方法ID, str(v1["version_id"]))
|
||
)
|
||
# 保存目录不是方法消费;不得凭绑定关系追加使用记录。
|
||
保存前消费 = 服务.列出消费(作者, str(v1["version_id"]))
|
||
from muse.作品规划.模型 import 目录节点
|
||
|
||
目录 = 装配.要求作品().读取节点目录(作者, "method-work")
|
||
节点 = tuple(
|
||
目录节点(n["node_id"], n["kind"], n["title"], n.get("parent_id")) for n in 目录["nodes"]
|
||
)
|
||
装配.要求作品().保存节点目录(
|
||
作者,
|
||
"plan-save-1",
|
||
节点目录保存("method-work", 目录["revision"], 节点),
|
||
)
|
||
消费 = 服务.列出消费(作者, str(v1["version_id"]))
|
||
assert 消费 == 保存前消费
|
||
# 来源回查链:版本→提案→证据→来源版本
|
||
链 = 服务.读取来源回查链(作者, str(v1["version_id"]))
|
||
assert 链["来源"] and 链["来源"][0]["source_id"] == str(来源.source_id)
|
||
assert 链["来源"][0]["内容哈希"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b07a",
|
||
environment="隔离数据库",
|
||
given="隔离库与已装配知识方法服务",
|
||
when="走公开表面执行整链",
|
||
then=["重建索引可检索;污染即陈旧拒绝;重建不改方法本体"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_索引重建检索与陈旧拒绝__20b07a(方法环境):
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
服务.提出方法(作者, "prop-7", _提案(source_id=str(来源.source_id)))
|
||
提案 = 服务.读取方法提案(作者, _提案身份(服务, 作者, "prop-7"))
|
||
_确认(服务, 作者, 提案["proposal_id"])
|
||
方法ID = 提案["method_id"]
|
||
v1 = 服务.读取方法详情(作者, 方法ID)["versions"][0]
|
||
_准备历史启用状态(库, 方法ID)
|
||
回执 = 服务.重建索引(作者)
|
||
assert 回执["数量"] >= 1
|
||
命中 = 服务.检索方法(作者, "埋设 回收 枪", work_id="method-work")
|
||
assert any(r["method_id"] == 方法ID for r in 命中["结果"])
|
||
assert 命中["结果"][0]["来源回读"]
|
||
# 各自篡改实际文本、向量或嵌入器,不配合修改自报哈希;每项都必须识别陈旧。
|
||
前 = 服务.读取方法详情(作者, 方法ID)
|
||
for 污染 in ("检索文本='污染'", "vector=ARRAY[0.1,0.2]", "embedder='obsolete'"):
|
||
with 库[用途.生产].连接() as 连:
|
||
连.execute(
|
||
f"UPDATE muse_method_index SET {污染} WHERE method_version_id=%s",
|
||
(v1["version_id"],),
|
||
)
|
||
assert not 服务.核对索引(作者)["新鲜"]
|
||
with pytest.raises(Muse错误, match="陈旧"):
|
||
服务.检索方法(作者, "埋设 回收 枪")
|
||
服务.重建索引(作者)
|
||
assert 服务.核对索引(作者)["新鲜"]
|
||
后 = 服务.读取方法详情(作者, 方法ID)
|
||
assert 前["versions"] == 后["versions"]
|
||
with 库[用途.生产].连接() as 连:
|
||
连.execute(
|
||
"UPDATE muse_source SET authorized_uses='[]' WHERE source_id=%s", (来源.source_id,)
|
||
)
|
||
assert not 服务.核对索引(作者)["新鲜"]
|
||
# 查询必须真能命中该条目:命中后才进入逐项授权核对并整体拒绝。
|
||
with pytest.raises(Muse错误, match="METHOD_CANDIDATES_UNAVAILABLE"):
|
||
服务.检索方法(作者, "埋设 回收 枪")
|
||
|
||
|
||
def _提案身份(服务, 作者, 命令ID: str) -> str:
|
||
"""提案身份是 uuid5(命令ID);经服务读取确认存在。"""
|
||
|
||
from uuid import NAMESPACE_URL, uuid5
|
||
|
||
return str(uuid5(NAMESPACE_URL, "muse:B04:proposal:" + 命令ID))
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b08b",
|
||
environment="隔离PostgreSQL与真实S01服务;不调用模型",
|
||
given="已确认的方法版本及索引,来源追加一个真实版本",
|
||
when="仅修订证据并确认,再复用旧版本后新增修订",
|
||
then=["提案不污染正式索引;证据修订产生独立版本;回查仅见对应版本证据;版本编号不复用"],
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_证据修订独立成版且回查不串版本__20b08b(方法环境):
|
||
from dataclasses import replace
|
||
|
||
_, 作者, 服务, 来源, _, 库 = 方法环境
|
||
原请求 = _提案(source_id=str(来源.source_id))
|
||
服务.提出方法(作者, "evidence-v1", 原请求)
|
||
_确认(服务, 作者, _提案身份(服务, 作者, "evidence-v1"))
|
||
服务.重建索引(作者)
|
||
with 库[用途.生产].连接() as 连:
|
||
新来源 = 资料服务().导入(
|
||
连,
|
||
作者.作者,
|
||
导入请求(
|
||
kind="reference",
|
||
title="参考书",
|
||
origin="seed://method-source",
|
||
content="第一章留下一把钥匙,第三章用它打开密室。",
|
||
authorized_uses=("analysis",),
|
||
),
|
||
)
|
||
assert 新来源.revision == 2
|
||
新请求 = replace(
|
||
原请求,
|
||
修订自版本=1,
|
||
证据=(
|
||
replace(原请求.证据[0], revision=2),
|
||
原请求.证据[1],
|
||
),
|
||
)
|
||
服务.提出方法(作者, "evidence-v2", 新请求)
|
||
assert 服务.核对索引(作者)["新鲜"], "未确认的证据提案不能污染正式索引"
|
||
_确认(服务, 作者, _提案身份(服务, 作者, "evidence-v2"))
|
||
版本 = 服务.读取方法详情(作者, 原请求.method_id)["versions"]
|
||
assert [v["version"] for v in 版本] == [1, 2], "只改证据也必须形成独立版本"
|
||
assert 服务.核对索引(作者)["新鲜"], "普通确认必须同事务更新派生索引"
|
||
for v in 版本:
|
||
链 = 服务.读取来源回查链(作者, v["version_id"])
|
||
assert 链["来源"][0]["revision"] == v["version"]
|
||
# 编号反例固定当前来源;不为测试回退而放宽 S01 的来源当前指针保护。
|
||
编号方法 = _方法身份("sequence")
|
||
for 命令, 标题, 基线 in (
|
||
("seq-a", "编号样本甲", None),
|
||
("seq-b", "编号样本乙", 1),
|
||
("seq-back", "编号样本甲", 2),
|
||
("seq-c", "编号样本丙", 1),
|
||
):
|
||
请求 = replace(新请求, method_id=编号方法, title=标题, 修订自版本=基线)
|
||
服务.提出方法(作者, 命令, 请求)
|
||
_确认(服务, 作者, _提案身份(服务, 作者, 命令))
|
||
assert [v["version"] for v in 服务.读取方法详情(作者, 编号方法)["versions"]] == [1, 2, 3]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w20-20b09c",
|
||
environment="隔离PostgreSQL、S01/S04/B09真实服务;不调用模型",
|
||
given="真实确认并绑定的craft方法;planning与generation字段用途不同",
|
||
when="按不同任务用途取材,并经S04更新字段策略",
|
||
then=[
|
||
"生成不读规划专用字段;S04策略变化使旧范围失效;重新授权不含禁止字段;缺少确认提案仅可回查"
|
||
],
|
||
contract="docs/系统架构/新版设计/模块设计/B09-上下文.md",
|
||
)
|
||
def test_方法投影遵循任务用途且策略收紧阻断旧范围__20b09c(方法环境):
|
||
from dataclasses import asdict, replace
|
||
|
||
from muse.上下文.接口 import 核对依赖, 核对范围方法
|
||
from muse.元数据.接口 import 启用命令, 字段限制, 定义哈希
|
||
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
提案 = 服务.提出方法(作者, "projection-method", _提案(source_id=str(来源.source_id)))
|
||
_确认(服务, 作者, 提案["target_ref"])
|
||
方法ID = 服务.读取方法提案(作者, 提案["target_ref"])["method_id"]
|
||
版本ID = 服务.读取方法详情(作者, 方法ID)["versions"][0]["version_id"]
|
||
_准备历史启用状态(库, 方法ID)
|
||
服务.绑定方法(作者, "projection-bind", 绑定命令("method-work", "work", "work", 方法ID, 版本ID))
|
||
生成作者 = replace(作者, 内容用途=内容用途.生成)
|
||
上下文 = 装配.要求上下文()
|
||
生成范围 = 上下文.授权任务范围(生成作者, "method-work", 0, 含事实=True, 含方法=True)
|
||
规划范围 = 上下文.授权任务范围(作者, "method-work", 0, 含事实=True, 含方法=True)
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
写作材料 = 核对范围方法(连, 生成范围)[0]
|
||
规划材料 = 核对范围方法(连, 规划范围)[0]
|
||
assert "例证出处:" not in 写作材料["材料"]
|
||
assert "例证出处:" in 规划材料["材料"]
|
||
assert 写作材料["projection_version"] != 规划材料["projection_version"]
|
||
with 库[用途.维护].连接() as 连, 连.transaction():
|
||
元数据 = 元数据服务(连)
|
||
字段 = next(项 for 项 in 元数据.读取有效结构("craft", 1).字段 if 项.key == "原理")
|
||
当前 = 元数据.当前策略("craft")
|
||
新 = replace(
|
||
当前,
|
||
version=当前.version + 1,
|
||
限制=(*当前.限制, 字段限制(字段.field_id, ("aiContext:generation",))),
|
||
)
|
||
元数据.更新策略(
|
||
新,
|
||
启用命令(
|
||
"restrict-method-context",
|
||
定义哈希(asdict(新)),
|
||
当前.version,
|
||
作者.作者,
|
||
"type:craft",
|
||
),
|
||
)
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert not 核对依赖(连, 生成范围).有效
|
||
新范围 = 上下文.授权任务范围(生成作者, "method-work", 0, 含事实=True, 含方法=True)
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert "原理:" not in 核对范围方法(连, 新范围)[0]["材料"]
|
||
# 历史资料可回查;缺少确认提案的版本不能重新进入模型材料。
|
||
with 库[用途.生产].连接() as 连:
|
||
连.execute("UPDATE muse_method_version SET proposal_id=NULL WHERE version_id=%s", (版本ID,))
|
||
assert 服务.读取版本(作者, 版本ID)["version"] == 1
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert not 核对依赖(连, 新范围).有效
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# 方法绑定/检索原观察承接:真实服务级等价边界(旧范式卡/双臂形状替代)
|
||
# 每条只绑定一个 TC;数据库真实公开入口,不引用旧 SQL 或内部表示。
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
def _绑定启用方法(装配, 作者, 服务, 来源, *, 标签="bind-material", work_id="method-work"):
|
||
"""提出→确认→历史启用→绑定,返回 (method_id, version_id)。"""
|
||
from uuid import uuid4
|
||
|
||
请求 = _提案(uuid4().hex[:8], 标签, str(来源.source_id), revision=1, owner=作者.作者)
|
||
服务.提出方法(作者, f"prop-{标签}", 请求)
|
||
提案 = 服务.读取方法提案(作者, _提案身份(服务, 作者, f"prop-{标签}"))
|
||
_确认(服务, 作者, 提案["proposal_id"])
|
||
版本 = 服务.读取方法详情(作者, 请求.method_id)["versions"][0]
|
||
with 服务.数据库.连接() as 连:
|
||
连.execute("UPDATE muse_method SET state='enabled' WHERE method_id=%s", (请求.method_id,))
|
||
服务.绑定方法(
|
||
作者,
|
||
f"bind-{标签}",
|
||
绑定命令(work_id, "work", "work", 请求.method_id, str(版本["version_id"])),
|
||
)
|
||
return 请求.method_id, str(版本["version_id"])
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"TC-c9aac0adb8a8",
|
||
environment="离线,固定方法库与结构",
|
||
when="选择或投影明确用途的固定方法版本。",
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_绑定材料向写手投影内容上限与来源出处__c9aac0(方法环境):
|
||
"""旧内容字段过合同且写手读到卡内容;新版等价:绑定材料含投影键值、参数上限与证据出处。"""
|
||
from muse.知识方法.接口 import 读取绑定材料
|
||
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
method_id, version_id = _绑定启用方法(装配, 作者, 服务, 来源, 标签="writer-content")
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
材料 = 读取绑定材料(连, "method-work", 内容用途="generation", 运行用途="production")
|
||
本条 = [m for m in 材料 if m["method_id"] == method_id]
|
||
assert len(本条) == 1
|
||
assert 本条[0]["version_id"] == version_id and 本条[0]["version"] == 1
|
||
assert 本条[0]["参数上限"] == {"单章上限": 1}
|
||
文本 = 本条[0]["材料"]
|
||
assert "writer-content" in 文本
|
||
assert "原理:" in 文本 and "埋设手法:" in 文本
|
||
assert "参数上限:" in 文本
|
||
出处行 = [行 for 行 in 文本.splitlines() if 行.startswith("出处:")]
|
||
assert 出处行 == [f"出处:{来源.source_id}@r{来源.revision} 第1-2章"]
|
||
来源链 = 服务.读取来源回查链(作者, version_id)["来源"]
|
||
assert [(条["source_id"], 条["revision"], 条["定位"]) for 条 in 来源链] == [
|
||
(str(来源.source_id), 来源.revision, "第1-2章")
|
||
]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"TC-cb5645be12fa",
|
||
environment="离线,固定方法库与结构",
|
||
when="选择或投影明确用途的固定方法版本。",
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_绑定材料携带反例与上限且不夹带不可编辑字段__cb5645(方法环境):
|
||
"""旧每卡投影为来源指针+内容字段;新版等价:材料含反例定位与参数上限。"""
|
||
from muse.知识方法.接口 import 读取绑定材料
|
||
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
_绑定启用方法(装配, 作者, 服务, 来源, 标签="counter-and-cap")
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
材料 = 读取绑定材料(连, "method-work", 内容用途="generation", 运行用途="production")
|
||
文本 = 材料[0]["材料"]
|
||
assert "反例:" in 文本 and "烂大街" in 文本
|
||
assert "单章上限:1" in 文本 or '"单章上限": 1' in 文本
|
||
assert "材料哈希" in 材料[0] and 材料[0]["材料哈希"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"TC-ccb4278b70c8",
|
||
environment="离线,固定方法库与结构",
|
||
when="选择或投影明确用途的固定方法版本。",
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_方法内容未知或越界字段在提出时拒绝__ccb427(方法环境):
|
||
"""旧合同失败关闭;新版等价:提案含不可编辑/未知字段被结构校验拒绝。"""
|
||
from dataclasses import replace
|
||
|
||
from muse.共享.错误 import Muse错误
|
||
|
||
_, 作者, 服务, 来源, _, _ = 方法环境
|
||
请求 = _提案(source_id=str(来源.source_id))
|
||
with pytest.raises(Muse错误):
|
||
服务.提出方法(
|
||
作者,
|
||
"oversize-field",
|
||
replace(请求, content={**请求.content, "未知字段": "不应进入"}),
|
||
)
|
||
with pytest.raises(Muse错误):
|
||
服务.提出方法(
|
||
作者,
|
||
"non-object-field",
|
||
replace(请求, content={**请求.content, "原理": {"不是字符串": True}}),
|
||
)
|
||
# 被拒绝的提案不落库:任何命令身份都查不到未被接受的提案。
|
||
for 命令 in ("oversize-field", "non-object-field"):
|
||
with pytest.raises(Muse错误) as 错:
|
||
服务.读取方法提案(作者, _提案身份(服务, 作者, 命令))
|
||
assert 错.value.错误码 == "METHOD_PROPOSAL_NOT_FOUND"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"TC-b9e8dac4520e",
|
||
environment="离线,固定方法库与结构",
|
||
when="选择或投影明确用途的固定方法版本。",
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_方法证据引用核对来源确切实体与版本__b9e8da(方法环境):
|
||
"""旧严格来源指针拒绝夹带;新版等价:证据引用必须命中已登记来源的真实版本。"""
|
||
from dataclasses import replace
|
||
from uuid import uuid4
|
||
|
||
from muse.共享.错误 import Muse错误
|
||
|
||
_, 作者, 服务, 来源, 无权, _ = 方法环境
|
||
请求 = _提案(uuid4().hex[:8], "strict-source", str(来源.source_id), revision=1, owner=作者.作者)
|
||
服务.提出方法(作者, "strict-source-ok", 请求)
|
||
# 错误版本:证据 revision 指向不存在的版本 → 提出拒绝
|
||
with pytest.raises(Muse错误):
|
||
服务.提出方法(
|
||
作者,
|
||
"strict-source-bad",
|
||
replace(
|
||
请求,
|
||
method_id=_方法身份("strict-bad"),
|
||
证据=(
|
||
证据引用(
|
||
kind="source",
|
||
source_id=str(来源.source_id),
|
||
revision=999,
|
||
定位="第1-2章",
|
||
),
|
||
证据引用(kind="counter", note="每章都埋即烂大街"),
|
||
),
|
||
),
|
||
)
|
||
# 未授权来源:analysis 授权缺失 → 提出拒绝
|
||
with pytest.raises(Muse错误):
|
||
服务.提出方法(
|
||
作者,
|
||
"strict-source-unlicensed",
|
||
replace(
|
||
请求,
|
||
method_id=_方法身份("strict-unlicensed"),
|
||
证据=(
|
||
证据引用(
|
||
kind="source",
|
||
source_id=str(无权.source_id),
|
||
revision=无权.revision,
|
||
定位="第1-2章",
|
||
),
|
||
证据引用(kind="counter", note="每章都埋即烂大街"),
|
||
),
|
||
),
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"TC-3a53564c2cf2",
|
||
environment="离线,固定方法库与结构",
|
||
when="选择或投影明确用途的固定方法版本。",
|
||
contract="docs/系统架构/新版设计/模块设计/B04-知识方法.md",
|
||
)
|
||
def test_绑定材料仅向启用且绑定版本投影__3a5356(方法环境):
|
||
"""旧无确认装配返回空;新版等价:未绑定或无启用状态的方法不返回绑定材料。"""
|
||
from muse.知识方法.接口 import 读取绑定材料
|
||
|
||
装配, 作者, 服务, 来源, _, 库 = 方法环境
|
||
保留ID, 保留版本 = _绑定启用方法(装配, 作者, 服务, 来源, 标签="only-bound")
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
assert (
|
||
len(读取绑定材料(连, "method-work", 内容用途="generation", 运行用途="production")) == 1
|
||
)
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
未绑定 = 读取绑定材料(连, "no-such-work", 内容用途="generation", 运行用途="production")
|
||
assert 未绑定 == []
|
||
# 停用的方法不返回材料:禁用一个已绑定方法后该方法的材料消失,其他启用绑定保留。
|
||
method_id, _ = _绑定启用方法(装配, 作者, 服务, 来源, 标签="disabled-material")
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
前 = 读取绑定材料(连, "method-work", 内容用途="generation", 运行用途="production")
|
||
assert {m["method_id"] for m in 前} == {保留ID, method_id}
|
||
with 服务.数据库.连接() as 连:
|
||
连.execute("UPDATE muse_method SET state='disabled' WHERE method_id=%s", (method_id,))
|
||
with 库[用途.生产].连接(只读=True) as 连:
|
||
材料 = 读取绑定材料(连, "method-work", 内容用途="generation", 运行用途="production")
|
||
assert {(m["method_id"], m["version_id"]) for m in 材料} == {(保留ID, 保留版本)}
|