muse-agent-example/tests/集成/test_审校报告与案例持久化.py

1408 lines
59 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""声音从实际样张到明确确认;报告和案例沿各自公共入口验证。"""
import hashlib
import itertools
import json
from copy import deepcopy
from dataclasses import replace
import pytest
from test_两阶段写手 import 生成环境 as _生成环境夹具
from test_完整文学审校 import 发起 as 发起全文审校
from test_完整文学审校 import 文学环境 as _文学环境夹具
from test_完整文学审校 import 片结果 as 合成片结果
from test_完整文学审校 import 跑 as 跑全文审校
from muse.作品规划.接口 import 档案保存
from muse.共享.错误 import Muse错误
from muse.审校修订.接口 import (
声音定位,
声音提案请求,
声音样张引用,
审校错误,
案例复核请求,
案例定位,
案例来源,
案例规则请求,
案例采集请求,
读取内置规则,
)
from muse.正式变更.接口 import 固定哈希
from muse.正文写作.接口 import 文本节点, 正文草稿, 段落
from muse.资料研究.接口 import 导入请求
pytestmark = pytest.mark.数据库
@pytest.fixture
def 全文生成环境(内置结构测试库, tmp_path):
# 生成环境正文直接建档案结构,必须复用已导入内置结构的种子库。
yield from _生成环境夹具.__wrapped__(内置结构测试库, tmp_path)
@pytest.fixture
def 全文报告环境(全文生成环境):
return _文学环境夹具.__wrapped__(全文生成环境)
@pytest.fixture
def 声音环境(章后环境):
env = 章后环境
装配, 身份 = env["装配"], env["作者"]
作品 = 装配.要求作品()
结构 = 作品.新档案结构(身份, "voice-work", "work_core", 1)
作品.保存档案(
身份, "voice-work-create", 档案保存("voice-work", 0, {"名称": "合成声音作品"}, 结构)
)
作品.添加章节(身份, "voice-ch-create", "voice-work", "voice-ch", "样张", 预期目录版本=0)
装配.要求正文().保存人工(
身份,
"voice-text",
"voice-ch",
0,
正文草稿((段落("v1", (文本节点("甲走进门。雨落下来。他没有再说话。"),)),)),
)
文 = 装配.要求正文().读取正文(身份, "voice-ch")
请求 = 声音提案请求(
"voice-work",
0,
"style",
1,
{"句式画像": "短句叙述", "达标样张": "甲走进门。"},
(声音样张引用("document", "voice-ch", 文["revision"], 文["document_hash"], "main"),),
verbal_tics=("没有再说话",),
protected_expressions=("雨落下来。",),
)
return 装配, 身份, 请求, 文
def _提出(服务, 身份, 命令, 请求):
回执 = 服务.提出声音(身份, 命令, 请求)
r = 回执["results"][0]
loc = 声音定位(
请求.work_id, r["proposal_id"], 1, r["proposal_hash"], 请求.expected_voice_revision
)
return 回执, loc
def _确认(服务, 身份, 命令, loc):
审阅 = 服务.打开声音审阅(身份, loc)
return 服务.决定声音(身份, 命令, loc, 审阅ID=审阅["review_id"], 审阅哈希=审阅["review_hash"])
@pytest.mark.case_id(
"NC-w22-22c001",
environment="隔离PG与合成样张",
given="本书真实正文样张、声音未决提案和有效/无效审阅",
when="经真实S04/S01/B05/B03公共用例保存和确认声音",
then=["候选无当前投影;真实S01确认后才可读取声音,原文不变"],
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音候选不当确认且真实审阅后才有当前投影__22c001(声音环境):
装配, 身份, 请求, 文 = 声音环境
服务 = 装配.要求审校()
assert 服务.当前声音(身份, 请求.work_id) is None
回执, loc = _提出(服务, 身份, "voice-proposal", 请求)
assert 回执 == 服务.提出声音(身份, "voice-proposal", 请求)
assert 服务.当前声音(身份, 请求.work_id) is None
for 假审阅 in ("missing", "", "00000000-0000-4000-8000-000000000000"):
with pytest.raises(Muse错误):
服务.决定声音(身份, "bad-review", loc, 审阅ID=假审阅, 审阅哈希="0" * 64)
assert 服务.当前声音(身份, 请求.work_id) is None
结果 = _确认(服务, 身份, "voice-confirm", loc)
assert 结果["action"] == "adopt" and 结果["results"][0]["revision"] == 1
声音 = 服务.当前声音(身份, 请求.work_id, 字段用途="aiContext")
assert 声音["narrator"] == 请求.narrator
assert 声音["protected_expressions"] == ["雨落下来。"]
assert 声音["revision"] == 1
assert 装配.要求正文().读取正文(身份, "voice-ch") == 文
@pytest.mark.case_id(
"NC-w22-22c002",
environment="隔离PG与合成样张",
given="无依据口癖/保护/样句、错哈希、第二人物声音或缺叙述者",
when="经真实S04/S01/B05/B03公共用例保存和确认声音",
then=["拒绝不完整/未grounding提案,不生成声音版本"],
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
@pytest.mark.parametrize("坏处", ["口癖", "保护", "样句", "哈希", "人物资产", "叙述者"])
def test_声音缺依据或重复人物归属不能创建提案__22c002(声音环境, 坏处):
from pydantic import ValidationError
装配, 身份, 请求, _ = 声音环境
with pytest.raises((Muse错误, ValidationError)):
if 坏处 == "口癖":
请求 = replace(请求, verbal_tics=("样张中不存在",))
elif 坏处 == "保护":
请求 = replace(请求, protected_expressions=("虚构保护句",))
elif 坏处 == "样句":
请求 = replace(请求, narrator={"达标样张": "虚构的达标样张"})
elif 坏处 == "哈希":
请求 = replace(请求, samples=(replace(请求.samples[0], source_hash="0" * 64),))
elif 坏处 == "人物资产":
声音提案请求(
work_id="voice-work",
expected_voice_revision=0,
schema_id="style",
base_version=1,
narrator={"句式画像": "短句"},
samples=请求.samples,
characters={"甲": "再造角色声"},
)
else:
请求 = replace(请求, narrator={})
装配.要求审校().提出声音(身份, "bad-voice", 请求)
assert 装配.要求审校().当前声音(身份, "voice-work") is None
@pytest.mark.case_id(
"NC-w22-22c003",
environment="隔离PG与合成样张",
given="同一声音基线上的两份已审提案",
when="经真实S04/S01/B05/B03公共用例保存和确认声音",
then=["另一声音已确认后拒绝旧提案,不覆盖新当前版本"],
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音确认不能覆盖已推进的当前版本__22c003(声音环境):
装配, 身份, 请求, _ = 声音环境
服务 = 装配.要求审校()
_, 一 = _提出(服务, 身份, "voice-a", 请求)
_, 二 = _提出(服务, 身份, "voice-b", replace(请求, narrator={"句式画像": "长短交替"}))
旧审阅 = 服务.打开声音审阅(身份, 二)
_确认(服务, 身份, "adopt-a", 一)
with pytest.raises(Muse错误, match="基线已变化"):
服务.决定声音(
身份, "adopt-b", 二, 审阅ID=旧审阅["review_id"], 审阅哈希=旧审阅["review_hash"]
)
assert 服务.当前声音(身份, "voice-work")["narrator"] == 请求.narrator
@pytest.mark.case_id(
"NC-w22-22c004",
environment="隔离PG与合成样张",
given="作者显式选入的B03资料样张,analysis许可存在/缺失",
when="经真实S04/S01/B05/B03公共用例保存和确认声音",
then=["核真实用途与源版本,未许可不出当前声音;合法来源可经明确确认"],
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
@pytest.mark.parametrize("授权", [True, False])
def test_显式资料样张核来源用途再进入声音__22c004(声音环境, 授权):
装配, 身份, 请求, _ = 声音环境
with 装配.要求数据库().连接() as 连, 连.transaction():
源 = 装配.要求资料().导入(
连,
身份.作者,
导入请求(
"note",
"作者选择的样张",
"synthetic://voice-source",
"甲走进门。雨落下来。他没有再说话。",
("analysis",) if 授权 else (),
),
)
请求 = replace(
请求, samples=(声音样张引用("source", 源.source_id, 源.revision, 源.content_hash),)
)
if not 授权:
with pytest.raises(Muse错误):
装配.要求审校().提出声音(身份, "unlicensed-voice", 请求)
else:
_, loc = _提出(装配.要求审校(), 身份, "source-voice", 请求)
_确认(装配.要求审校(), 身份, "source-voice-adopt", loc)
assert 装配.要求审校().当前声音(身份, "voice-work")["revision"] == 1
@pytest.mark.case_id(
"TC-a724239fa69b",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音样张引用另一作品正文时基线强制拒绝__a72423(声音环境):
装配, 身份, 请求, 文 = 声音环境
作品 = 装配.要求作品()
结构 = 作品.新档案结构(身份, "voice-work-b", "work_core", 1)
作品.保存档案(
身份, "voice-work-b-create", 档案保存("voice-work-b", 0, {"名称": "另一作品"}, 结构)
)
作品.添加章节(
身份, "voice-ch-b-create", "voice-work-b", "voice-ch-b", "别气样张", 预期目录版本=0
)
装配.要求正文().保存人工(
身份,
"voice-text-b",
"voice-ch-b",
0,
正文草稿((段落("vb-p1", (文本节点("另一作品的正文。"),)),)),
)
文B = 装配.要求正文().读取正文(身份, "voice-ch-b")
错配 = replace(
请求,
samples=(
声音样张引用("document", "voice-ch-b", 文B["revision"], 文B["document_hash"], "main"),
),
)
with pytest.raises(审校错误, match="样张的作品或正文哈希不符"):
装配.要求审校().提出声音(身份, "voice-mismatch-baseline", 错配)
@pytest.mark.case_id(
"TC-21ed6b89ab91",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音预防输入作品不一致同样拒绝__21ed6b(声音环境):
装配, 身份, 请求, _ = 声音环境
作品 = 装配.要求作品()
结构 = 作品.新档案结构(身份, "voice-work-c", "work_core", 1)
作品.保存档案(
身份, "voice-work-c-create", 档案保存("voice-work-c", 0, {"名称": "第三作品"}, 结构)
)
作品.添加章节(
身份, "voice-ch-c-create", "voice-work-c", "voice-ch-c", "预防样张", 预期目录版本=0
)
装配.要求正文().保存人工(
身份,
"voice-text-c",
"voice-ch-c",
0,
正文草稿((段落("vc-p1", (文本节点("预防输入正文。"),)),)),
)
文C = 装配.要求正文().读取正文(身份, "voice-ch-c")
错配 = replace(
请求,
verbal_tics=("预防输入正文",),
samples=(
声音样张引用("document", "voice-ch-c", 文C["revision"], 文C["document_hash"], "main"),
),
)
with pytest.raises(审校错误, match="作品或正文哈希"):
装配.要求审校().提出声音(身份, "voice-mismatch-prevention", 错配)
@pytest.mark.case_id(
"TC-ab52c9241b8e",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音提案口癖必须来自真实样张__ab52c9(声音环境):
装配, 身份, 请求, _ = 声音环境
with pytest.raises(Muse错误):
装配.要求审校().提出声音(
身份, "voice-tic-mismatch", replace(请求, verbal_tics=("样张中不存在",))
)
@pytest.mark.case_id(
"TC-30b54339e6dd",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音保护表达必须来自真实样张__30b543(声音环境):
装配, 身份, 请求, _ = 声音环境
with pytest.raises(Muse错误):
装配.要求审校().提出声音(
身份, "voice-protect-mismatch", replace(请求, protected_expressions=("虚构保护句",))
)
@pytest.mark.case_id(
"TC-3d99ae0ba955",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音决定需要真实审阅回执__3d99ae(声音环境):
装配, 身份, 请求, _ = 声音环境
服务 = 装配.要求审校()
回执, loc = _提出(服务, 身份, "voice-real-review", 请求)
for 假审阅 in ("missing", "", "00000000-0000-4000-8000-000000000000"):
with pytest.raises(Muse错误):
服务.决定声音(身份, "voice-fake-review", loc, 审阅ID=假审阅, 审阅哈希="0" * 64)
assert 服务.当前声音(身份, 请求.work_id) is None
@pytest.mark.case_id(
"TC-8b9b9e08b9ac",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音提案来源哈希漂移在提出时拒绝__8b9b9e(声音环境):
装配, 身份, 请求, _ = 声音环境
with pytest.raises(Muse错误):
装配.要求审校().提出声音(
身份,
"voice-hash-drift",
replace(请求, samples=(replace(请求.samples[0], source_hash="0" * 64),)),
)
assert 装配.要求审校().当前声音(身份, "voice-work") is None
@pytest.mark.case_id(
"TC-3abe1c197cb2",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_声音候选不进入当前投影__3abe1c(声音环境):
装配, 身份, 请求, _ = 声音环境
服务 = 装配.要求审校()
assert 服务.当前声音(身份, 请求.work_id) is None
_提出(服务, 身份, "voice-no-projection", 请求)
assert 服务.当前声音(身份, 请求.work_id) is None
@pytest.mark.case_id(
"TC-ab359a56afb0",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则候选保持候选且例证跨作品可回查__ab359a(四类案例):
装配, 身份, 建立 = 四类案例
服务 = 装配.要求审校()
引用 = 建立()
结果 = 服务.提出案例规则(身份, 案例规则请求(_规则定义("candidate-ab359a"), 引用))
assert 结果["status"] == "candidate"
库 = 服务.读取规则库(身份, 预览候选=True)
规则 = 库["rules"]["candidate-ab359a"]
assert 规则["case_card_ids"] == sorted(c.case_id for c in 引用)
# 四类案例夹具按 work-a/work-b 交替建立;例证作品分组可从已确认案例回查。
作品集 = {
服务.读取案例(身份, c.case_id)["payload"]["evidence"]["before"]["work_group"] for c in 引用
}
assert 作品集 == {"work-a", "work-b"}
@pytest.fixture
def 案例环境(章后环境):
装配, 身份 = 章后环境["装配"], 章后环境["作者"]
def 请求(
*,
文本="值得注意的是,门外下雨了。",
来源="synthetic:card-backfill-l002-001",
用途=("sample",),
作品="work-a",
起点=0,
终点=None,
保全=True,
片段哈希=None,
来源哈希=None,
):
with 装配.要求数据库().连接() as 连, 连.transaction():
源 = 装配.要求资料().导入(
连,
身份.作者,
导入请求("reference", "合成案例来源", 来源, 文本, 用途, 作品),
)
end = len(文本) if 终点 is None else 终点
定位 = 案例来源(
"source",
源.source_id,
源.revision,
来源哈希 or 源.content_hash,
起点,
end,
片段哈希 or hashlib.sha256(文本[起点:end].encode()).hexdigest(),
)
return 案例采集请求(定位, preserve_text=保全), 源, 文本
return 装配, 身份, 请求
def _采集(装配, 身份, 命令, 请求):
结果 = 装配.要求审校().采集案例(身份, 命令, 请求)["results"][0]
return 案例定位(结果["case_id"], 结果["revision"], 结果["case_hash"])
def _复核(定位, 标签="sf"):
return 案例复核请求(
定位,
标签,
"retain" if 标签 == "snf" else "repair",
"作者确认:此处无功能" if 标签 == "sf" else "作者确认案例边界。",
)
def _确认案例(装配, 身份, 定位, 命令="confirm-case", 标签="sf"):
服务 = 装配.要求审校()
请求 = _复核(定位, 标签)
审阅 = 服务.打开案例审阅(身份, 请求)
return 服务.决定案例(
身份,
命令,
请求,
审阅ID=审阅["review_id"],
审阅哈希=审阅["review_hash"],
)
def _规则定义(rule_id="candidate-l999", version=1):
return {
"id": rule_id,
"name": "待评估元话语",
"layer": "lexical",
"carrier_scope": "narration",
"trigger": {"type": "model_judgment", "criteria": "上下文功能判断"},
"default_disposition": "candidate",
"function_check": ["是否承担叙事功能"],
"fix_hint": "先仲裁再决定",
"version": version,
}
@pytest.fixture
def 四类案例(案例环境):
装配, 身份, 生成 = 案例环境
轮次 = itertools.count()
def 建立(*, 标签=("sf", "snf", "boundary", "regression"), 作品=("work-a", "work-b")):
# 采集与确认命令按调用轮次唯一:同命令重放返回历史回执,其定位在确认后已过期。
轮 = next(轮次)
结果 = []
for i, kind in enumerate(标签):
文本 = f"第{轮}轮第{i}份例证。前半句作为证据,后半句保留语境。"
请求, _, _ = 生成(
文本=文本,
来源=f"synthetic:rule-source:{轮}-{i}",
作品=作品[i % len(作品)],
终点=7,
)
定位 = _采集(装配, 身份, f"sample-case-{轮}-{i}", 请求)
_确认案例(装配, 身份, 定位, f"confirm-{轮}-{i}", kind)
已存 = 装配.要求审校().读取案例(身份, 定位.case_id)
结果.append(案例定位(定位.case_id, 已存["revision"], 已存["case_hash"]))
return tuple(结果)
return 装配, 身份, 建立
@pytest.mark.case_id(
"TC-31eae78cbd8c",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_发布案例改用公开请求合同读回__31eae7(案例环境):
from pydantic import TypeAdapter
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成()
定位 = _采集(装配, 身份, "card-backfill-l002-001", 请求)
案例 = 装配.要求审校().读取案例(身份, 定位.case_id)
重验 = TypeAdapter(案例采集请求).validate_python(案例["payload"]["request"])
assert 重验 == 请求 and 案例["case_hash"] == 定位.case_hash
@pytest.mark.case_id(
"TC-9b9153c89f65",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_回填先为待复核且明确确认后才可作样例__9b9153(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成()
定位 = _采集(装配, 身份, "backfill-shadow", 请求)
服务 = 装配.要求审校()
assert 服务.读取案例(身份, 定位.case_id)["state"] == "shadow"
assert not 服务.读取案例(身份, 定位.case_id)["sampleable"]
_确认案例(装配, 身份, 定位)
已确认 = 服务.读取案例(身份, 定位.case_id)
assert 已确认["state"] == "confirmed" and 已确认["sampleable"]
assert 已确认["payload"]["label"] == "sf"
@pytest.mark.case_id(
"TC-07715d77085a",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_未授权来源仅留哈希且不能确认__07715d(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, 文本 = 生成(用途=("analysis",))
定位 = _采集(装配, 身份, "unlicensed-card", 请求)
服务 = 装配.要求审校()
案例 = 服务.读取案例(身份, 定位.case_id)
证据 = 案例["payload"]["evidence"]["before"]
assert 证据["excerpt"] is None and 证据["context"] is None
assert 证据["excerpt_hash"] == hashlib.sha256(文本.encode()).hexdigest()
with pytest.raises(Muse错误, match="哈希元信息"):
服务.打开案例审阅(身份, _复核(定位))
@pytest.mark.case_id(
"TC-3bb0818a8c8d",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_只有哈希的案例请求拒绝偷注原文__3bb081(案例环境):
from dataclasses import asdict
from pydantic import TypeAdapter, ValidationError
_, _, 生成 = 案例环境
请求, _, _ = 生成(用途=("analysis",))
with pytest.raises(ValidationError):
TypeAdapter(案例采集请求).validate_python(
{**asdict(请求), "excerpt": "偷偷保存的第三方原文"}
)
@pytest.mark.case_id(
"TC-876da9487f56",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则始终是候选且缺反证不能建立__876da9(四类案例):
装配, 身份, 建立 = 四类案例
服务 = 装配.要求审校()
引用 = 建立()
结果 = 服务.提出案例规则(身份, 案例规则请求(_规则定义(), 引用))
assert 结果["status"] == "candidate"
assert 服务.读取规则库(身份)["rules"] == {}
单边 = 建立(标签=("sf", "sf", "sf", "sf"))
with pytest.raises(Muse错误, match="四类"):
服务.提出案例规则(身份, 案例规则请求(_规则定义("candidate-l998"), 单边))
@pytest.mark.case_id(
"TC-aff5d8c29022",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_实时反馈必须同时绑定前后版本和决定回执__aff5d8(案例环境):
from pydantic import ValidationError
_, _, 生成 = 案例环境
请求, _, _ = 生成()
with pytest.raises((Muse错误, ValidationError)):
案例采集请求(请求.before, mode="live_feedback")
@pytest.mark.case_id(
"TC-8246112bdea3",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_内置发布输入导入后与候选读回指纹一致__824611(章后环境):
装配, 身份 = 章后环境["装配"], 章后环境["作者"]
内置 = 读取内置规则()
装配.要求审校().导入规则候选(身份)
已存 = 装配.要求审校().读取规则库(身份, 预览候选=True)
# 正式合同:导入按规则不可变版本持久化其引用的例证;发布输入中未被任何规则引用
# (反向引用为空)的事故锚点留在安装包发布输入内,不进入规则版本,也不改变指纹。
引用 = {ref for r in 内置["rules"].values() for refs in r["samples"].values() for ref in refs}
未引用 = {ref for ref, s in 内置["samples"].items() if not s["rules"]}
assert 未引用 == set(内置["samples"]) - set(已存["samples"])
assert 已存["rules"] == 内置["rules"]
assert 已存["samples"] == {ref: 内置["samples"][ref] for ref in 引用}
assert 已存["fingerprint"] == 内置["fingerprint"]
@pytest.mark.case_id(
"TC-179b3c21692c",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则库失败关闭且不回退内置种子__179b3c(章后环境, monkeypatch):
from psycopg import OperationalError
服务 = 章后环境["装配"].要求审校()
def 断库(*args, **kwargs):
raise OperationalError("connection refused")
monkeypatch.setattr(type(服务.数据库), "连接", 断库)
with pytest.raises(OperationalError, match="connection refused"):
服务.读取规则库(章后环境["作者"], 预览候选=True)
@pytest.mark.case_id(
"TC-e88dcd446de0",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则缺少正例时在入库前失败关闭__e88dcd(章后环境):
库 = deepcopy(读取内置规则())
库["rules"]["l001"]["samples"]["sf"] = []
with pytest.raises(Muse错误, match="四类例证"):
章后环境["装配"].要求审校().导入规则候选(章后环境["作者"], 规则库=库)
@pytest.mark.case_id(
"TC-ac1163ea3657",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则与样例读回还原不可变载荷哈希__ac1163(章后环境):
装配, 身份 = 章后环境["装配"], 章后环境["作者"]
服务 = 装配.要求审校()
服务.导入规则候选(身份)
库 = 服务.读取规则库(身份, 预览候选=True)
for rule_id, rule in 库["rules"].items():
版本 = 服务.读取规则版本(身份, rule_id, rule["version"])
assert 版本["payload"]["rule"] == rule
assert 版本["payload_hash"] == 固定哈希(版本["payload"])
assert set(版本["payload"]["samples"]) == {
ref for refs in rule["samples"].values() for ref in refs
}
@pytest.mark.case_id(
"TC-64a9f29d1b89",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_真实隔离库候选读回不需要旧烟雾开关__64a9f2(章后环境):
服务, 身份 = 章后环境["装配"].要求审校(), 章后环境["作者"]
导入 = 服务.导入规则候选(身份)
读回 = 服务.读取规则库(身份, 预览候选=True)
assert 导入["persisted"] and len(读回["rules"]) == len(导入["results"])
assert all(rule["status"] == "candidate" for rule in 读回["rules"].values())
@pytest.mark.case_id(
"TC-a0fd532aef44",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_首次发布输入为每条规则建立独立版本__a0fd53(章后环境):
服务, 身份 = 章后环境["装配"].要求审校(), 章后环境["作者"]
结果 = 服务.导入规则候选(身份)
assert 结果["persisted"] and all(not r["duplicate"] for r in 结果["results"])
assert len({r["version_id"] for r in 结果["results"]}) == len(读取内置规则()["rules"])
@pytest.mark.case_id(
"TC-5ddea6a3d165",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_同内容第二次导入幂等且不增加版本__5ddea6(章后环境):
服务, 身份 = 章后环境["装配"].要求审校(), 章后环境["作者"]
首次 = 服务.导入规则候选(身份)
再次 = 服务.导入规则候选(身份)
assert all(r["duplicate"] for r in 再次["results"])
assert [r["version_id"] for r in 再次["results"]] == [r["version_id"] for r in 首次["results"]]
@pytest.mark.case_id(
"TC-ab996bb220ec",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_变更规则只能追加递增候选版本__ab996b(章后环境):
服务, 身份 = 章后环境["装配"].要求审校(), 章后环境["作者"]
服务.导入规则候选(身份)
库 = deepcopy(读取内置规则())
库["rules"]["l001"]["fix_hint"] = "更新后的修复提示"
with pytest.raises(Muse错误, match="递增版本"):
服务.导入规则候选(身份, 规则库=库)
库["rules"]["l001"]["version"] = 2
服务.导入规则候选(身份, 规则库=库)
assert 服务.读取规则版本(身份, "l001", 1)["version"] == 1
assert 服务.读取规则版本(身份, "l001", 2)["payload"]["rule"]["fix_hint"] == "更新后的修复提示"
@pytest.mark.case_id(
"TC-bc74312a5f4f",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_作者额外候选不被种子强制覆盖__bc7431(四类案例):
装配, 身份, 建立 = 四类案例
服务 = 装配.要求审校()
服务.提出案例规则(身份, 案例规则请求(_规则定义("z999"), 建立()))
服务.导入规则候选(身份)
读回 = 服务.读取规则库(身份, 预览候选=True)
assert "z999" in 读回["rules"] and 读回["rules"]["z999"]["status"] == "candidate"
@pytest.mark.case_id(
"TC-59c4d824b968",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则预览只返回计划且不触碰数据库__59c4d8(章后环境):
服务, 身份 = 章后环境["装配"].要求审校(), 章后环境["作者"]
计划 = 服务.导入规则候选(身份, 预览=True)
assert 计划 == {
"persisted": False,
"fingerprint": 读取内置规则()["fingerprint"],
"count": len(读取内置规则()["rules"]),
}
assert 服务.读取规则库(身份, 预览候选=True)["rules"] == {}
def _实时反馈请求(案例环境):
装配, 身份, _ = 案例环境
正文 = 装配.要求正文()
原文, 候选 = "候选正文", "候选正文。"
正文.保存人工(
身份,
"feedback-before",
"ch-1",
0,
正文草稿((段落("feedback-p1", (文本节点(原文),)),)),
)
前 = 正文.读取正文(身份, "ch-1")
回执 = 正文.保存人工(
身份,
"run-7",
"ch-1",
1,
正文草稿((段落("feedback-p1", (文本节点(候选),)),)),
)
后 = 正文.读取正文(身份, "ch-1")
before = 案例来源(
"document",
"ch-1",
1,
前["document_hash"],
0,
len(原文),
hashlib.sha256(原文.encode()).hexdigest(),
"main",
)
after = 案例来源(
"document",
"ch-1",
2,
后["document_hash"],
0,
len(候选),
hashlib.sha256(候选.encode()).hexdigest(),
"main",
)
return (
装配,
身份,
案例采集请求(
before,
after,
mode="live_feedback",
preserve_text=True,
feedback_command_id=回执["command_id"],
),
回执,
)
def _配置审校CLI(装配, 身份, tmp_path):
from muse.配置 import 服务配置
口令 = tmp_path / "cli-password"
口令.write_text("synthetic-cli-password")
口令.chmod(0o600)
装配.配置 = replace(
装配.配置,
HTTP=服务配置(
str(口令),
作者ID=身份.作者,
公开地址="http://testserver",
允许来源=("http://testserver",),
),
)
@pytest.mark.case_id(
"TC-b3ddcb88c6f9",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_来源哈希必须由所登记的原文字节决定__b3ddcb(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成(文本="值得注意的是。", 来源哈希="0" * 64)
with pytest.raises(Muse错误, match="来源哈希"):
装配.要求审校().采集案例(身份, "wrong-source-hash", 请求)
assert 装配.要求审校().列出案例(身份) == []
@pytest.mark.case_id(
"TC-7720243828bc",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_多来源清单按稳定身份排序且研究授权只显示哈希__772024(案例环境):
装配, 身份, 生成 = 案例环境
for i, 文本 in enumerate(("值得注意的是。", "嘴角微微上扬。")):
请求, _, _ = 生成(文本=文本, 来源=f"synthetic:inventory:{i}", 用途=("analysis",))
_采集(装配, 身份, f"inventory-{1 - i}", 请求)
清单 = 装配.要求审校().列出案例(身份)
assert len(清单) == len({c["case_id"] for c in 清单}) == 2
assert [c["case_id"] for c in 清单] == sorted(c["case_id"] for c in 清单)
assert all(c["state"] == "shadow" for c in 清单)
assert all(c["payload"]["evidence"]["before"]["excerpt"] is None for c in 清单)
@pytest.mark.case_id(
"TC-31b308b1132c",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_研究授权回填可以从同一数据库身份重放__31b308(案例环境):
装配, 身份, 生成 = 案例环境
文本 = "第一行。值得注意的是,门外下雨了。\n"
起点 = 文本.index("值得")
请求, _, _ = 生成(文本=文本, 用途=("analysis",), 起点=起点, 终点=len(文本) - 1)
定位 = _采集(装配, 身份, "research-replay", 请求)
首次 = 装配.要求审校().读取案例(身份, 定位.case_id)
再次 = 装配.要求审校().读取案例(身份, 定位.case_id)
assert 首次 == 再次 and 首次["state"] == "shadow"
assert 首次["payload"]["request"]["before"]["start"] == 起点
assert 首次["payload"]["evidence"]["before"]["excerpt"] is None
@pytest.mark.case_id(
"TC-5f50720bdba1",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_可作样例来源保留确切片段上下文与位置__5f5072(案例环境):
装配, 身份, 生成 = 案例环境
文本 = "她抬头。嘴角微微上扬。\n"
起点, 终点 = 文本.index("嘴角"), 文本.index("。\n") + 1
请求, _, _ = 生成(文本=文本, 来源="synthetic:owned", 起点=起点, 终点=终点)
定位 = _采集(装配, 身份, "owned-backfill", 请求)
证据 = 装配.要求审校().读取案例(身份, 定位.case_id)["payload"]["evidence"]["before"]
assert 证据["excerpt"] == "嘴角微微上扬。"
assert "她抬头" in 证据["context"]
assert 请求.before.start == 起点 and 请求.before.end == 终点
@pytest.mark.case_id(
"TC-81d43ffe316c",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_同一采集命令重放去重且只留一个案例__81d43f(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成(文本="值得注意的是。", 来源="synthetic:same")
首次 = _采集(装配, 身份, "same-pattern", 请求)
重放 = _采集(装配, 身份, "same-pattern", 请求)
assert 重放 == 首次 and len(装配.要求审校().列出案例(身份)) == 1
@pytest.mark.case_id(
"TC-1062c87c6b5e",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_反馈案例绑定实际创作决定回执__1062c8(案例环境):
装配, 身份, 请求, 回执 = _实时反馈请求(案例环境)
定位 = _采集(装配, 身份, "feedback-case", 请求)
案例 = 装配.要求审校().读取案例(身份, 定位.case_id)
assert 案例["payload"]["request"]["mode"] == "live_feedback"
assert 案例["payload"]["feedback"] == {
key: 回执[key] for key in ("receipt_id", "command_id", "action")
}
assert 案例["state"] == "shadow"
@pytest.mark.case_id(
"TC-2a5b8f6ffec2",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_未授权案例数据库也不存原文且不能确认__2a5b8f(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, 文本 = 生成(文本="值得注意的是。", 来源="synthetic:third-party", 用途=("analysis",))
定位 = _采集(装配, 身份, "unlicensed-persist", 请求)
with pytest.raises(Muse错误, match="哈希元信息"):
_确认案例(装配, 身份, 定位)
with 装配.要求数据库().连接(只读=True) as 连:
载荷 = 连.execute(
"SELECT payload::text FROM muse_quality_case_version WHERE case_id=%s", (定位.case_id,)
).fetchone()[0]
assert 文本 not in 载荷
@pytest.mark.case_id(
"TC-055c73ace6f4",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_未变来源每次读回都重验为当前可用__055c73(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成()
定位 = _采集(装配, 身份, "verified-source", 请求)
_确认案例(装配, 身份, 定位)
读回 = 装配.要求审校().读取案例(身份, 定位.case_id)
assert 读回["sampleable"] and 读回["unavailable_sources"] == []
assert 读回 == 装配.要求审校().读取案例(身份, 定位.case_id)
@pytest.mark.case_id(
"TC-7464250e5d21",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_来源推进不改写案例但使旧审阅失效__746425(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, 文本 = 生成(来源="synthetic:changed-source")
定位 = _采集(装配, 身份, "source-before-change", 请求)
服务 = 装配.要求审校()
复核 = _复核(定位)
审阅 = 服务.打开案例审阅(身份, 复核)
with 装配.要求数据库().连接() as 连, 连.transaction():
装配.要求资料().导入(
连,
身份.作者,
导入请求(
"reference",
"合成案例来源",
"synthetic:changed-source",
文本 + "已经改成另一版正文。",
("sample",),
"work-a",
),
)
with pytest.raises(Muse错误):
服务.决定案例(
身份,
"stale-source-review",
复核,
审阅ID=审阅["review_id"],
审阅哈希=审阅["review_hash"],
)
assert 服务.读取案例(身份, 定位.case_id)["state"] == "shadow"
@pytest.mark.case_id(
"TC-c83b1ea9504a",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_篡改片段锚点在案例入库前拒绝__c83b1e(案例环境):
装配, 身份, 生成 = 案例环境
文本 = "值得注意的是。"
请求, _, _ = 生成(文本=文本, 起点=1, 片段哈希=hashlib.sha256(文本.encode()).hexdigest())
with pytest.raises(Muse错误, match="片段或来源哈希"):
装配.要求审校().采集案例(身份, "tampered-anchor", 请求)
assert 装配.要求审校().列出案例(身份) == []
@pytest.mark.case_id(
"TC-7774c6a20238",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_来源授权不可用时读回失败关闭__7774c6(案例环境):
装配, 身份, 生成 = 案例环境
请求, 来源, _ = 生成(来源="synthetic:unavailable")
定位 = _采集(装配, 身份, "unavailable-source", 请求)
with 装配.要求数据库().连接() as 连, 连.transaction():
连.execute(
"UPDATE muse_source SET authorized_uses=%s::jsonb WHERE source_id=%s",
('["analysis"]', 来源.source_id),
)
读回 = 装配.要求审校().读取案例(身份, 定位.case_id)
assert 读回["unavailable_sources"] == ["before"] and not 读回["sampleable"]
assert 读回["payload"]["evidence"]["before"]["excerpt"] is None
@pytest.mark.case_id(
"TC-1c36821e1438",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_案例清单对同来源多案例各只读回一次__1c3682(案例环境):
装配, 身份, 生成 = 案例环境
文本 = "值得注意的是。嘴角微微上扬。"
请求一, 来源, _ = 生成(文本=文本, 来源="synthetic:shared-source", 终点=9)
请求二 = 案例采集请求(
replace(
请求一.before,
start=9,
end=len(文本),
excerpt_hash=hashlib.sha256(文本[9:].encode()).hexdigest(),
),
preserve_text=True,
)
for i, 请求 in enumerate((请求一, 请求二)):
定位 = _采集(装配, 身份, f"shared-source-{i}", 请求)
_确认案例(装配, 身份, 定位, f"confirm-shared-{i}")
清单 = 装配.要求审校().列出案例(身份)
assert len(清单) == 2 and all(c["sampleable"] for c in 清单)
assert {c["payload"]["request"]["before"]["ref_id"] for c in 清单} == {来源.source_id}
@pytest.mark.case_id(
"TC-5a6e2469583a",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_采集回执可以直接固定下一步审阅定位__5a6e24(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成(文本="值得注意的是。", 来源="synthetic:owned-wrapper")
定位 = _采集(装配, 身份, "receipt-wrapper", 请求)
# S01 审阅回执结构:候选身份在快照内以候选ID/候选版本/候选哈希固定。
审阅 = 装配.要求审校().打开案例审阅(身份, _复核(定位))
快照 = 审阅["snapshot"]
assert 快照["候选ID"] == 定位.case_id
assert 快照["候选版本"] == 定位.revision
assert 快照["候选哈希"] == 定位.case_hash
@pytest.mark.case_id(
"TC-98a3969a2ce3",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_案例确认要求真实审阅回执__98a396(案例环境):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成()
定位 = _采集(装配, 身份, "verified-review", 请求)
with pytest.raises(Muse错误):
装配.要求审校().决定案例(
身份,
"fake-case-review",
_复核(定位),
审阅ID="00000000-0000-4000-8000-000000000000",
审阅哈希="0" * 64,
)
_确认案例(装配, 身份, 定位, "verified-confirm")
assert 装配.要求审校().读取案例(身份, 定位.case_id)["state"] == "confirmed"
@pytest.mark.case_id(
"TC-f21ddb209c73",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_未知载体字段不能混入案例合同__f21ddb(案例环境):
from dataclasses import asdict
from pydantic import TypeAdapter, ValidationError
_, _, 生成 = 案例环境
请求, _, _ = 生成()
with pytest.raises(ValidationError):
TypeAdapter(案例采集请求).validate_python({**asdict(请求), "carrier": "unknown"})
@pytest.mark.case_id(
"TC-41e8763c05b4",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_案例只在人工复核后投影成规则样例__41e876(四类案例):
装配, 身份, 建立 = 四类案例
引用 = 建立()
服务 = 装配.要求审校()
服务.提出案例规则(身份, 案例规则请求(_规则定义(), 引用))
库 = 服务.读取规则库(身份, 预览候选=True)
for sample in 库["samples"].values():
assert sample["case_card_id"] in {c.case_id for c in 引用}
assert sample["source"] == "licensed" and sample["carrier"] == "mixed"
@pytest.mark.case_id(
"TC-b1ebdb636f6f",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_CLI采集后自动进入数据库案例清单__b1ebdb(案例环境, tmp_path):
from muse.接入.cli.审校命令 import 运行审校命令
装配, 身份, 生成 = 案例环境
_配置审校CLI(装配, 身份, tmp_path)
请求, _, _ = 生成(来源="synthetic:cli-inventory")
路径 = tmp_path / "capture.json"
from dataclasses import asdict
路径.write_text(
json.dumps({"command_id": "cli-capture", "request": asdict(请求)}, ensure_ascii=False)
)
回执 = 运行审校命令(装配, "案例采集", str(路径))
清单 = 运行审校命令(装配, "案例清单", None)
assert [c["case_id"] for c in 清单] == [回执["results"][0]["case_id"]]
@pytest.mark.case_id(
"TC-95a9024e5a4c",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_CLI拒绝从文件扫描原文暗中生成案例__95a902(案例环境, tmp_path):
from muse.共享.错误 import 配置错误
from muse.接入.cli.审校命令 import 运行审校命令
装配, 身份, _ = 案例环境
_配置审校CLI(装配, 身份, tmp_path)
路径 = tmp_path / "work.txt"
路径.write_text("值得注意的是。")
with pytest.raises(配置错误, match="未知审校动作"):
运行审校命令(装配, "scan", str(路径))
assert 装配.要求审校().列出案例(身份) == []
@pytest.mark.case_id(
"TC-5bb941f5815a",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_CLI实时反馈默认持久化真实决定回执__5bb941(案例环境, tmp_path):
from dataclasses import asdict
from muse.接入.cli.审校命令 import 运行审校命令
装配, 身份, 请求, 回执 = _实时反馈请求(案例环境)
_配置审校CLI(装配, 身份, tmp_path)
路径 = tmp_path / "feedback.yaml"
路径.write_text(
json.dumps({"command_id": "cli-feedback", "request": asdict(请求)}, ensure_ascii=False)
)
定位 = 运行审校命令(装配, "案例采集", str(路径))["results"][0]
读回 = 运行审校命令(装配, "案例", 定位["case_id"])
assert 读回["payload"]["feedback"]["command_id"] == 回执["command_id"] == "run-7"
@pytest.mark.case_id(
"TC-4b6ea1536967",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_案例重验直接读数据库不生成仓外边车文件__4b6ea1(案例环境, tmp_path):
装配, 身份, 生成 = 案例环境
请求, _, _ = 生成(来源="synthetic:no-sidecar")
定位 = _采集(装配, 身份, "no-sidecar", 请求)
装配.要求审校().读取案例(身份, 定位.case_id)
assert not list(tmp_path.glob("*.inventory.json"))
@pytest.mark.case_id(
"TC-fc782c51428f",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_案例CLI不暴露确认投影或启用旧命令__fc782c(案例环境, tmp_path):
from muse.共享.错误 import 配置错误
from muse.接入.cli.审校命令 import 运行审校命令
装配, 身份, _ = 案例环境
_配置审校CLI(装配, 身份, tmp_path)
目标 = tmp_path / "request.json"
目标.write_text("{}")
for 旧命令 in ("confirm", "project", "activate"):
with pytest.raises(配置错误, match="未知审校动作"):
运行审校命令(装配, 旧命令, str(目标))
@pytest.fixture
def 诊断环境(章后环境):
装配, 身份 = 章后环境["装配"], 章后环境["作者"]
文本 = "值得注意的是,窗外正在下雨。"
装配.要求正文().保存人工(
身份,
"diagnosis-source",
"ch-2",
0,
正文草稿((段落("diagnosis-p1", (文本节点(文本),)),)),
)
正文 = 装配.要求正文().读取正文(身份, "ch-2")
目标 = {
"work_id": "post-work",
"chapter_id": "ch-2",
"branch_id": "main",
"revision": 正文["revision"],
"document_hash": 正文["document_hash"],
}
return 装配, 身份, 目标
@pytest.mark.case_id(
"TC-71e761cc843b",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_诊断正文哈希不匹配时在报告入库前拒绝__71e761(诊断环境):
装配, 身份, 目标 = 诊断环境
with pytest.raises(审校错误, match="哈希不符"):
装配.要求审校().生成诊断(
身份,
{
**目标,
"document_hash": "0" * 64,
},
)
with 装配.要求数据库().连接(只读=True) as 连:
assert 连.execute("SELECT count(*) FROM muse_diagnosis").fetchone()[0] == 0
@pytest.mark.case_id(
"TC-4c7dc3fdcabe",
environment="隔离 PostgreSQL,检测替身",
when="持久化、重验或查询诊断、声音、规则与案例。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_CLI默认持久化诊断并可按身份读回__4c7dc3(诊断环境, tmp_path):
from pydantic import TypeAdapter
from muse.接入.cli.审校命令 import 运行审校命令
from muse.接入.审校操作 import 诊断请求
装配, 身份, 目标 = 诊断环境
_配置审校CLI(装配, 身份, tmp_path)
请求 = TypeAdapter(诊断请求).validate_python({"target": 目标, "preview_candidates": True})
路径 = tmp_path / "diagnosis.json"
路径.write_text(json.dumps(请求.model_dump(mode="json"), ensure_ascii=False))
结果 = 运行审校命令(装配, "诊断", str(路径))
assert 结果["report"]["persisted"] is True
assert 运行审校命令(装配, "诊断报告", 结果["diagnosis_id"])["report"] == 结果["report"]
@pytest.mark.case_id(
"TC-873f1d93b7df",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_CLI诊断默认持久化且同一作者可读回__873f1d(诊断环境, tmp_path):
from pydantic import TypeAdapter
from muse.接入.cli.审校命令 import 运行审校命令
from muse.接入.审校操作 import 诊断请求
装配, 身份, 目标 = 诊断环境
_配置审校CLI(装配, 身份, tmp_path)
装配.要求审校().导入规则候选(身份)
请求 = TypeAdapter(诊断请求).validate_python({"target": 目标, "preview_candidates": True})
路径 = tmp_path / "diag-cli.json"
路径.write_text(json.dumps(请求.model_dump(mode="json"), ensure_ascii=False))
结果 = 运行审校命令(装配, "诊断", str(路径))
assert 结果["report"]["persisted"] is True
读回 = 运行审校命令(装配, "诊断报告", 结果["diagnosis_id"])
assert 读回["diagnosis_id"] == 结果["diagnosis_id"]
assert 读回["stale"] is False and 读回["stale_reasons"] == []
报告 = 读回["report"]
assert 报告 == 结果["report"]
assert 报告["target"] == 目标 and 报告["work_id"] == "post-work"
assert 报告["schema_version"] == "diagnosis-v1"
assert 报告["mode"] == "candidate_preview" and 报告["persisted"] is True
assert 报告["text_hash"] == hashlib.sha256("值得注意的是,窗外正在下雨。".encode()).hexdigest()
assert 报告["coverage"]["text_length"] == len("值得注意的是,窗外正在下雨。")
规则库 = 读取内置规则()
assert 报告["rule_library_version"] == 规则库["fingerprint"]
assert 报告["findings"]
for 发现 in 报告["findings"]:
assert 发现["rule_version"] == 规则库["rules"][发现["rule_id"]]["version"]
旧配置 = 装配.配置
assert 旧配置.HTTP is not None
try:
装配.配置 = replace(旧配置, HTTP=replace(旧配置.HTTP, 作者ID="synthetic-other-author"))
with pytest.raises(审校错误, match="诊断不存在、不属于当前作者或哈希不符") as 错误:
运行审校命令(装配, "诊断报告", 结果["diagnosis_id"])
assert "值得注意的是" not in json.dumps(错误.value.呈现(), ensure_ascii=False)
assert 结果["diagnosis_id"] not in json.dumps(错误.value.呈现(), ensure_ascii=False)
finally:
装配.配置 = 旧配置
@pytest.mark.case_id(
"TC-93daa381c03a",
environment="离线,固定样张、规则和四类反例",
when="生成声音草案、投影保护项或验证规则候选。",
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_规则验证CLI无离线旗标必须显式评测配置__93daa3(章后环境, tmp_path):
from pydantic import TypeAdapter
from muse.共享.错误 import 配置错误
from muse.接入.cli.经验命令 import 运行经验命令
from muse.接入.经验操作 import 规则改进验证输入
装配, 身份 = 章后环境["装配"], 章后环境["作者"]
_配置审校CLI(装配, 身份, tmp_path)
输入 = TypeAdapter(规则改进验证输入).validate_python(
{
"command_id": "cli-rule-verify",
"selection": {
"improvement_id": "11111111-1111-4111-8111-111111111111",
"revision": 1,
"content_hash": "0" * 64,
},
"request": {
"dataset_version_id": "22222222-2222-4222-8222-222222222222",
"rule_id": "l001",
"rule_version": 1,
"rule_hash": "1" * 64,
},
}
)
目标 = tmp_path / "rule-verify.json"
目标.write_text(json.dumps(输入.model_dump(mode="json"), ensure_ascii=False), encoding="utf-8")
with pytest.raises(配置错误, match="评测配置"):
运行经验命令(装配, "申请规则验证", str(目标))
@pytest.mark.case_id(
"NC-w22-22c005",
environment="隔离PG与合成模型回执",
given="真实逐片审校任务完成回执与隔离 CLI 配置",
when="任务完成后经服务与统一 CLI 读回同一报告并核对逐片证据",
then=["全文报告由实际逐片回执持久化并原样读回;审校不改写原文"],
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md",
)
def test_全文报告由实际逐片回执持久化并经CLI原样读回__22c005(全文报告环境, tmp_path):
from muse.任务运行.接口 import 任务状态
from muse.接入.cli.审校命令 import 运行审校命令
环境 = 全文报告环境
原正文 = 环境["审校正文"]
任务ID, 分片 = 发起全文审校(环境)
环境["剧本"].extend({"类型": "文本", "文本": 合成片结果(片, 有声音=False)} for 片 in 分片)
assert 跑全文审校(环境, 任务ID).状态 == 任务状态.已完成
服务读回 = (
环境["装配"].要求审校().读取文学工作面(环境["审校作者"], 环境["装配"].任务运行, 任务ID)
)
_配置审校CLI(环境["装配"], 环境["审校作者"], tmp_path)
assert 运行审校命令(环境["装配"], "报告", 任务ID) == 服务读回
assert 服务读回["report"]["persisted"] and 服务读回["report"]["model_verified"]
assert len(服务读回["report"]["evidence"]) == len(分片)
assert 环境["正文"].读取正文(环境["审校作者"], "ch-2") == 原正文