- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
340 lines
15 KiB
Python
340 lines
15 KiB
Python
"""PG逐源台账的冻结与共享回执核对;不写业务owner表。"""
|
||
|
||
from PG作品映射 import 指针
|
||
from 建立映射 import 稳定JSON, 旧快照, 迁移错误
|
||
from 转换PG作品 import PG命令ID, 准备PG请求, 提交PG请求, 核对PG产物
|
||
from 转换PG正文 import 转换PG正文
|
||
|
||
|
||
def 检查PG冻结(行, 请求):
|
||
kind = 请求.get("pg_kind")
|
||
if set(请求) != {
|
||
"owner",
|
||
"scope",
|
||
"subject_ref",
|
||
"pg_kind",
|
||
"legacy_source",
|
||
"command_source",
|
||
"dependencies",
|
||
"mapping_hash",
|
||
"payload",
|
||
} or kind not in {"profile", "directory", "body", "reference"}:
|
||
raise 迁移错误("target_request_invalid", "PG冻结字段或目标种类不合法")
|
||
if (
|
||
请求["legacy_source"] != {"source_key": 行["source_key"], "source_hash": 行["source_hash"]}
|
||
or 请求["mapping_hash"] != 行["mapping_hash"]
|
||
or any(
|
||
请求[k] != 行["route"][v]
|
||
for k, v in (("owner", "owner"), ("scope", "scope"), ("subject_ref", "target_ref"))
|
||
)
|
||
or 行["route"]["type_id"] != "pg_" + kind
|
||
):
|
||
raise 迁移错误("target_request_invalid", "PG冻结与原台账身份/映射不同")
|
||
p = 请求["payload"]
|
||
owner = {"profile": "B01", "directory": "B01", "body": "B05", "reference": "B03"}[kind]
|
||
if 请求["owner"] != owner or not isinstance(p, dict):
|
||
raise 迁移错误("target_request_invalid", "PG目标种类与owner或载荷不同")
|
||
if kind == "profile" and (
|
||
p.get("work_id") != 请求["scope"]
|
||
or p.get("work_id") != 请求["subject_ref"]
|
||
or p.get("expected_revision") != 0
|
||
or p.get("schema", {}).get("schema_id") != "work_core"
|
||
or p.get("schema", {}).get("base_version") != 1
|
||
):
|
||
raise 迁移错误("target_request_invalid", "作品冻结必须是指定作品的初始档案")
|
||
if kind == "directory" and (
|
||
p.get("work_id") != 请求["scope"]
|
||
or p.get("chapter_id") != 请求["subject_ref"]
|
||
or not isinstance(p.get("chapter_ids"), list)
|
||
or not p["chapter_ids"]
|
||
or p["chapter_ids"][-1] != 请求["subject_ref"]
|
||
or p.get("title") != 行["original"]["record"].get("title")
|
||
):
|
||
raise 迁移错误("target_request_invalid", "目录冻结不能改变原章、标题或作品")
|
||
if kind == "body" and (
|
||
p.get("chapter_id") != 请求["subject_ref"]
|
||
or p.get("branch_id") != "main"
|
||
or type(p.get("base_revision")) is not int
|
||
or p["base_revision"] != 0
|
||
):
|
||
raise 迁移错误("target_request_invalid", "PG正文冻结只允许指定章的main/0候选")
|
||
if (
|
||
kind == "reference"
|
||
and "member_of" not in p
|
||
and (
|
||
p.get("request", {}).get("kind") != "reference"
|
||
or p.get("request", {}).get("authorized_uses") not in ([], ())
|
||
or p.get("request", {}).get("reference_work_ref") is not None
|
||
)
|
||
):
|
||
raise 迁移错误("target_request_invalid", "参考导入不能扩大用途或伪造作品归属")
|
||
deps = 请求["dependencies"]
|
||
if (
|
||
not isinstance(deps, list)
|
||
or not deps
|
||
or any(not isinstance(p, dict) or set(p) != {"source_key", "source_hash"} for p in deps)
|
||
or 请求["legacy_source"] not in deps
|
||
or 请求["command_source"] not in deps
|
||
):
|
||
raise 迁移错误("target_request_invalid", "PG冻结缺少自身或共享命令原来源")
|
||
|
||
|
||
def 核对PG台账(台账, 行, 回执):
|
||
冻结 = 行["frozen_request"]
|
||
检查PG冻结(行, 冻结)
|
||
if 台账.装配 is None:
|
||
raise 迁移错误("target_owner_read_unavailable", "PG台账需要真实owner公开读回能力")
|
||
if 冻结["pg_kind"] == "reference" and "member_of" in 冻结["payload"]:
|
||
root_pointer = 冻结["payload"]["member_of"]
|
||
if (
|
||
root_pointer != 冻结["command_source"]
|
||
or set(冻结["payload"]) != {"member_of"}
|
||
or 冻结["dependencies"] != [root_pointer, 冻结["legacy_source"]]
|
||
):
|
||
raise 迁移错误("target_request_invalid", "参考成员只能引用明确原根与自身")
|
||
key = root_pointer["source_key"]
|
||
cached = 台账.PG参考根缓存.get(key)
|
||
if cached is None:
|
||
root = 台账.读取(key)
|
||
if (
|
||
root is None
|
||
or root["state"] != "mapped"
|
||
or root["frozen_request"] is None
|
||
or "member_of" in root["frozen_request"]["payload"]
|
||
):
|
||
raise 迁移错误("pg_dependency_pending", "参考原文根尚无真实版本")
|
||
targets = 核对PG台账(台账, root, root["receipt"])
|
||
cached = (
|
||
root,
|
||
targets,
|
||
{p["source_key"]: p for p in root["frozen_request"]["dependencies"]},
|
||
)
|
||
台账.PG参考根缓存[key] = cached
|
||
root, targets, members = cached
|
||
if (
|
||
root["source_hash"] != root_pointer["source_hash"]
|
||
or root["mapping_hash"] != 行["mapping_hash"]
|
||
or members.get(行["source_key"]) != 冻结["legacy_source"]
|
||
or 稳定JSON(root["receipt"]) != 稳定JSON(回执)
|
||
or any(
|
||
冻结[k] != root["frozen_request"][k]
|
||
for k in ("owner", "scope", "subject_ref", "command_source")
|
||
)
|
||
):
|
||
raise 迁移错误("target_request_invalid", "参考成员来源/回执与已核原根不一致")
|
||
return targets
|
||
sources = []
|
||
for p in 冻结["dependencies"]:
|
||
dep = 台账.读取(p["source_key"])
|
||
if (
|
||
dep is None
|
||
or dep["source_hash"] != p["source_hash"]
|
||
or dep["mapping_hash"] != 行["mapping_hash"]
|
||
):
|
||
raise 迁移错误("pg_dependency_missing", "PG依赖尚未逐源留存或哈希/映射漂移")
|
||
sources.append(旧快照.从载荷(dep["original"]))
|
||
if (
|
||
冻结["pg_kind"] in {"body", "directory"}
|
||
and dep["source_key"] != 行["source_key"]
|
||
and dep["original"]["source"]["table"].split(".")[-1]
|
||
in {"muse_content_work", "muse_content_chapter"}
|
||
and dep["state"] != "mapped"
|
||
):
|
||
raise 迁移错误("pg_dependency_pending", "作品/前序章节依赖尚无真实成功回执")
|
||
根 = 台账.读取(冻结["command_source"]["source_key"])
|
||
if 根 is None:
|
||
raise 迁移错误("pg_dependency_missing", "共享命令源缺失")
|
||
if 根["source_key"] != 行["source_key"]:
|
||
if 根["state"] != "mapped" or 根["frozen_request"] is None:
|
||
raise 迁移错误("pg_dependency_pending", "共享产物根尚未成功映射")
|
||
|
||
def operation(f):
|
||
return {k: v for k, v in f.items() if k != "legacy_source"}
|
||
|
||
if 稳定JSON(operation(根["frozen_request"])) != 稳定JSON(operation(冻结)) or 稳定JSON(
|
||
根["receipt"]
|
||
) != 稳定JSON(回执):
|
||
raise 迁移错误("target_request_invalid", "共享产物与原根操作/真实回执不同")
|
||
if 冻结["pg_kind"] == "body":
|
||
blocks = [s for s in sources if s.source.table.split(".")[-1] == "muse_content_block"]
|
||
if 稳定JSON(转换PG正文(blocks)) != 稳定JSON(冻结["payload"]["draft"]):
|
||
raise 迁移错误("target_request_invalid", "逐block原内容/段映射与冻结正文不同")
|
||
if 冻结["pg_kind"] == "reference":
|
||
from 转换PG作品 import 组装PG参考
|
||
|
||
chapters = []
|
||
for source in sources[1:]:
|
||
if source.source.table.split(".")[-1] == "muse_content_chapter":
|
||
chapters.append((source, []))
|
||
elif (
|
||
source.source.table.split(".")[-1] == "muse_content_block"
|
||
and chapters
|
||
and source.原行["chapter_id"] == chapters[-1][0].原行["id"]
|
||
):
|
||
chapters[-1][1].append(source)
|
||
else:
|
||
raise 迁移错误("target_request_invalid", "参考冻结依赖不是原章/块顺序")
|
||
text, chapter_map = 组装PG参考(chapters)
|
||
p = 冻结["payload"]
|
||
if (
|
||
text != p["request"]["content"]
|
||
or 稳定JSON(chapter_map) != 稳定JSON(p["chapter_map"])
|
||
or p["request"]["authorized_uses"] not in ([], ())
|
||
or p["request"]["title"] != sources[0].原行["title"]
|
||
):
|
||
raise 迁移错误("target_request_invalid", "参考原文、逐章/块对照或用途与原行不同")
|
||
if 冻结["pg_kind"] == "profile" and 冻结["payload"]["content"] != {
|
||
"名称": sources[0].原行["title"]
|
||
}:
|
||
raise 迁移错误("target_request_invalid", "作品名称与原行不同")
|
||
return 核对PG产物(台账.装配, 台账.身份, 冻结, 回执)
|
||
|
||
|
||
def 执行PG行(装配, 身份, 台账, 源, 判定, 映射, 可用源, 行):
|
||
"""根先提交,子源只关联原根冻结;恢复不重新生成候选或改变基线。"""
|
||
from pydantic import ValidationError
|
||
|
||
from muse.共享.错误 import Muse错误
|
||
from muse.正式变更.接口 import 变更错误
|
||
|
||
key = 源.source.记录键
|
||
if 行["state"] == "mapped":
|
||
targets = 核对PG台账(台账, 行, 行["receipt"])
|
||
if 稳定JSON(targets) != 稳定JSON(行["targets"]):
|
||
raise 迁移错误("target_mismatch", "PG已有映射与真实owner不同")
|
||
return
|
||
try:
|
||
frozen = 行["frozen_request"]
|
||
if frozen is None:
|
||
# 共享成员复用根已经核验的冻结操作,不以当前状态重造初始基线。
|
||
from PG作品映射 import PG绑定
|
||
|
||
group, chapter = PG绑定(映射, 源)
|
||
root = (
|
||
group["work"]
|
||
if group["use"] == "reference"
|
||
else (chapter["blocks"][0] if 判定.type_id == "pg_body" else 指针(源))
|
||
)
|
||
if root != 指针(源):
|
||
cached = (
|
||
台账.PG参考根缓存.get(root["source_key"])
|
||
if group["use"] == "reference"
|
||
else None
|
||
)
|
||
parent = cached[0] if cached else 台账.读取(root["source_key"])
|
||
if parent is not None and parent["state"] in {"historical", "quarantined"}:
|
||
raise 迁移错误("pg_dependency_failed", "共享根已隔离或历史保留,不能映射成员")
|
||
if (
|
||
parent is None
|
||
or parent["state"] != "mapped"
|
||
or parent["source_hash"] != root["source_hash"]
|
||
or parent["mapping_hash"] != 行["mapping_hash"]
|
||
):
|
||
raise 迁移错误("pg_dependency_pending", "共享根尚未成功,保留reserved供恢复")
|
||
frozen = {**parent["frozen_request"], "legacy_source": 指针(源)}
|
||
if group["use"] == "reference":
|
||
frozen = {
|
||
**frozen,
|
||
"dependencies": [root, 指针(源)],
|
||
"payload": {"member_of": root},
|
||
}
|
||
else:
|
||
if 判定.type_id != "pg_reference":
|
||
try:
|
||
装配.要求作品().正式.读取回执(身份, 行["command_id"])
|
||
except 变更错误 as exc:
|
||
if exc.错误码 != "RECEIPT_NOT_FOUND":
|
||
raise
|
||
else:
|
||
raise 迁移错误("command_collision", "PG未冻结请求但命令已有回执")
|
||
frozen = 准备PG请求(装配, 身份, 源, 判定, 映射, 可用源)
|
||
frozen = 台账.冻结请求(key, frozen)
|
||
receipt = None
|
||
if frozen["command_source"] != 指针(源):
|
||
cached = 台账.PG参考根缓存.get(frozen["command_source"]["source_key"])
|
||
receipt = (
|
||
cached[0]["receipt"]
|
||
if cached
|
||
else 台账.读取(frozen["command_source"]["source_key"])["receipt"]
|
||
)
|
||
elif frozen["pg_kind"] != "reference":
|
||
try:
|
||
receipt = 装配.要求作品().正式.读取回执(身份, PG命令ID(身份, frozen))
|
||
except 变更错误 as exc:
|
||
if exc.错误码 != "RECEIPT_NOT_FOUND":
|
||
raise
|
||
if receipt is None:
|
||
receipt = 提交PG请求(装配, 身份, frozen)
|
||
except (迁移错误, Muse错误, ValidationError) as exc:
|
||
code = (
|
||
exc.code
|
||
if isinstance(exc, 迁移错误)
|
||
else getattr(exc, "错误码", "target_request_invalid")
|
||
)
|
||
if code in {"pg_dependency_pending", "pg_dependency_missing"}:
|
||
return # 固定映射下等待后续快照分片,不将缺少输入写成不可逆隔离。
|
||
if (
|
||
code in {"target_owner_read_unavailable", "command_collision"}
|
||
or isinstance(exc, Muse错误)
|
||
and exc.可重试
|
||
):
|
||
raise
|
||
# 已冻结的操作可能已在owner成功;不把保存回执中断伪判为未写。
|
||
if 台账.读取(key)["frozen_request"] is not None:
|
||
raise
|
||
台账.保存终态(key, "quarantined", 原因=code)
|
||
return
|
||
行 = 台账.读取(key)
|
||
targets = 核对PG台账(台账, 行, receipt)
|
||
updated = 台账.保存终态(key, "mapped", 目标=targets, 回执=receipt)
|
||
if frozen["pg_kind"] == "reference" and "member_of" not in frozen["payload"]:
|
||
台账.PG参考根缓存[key] = (
|
||
updated,
|
||
targets,
|
||
{p["source_key"]: p for p in frozen["dependencies"]},
|
||
)
|
||
|
||
|
||
def PG分片等待(台账, 映射, 可用源, 待处理):
|
||
"""只把实缺原源的未冻结reserved归为等待;不把异常中断/隔离放行。"""
|
||
from collections import Counter
|
||
|
||
from PG作品映射 import PG依赖指针, 是PG内容
|
||
|
||
状态, 缺源, 已解释 = Counter(), set(), 0
|
||
for 源 in {s.source.记录键: s for s in 待处理}.values():
|
||
行 = 台账.读取(源.source.记录键)
|
||
if 行 is None:
|
||
状态["missing_ledger"] += 1
|
||
continue
|
||
状态[行["state"]] += 1
|
||
if 行["state"] != "reserved" or 行["frozen_request"] is not None or not 是PG内容(源):
|
||
continue
|
||
try:
|
||
依赖 = PG依赖指针(映射, 源)
|
||
if any(
|
||
p["source_key"] in 可用源 and 可用源[p["source_key"]].源哈希 != p["source_hash"]
|
||
for p in 依赖
|
||
):
|
||
continue
|
||
本缺 = {p["source_key"] for p in 依赖 if p["source_key"] not in 可用源}
|
||
if 本缺:
|
||
已解释 += 1
|
||
缺源.update(本缺)
|
||
except 迁移错误:
|
||
continue
|
||
允许继续 = (
|
||
bool(缺源)
|
||
and 已解释 == 状态["reserved"]
|
||
and not 状态["quarantined"]
|
||
and not 状态["missing_ledger"]
|
||
)
|
||
return {
|
||
"can_continue": 允许继续,
|
||
"reason_code": "pg_dependency_missing" if 允许继续 else "not_fragment_wait",
|
||
"pending_record_count": 状态["reserved"],
|
||
"classified_pending_count": 已解释,
|
||
"missing_source_count": len(缺源),
|
||
"affected_states": dict(状态),
|
||
}
|