- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
707 lines
29 KiB
Python
707 lines
29 KiB
Python
"""旧规则/质量案例只经B06公开用例迁移,原状态不升格。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import base64
|
||
import hashlib
|
||
import json
|
||
from copy import deepcopy
|
||
from types import SimpleNamespace
|
||
|
||
import psycopg
|
||
import pytest
|
||
import yaml
|
||
from psycopg import sql
|
||
|
||
from muse.作品规划.接口 import 档案保存
|
||
from muse.元数据.接口 import 元数据服务, 导入内置结构
|
||
from muse.共享.调用身份 import 内容用途, 用途, 调用身份
|
||
from muse.启动 import 构建
|
||
from muse.基础设施.数据库.连接 import 数据库工厂
|
||
from muse.正文写作.接口 import 文本节点, 正文结构哈希, 正文草稿, 段落
|
||
from muse.配置 import 应用配置, 数据库引用
|
||
from 导入新库 import 初始化目标, 导入快照
|
||
from 建立映射 import 稳定JSON, 旧快照, 映射配置, 迁移错误
|
||
from 映射台账 import 迁移台账
|
||
from 核对结果 import 核对迁移结果
|
||
from 知识记录分流 import 分流批次
|
||
from 读取旧文件 import 读取文件集
|
||
from 转换业务对象 import 准备目标请求, 提交目标请求
|
||
from 转换质量资产 import 准备质量请求, 转换规则库
|
||
|
||
|
||
def _文件快照(路径: str, 值: dict) -> 旧快照:
|
||
字节 = yaml.safe_dump(值, allow_unicode=True, sort_keys=False).encode()
|
||
哈希 = hashlib.sha256(字节).hexdigest()
|
||
return 旧快照.从载荷(
|
||
{
|
||
"source": {
|
||
"system": "file",
|
||
"database": '{"dataset":"synthetic-quality-files","scope":"files"}',
|
||
"table": "legacy_file",
|
||
"id": 路径,
|
||
"revision": "snapshot-v1",
|
||
},
|
||
"record": {
|
||
"id": 路径,
|
||
"revision": "snapshot-v1",
|
||
"relpath": 路径,
|
||
"size": len(字节),
|
||
"sha256": 哈希,
|
||
"content_base64": base64.b64encode(字节).decode(),
|
||
},
|
||
}
|
||
)
|
||
|
||
|
||
def _例证(kind: str) -> dict:
|
||
前缀 = {"sf": "sf", "snf": "snf", "boundary": "b", "regression": "reg"}[kind]
|
||
return {
|
||
"id": f"{前缀}-l002-01",
|
||
"type": kind,
|
||
"rules": ["l002"],
|
||
"carrier": "narration",
|
||
"source": "synthetic",
|
||
"text": f"{kind}的合成句子。",
|
||
"note": f"{kind}的合成说明。",
|
||
}
|
||
|
||
|
||
def _规则文件批次():
|
||
规则 = {
|
||
"id": "l002",
|
||
"name": "空洞元话语",
|
||
"layer": "lexical",
|
||
"carrier_scope": "narration",
|
||
"trigger": {"type": "regex", "pattern": "值得注意的是"},
|
||
"carve_out": ["评书腔"],
|
||
"default_disposition": "candidate",
|
||
"function_check": ["是否承担声线功能"],
|
||
"fix_hint": "无功能时删除",
|
||
"samples": {kind: [_例证(kind)["id"]] for kind in ("sf", "snf", "boundary", "regression")},
|
||
"version": 1,
|
||
"status": "active",
|
||
"evidence": "旧库人工已激活历史",
|
||
"case_card_ids": [],
|
||
}
|
||
主 = _文件快照("rules/l002.yaml", 规则)
|
||
例证 = [
|
||
_文件快照(f"samples/{kind}.yaml", {"samples": [_例证(kind)]})
|
||
for kind in ("sf", "snf", "boundary", "regression")
|
||
]
|
||
绑定 = [
|
||
{
|
||
"source_key": 主.source.记录键,
|
||
"source_hash": 主.源哈希,
|
||
"route": "rule_version",
|
||
"author_id": "migration-author",
|
||
"sources": [
|
||
{
|
||
"source_key": s.source.记录键,
|
||
"source_hash": s.源哈希,
|
||
"sample_ids": [
|
||
next(
|
||
iter(
|
||
yaml.safe_load(base64.b64decode(s.原行["content_base64"]))[
|
||
"samples"
|
||
]
|
||
)
|
||
)["id"]
|
||
],
|
||
"license": "synthetic",
|
||
"source_ref": "legacy-file:" + s.source.id,
|
||
}
|
||
for s in 例证
|
||
],
|
||
"parameters": {"rule_id": "l002", "version": 1},
|
||
},
|
||
*[
|
||
{
|
||
"source_key": s.source.记录键,
|
||
"source_hash": s.源哈希,
|
||
"route": "rule_example",
|
||
"author_id": "migration-author",
|
||
"sources": [],
|
||
"parameters": {"sample_ids": [例["id"]]},
|
||
}
|
||
for s, 例 in zip(
|
||
例证, [_例证(k) for k in ("sf", "snf", "boundary", "regression")], strict=True
|
||
)
|
||
],
|
||
]
|
||
return [主, *例证], 映射配置({"works": [], "quality_bindings": 绑定})
|
||
|
||
|
||
def _PG规则批次():
|
||
快照, 映射 = _规则文件批次()
|
||
规则 = yaml.safe_load(base64.b64decode(快照[0].原行["content_base64"]))
|
||
行 = deepcopy(规则)
|
||
行["id"], 行["rule_id"] = 1, 行.pop("id")
|
||
行["trigger_json"] = 行.pop("trigger")
|
||
行["sample_refs"] = 行.pop("samples")
|
||
del 规则["case_card_ids"]
|
||
行["payload"] = 规则
|
||
行["content_sha256"] = hashlib.sha256(稳定JSON(规则).encode()).hexdigest()
|
||
主 = 旧快照.从载荷(
|
||
{
|
||
"source": {
|
||
"system": "pg",
|
||
"database": "synthetic-quality-pg",
|
||
"table": "example_ai_flavor_rule",
|
||
"id": "1",
|
||
"revision": "export-v1",
|
||
},
|
||
"record": 行,
|
||
}
|
||
)
|
||
绑定 = {**映射.质量绑定(快照[0]), "source_key": 主.source.记录键, "source_hash": 主.源哈希}
|
||
可用源 = {}
|
||
for i, 源绑定 in enumerate(绑定["sources"], start=1):
|
||
样例 = yaml.safe_load(base64.b64decode(快照[i].原行["content_base64"]))["samples"][0]
|
||
行 = {
|
||
**样例,
|
||
"id": i,
|
||
"sample_id": 样例["id"],
|
||
"sample_type": 样例["type"],
|
||
"source_license": "synthetic",
|
||
"source_ref": "",
|
||
"payload": 样例,
|
||
"content_sha256": hashlib.sha256(稳定JSON(样例).encode()).hexdigest(),
|
||
}
|
||
del 行["type"]
|
||
源 = 旧快照.从载荷(
|
||
{
|
||
"source": {
|
||
"system": "pg",
|
||
"database": "synthetic-quality-pg",
|
||
"table": "example_ai_flavor_sample",
|
||
"id": str(i),
|
||
"revision": "export-v1",
|
||
},
|
||
"record": 行,
|
||
}
|
||
)
|
||
源绑定.update(
|
||
source_key=源.source.记录键,
|
||
source_hash=源.源哈希,
|
||
source_ref="migration-source:" + 源.源哈希,
|
||
)
|
||
可用源[源.source.记录键] = 源
|
||
return 主, 绑定, 可用源
|
||
|
||
|
||
def test_PG规则及样例缺省字段仅兼容DDL默认且保留原哈希__2b8da9():
|
||
主, 绑定, 可用源 = _PG规则批次()
|
||
原始 = deepcopy(主.导出())
|
||
原例证 = {键: deepcopy(源.导出()) for 键, 源 in 可用源.items()}
|
||
库, 旧源 = 转换规则库(主, 绑定, 可用源)
|
||
assert 库["rules"]["l002"]["case_card_ids"] == []
|
||
assert 库["rules"]["l002"]["status"] == "candidate"
|
||
assert set(库["rules"]["l002"]["samples"]) == {"sf", "snf", "boundary", "regression"}
|
||
assert 旧源 == {
|
||
"status": "active",
|
||
"source_key": 主.source.记录键,
|
||
"source_hash": 原始["source_hash"],
|
||
}
|
||
assert 主.导出() == 原始
|
||
assert "case_card_ids" not in 主.原行["payload"]
|
||
assert 主.原行["content_sha256"] == 原始["record"]["content_sha256"]
|
||
assert {键: 源.导出() for 键, 源 in 可用源.items()} == 原例证
|
||
assert {样例["source_license"] for 样例 in 库["samples"].values()} == {"synthetic"}
|
||
for 源绑定 in 绑定["sources"]:
|
||
源 = 可用源[源绑定["source_key"]]
|
||
assert "source_license" not in 源.原行["payload"]
|
||
assert "source_ref" not in 源.原行["payload"]
|
||
样例 = 库["samples"][源绑定["sample_ids"][0]]
|
||
assert 样例["source_ref"] == "migration-source:" + 源.源哈希
|
||
|
||
|
||
@pytest.mark.parametrize("字段", ["case_card_ids", "source_license", "source_ref"])
|
||
@pytest.mark.parametrize(
|
||
"反例",
|
||
["显式null", "非默认投影", "投影错型", "载荷错型", "投影缺失", "其他字段缺省", "内容哈希不符"],
|
||
)
|
||
def test_PG质量默认投影兼容不放过异常__f2e3f7(字段, 反例):
|
||
主, 绑定, 可用源 = _PG规则批次()
|
||
源 = 主 if 字段 == "case_card_ids" else next(iter(可用源.values()))
|
||
导出 = 源.导出()
|
||
del 导出["source_hash"]
|
||
行 = 导出["record"]
|
||
错误码 = "quality_projection_mismatch"
|
||
不符字段 = 字段
|
||
if 反例 == "显式null":
|
||
行["payload"][字段] = None
|
||
elif 反例 == "非默认投影":
|
||
行[字段] = ["case-other"] if 字段 == "case_card_ids" else "other"
|
||
elif 反例 == "投影错型":
|
||
行[字段] = {}
|
||
if 字段 == "case_card_ids":
|
||
错误码 = "quality_source_invalid"
|
||
elif 反例 == "载荷错型":
|
||
行["payload"][字段] = {}
|
||
elif 反例 == "投影缺失":
|
||
del 行[字段]
|
||
if 字段 == "case_card_ids":
|
||
错误码 = "quality_source_invalid"
|
||
elif 反例 == "其他字段缺省":
|
||
不符字段 = "carve_out" if 字段 == "case_card_ids" else "note"
|
||
del 行["payload"][不符字段]
|
||
行[不符字段] = [] if 字段 == "case_card_ids" else ""
|
||
行["content_sha256"] = hashlib.sha256(稳定JSON(行["payload"]).encode()).hexdigest()
|
||
if 反例 == "内容哈希不符":
|
||
行["content_sha256"] = "0" * 64
|
||
错误码 = "quality_content_hash_mismatch"
|
||
异常源 = 旧快照.从载荷(导出)
|
||
if 字段 == "case_card_ids":
|
||
主 = 异常源
|
||
绑定["source_hash"] = 主.源哈希
|
||
else:
|
||
可用源[源.source.记录键] = 异常源
|
||
绑定["sources"][0]["source_hash"] = 异常源.源哈希
|
||
原始 = 异常源.导出()
|
||
匹配 = 错误码
|
||
if 错误码 == "quality_projection_mismatch":
|
||
匹配 += ".*:" + 不符字段
|
||
with pytest.raises(迁移错误, match=匹配):
|
||
转换规则库(主, 绑定, 可用源)
|
||
assert 异常源.导出() == 原始
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("字段", "值", "错误码"),
|
||
[
|
||
("source_ref", "", "quality_binding_invalid"),
|
||
("source_ref", None, "quality_binding_invalid"),
|
||
("source_hash", "0" * 64, "quality_source_drift"),
|
||
("license", "unknown", "quality_binding_invalid"),
|
||
],
|
||
ids=["出处为空", "出处null", "源哈希漂移", "许可未知"],
|
||
)
|
||
def test_PG样例默认投影不替代冻结来源绑定__ba3dc2(字段, 值, 错误码):
|
||
主, 绑定, 可用源 = _PG规则批次()
|
||
绑定["sources"][0][字段] = 值
|
||
with pytest.raises(迁移错误, match=错误码):
|
||
转换规则库(主, 绑定, 可用源)
|
||
|
||
|
||
def _保全规则文件(snapshots, tmp_path):
|
||
source = tmp_path / "source-quality"
|
||
source.mkdir()
|
||
files = []
|
||
for snapshot in snapshots:
|
||
path = source / snapshot.source.id
|
||
path.parent.mkdir(parents=True, exist_ok=True)
|
||
path.write_bytes(base64.b64decode(snapshot.原行["content_base64"]))
|
||
files.append(
|
||
{
|
||
"relpath": snapshot.source.id,
|
||
"revision": snapshot.source.revision,
|
||
"sha256": snapshot.原行["sha256"],
|
||
}
|
||
)
|
||
output = tmp_path / "preserved-quality"
|
||
actual = 读取文件集(source, files, 数据集="synthetic-quality-files", 输出目录=output)
|
||
assert [s.导出() for s in actual] == [s.导出() for s in snapshots]
|
||
return output / "文件回执.json"
|
||
|
||
|
||
def test_文件规则及四类例证冻结且旧active只留历史__29e201():
|
||
快照, 映射 = _规则文件批次()
|
||
分流 = 分流批次(快照, 映射)
|
||
assert 分流[0].reason_code == "quality_rule_version"
|
||
assert all(r.reason_code == "rule_example_dependency" for r in 分流[1:])
|
||
绑定 = 映射.质量绑定(快照[0])
|
||
库, 旧源 = 转换规则库(快照[0], 绑定, {s.source.记录键: s for s in 快照})
|
||
assert 旧源["status"] == "active"
|
||
assert 库["rules"]["l002"]["status"] == "candidate"
|
||
assert set(库["rules"]["l002"]["samples"]) == {"sf", "snf", "boundary", "regression"}
|
||
assert {s["source_license"] for s in 库["samples"].values()} == {"synthetic"}
|
||
|
||
|
||
def test_缺例证和经验表不会被猜成质量案例__29e202():
|
||
快照, 映射 = _规则文件批次()
|
||
with pytest.raises(迁移错误, match="quality_dependency_missing"):
|
||
转换规则库(快照[0], 映射.质量绑定(快照[0]), {s.source.记录键: s for s in 快照[:-1]})
|
||
经验 = 旧快照.从载荷(
|
||
{
|
||
"source": {
|
||
"system": "sqlite",
|
||
"database": "synthetic-lessons",
|
||
"table": "lessons",
|
||
"id": "lesson-1",
|
||
"revision": "v1",
|
||
},
|
||
"record": {
|
||
"id": "lesson-1",
|
||
"status": "promoted",
|
||
"kind": "lesson",
|
||
"title": "不是案例",
|
||
},
|
||
}
|
||
)
|
||
判定 = 分流批次([经验], 映射配置({"works": []}))[0]
|
||
assert (判定.disposition, 判定.reason_code) == ("隔离", "deferred_W26_27")
|
||
|
||
|
||
def test_旧案例准备固定为未决backfill并核真实片段__29e207():
|
||
文本 = "她把伞递过去。值得注意的是,门外下雨了。"
|
||
片段 = "值得注意的是,门外下雨了。"
|
||
开始, 结束 = 文本.index(片段), len(文本)
|
||
草稿 = 正文草稿((段落("p1", (文本节点(文本),)),))
|
||
文档哈希 = 正文结构哈希(草稿)
|
||
片段哈希 = hashlib.sha256(片段.encode()).hexdigest()
|
||
行 = {
|
||
"id": 9,
|
||
"card_id": "case-legacy-9",
|
||
"state": "canonical",
|
||
"label": "snf",
|
||
"capture_mode": "live_feedback",
|
||
"source_license": "owned",
|
||
"source_sha256": "9" * 64,
|
||
"excerpt_sha256": 片段哈希,
|
||
"source_ref": "legacy:chapter:9",
|
||
"work_ref": "legacy-work-9",
|
||
"source_location": {"chapter": 9, "start": 开始, "end": 结束},
|
||
"excerpt": 片段,
|
||
"context": 文本,
|
||
"deleted": False,
|
||
}
|
||
快照 = 旧快照.从载荷(
|
||
{
|
||
"source": {
|
||
"system": "pg",
|
||
"database": "synthetic-case",
|
||
"table": "example_ai_flavor_case",
|
||
"id": "9",
|
||
"revision": "export-v1",
|
||
},
|
||
"record": 行,
|
||
}
|
||
)
|
||
绑定 = {
|
||
"source_key": 快照.source.记录键,
|
||
"source_hash": 快照.源哈希,
|
||
"route": "quality_case",
|
||
"author_id": "migration-author",
|
||
"sources": [
|
||
{
|
||
"role": "before",
|
||
"license": "owned",
|
||
"license_ref": "legacy-decision:9",
|
||
"legacy": {
|
||
"source_ref": 行["source_ref"],
|
||
"work_ref": 行["work_ref"],
|
||
"source_sha256": 行["source_sha256"],
|
||
"excerpt_sha256": 片段哈希,
|
||
"location": 行["source_location"],
|
||
},
|
||
"target": {
|
||
"kind": "document",
|
||
"ref_id": "chapter-9",
|
||
"revision": 3,
|
||
"source_hash": 文档哈希,
|
||
"start": 开始,
|
||
"end": 结束,
|
||
"excerpt_hash": 片段哈希,
|
||
"branch_id": "main",
|
||
"work_ref": "work:new-work-9",
|
||
},
|
||
}
|
||
],
|
||
"parameters": {"card_id": 行["card_id"], "preserve_text": True},
|
||
}
|
||
|
||
class 正文替身:
|
||
def 读取正文(self, 身份, chapter_id, *, 分支, 版本):
|
||
assert (chapter_id, 分支, 版本) == ("chapter-9", "main", 3)
|
||
return {"work_id": "new-work-9", "document": 草稿}
|
||
|
||
class 装配替身:
|
||
def 要求正文(self):
|
||
return 正文替身()
|
||
|
||
身份 = SimpleNamespace(作者="migration-author")
|
||
冻结 = 准备质量请求(
|
||
装配替身(),
|
||
身份,
|
||
快照,
|
||
映射配置({"works": [], "quality_bindings": [绑定]}),
|
||
{快照.source.记录键: 快照},
|
||
)
|
||
assert 冻结["payload"]["mode"] == "backfill"
|
||
assert 冻结["payload"]["feedback_command_id"] is None
|
||
assert 冻结["payload"]["label"] == "snf"
|
||
assert 冻结["payload"]["disposition"] == "ask"
|
||
assert 冻结["legacy_source"]["state"] == "canonical"
|
||
|
||
|
||
@pytest.fixture
|
||
def 质量迁移目标(应用测试库, 隔离数据库URL, tmp_path):
|
||
管理文件 = tmp_path / "管理连接.txt"
|
||
管理文件.write_text(隔离数据库URL, encoding="utf-8")
|
||
管理文件.chmod(0o600)
|
||
with psycopg.connect(隔离数据库URL) as 连:
|
||
允许 = {
|
||
"endpoint_type": "unix" if 连.info.host.startswith("/") else "tcp",
|
||
"host": 连.info.host,
|
||
"port": 连.info.port,
|
||
"system_identifier": str(
|
||
连.execute(
|
||
"SELECT system_identifier FROM pg_catalog.pg_control_system()"
|
||
).fetchone()[0]
|
||
),
|
||
"data_directory": 连.execute("SHOW data_directory").fetchone()[0],
|
||
}
|
||
允许文件 = tmp_path / "允许实例.json"
|
||
允许文件.write_text(json.dumps(允许), encoding="utf-8")
|
||
输出 = tmp_path / "目标"
|
||
管理引用 = 数据库引用("受控存储", str(管理文件))
|
||
回执 = 初始化目标(
|
||
允许实例=允许文件,
|
||
管理引用=管理引用,
|
||
维护模板=应用测试库[用途.维护].引用,
|
||
应用模板=应用测试库[用途.生产].引用,
|
||
输出目录=输出,
|
||
)
|
||
库 = 数据库工厂(数据库引用("受控存储", str(输出 / "应用连接.txt")), 用途.生产)
|
||
维护 = 数据库工厂(数据库引用("受控存储", str(输出 / "维护连接.txt")), 用途.维护)
|
||
with 维护.连接() as 连, 连.transaction():
|
||
导入内置结构(元数据服务(连))
|
||
装配 = 构建(应用配置(库.引用, "synthetic"))
|
||
身份 = 调用身份("migration-author", None, 用途.生产, 内容用途.规划)
|
||
作品 = 装配.要求作品()
|
||
结构 = 作品.新档案结构(身份, "quality-work", "work_core", 1)
|
||
作品.保存档案(
|
||
身份, "quality-work", 档案保存("quality-work", 0, {"名称": "质量迁移合成作品"}, 结构)
|
||
)
|
||
作品.添加章节(身份, "quality-ch", "quality-work", "quality-ch", "合成章", 预期目录版本=0)
|
||
文本 = "她把伞递过去。值得注意的是,门外已经下起了雨。两人谁也没说话。"
|
||
装配.要求正文().保存人工(
|
||
身份, "quality-body", "quality-ch", 0, 正文草稿((段落("p1", (文本节点(文本),)),))
|
||
)
|
||
参数 = {"允许实例": 允许文件, "管理引用": 管理引用, "目标回执": 输出 / "目标回执.json"}
|
||
try:
|
||
yield 装配, 身份, 库, 参数, 文本
|
||
finally:
|
||
with psycopg.connect(隔离数据库URL, autocommit=True) as 连:
|
||
连.execute(
|
||
sql.SQL("DROP DATABASE {} WITH (FORCE)").format(sql.Identifier(回执["database"]))
|
||
)
|
||
|
||
|
||
def _案例快照与映射(装配, 身份, 文本, *, 错作品=False):
|
||
正文 = 装配.要求正文().读取正文(身份, "quality-ch")
|
||
片段 = "值得注意的是,门外已经下起了雨。"
|
||
开始, 结束 = 文本.index(片段), 文本.index(片段) + len(片段)
|
||
片段哈希 = hashlib.sha256(片段.encode()).hexdigest()
|
||
行 = {
|
||
"id": 1,
|
||
"card_id": "case-legacy-quality-001",
|
||
"schema_version": "ai-flavor-case-v1",
|
||
"card_type": "ai_flavor_case",
|
||
"state": "canonical",
|
||
"label": "sf",
|
||
"layer": "lexical",
|
||
"carrier": "narration",
|
||
"capture_mode": "live_feedback",
|
||
"source_kind": "existing_work",
|
||
"source_license": "owned",
|
||
"source_sha256": "1" * 64,
|
||
"excerpt_sha256": 片段哈希,
|
||
"source_ref": "legacy:quality-ch:1",
|
||
"work_ref": "legacy-work-7",
|
||
"source_location": {"chapter": 1, "start": 开始, "end": 结束},
|
||
"excerpt": 片段,
|
||
"context": 文本,
|
||
"deleted": False,
|
||
}
|
||
快照 = 旧快照.从载荷(
|
||
{
|
||
"source": {
|
||
"system": "pg",
|
||
"database": "synthetic-quality-pg",
|
||
"table": "example_ai_flavor_case",
|
||
"id": "1",
|
||
"revision": "export-v1",
|
||
},
|
||
"record": 行,
|
||
}
|
||
)
|
||
绑定 = {
|
||
"source_key": 快照.source.记录键,
|
||
"source_hash": 快照.源哈希,
|
||
"route": "quality_case",
|
||
"author_id": 身份.作者,
|
||
"sources": [
|
||
{
|
||
"role": "before",
|
||
"license": "owned",
|
||
"license_ref": "legacy-decision:owned-1",
|
||
"legacy": {
|
||
"source_ref": 行["source_ref"],
|
||
"work_ref": 行["work_ref"],
|
||
"source_sha256": 行["source_sha256"],
|
||
"excerpt_sha256": 行["excerpt_sha256"],
|
||
"location": 行["source_location"],
|
||
},
|
||
"target": {
|
||
"kind": "document",
|
||
"ref_id": "quality-ch",
|
||
"revision": 正文["revision"],
|
||
"source_hash": 正文["document_hash"],
|
||
"start": 开始,
|
||
"end": 结束,
|
||
"excerpt_hash": 片段哈希,
|
||
"branch_id": "main",
|
||
"work_ref": "work:wrong" if 错作品 else "work:quality-work",
|
||
},
|
||
}
|
||
],
|
||
"parameters": {"card_id": 行["card_id"], "preserve_text": True},
|
||
}
|
||
return 快照, 映射配置({"works": [], "quality_bindings": [绑定]})
|
||
|
||
|
||
@pytest.mark.数据库
|
||
def test_案例经B06回填为未决且重放不重复创建__29e203(质量迁移目标):
|
||
装配, 身份, 库, 参数, 文本 = 质量迁移目标
|
||
快照, 映射 = _案例快照与映射(装配, 身份, 文本)
|
||
首次 = 导入快照(装配, 身份, [快照], 映射, **参数)
|
||
重放 = 导入快照(装配, 身份, [快照], 映射, **参数)
|
||
assert 首次["states"] == 重放["states"] == {"mapped": 1}
|
||
记录 = 迁移台账(库, 身份, 装配.要求知识方法().正式, 装配.要求故事世界(), 装配.要求审校()).读取(
|
||
快照.source.记录键
|
||
)
|
||
案例 = 装配.要求审校().读取案例(身份, 记录["targets"][0]["id"], revision=1)
|
||
assert 案例["state"] == "shadow" and not 案例["sampleable"]
|
||
assert 案例["payload"]["request"]["mode"] == "backfill"
|
||
assert 案例["payload"]["request"]["label"] == "sf"
|
||
assert 记录["frozen_request"]["legacy_source"]["state"] == "canonical"
|
||
with 库.连接(只读=True) as 连:
|
||
assert 连.execute("SELECT count(*) FROM muse_quality_case").fetchone()[0] == 1
|
||
assert 核对迁移结果(装配, 身份, [快照], 映射, **参数)["data_checks_passed"]
|
||
|
||
|
||
@pytest.mark.数据库
|
||
def test_S01已写案例但台账中断时只补原回执__29e204(质量迁移目标):
|
||
装配, 身份, 库, 参数, 文本 = 质量迁移目标
|
||
快照, 映射 = _案例快照与映射(装配, 身份, 文本)
|
||
判定 = 分流批次([快照], 映射)[0]
|
||
台账 = 迁移台账(库, 身份, 装配.要求知识方法().正式, 装配.要求故事世界(), 装配.要求审校())
|
||
映射哈希 = hashlib.sha256(稳定JSON(映射.值).encode()).hexdigest()
|
||
行 = 台账.占用(快照, 映射哈希, 判定)
|
||
冻结 = 准备目标请求(装配, 身份, 快照, 判定, 映射, 可用源={快照.source.记录键: 快照})
|
||
台账.冻结请求(快照.source.记录键, 冻结)
|
||
原回执 = 提交目标请求(装配, 身份, 行["command_id"], 冻结)
|
||
assert 台账.读取(快照.source.记录键)["state"] == "reserved"
|
||
导入快照(装配, 身份, [快照], 映射, **参数)
|
||
恢复 = 台账.读取(快照.source.记录键)
|
||
assert 恢复["state"] == "mapped" and 恢复["receipt"] == 原回执
|
||
with 库.连接(只读=True) as 连:
|
||
assert 连.execute("SELECT count(*) FROM muse_quality_case").fetchone()[0] == 1
|
||
|
||
|
||
@pytest.mark.数据库
|
||
def test_错作品绑定在B06业务前隔离且零案例写入__29e205(质量迁移目标):
|
||
装配, 身份, 库, 参数, 文本 = 质量迁移目标
|
||
with 库.连接(只读=True) as 连:
|
||
原命令数 = 连.execute("SELECT count(*) FROM muse_change_command").fetchone()[0]
|
||
快照, 映射 = _案例快照与映射(装配, 身份, 文本, 错作品=True)
|
||
结果 = 导入快照(装配, 身份, [快照], 映射, **参数)
|
||
assert 结果["states"] == {"quarantined": 1}
|
||
assert 结果["errors"][0]["reason_code"] == "quality_source_mismatch"
|
||
with 库.连接(只读=True) as 连:
|
||
assert 连.execute("SELECT count(*) FROM muse_quality_case").fetchone()[0] == 0
|
||
assert 连.execute("SELECT count(*) FROM muse_change_command").fetchone()[0] == 原命令数
|
||
|
||
|
||
@pytest.mark.数据库
|
||
def test_规则缺精确单版读回时保留reserved且不调B06导入__29e206(质量迁移目标, monkeypatch):
|
||
装配, 身份, 库, _, _ = 质量迁移目标
|
||
monkeypatch.setattr(装配.要求审校(), "读取规则版本", None)
|
||
快照, 映射 = _规则文件批次()
|
||
判定 = 分流批次(快照, 映射)[0]
|
||
台账 = 迁移台账(库, 身份, 装配.要求知识方法().正式, 装配.要求故事世界(), 装配.要求审校())
|
||
台账.占用(快照[0], hashlib.sha256(稳定JSON(映射.值).encode()).hexdigest(), 判定)
|
||
with pytest.raises(迁移错误, match="target_owner_read_unavailable"):
|
||
准备目标请求(
|
||
装配,
|
||
身份,
|
||
快照[0],
|
||
判定,
|
||
映射,
|
||
可用源={s.source.记录键: s for s in 快照},
|
||
)
|
||
with 库.连接(只读=True) as 连:
|
||
assert 连.execute("SELECT count(*) FROM muse_rule_version").fetchone()[0] == 0
|
||
assert (
|
||
连.execute(
|
||
"SELECT count(*) FROM muse_legacy_import_record WHERE state='reserved'"
|
||
).fetchone()[0]
|
||
== 1
|
||
)
|
||
|
||
|
||
@pytest.mark.数据库
|
||
def test_真实规则导入未启用且新版本存在时仍核对原版本__29e208(质量迁移目标, tmp_path):
|
||
app, author, db, options, _ = 质量迁移目标
|
||
snapshots, mapping = _规则文件批次()
|
||
options = {**options, "文件回执": _保全规则文件(snapshots, tmp_path)}
|
||
imported = 导入快照(app, author, snapshots, mapping, **options)
|
||
assert imported["states"].get("mapped") == 1, imported
|
||
assert imported["states"].get("quarantined", 0) == 0, imported
|
||
assert sum(imported["states"].values()) == 5
|
||
ledger = 迁移台账(db, author, app.要求知识方法().正式, app.要求故事世界(), app.要求审校())
|
||
entry = ledger.读取(snapshots[0].source.记录键)
|
||
original = app.要求审校().读取规则版本(author, "l002", 1)
|
||
assert original["status"] == "candidate" and not original["activation_recorded"]
|
||
assert original["version_id"] == entry["targets"][0]["id"]
|
||
assert entry["frozen_request"]["legacy_source"]["status"] == "active"
|
||
later = deepcopy(entry["frozen_request"]["payload"])
|
||
later["rules"]["l002"]["version"] = 2
|
||
later["rules"]["l002"]["name"] = "后续候选版本"
|
||
app.要求审校().导入规则候选(author, 规则库=later)
|
||
assert app.要求审校().读取规则版本(author, "l002", 2)["version_id"] != original["version_id"]
|
||
assert app.要求审校().读取规则版本(author, "l002", 1) == original
|
||
assert 导入快照(app, author, snapshots, mapping, **options)["states"].get("mapped") == 1
|
||
assert 核对迁移结果(app, author, snapshots, mapping, **options)["data_checks_passed"]
|
||
with db.连接(只读=True) as conn:
|
||
assert conn.execute("SELECT count(*) FROM muse_rule_activation").fetchone()[0] == 0
|
||
|
||
|
||
@pytest.mark.数据库
|
||
def test_规则已保存但台账中断时补同一版本且不伪造S01回执__29e209(质量迁移目标, tmp_path):
|
||
app, author, db, options, _ = 质量迁移目标
|
||
snapshots, mapping = _规则文件批次()
|
||
options = {**options, "文件回执": _保全规则文件(snapshots, tmp_path)}
|
||
route = 分流批次(snapshots, mapping)[0]
|
||
ledger = 迁移台账(db, author, app.要求知识方法().正式, app.要求故事世界(), app.要求审校())
|
||
row = ledger.占用(
|
||
snapshots[0],
|
||
hashlib.sha256(稳定JSON(mapping.值).encode()).hexdigest(),
|
||
route,
|
||
)
|
||
frozen = 准备目标请求(
|
||
app,
|
||
author,
|
||
snapshots[0],
|
||
route,
|
||
mapping,
|
||
可用源={s.source.记录键: s for s in snapshots},
|
||
)
|
||
ledger.冻结请求(snapshots[0].source.记录键, frozen)
|
||
receipt = 提交目标请求(app, author, row["command_id"], frozen)
|
||
assert ledger.读取(snapshots[0].source.记录键)["state"] == "reserved"
|
||
result = 导入快照(app, author, snapshots, mapping, **options)
|
||
assert result["states"].get("mapped") == 1, result
|
||
current = ledger.读取(snapshots[0].source.记录键)
|
||
assert current["targets"][0]["id"] == receipt["results"][0]["version_id"]
|
||
with db.连接(只读=True) as conn:
|
||
assert conn.execute("SELECT count(*) FROM muse_rule_version").fetchone()[0] == 1
|
||
assert (
|
||
conn.execute(
|
||
"SELECT count(*) FROM muse_change_command WHERE command_id=%s", (row["command_id"],)
|
||
).fetchone()[0]
|
||
== 0
|
||
)
|