接续 88dd570,保存 W20–W24 已实现的共享接口、业务入口、迁移、工作台、测试与文档。 W20/W22/W23 保持 in_progress,W21/W24 保持 verified;此提交不宣称方法或规则正式启用、多轮返修、真实角色评测完成。 W25 新增实验、标定、逐调用交付与角色执行及其迁移/测试/索引留在实施工作树,原有私人和旧实现保留项不纳入。 验证:离线 571、前端 33 通过;PG 469 项通过、2 项浏览器未启用,2 项误带入的 W25 用例已移出本提交;最终任务与交付边界 37 项通过。make 检查、最终类型、84 项资源及 diff 检查通过。未重跑浏览器或 Pi 宿主,不以合成调用认证外部模型效果。 独立整体审查四维通过;证据保存在 R2-20260909/提交W20-W24。
468 lines
19 KiB
Python
468 lines
19 KiB
Python
"""明确旧源与既存确认的关联;纯解释和公开只读核验,不导入分流/台账或写业务。"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from typing import Any
|
||
|
||
from muse.元数据.接口 import 元数据服务, 校验值
|
||
from muse.共享.错误 import Muse错误
|
||
from muse.正式变更.接口 import 固定哈希
|
||
from 建立映射 import 稳定JSON, 旧快照, 映射配置, 迁移错误
|
||
from 转换结构 import 转换动态字段, 转换目标字段
|
||
|
||
正式表 = {"muse_knowledge_entity": "entity", "muse_knowledge_relation": "relation"}
|
||
实体型 = {"character", "location", "item", "faction", "power_system", "event"}
|
||
|
||
|
||
def 冻结模式(冻结: dict) -> str:
|
||
if (
|
||
not isinstance(冻结, dict)
|
||
or not isinstance(冻结.get("mode", "create"), str)
|
||
or 冻结.get("mode", "create") not in {"create", "link_confirmed"}
|
||
):
|
||
raise 迁移错误("target_request_invalid", "未知冻结模式")
|
||
模式 = 冻结.get("mode", "create")
|
||
if 模式 == "link_confirmed" and 冻结.get("owner") != "B02":
|
||
raise 迁移错误("target_request_invalid", "历史关联仅支持B02")
|
||
return 模式
|
||
|
||
|
||
def _字符串(值: Any) -> bool:
|
||
return isinstance(值, str) and bool(值.strip())
|
||
|
||
|
||
def _正整(值: Any) -> bool:
|
||
return type(值) is int and 值 > 0
|
||
|
||
|
||
def _哈希(值: Any) -> bool:
|
||
return isinstance(值, str) and re.fullmatch(r"[a-f0-9]{64}", 值) is not None
|
||
|
||
|
||
def _指针(值: Any) -> bool:
|
||
return (
|
||
isinstance(值, dict)
|
||
and set(值) == {"source_key", "source_hash"}
|
||
and _字符串(值["source_key"])
|
||
and _哈希(值["source_hash"])
|
||
)
|
||
|
||
|
||
def _确认指针(值: Any) -> bool:
|
||
return (
|
||
isinstance(值, dict)
|
||
and set(值) == {"command_id", "receipt_id", "revision"}
|
||
and all(_字符串(值[k]) for k in ("command_id", "receipt_id"))
|
||
and _正整(值["revision"])
|
||
)
|
||
|
||
|
||
def 正式旧表(快照: 旧快照) -> bool:
|
||
return 快照.source.table.rsplit(".", 1)[-1] in 正式表
|
||
|
||
|
||
def 请求历史关联(快照: 旧快照, 映射: 映射配置) -> bool:
|
||
return any(
|
||
r.get("source_key") == 快照.source.记录键
|
||
and ("existing_confirmation" in r or "target_source" in r)
|
||
for r in 映射.条目("confirmations", 快照.source)
|
||
)
|
||
|
||
|
||
def 关联源指针(快照: 旧快照, 映射: 映射配置) -> dict | None:
|
||
"""只供导入层按显式key读取己台账;非法形状由纯解释保全拒绝。"""
|
||
项 = [
|
||
r
|
||
for r in 映射.条目("confirmations", 快照.source)
|
||
if r.get("source_key") == 快照.source.记录键
|
||
]
|
||
if len(项) == 1 and _指针(项[0].get("target_source")):
|
||
return 项[0]["target_source"]
|
||
return None
|
||
|
||
|
||
def _原行资格(快照: 旧快照) -> str:
|
||
行 = 快照.原行
|
||
表 = 快照.source.table.rsplit(".", 1)[-1]
|
||
if (
|
||
快照.source.system != "pg"
|
||
or 行.get("source_status") not in ("active", "authorized")
|
||
or 行.get("source_action_policy") != "allowed"
|
||
):
|
||
raise 迁移错误("legacy_link_conflict", "旧来源不具备历史关联资格")
|
||
if 行.get("deleted", False) not in (False, 0, "false", "0"):
|
||
raise 迁移错误("legacy_link_conflict", "删除或未知删除标记不关联")
|
||
if 表 in 正式表:
|
||
if 表 == "muse_knowledge_entity" and 行.get("status") != "active":
|
||
raise 迁移错误("legacy_link_conflict", "正式实体非active")
|
||
return 正式表[表]
|
||
if (
|
||
表 != "muse_knowledge_draft"
|
||
or 行.get("status") != "confirmed"
|
||
or 行.get("draft_type") not in ("entity", "relation")
|
||
):
|
||
raise 迁移错误("legacy_link_conflict", "只有正式旧源与已确认草稿可关联")
|
||
return 行["draft_type"]
|
||
|
||
|
||
def _确认条目(快照, 映射, 作品, 家族):
|
||
项 = [
|
||
r
|
||
for r in 映射.条目("confirmations", 快照.source)
|
||
if r.get("source_key") == 快照.source.记录键
|
||
]
|
||
必需 = {
|
||
"system",
|
||
"database",
|
||
"source_key",
|
||
"source_hash",
|
||
"target_ref",
|
||
"decision_ref",
|
||
"existing_confirmation",
|
||
}
|
||
if not 正式旧表(快照):
|
||
必需.add("target_source")
|
||
if (
|
||
len(项) != 1
|
||
or set(项[0]) != 必需
|
||
or 项[0]["source_hash"] != 快照.源哈希
|
||
or not all(_字符串(项[0].get(k)) for k in ("target_ref", "decision_ref"))
|
||
):
|
||
raise 迁移错误("legacy_link_conflict", "确认关联字段、身份或源哈希不一致")
|
||
声明 = 项[0]
|
||
if not _确认指针(声明["existing_confirmation"]):
|
||
raise 迁移错误("existing_confirmation_invalid", "必须明确既存真实确认指针")
|
||
行 = 快照.原行
|
||
id_ = (
|
||
快照.source.id
|
||
if 正式旧表(快照)
|
||
else 行.get("entity_id" if 家族 == "entity" else "target_object_id")
|
||
)
|
||
if type(id_) not in (str, int) or not str(id_).strip():
|
||
raise 迁移错误("legacy_link_conflict", "旧目标强关联缺失")
|
||
if (
|
||
not 正式旧表(快照)
|
||
and 行.get("target_object_id" if 家族 == "entity" else "entity_id") is not None
|
||
):
|
||
raise 迁移错误("legacy_link_conflict", "草稿目标族存在矛盾指针")
|
||
if 映射.确认(快照, [家族 + ":" + str(id_)], 作品=作品) is None:
|
||
raise 迁移错误("legacy_link_conflict", "旧目标与明确对象映射不一致")
|
||
return 声明, str(id_)
|
||
|
||
|
||
def 生成关联建议(快照: 旧快照, 映射: 映射配置, 已知源: dict[str, 旧快照]) -> dict:
|
||
家族 = _原行资格(快照)
|
||
作品 = 映射.作品(快照.source, 快照.原行.get("work_id"), 映射.选择用途(快照))
|
||
if 作品["use"] != "author":
|
||
raise 迁移错误("target_adapter_unavailable", "其他用途的确认不进入B02关联")
|
||
声明, 旧目标ID = _确认条目(快照, 映射, 作品, 家族)
|
||
正式源 = 快照
|
||
if not 正式旧表(快照):
|
||
指针 = 声明.get("target_source")
|
||
if not _指针(指针):
|
||
raise 迁移错误("legacy_link_conflict", "正式源指针格式不正确")
|
||
正式源 = 已知源.get(指针["source_key"])
|
||
if 正式源 is None:
|
||
raise 迁移错误("legacy_target_source_missing", "未提供或留存明确正式旧源")
|
||
if (
|
||
正式源.源哈希 != 指针["source_hash"]
|
||
or not 正式旧表(正式源)
|
||
or (正式源.source.system, 正式源.source.database)
|
||
!= (快照.source.system, 快照.source.database)
|
||
or 正式源.source.id != 旧目标ID
|
||
or str(正式源.原行.get("work_id")) != str(快照.原行.get("work_id"))
|
||
or _原行资格(正式源) != 家族
|
||
):
|
||
raise 迁移错误("legacy_link_conflict", "正式源与旧草稿身份、主键或族不一致")
|
||
正式声明, _ = _确认条目(正式源, 映射, 作品, 家族)
|
||
if any(
|
||
稳定JSON(正式声明[k]) != 稳定JSON(声明[k])
|
||
for k in ("target_ref", "existing_confirmation")
|
||
):
|
||
raise 迁移错误("legacy_link_conflict", "两旧源未指向同一新目标与确认")
|
||
行 = 正式源.原行
|
||
字段 = {} if 行.get("attributes") is None else 行["attributes"]
|
||
类型 = "character_relation" if 家族 == "relation" else 行.get("entity_type")
|
||
if not isinstance(类型, str):
|
||
raise 迁移错误("unknown_type", "正式旧源缺明确类型")
|
||
别名 = 映射.值.get("type_aliases", {})
|
||
if not isinstance(别名, dict):
|
||
raise 迁移错误("unknown_type", "类型映射必须是对象")
|
||
类型 = 别名.get(类型, 类型)
|
||
if (
|
||
not isinstance(类型, str)
|
||
or 类型 not in 实体型 | {"character_relation"}
|
||
or (类型 == "character_relation") != (家族 == "relation")
|
||
):
|
||
raise 迁移错误("unknown_type", "正式旧源类型与族不一致")
|
||
内容 = {
|
||
"name": 行.get("normalized_name", ""),
|
||
"brief": 行.get("description") or "",
|
||
"fields": 转换动态字段(字段, 类型, 映射.值.get("schemas", {})),
|
||
}
|
||
if (家族 == "entity" and not _字符串(内容["name"])) or (
|
||
行.get("description") is not None and not isinstance(行["description"], str)
|
||
):
|
||
raise 迁移错误("field_type_mismatch", "正式旧源名称或描述类型不正确")
|
||
if 家族 == "relation":
|
||
if not _字符串(行.get("relation_type")):
|
||
raise 迁移错误("field_type_mismatch", "关系类型缺失")
|
||
for 侧 in ("source", "target"):
|
||
id_ = 行.get(侧 + "_entity_id")
|
||
if type(id_) not in (str, int) or not str(id_).strip():
|
||
raise 迁移错误("legacy_link_conflict", "关系旧端点必须明确")
|
||
内容[侧] = 映射.端点(正式源.source, 行["work_id"], ["entity:" + str(id_)], 作品=作品)
|
||
内容["relation_type"] = 行["relation_type"]
|
||
结构 = 映射.值.get("target_schemas", {}).get("B02:" + 类型)
|
||
if (
|
||
not isinstance(结构, dict)
|
||
or not _字符串(结构.get("schema_id"))
|
||
or not _正整(结构.get("base_version"))
|
||
):
|
||
raise 迁移错误("target_schema_missing", "关联需要明确目标历史结构")
|
||
扩展 = 结构.get("extension_version")
|
||
if 扩展 is not None and not _正整(扩展):
|
||
raise 迁移错误("target_schema_missing", "扩展版本不正确")
|
||
if 家族 == "relation" and any(
|
||
结构.get("fields", {}).get(k) != v
|
||
for k, v in (("甲方", "source"), ("乙方", "target"), ("关系类型", "relation_type"))
|
||
):
|
||
raise 迁移错误("target_relation_mapping", "关系必须映射核对后的两端与类型")
|
||
规范前值 = 转换目标字段(内容, 结构.get("fields"))
|
||
spec = {
|
||
"target_source": {"source_key": 正式源.source.记录键, "source_hash": 正式源.源哈希},
|
||
"decision_ref": 声明["decision_ref"],
|
||
"existing_confirmation": 声明["existing_confirmation"],
|
||
"type_id": 类型,
|
||
"instance_family": 家族,
|
||
"content": 规范前值,
|
||
"legacy_draft_id": None if 正式旧表(快照) else 快照.source.id,
|
||
"legacy_target_id": 正式源.source.id,
|
||
"s04": {
|
||
"schema_id": 结构["schema_id"],
|
||
"base_version": 结构["base_version"],
|
||
"extension_version": 扩展,
|
||
},
|
||
}
|
||
return {
|
||
"owner": "B02",
|
||
"scope": 作品["target_ref"],
|
||
"target_ref": 声明["target_ref"],
|
||
"type_id": 类型,
|
||
"state": "confirmed_history",
|
||
"content": {"link": spec},
|
||
}
|
||
|
||
|
||
def 检查关联冻结(行: dict, 冻结: dict) -> None:
|
||
if 冻结模式(冻结) != "link_confirmed":
|
||
if 行["route"].get("reason_code") == "linked_existing_confirmation":
|
||
raise 迁移错误("target_request_invalid", "历史关联不能降为候选创建模式")
|
||
return
|
||
if set(冻结) != {"mode", "owner", "scope", "subject_ref", "payload"}:
|
||
raise 迁移错误("target_request_invalid", "关联冻结外层字段不完整")
|
||
原 = 旧快照.从载荷(行["original"])
|
||
家族 = _原行资格(原)
|
||
路由 = 行["route"]
|
||
spec = 路由.get("content", {}).get("link")
|
||
p = 冻结.get("payload")
|
||
if (
|
||
not isinstance(spec, dict)
|
||
or not isinstance(p, dict)
|
||
or set(p)
|
||
!= {
|
||
"source_key",
|
||
"source_hash",
|
||
"mapping_hash",
|
||
"target_source",
|
||
"decision_ref",
|
||
"existing_confirmation",
|
||
"target",
|
||
"s04",
|
||
}
|
||
):
|
||
raise 迁移错误("target_request_invalid", "关联冻结载荷字段不正确")
|
||
if (
|
||
路由.get("rule_id") != "K06"
|
||
or 路由.get("state") != "confirmed_history"
|
||
or any(
|
||
冻结[k] != 路由[r]
|
||
for k, r in (("owner", "owner"), ("scope", "scope"), ("subject_ref", "target_ref"))
|
||
)
|
||
or any(p[k] != 行[k] for k in ("source_key", "source_hash", "mapping_hash"))
|
||
):
|
||
raise 迁移错误("target_request_invalid", "原来源、路由与冻结不一致")
|
||
if (
|
||
any(
|
||
稳定JSON(p[k]) != 稳定JSON(spec[k])
|
||
for k in ("target_source", "decision_ref", "existing_confirmation")
|
||
)
|
||
or not _指针(p["target_source"])
|
||
or not _确认指针(p["existing_confirmation"])
|
||
):
|
||
raise 迁移错误("target_request_invalid", "冻结确认与正式源指针不一致")
|
||
自身 = p["target_source"] == {"source_key": 行["source_key"], "source_hash": 行["source_hash"]}
|
||
if 自身 != 正式旧表(原) or (
|
||
not 自身
|
||
and str(原.原行.get("entity_id" if 家族 == "entity" else "target_object_id"))
|
||
!= spec["legacy_target_id"]
|
||
):
|
||
raise 迁移错误("target_request_invalid", "根源或草稿强关联不一致")
|
||
t, s = p["target"], p["s04"]
|
||
if (
|
||
not isinstance(t, dict)
|
||
or set(t) != {"instance_family", "type_id", "revision", "system_revision", "content_hash"}
|
||
or t["instance_family"] != 家族
|
||
or t["type_id"] != spec["type_id"]
|
||
or not all(_正整(t[k]) for k in ("revision", "system_revision"))
|
||
or not _哈希(t["content_hash"])
|
||
):
|
||
raise 迁移错误("target_request_invalid", "冻结世界版本不正确")
|
||
if (
|
||
not isinstance(s, dict)
|
||
or set(s)
|
||
!= {
|
||
"schema_id",
|
||
"base_version",
|
||
"extension_version",
|
||
"effective_schema_hash",
|
||
"projection_version",
|
||
}
|
||
or any(s[k] != spec["s04"][k] for k in spec["s04"])
|
||
or not _哈希(s["effective_schema_hash"])
|
||
or not _字符串(s["projection_version"])
|
||
):
|
||
raise 迁移错误("target_request_invalid", "冻结历史结构不正确")
|
||
|
||
|
||
def _世界回执(回执: dict, target: str, 声明: dict) -> dict:
|
||
b = 回执.get("basis", {})
|
||
行 = 回执.get("results")
|
||
if (
|
||
回执.get("command_id") != 声明["command_id"]
|
||
or 回执.get("receipt_id") != 声明["receipt_id"]
|
||
or 回执.get("action") != "adopt"
|
||
or not _字符串(回执.get("author_review_id"))
|
||
or 回执.get("target_ref") != "world:" + target
|
||
or not isinstance(b, dict)
|
||
or b.get("目标") != "world:" + target
|
||
or b.get("变更项") != ["object"]
|
||
or not _字符串(b.get("候选ID"))
|
||
or not _正整(b.get("候选版本"))
|
||
or not _哈希(b.get("候选哈希"))
|
||
or not _哈希(b.get("有效结构哈希"))
|
||
or not _字符串(b.get("投影版本"))
|
||
or not _哈希(b.get("来源摘要哈希"))
|
||
or type(b.get("数据版本")) is not int
|
||
or b["数据版本"] < 0
|
||
or not isinstance(行, list)
|
||
or len(行) != 1
|
||
):
|
||
raise 迁移错误("existing_confirmation_invalid", "必须是明确的独立世界采纳回执")
|
||
r = 行[0]
|
||
if (
|
||
not isinstance(r, dict)
|
||
or r.get("object_id") != target
|
||
or any(k in r for k in ("proposal_id", "candidate_id"))
|
||
or not all(_正整(r.get(k)) for k in ("revision", "system_revision"))
|
||
or not _哈希(r.get("content_hash"))
|
||
or r["revision"] != 声明["revision"]
|
||
or r["revision"] != b["数据版本"] + 1
|
||
):
|
||
raise 迁移错误("existing_confirmation_invalid", "回执没有唯一正确世界版本")
|
||
return r
|
||
|
||
|
||
def 关联回执产物(冻结: dict, 回执: dict) -> dict:
|
||
r = _世界回执(回执, 冻结["subject_ref"], 冻结["payload"]["existing_confirmation"])
|
||
if any(
|
||
r[k] != 冻结["payload"]["target"][k]
|
||
for k in ("revision", "system_revision", "content_hash")
|
||
):
|
||
raise 迁移错误("existing_confirmation_invalid", "真实确认与冻结版本不同")
|
||
return {
|
||
"id": r["object_id"],
|
||
"revision": r["revision"],
|
||
"kind": "world_object",
|
||
"owner": "B02",
|
||
"subject_ref": 冻结["subject_ref"],
|
||
"scope": 冻结["scope"],
|
||
}
|
||
|
||
|
||
def 核对既存目标于(连, 身份, 正式, 世界, route: dict) -> tuple[dict, dict, dict]:
|
||
spec = route["content"]["link"]
|
||
声明 = spec["existing_confirmation"]
|
||
try:
|
||
回 = 正式.读取回执于(连, 身份, 声明["command_id"])
|
||
except Muse错误 as 错:
|
||
if 错.可重试:
|
||
raise
|
||
raise 迁移错误("existing_confirmation_invalid", "无法读取本作者既存确认") from None
|
||
r = _世界回执(回, route["target_ref"], 声明)
|
||
try:
|
||
历史 = 世界.读取实体轨迹于(连, 身份, route["scope"], route["target_ref"])
|
||
命中 = [v for v in 历史["版本"] if v["revision"] == r["revision"]]
|
||
if (
|
||
历史["instance_family"] != spec["instance_family"]
|
||
or 历史["type_id"] != spec["type_id"]
|
||
or len(命中) != 1
|
||
or any(命中[0][k] != r[k] for k in ("system_revision", "content_hash"))
|
||
):
|
||
raise 迁移错误("existing_target_mismatch", "世界原版本、类型或哈希不符")
|
||
s = spec["s04"]
|
||
结构 = 元数据服务(连).读取有效结构(
|
||
s["schema_id"],
|
||
s["base_version"],
|
||
work_id=route["scope"],
|
||
extension_version=s["extension_version"],
|
||
)
|
||
if 结构.基础.type_id != spec["type_id"]:
|
||
raise 迁移错误("target_type_mismatch", "目标结构类型与旧正式源不一致")
|
||
规范值 = 校验值(结构, spec["content"]).值
|
||
if (
|
||
结构.effective_schema_hash != 回["basis"]["有效结构哈希"]
|
||
or 固定哈希(规范值) != r["content_hash"]
|
||
):
|
||
raise 迁移错误("existing_target_mismatch", "原正式源规范值或历史结构不符")
|
||
except 迁移错误:
|
||
raise
|
||
except (Muse错误, ValueError) as 错:
|
||
if getattr(错, "可重试", False):
|
||
raise
|
||
raise 迁移错误("existing_target_mismatch", "公开历史或结构核验失败") from None
|
||
目标 = {
|
||
"instance_family": spec["instance_family"],
|
||
"type_id": spec["type_id"],
|
||
**{k: r[k] for k in ("revision", "system_revision", "content_hash")},
|
||
}
|
||
结构快照 = {
|
||
**s,
|
||
"effective_schema_hash": 结构.effective_schema_hash,
|
||
"projection_version": 回["basis"]["投影版本"],
|
||
}
|
||
return 回, 目标, 结构快照
|
||
|
||
|
||
def 构建关联冻结(数据库, 身份, 正式, 世界, 快照, route: dict, mapping_hash: str) -> dict:
|
||
if 正式.数据库 != 数据库 or 世界.数据库 != 数据库:
|
||
raise 迁移错误("target_denied", "关联读取器必须属于同一工厂")
|
||
with 数据库.连接(只读=True) as 连:
|
||
_, 目标, 结构 = 核对既存目标于(连, 身份, 正式, 世界, route)
|
||
spec = route["content"]["link"]
|
||
return {
|
||
"mode": "link_confirmed",
|
||
"owner": "B02",
|
||
"scope": route["scope"],
|
||
"subject_ref": route["target_ref"],
|
||
"payload": {
|
||
"source_key": 快照.source.记录键,
|
||
"source_hash": 快照.源哈希,
|
||
"mapping_hash": mapping_hash,
|
||
**{k: spec[k] for k in ("target_source", "decision_ref", "existing_confirmation")},
|
||
"target": 目标,
|
||
"s04": 结构,
|
||
},
|
||
}
|