实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
423 lines
18 KiB
Python
423 lines
18 KiB
Python
"""固定正文逐片文学审校:真实S02/PG与合成HTTP贯通,不据此声明文学收益。"""
|
||
|
||
import json
|
||
from dataclasses import replace
|
||
from datetime import UTC, datetime, timedelta
|
||
from decimal import Decimal
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
import test_两阶段写手 as 写手测试
|
||
|
||
from muse.任务运行.接口 import 任务状态, 任务预算计划, 角色预算, 预算管理, 额度策略
|
||
from muse.共享.调用身份 import 内容用途
|
||
from muse.共享.错误 import Muse错误
|
||
from muse.启动 import 装配完整审校
|
||
from muse.审校修订.接口 import 审校错误, 生成审校分片
|
||
from muse.正文写作.接口 import 文本节点, 正文草稿, 段落
|
||
from muse.编排.完整审校 import 发起完整审校
|
||
|
||
pytestmark = pytest.mark.数据库
|
||
生成环境 = 写手测试.生成环境
|
||
|
||
|
||
def 片结果(片, *, 有声音):
|
||
return {
|
||
"shard_id": 片.片ID,
|
||
"dimensions": {
|
||
d: {"status": s, "summary": "合成片判断", "findings": []}
|
||
for d, s in [
|
||
("continuity", "reviewed"),
|
||
("craft", "reviewed"),
|
||
("voice", "reviewed" if 有声音 else "uncalibrated"),
|
||
]
|
||
},
|
||
}
|
||
|
||
|
||
@pytest.fixture
|
||
def 文学环境(生成环境):
|
||
env = 生成环境
|
||
app = env["装配"]
|
||
装配完整审校(
|
||
app, 写手测试.合成计价(), Path(__file__).parents[2] / "配置/流程模板/完整审校.yaml"
|
||
)
|
||
管理 = env["管理"]
|
||
原 = 管理.读取版本("gen-config", "1").内容
|
||
新 = replace(
|
||
原,
|
||
角色配置={
|
||
"judge": {
|
||
"provider": "synthetic",
|
||
# judge-fixed 白名单内的模型;合成宿主按请求回声身份。
|
||
"model": "deepseek-v4.1-flash",
|
||
"thinking": "high",
|
||
"stage_tools": {"执行": []},
|
||
}
|
||
},
|
||
)
|
||
管理.保存草案("review-config", "1", 新)
|
||
验证 = 管理.验证版本("review-config", "1")
|
||
管理.启用("review-config", "1", 验证回执=验证, 批准引用="synthetic-review-approval", 预期代次=0)
|
||
作者 = replace(env["作者"], 内容用途=内容用途.检测)
|
||
文本 = "林深站在门外。雨下了很久。屋里的人没有回答。门框上还留着旧划痕。"
|
||
env["正文"].保存人工(
|
||
作者, "review-document", "ch-2", 0, 正文草稿((段落("r1", (文本节点(文本),)),))
|
||
)
|
||
文 = env["正文"].读取正文(作者, "ch-2")
|
||
env.update(审校作者=作者, 审校正文=文)
|
||
return env
|
||
|
||
|
||
def 发起(env):
|
||
app = env["装配"]
|
||
文 = env["审校正文"]
|
||
r = 发起完整审校(
|
||
app,
|
||
env["审校作者"],
|
||
"review-run",
|
||
work_id="gen-work",
|
||
chapter_id=env.get("审校章", "ch-2"),
|
||
revision=文["revision"],
|
||
document_hash=文["document_hash"],
|
||
配置ID="review-config",
|
||
candidate_id=文.get("candidate_id"),
|
||
核心上限=12,
|
||
上下文字数=3,
|
||
)
|
||
任务 = app.任务运行.读取任务(r["task_id"])
|
||
输入 = 任务.冻结输入["输入"]["文学审校"]
|
||
片 = 生成审校分片(文["visible_text"], 输入["basis_hash"], 核心上限=12, 上下文字数=3)
|
||
预算 = 预算管理(env["库"], "synthetic")
|
||
预算.登记策略(额度策略("synthetic", "1", Decimal("20"), 40))
|
||
预算.登记任务预算(
|
||
r["task_id"],
|
||
任务预算计划(
|
||
Decimal("10"),
|
||
(角色预算("judge", 10, 10, Decimal("1")),),
|
||
"approval",
|
||
datetime.now(UTC) + timedelta(minutes=10),
|
||
),
|
||
)
|
||
return r["task_id"], 片
|
||
|
||
|
||
def 跑(env, tid):
|
||
运行 = env["装配"].任务运行
|
||
while 运行.读取任务(tid).状态 not in {任务状态.已完成, 任务状态.已失败, 任务状态.已取消}:
|
||
领取 = 运行.领取步骤("review-worker", ["review.prepare", "review.literary"])
|
||
assert 领取 is not None
|
||
运行.执行一步(领取)
|
||
return 运行.读取任务(tid)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-227001",
|
||
environment="隔离PG与合成HTTP提供方;production/muse_app测试角色",
|
||
given="本书已保存正文分为多片、零问题、没有确认声音",
|
||
when="经发布模板、S02当前任务、B09合法背景和B06片结果/报告用例执行",
|
||
then=["逐片真实judge交付后有唯一完整B06报告;正文不变,声音未标定,真实入参不截前缀"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
def test_全文零命中仍有真实版本化报告和逐片证据__227001(文学环境):
|
||
env = 文学环境
|
||
tid, 片 = 发起(env)
|
||
env["剧本"].extend({"类型": "文本", "文本": 片结果(p, 有声音=False)} for p in 片)
|
||
task = 跑(env, tid)
|
||
assert task.状态 == 任务状态.已完成
|
||
report = env["装配"].要求审校().读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
assert report["report"]["persisted"] and report["report"]["model_verified"]
|
||
assert report["report"]["coverage"]["范围完整"]
|
||
assert report["report"]["counts"] == {"repair": 0, "retain": 0, "ask": 0}
|
||
assert report["report"]["dimension_status"]["voice"] == "uncalibrated"
|
||
assert len(report["report"]["evidence"]) == len(片) == len(env["收到"])
|
||
assert report["current_status"] == "valid"
|
||
for req, p in zip(env["收到"], 片, strict=True):
|
||
data = json.loads(req["input"])
|
||
assert data["审校分片"]["片ID"] == p.片ID and data["审校分片"]["文本"] == p.文本
|
||
assert not req.get("tools")
|
||
assert env["正文"].读取正文(env["审校作者"], "ch-2") == env["审校正文"]
|
||
with env["库"].连接(只读=True) as 连:
|
||
assert 连.execute(
|
||
"SELECT COUNT(*) FROM muse_literary_shard WHERE task_id=%s", (tid,)
|
||
).fetchone()[0] == len(片)
|
||
assert (
|
||
连.execute(
|
||
"SELECT COUNT(*) FROM muse_literary_report WHERE task_id=%s", (tid,)
|
||
).fetchone()[0]
|
||
== 1
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-227002",
|
||
environment="隔离PG与合成HTTP提供方;production/muse_app测试角色",
|
||
given="首片合法、后片实际引文不符",
|
||
when="经发布模板、S02当前任务、B09合法背景和B06片结果/报告用例执行",
|
||
then=["只有首片进入已审记录,失败状态和未审尾部独立展示,不生成完整报告"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
def test_后片坏引文失败仍展示前片和未审尾部__227002(文学环境):
|
||
env = 文学环境
|
||
tid, 片 = 发起(env)
|
||
坏 = 片结果(片[1], 有声音=False)
|
||
坏["dimensions"]["craft"]["findings"] = [
|
||
{
|
||
"start": 片[1].核心起点,
|
||
"end": 片[1].核心起点 + 1,
|
||
"quote": "伪",
|
||
"judgment": "ask",
|
||
"reason": "合成坏引文",
|
||
"basis_ids": [],
|
||
}
|
||
]
|
||
env["剧本"].extend(
|
||
[{"类型": "文本", "文本": 片结果(片[0], 有声音=False)}, {"类型": "文本", "文本": 坏}]
|
||
)
|
||
with pytest.raises(Muse错误):
|
||
跑(env, tid)
|
||
view = env["装配"].要求审校().读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
assert view["task_state"] == "failed" and view["report_id"] is None
|
||
assert view["report"]["coverage"]["已审区"] == [[片[0].核心起点, 片[0].核心终点]]
|
||
assert view["report"]["coverage"]["未审区"]
|
||
assert not view["report"]["persisted"] and not view["report"]["model_verified"]
|
||
assert len(env["收到"]) == 2
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-227003",
|
||
environment="隔离PG与合成HTTP提供方;production/muse_app测试角色",
|
||
given="真实模型响应后在保存公共接缝篡改结果字典",
|
||
when="经发布模板、S02当前任务、B09合法背景和B06片结果/报告用例执行",
|
||
then=["S02实际结构化哈希拒任意新结论,B06不保存假片"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
def test_文学登记不能把任意字典冒真模型结果__227003(文学环境, monkeypatch):
|
||
env = 文学环境
|
||
tid, 片 = 发起(env)
|
||
env["剧本"].append({"类型": "文本", "文本": 片结果(片[0], 有声音=False)})
|
||
服务 = env["装配"].要求审校()
|
||
原 = 服务.保存文学片
|
||
|
||
def 篡改(连, 运行, 领取, 片ID, 快照ID, 用户输入, 输出, 调用ID):
|
||
输出["dimensions"]["craft"]["summary"] = "非模型原结论"
|
||
return 原(连, 运行, 领取, 片ID, 快照ID, 用户输入, 输出, 调用ID)
|
||
|
||
monkeypatch.setattr(服务, "保存文学片", 篡改)
|
||
with pytest.raises(Muse错误):
|
||
跑(env, tid)
|
||
view = 服务.读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
assert view["report"]["coverage"]["已审字数"] == 0
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-227004",
|
||
environment="隔离PG与合成HTTP提供方;production/muse_app测试角色",
|
||
given="报告生成后正文版本推进及异作者读取",
|
||
when="经发布模板、S02当前任务、B09合法背景和B06片结果/报告用例执行",
|
||
then=["历史报告保持,当前状态SOURCE_STALE,异作者拒读;工作面使用只读可重复读快照"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
def test_正文漂移不改历史报告但标当前失效且隔离作者__227004(文学环境):
|
||
env = 文学环境
|
||
tid, 片 = 发起(env)
|
||
env["剧本"].extend({"类型": "文本", "文本": 片结果(p, 有声音=False)} for p in 片)
|
||
跑(env, tid)
|
||
服务 = env["装配"].要求审校()
|
||
初 = 服务.读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
env["正文"].保存人工(
|
||
env["审校作者"],
|
||
"new-review-body",
|
||
"ch-2",
|
||
1,
|
||
正文草稿((段落("r1", (文本节点("作者重写了这一章。"),)),)),
|
||
)
|
||
后 = 服务.读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
assert 后["current_status"] == "SOURCE_STALE" and 后["report"] == 初["report"]
|
||
with pytest.raises(Muse错误):
|
||
服务.读取文学工作面(
|
||
replace(env["审校作者"], 作者="foreign-author"), env["装配"].任务运行, tid
|
||
)
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-227005",
|
||
environment="隔离PG与合成HTTP提供方;production/muse_app测试角色",
|
||
given="一次片后失败,再由作者明确恢复同一任务",
|
||
when="经发布模板、S02当前任务、B09合法背景和B06片结果/报告用例执行",
|
||
then=["原片不重发,保原尝试/调用归属,只调用剩余片并形成完整报告"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
def test_恢复复用已存片而不重发或伪造原尝试__227005(文学环境):
|
||
env = 文学环境
|
||
tid, 片 = 发起(env)
|
||
env["剧本"].extend(
|
||
[
|
||
{"类型": "文本", "文本": 片结果(片[0], 有声音=False)},
|
||
{"类型": "文本", "文本": {"不是": "片结果"}},
|
||
]
|
||
)
|
||
with pytest.raises(Muse错误):
|
||
跑(env, tid)
|
||
服务 = env["装配"].要求审校()
|
||
前 = 服务.读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
assert len(前["report"]["evidence"]) == 1
|
||
env["装配"].任务运行.控制任务(
|
||
tid, env["审校作者"].作者, 任务状态.已失败, "恢复", 命令ID="resume-review"
|
||
)
|
||
env["剧本"].extend({"类型": "文本", "文本": 片结果(p, 有声音=False)} for p in 片[1:])
|
||
assert 跑(env, tid).状态 == 任务状态.已完成
|
||
后 = 服务.读取文学工作面(env["审校作者"], env["装配"].任务运行, tid)
|
||
assert 后["report"]["evidence"][0] == 前["report"]["evidence"][0]
|
||
assert len({r["attempt_id"] for r in 后["report"]["evidence"]}) == 2
|
||
assert len(env["收到"]) == len(片) + 1
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-227006",
|
||
environment="隔离PG与合成HTTP提供方;production/muse_app测试角色",
|
||
given="缺配置/缺judge/错正文版本/错哈希四种前置",
|
||
when="经发布模板、S02当前任务、B09合法背景和B06片结果/报告用例执行",
|
||
then=["明确拒绝且任务与配置原子绑定,不留下可领取孤儿任务"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
@pytest.mark.parametrize("错误", ["缺配置", "缺judge", "错版本", "错哈希"])
|
||
def test_错误前置不留下可领取孤儿审校任务__227006(文学环境, 错误):
|
||
env = 文学环境
|
||
文 = env["审校正文"]
|
||
config = (
|
||
"missing" if 错误 == "缺配置" else "gen-config" if 错误 == "缺judge" else "review-config"
|
||
)
|
||
with pytest.raises(Muse错误):
|
||
发起完整审校(
|
||
env["装配"],
|
||
env["审校作者"],
|
||
"bad-start",
|
||
work_id="gen-work",
|
||
chapter_id="ch-2",
|
||
revision=2 if 错误 == "错版本" else 1,
|
||
document_hash="0" * 64 if 错误 == "错哈希" else 文["document_hash"],
|
||
配置ID=config,
|
||
)
|
||
assert env["装配"].任务运行.读取命令任务(env["审校作者"].作者, "bad-start") is None
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-229005",
|
||
environment="隔离PG与合成提供方",
|
||
given="审校真实方法消费绑定逐片调用且预算省略不消费",
|
||
when="经B06公共用例及真实声明环境执行",
|
||
then=["审校真实方法消费绑定逐片调用且预算省略不消费"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
@pytest.mark.parametrize("省略", [False, True])
|
||
def test_审校真实方法消费绑定逐片调用且预算省略不消费__229005(文学环境, 省略):
|
||
from test_上下文快照与索引写入 import 准备已确认消费方法
|
||
|
||
env = 文学环境
|
||
原理 = "用动作的停顿显示迟疑。" * (10000 if 省略 else 1)
|
||
_, version = 准备已确认消费方法(
|
||
env["装配"], env["作者"], "gen-work", 标题="审校动作依据", 原理=原理
|
||
)
|
||
tid, pieces = 发起(env)
|
||
env["剧本"].extend({"类型": "文本", "文本": 片结果(p, 有声音=False)} for p in pieces)
|
||
assert 跑(env, tid).状态 == 任务状态.已完成
|
||
usages = env["装配"].要求知识方法().列出消费(env["作者"], version)
|
||
if 省略:
|
||
assert usages == []
|
||
assert all(原理 not in json.loads(r["input"])["装配材料"] for r in env["收到"])
|
||
else:
|
||
assert len(usages) == len(pieces)
|
||
assert {r["kind"] for r in usages} == {"review"}
|
||
assert len({r["result_ref"] for r in usages}) == len(pieces)
|
||
for r in env["收到"]:
|
||
assert 原理 in json.loads(r["input"])["装配材料"]
|
||
assert env["正文"].读取正文(env["审校作者"], "ch-2") == env["审校正文"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-229006",
|
||
environment="隔离PG与合成提供方",
|
||
given="候选全文审校不先采纳且修改后旧报告过期",
|
||
when="经B06公共用例及真实声明环境执行",
|
||
then=["候选全文审校不先采纳且修改后旧报告过期"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
@pytest.mark.parametrize("空章", [False, True])
|
||
def test_候选全文审校不先采纳且修改后旧报告过期__229006(文学环境, 空章):
|
||
env = 文学环境
|
||
svc = env["正文"]
|
||
chapter = "ch-3" if 空章 else "ch-2"
|
||
base = 0 if 空章 else 1
|
||
draft = 正文草稿((段落("c1", (文本节点("林深停了下来。他望向门上的旧划痕,没有再向前走。"),)),))
|
||
receipt = svc.创建人工候选(env["作者"], "review-candidate", chapter, base, draft)
|
||
candidate = svc.读取候选(env["作者"], receipt["target_ref"])
|
||
original = svc.读取正文(env["作者"], chapter)
|
||
env["审校正文"] = candidate
|
||
# 共用批准与片驱动,目标章改为候选实际归属。
|
||
env["审校章"] = chapter
|
||
tid, pieces = 发起(env)
|
||
env["剧本"].extend({"类型": "文本", "文本": 片结果(p, 有声音=False)} for p in pieces)
|
||
assert 跑(env, tid).状态 == 任务状态.已完成
|
||
report = env["装配"].要求审校().读取文学工作面(env["作者"], env["装配"].任务运行, tid)
|
||
assert report["report"]["model_verified"]
|
||
assert report["current_status"] == "valid"
|
||
assert svc.读取正文(env["作者"], chapter) == original
|
||
svc.编辑候选(
|
||
env["作者"],
|
||
"edit-reviewed-candidate",
|
||
candidate["candidate_id"],
|
||
candidate["revision"],
|
||
candidate["candidate_hash"],
|
||
正文草稿((段落("c1", (文本节点("林深继续走了过去。"),)),)),
|
||
)
|
||
stale = env["装配"].要求审校().读取文学工作面(env["作者"], env["装配"].任务运行, tid)
|
||
assert stale["current_status"] == "SOURCE_STALE" and stale["report"] == report["report"]
|
||
|
||
|
||
@pytest.mark.case_id(
|
||
"NC-w22-229007",
|
||
environment="隔离PG与合成提供方",
|
||
given="审校回执未知重试同命令不受后来正文变化影响",
|
||
when="经B06公共用例及真实声明环境执行",
|
||
then=["审校回执未知重试同命令不受后来正文变化影响"],
|
||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
|
||
)
|
||
def test_审校回执未知重试同命令不受后来正文变化影响__229007(文学环境):
|
||
env = 文学环境
|
||
tid, _ = 发起(env)
|
||
doc = env["审校正文"]
|
||
env["正文"].保存人工(
|
||
env["作者"],
|
||
"review-new-body",
|
||
"ch-2",
|
||
1,
|
||
正文草稿((段落("r1", (文本节点("林深走开了。"),)),)),
|
||
)
|
||
result = 发起完整审校(
|
||
env["装配"],
|
||
env["审校作者"],
|
||
"review-run",
|
||
work_id="gen-work",
|
||
chapter_id="ch-2",
|
||
revision=doc["revision"],
|
||
document_hash=doc["document_hash"],
|
||
配置ID="review-config",
|
||
核心上限=12,
|
||
上下文字数=3,
|
||
)
|
||
assert result["task_id"] == tid
|
||
with pytest.raises(审校错误, match="同一审校命令"):
|
||
发起完整审校(
|
||
env["装配"],
|
||
env["审校作者"],
|
||
"review-run",
|
||
work_id="gen-work",
|
||
chapter_id="ch-2",
|
||
revision=2,
|
||
document_hash=doc["document_hash"],
|
||
配置ID="review-config",
|
||
核心上限=12,
|
||
上下文字数=3,
|
||
)
|