实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
285 lines
12 KiB
Python
285 lines
12 KiB
Python
"""F04.02/F06.05/F07.04 三项补齐功能的单元行为;离线纯域校验,无数据库。"""
|
||
|
||
import pytest
|
||
|
||
from muse.作品规划.分层规划 import 内置题材档位, 校验节奏标注, 检查节奏分布, 登记题材档位
|
||
from muse.作品规划.接口 import 作品错误
|
||
from muse.故事世界.接口 import (
|
||
事实提案请求,
|
||
事实结构,
|
||
事实错误,
|
||
伏笔总览,
|
||
伏笔条目,
|
||
叙事窗口,
|
||
推进伏笔,
|
||
校验规则卡,
|
||
登记伏笔,
|
||
读取规则卡,
|
||
)
|
||
|
||
章节顺序 = {f"chapter-{i}": i for i in range(1, 13)}
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# F07.04 伏笔与承诺
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-c03dfd",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="伏笔/悬念/读者期待/章末钩子条目与本书章节顺序",
|
||
when="登记一批伏笔条目",
|
||
then=["合法条目按原样返回并默认铺设状态;", "铺设章节不存在或目标窗口早于铺设时精确拒绝"],
|
||
contract="docs/系统架构/新版设计/模块设计/B02-故事世界.md",
|
||
)
|
||
def test_伏笔登记校验种类ID章节与窗口__c03dfd():
|
||
条目们 = 登记伏笔(
|
||
(
|
||
伏笔条目(
|
||
"hook-blackvein",
|
||
"章末钩子",
|
||
"黑纹第一次缠上手臂",
|
||
"chapter-2",
|
||
目标窗口=叙事窗口("chapter-8", "chapter-12"),
|
||
),
|
||
伏笔条目("susp-core", "悬念", "异种核心的来历不明", "chapter-1"),
|
||
),
|
||
章节顺序,
|
||
)
|
||
assert [t.条目ID for t in 条目们] == ["hook-blackvein", "susp-core"]
|
||
assert 条目们[0].状态 == "铺设" and 条目们[1].目标窗口 is None
|
||
with pytest.raises(事实错误) as 错误:
|
||
登记伏笔((伏笔条目("a", "伏笔", "x", "chapter-99"),), 章节顺序)
|
||
assert 错误.value.错误码 == "FACT_TIME_INVALID"
|
||
with pytest.raises(事实错误) as 错误:
|
||
登记伏笔(
|
||
(伏笔条目("b", "伏笔", "x", "chapter-5", 目标窗口=叙事窗口("chapter-2", "chapter-3")),),
|
||
章节顺序,
|
||
)
|
||
assert 错误.value.错误码 == "FACT_TIME_INVALID"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-325b88",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="一条处于铺设状态的伏笔条目",
|
||
when="按状态机推进到推进、兑现并尝试越界转移",
|
||
then=["铺设→推进→兑现合法;", "终态再转移被拒绝;", "推进章节早于铺设被拒绝"],
|
||
contract="docs/系统架构/新版设计/模块设计/B02-故事世界.md",
|
||
)
|
||
def test_伏笔状态机拒绝非法转移与过早推进__325b88():
|
||
铺设 = 伏笔条目("hook", "伏笔", "旧钥匙的用途", "chapter-3")
|
||
推进 = 推进伏笔(铺设, "推进", 章节顺序, 截止章节="chapter-6")
|
||
assert 推进.状态 == "推进"
|
||
兑现 = 推进伏笔(推进, "兑现", 章节顺序, 截止章节="chapter-9")
|
||
with pytest.raises(事实错误) as 错误:
|
||
推进伏笔(兑现, "推进", 章节顺序) # 终态不可再动
|
||
assert 错误.value.错误码 == "FORESHADOW_TRANSITION_INVALID"
|
||
with pytest.raises(事实错误) as 错误:
|
||
推进伏笔(铺设, "推进", 章节顺序, 截止章节="chapter-1") # 早于铺设
|
||
assert 错误.value.错误码 == "FACT_TIME_INVALID"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-0ff84c",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="铺设/长期/已兑现三类条目与当前章节",
|
||
when="读取伏笔总览",
|
||
then=["未决与已决分组正确;", "越过目标窗口仍未兑现的条目标入逾期未兑现"],
|
||
contract="docs/系统架构/新版设计/模块设计/B02-故事世界.md",
|
||
)
|
||
def test_伏笔总览分组并标逾期未兑现__0ff84c():
|
||
条目们 = (
|
||
伏笔条目("a", "伏笔", "已铺未兑", "chapter-2", 目标窗口=叙事窗口("chapter-4", "chapter-6")),
|
||
伏笔条目("b", "读者期待", "长期期待", "chapter-1"),
|
||
推进伏笔(伏笔条目("c", "悬念", "已兑现", "chapter-2"), "兑现", 章节顺序),
|
||
)
|
||
总览 = 伏笔总览(条目们, 章节顺序, "chapter-9")
|
||
assert [行["条目ID"] for 行 in 总览["未决"]] == ["a", "b"]
|
||
assert [行["条目ID"] for 行 in 总览["已决"]] == ["c"]
|
||
assert 总览["逾期未兑现"] == ["a"] # 目标窗口止于 chapter-6,当前 chapter-9
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# F04.02 规则与代价
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-cb8b0c",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="能力边界/行动代价/例外条目输入",
|
||
when="校验规则卡并读回",
|
||
then=["缺省适用条件补全书适用;", "例外缺适用条件与重复条目精确拒绝"],
|
||
contract="docs/系统架构/新版设计/模块设计/B02-故事世界.md",
|
||
)
|
||
def test_规则卡校验重复例外条件与读回__cb8b0c():
|
||
内容 = 校验规则卡(
|
||
(
|
||
{"类别": "能力边界", "内容": "黑纹一次最多缠一臂"},
|
||
{"类别": "行动代价", "内容": "每次缠臂消耗一日记忆", "适用条件": "主动使用"},
|
||
{"类别": "例外", "内容": "核心共鸣时全身覆盖", "适用条件": "濒死"},
|
||
)
|
||
)
|
||
assert 内容["规则条目"][0]["适用条件"] == "全书适用"
|
||
assert 读取规则卡(内容)[2]["适用条件"] == "濒死"
|
||
with pytest.raises(事实错误) as 错误:
|
||
校验规则卡(
|
||
(
|
||
{"类别": "资源限制", "内容": "x"},
|
||
{"类别": "资源限制", "内容": "x"},
|
||
)
|
||
)
|
||
assert 错误.value.错误码 == "RULE_CARD_DUPLICATE"
|
||
with pytest.raises(事实错误) as 错误:
|
||
校验规则卡(({"类别": "例外", "内容": "无条件的例外"},))
|
||
assert 错误.value.错误码 == "RULE_CARD_INVALID"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-4f0cda",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="空批次、未知类别与非规则卡形状",
|
||
when="执行规则卡校验与读回",
|
||
then=["空批次、坏类别、坏形状均精确失败,不猜测兼容"],
|
||
contract="docs/系统架构/新版设计/模块设计/B02-故事世界.md",
|
||
)
|
||
def test_规则卡空批与坏类别精确失败__4f0cda():
|
||
with pytest.raises(事实错误) as 错误:
|
||
校验规则卡(())
|
||
assert 错误.value.错误码 == "RULE_CARD_EMPTY"
|
||
with pytest.raises(事实错误) as 错误:
|
||
校验规则卡(({"类别": "未知类别", "内容": "x"},))
|
||
assert 错误.value.错误码 == "RULE_CARD_INVALID"
|
||
with pytest.raises(事实错误) as 错误:
|
||
读取规则卡({"别的形状": []})
|
||
assert 错误.value.错误码 == "RULE_CARD_INVALID"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-365f8a",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="规则卡内容与既有事实提案请求合同",
|
||
when="组装独立设定提案并测试非独立约束",
|
||
then=["规则卡内容可作为实体content进入提案管线;", "非独立编辑仍须正文依据,与既有合同一致"],
|
||
contract="docs/系统架构/新版设计/模块设计/B02-故事世界.md",
|
||
)
|
||
def test_规则卡内容经实体提案形状保存__365f8a():
|
||
内容 = 校验规则卡(({"类别": "能力边界", "内容": "灵力上限三章不涨"},))
|
||
请求 = 事实提案请求(
|
||
work_id="synthetic-work",
|
||
object_id="rule-card-1",
|
||
expected_revision=0,
|
||
schema=事实结构("world_rule", 1, "hash", "proj", None),
|
||
content=内容,
|
||
sources=(),
|
||
effective_from=None,
|
||
independent=True,
|
||
knowledge_mode="setting",
|
||
)
|
||
assert 请求.content == 内容 and 请求.independent
|
||
# 非独立编辑仍须正文依据,与既有实体提案合同一致
|
||
with pytest.raises(事实错误) as 错误:
|
||
事实提案请求(
|
||
work_id="synthetic-work",
|
||
object_id="rule-card-1",
|
||
expected_revision=0,
|
||
schema=事实结构("world_rule", 1, "hash", "proj", None),
|
||
content=内容,
|
||
sources=(),
|
||
effective_from=None,
|
||
knowledge_mode="setting",
|
||
)
|
||
assert 错误.value.错误码 == "FACT_INVALID"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# F06.05 节奏安排
|
||
|
||
|
||
def 场景们(节奏表):
|
||
return tuple(
|
||
{"场景ID": f"s{i}", "顺序": i, "节奏": (节奏,)} for i, 节奏 in enumerate(节奏表, start=1)
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-cd21a9",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="带节奏标注的场景序列与七类基础节奏",
|
||
when="校验节奏标注",
|
||
then=["合法标注排序规范化;", "未登记类别与缺失标注精确拒绝"],
|
||
contract="docs/系统架构/新版设计/模块设计/B01-作品规划.md",
|
||
)
|
||
def test_节奏标注校验非法类别与缺失标注__cd21a9():
|
||
允许 = frozenset(内置题材档位)
|
||
规范 = 校验节奏标注(场景们(["冲突", "行动", "日常"]), 允许)
|
||
assert [行["顺序"] for 行 in 规范] == [1, 2, 3]
|
||
with pytest.raises(作品错误) as 错误:
|
||
校验节奏标注(场景们(["打斗"]), 允许)
|
||
assert 错误.value.错误码 == "RHYTHM_INVALID"
|
||
with pytest.raises(作品错误) as 错误:
|
||
校验节奏标注(({"场景ID": "s1", "顺序": 1, "节奏": ()},), 允许)
|
||
assert 错误.value.错误码 == "RHYTHM_INVALID"
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-fc17cf",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="偏斜与均衡两组场景节奏",
|
||
when="对照内置题材档位检查分布",
|
||
then=["区间外类别列出不足/过密偏差;", "均衡分布零偏差"],
|
||
contract="docs/系统架构/新版设计/模块设计/B01-作品规划.md",
|
||
)
|
||
def test_节奏分布对照题材档位列偏差__fc17cf():
|
||
表 = ["冲突", "行动", "日常"] * 4 # 12 场景:各 1/3
|
||
报告 = 检查节奏分布(场景们(表), 内置题材档位)
|
||
assert 报告["场景数"] == 12
|
||
assert abs(报告["分布"]["冲突"] - 1 / 3) < 1e-9
|
||
assert "铺垫" in 报告["偏差"] and "不足" in 报告["偏差"]["铺垫"]
|
||
assert "日常" in 报告["偏差"] and "过密" in 报告["偏差"]["日常"]
|
||
均衡表 = [
|
||
"冲突",
|
||
"铺垫",
|
||
"冲突",
|
||
"日常",
|
||
"行动",
|
||
"情绪",
|
||
"行动",
|
||
"解释",
|
||
"行动",
|
||
"兑现",
|
||
"冲突",
|
||
"铺垫",
|
||
"日常",
|
||
"行动",
|
||
"情绪",
|
||
"冲突",
|
||
"行动",
|
||
"铺垫",
|
||
"日常",
|
||
"行动",
|
||
]
|
||
均衡 = 检查节奏分布(场景们(均衡表), 内置题材档位)
|
||
assert 均衡["偏差"] == {} and 均衡["连续单一提示"] == []
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w31-a1b1ec",
|
||
environment="离线单元;临时目录与协议替身",
|
||
given="自定义题材档位与全日常场景",
|
||
when="登记调整档位并检查分布",
|
||
then=["按题材调整档位生效;", "缺基础类别与非法区间拒绝"],
|
||
contract="docs/系统架构/新版设计/模块设计/B01-作品规划.md",
|
||
)
|
||
def test_题材档位自定义调整与非法区间__a1b1ec():
|
||
档位 = 登记题材档位({**内置题材档位, "日常": (0.01, 1.0)})
|
||
assert 档位["日常"] == (0.01, 1.0) and 档位["冲突"] == 内置题材档位["冲突"]
|
||
报告 = 检查节奏分布(场景们(["日常"] * 10), 档位)
|
||
assert "日常" not in 报告["偏差"] # 放宽后 100% 日常不再判过密
|
||
with pytest.raises(作品错误) as 错误:
|
||
登记题材档位({k: v for k, v in 内置题材档位.items() if k != "情绪"})
|
||
assert 错误.value.错误码 == "RHYTHM_PROFILE_INVALID"
|
||
with pytest.raises(作品错误) as 错误:
|
||
登记题材档位({**内置题材档位, "冲突": (0.8, 0.2)})
|
||
assert 错误.value.错误码 == "RHYTHM_PROFILE_INVALID"
|