muse-agent-example/数据库/旧库迁移/转换业务对象.py
zizi cf0f4fb985 W20–W24:保存知识方法、迁移框架、审校修订与案例行为的集成成果
接续 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。
2026-09-13 16:24:42 +08:00

339 lines
16 KiB
Python
Raw 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.

"""B02/B04提案与B05正文候选的类型化转换;不确认,不伪造研究或模型任务。"""
from __future__ import annotations
import hashlib
from dataclasses import asdict
from pydantic import TypeAdapter
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 转换目标字段
def _正版本(值) -> int:
if isinstance(值, bool) or not isinstance(值, (str, int)):
raise 迁移错误("target_version_invalid", "目标版本必须是已登记的正整数")
try:
版本 = int(值)
except ValueError:
raise 迁移错误("target_version_invalid", "目标版本不是已登记的正整数") from None
if 版本 < 1:
raise 迁移错误("target_version_invalid", "目标版本必须大于0")
return 版本
def 准备目标请求(
装配: 应用装配, 身份: 调用身份, 快照: 旧快照, 结果: 分流结果, 映射: 映射配置
) -> dict:
if not 身份.允许写正式内容 or not 身份.作者:
raise 迁移错误("target_denied", "目标提案要求已认证作者")
if 快照.source.system == "file":
return _准备正文请求(装配, 身份, 快照, 结果, 映射)
作品 = 映射.作品(快照.source, 快照.原行.get("work_id"), 映射.选择用途(快照))
if 作品["author_id"] != 身份.作者:
raise 迁移错误("target_denied", "映射中的作者与本地已认证身份不同")
if 结果.reason_code == "linked_existing_confirmation" and 结果.owner == "B02":
世界 = 装配.要求故事世界()
return 构建关联冻结(
世界.数据库,
身份,
世界.正式,
世界,
快照,
结果.导出(),
hashlib.sha256(稳定JSON(映射.值).encode()).hexdigest(),
)
if 结果.disposition != "待转换" or 结果.state != "pending":
raise 迁移错误("legacy_target_unavailable", "本入口不把旧确认声明转成新批准或活跃目标")
if 结果.owner not in {"B02", "B03", "B04"}:
raise 迁移错误("target_adapter_unavailable", "该目标域尚无旧记录公开导入接缝")
绑定 = 映射.值.get("target_schemas", {}).get(结果.owner + ":" + 结果.type_id)
if not isinstance(绑定, dict) or not isinstance(绑定.get("schema_id"), str):
raise 迁移错误("target_schema_missing", "必须明确目标S04结构与字段映射")
版本 = _正版本(绑定.get("base_version"))
with 装配.要求知识方法().数据库.连接(只读=True) as 连:
目标结构 = 元数据服务(连).读取结构(绑定["schema_id"], 版本)
if 目标结构.type_id != 结果.type_id:
raise 迁移错误("target_type_mismatch", "目标S04结构不属于已分流类型")
if 结果.type_id == "character_relation":
字段映射 = 绑定.get("fields")
if not isinstance(字段映射, dict) or any(
字段映射.get(k) != v
for k, v in (("甲方", "source"), ("乙方", "target"), ("关系类型", "relation_type"))
):
raise 迁移错误("target_relation_mapping", "关系必须使用已核对的归一端点和关系类型")
内容 = 转换目标字段(结果.content, 绑定.get("fields"))
if 结果.owner == "B03":
if 绑定.get("extension_version") is not None:
raise 迁移错误("target_schema_invalid", "参考分析不使用作品扩展")
引用 = [r for r in 结果.references if "target_ref" in r]
if not 引用 or any(r["target_ref"] != 结果.scope for r in 引用):
raise 迁移错误("target_scope_mismatch", "参考卡必须指向作品用途映射的真实资料源")
源版本 = _正版本(引用[0].get("revision"))
if any(_正版本(r.get("revision")) != 源版本 for r in 引用):
raise 迁移错误("target_source_mismatch", "同卡引用版本不一致")
with 装配.要求数据库().连接(只读=True) as 连:
源 = 装配.要求资料().读取版本依据(连, 结果.scope, 源版本)
请求 = 参考分析请求(
analysis_id=结果.target_ref,
source_id=结果.scope,
source_revision=源版本,
source_content_hash=源["content_hash"],
type_id=结果.type_id,
schema_id=绑定["schema_id"],
base_version=版本,
content=内容,
title=结果.content["name"],
brief=结果.content.get("brief", ""),
references=引用,
legacy_source={"source_key": 快照.source.记录键, "source_hash": 快照.源哈希},
)
elif 结果.owner == "B04":
引用 = tuple(
证据引用(
kind="source",
source_id=r["target_ref"],
revision=_正版本(r.get("revision")),
定位=r.get("locator", ""),
)
for r in 结果.references
if "target_ref" in r
)
if not 引用:
raise 迁移错误("target_source_missing", "方法提案必须钉住真实B03来源版本")
请求 = 方法提案请求(
method_id=结果.target_ref,
owner=身份.作者,
type_id=结果.type_id,
title=结果.content["name"],
schema=方法结构(绑定["schema_id"], 版本),
content=内容,
证据=引用,
)
else:
依据行 = [
r
for r in 映射.值.get("body_bindings", [])
if r.get("source_key") == 快照.source.记录键 and r.get("source_hash") == 快照.源哈希
]
if len(依据行) != 1 or not 依据行[0].get("sources"):
raise 迁移错误(
"target_source_missing", "有源事实缺少明确正文版本及引文,不能降级独立设定"
)
原依据 = 依据行[0]["sources"]
章级 = 快照.原行.get("source_type") == "chapter_extract"
if 章级 and (
not isinstance(原依据, list)
or any(
not isinstance(s, dict) or type(s.get("revision")) not in (str, int) for s in 原依据
)
):
raise 迁移错误(
"target_source_mismatch", "正文依据必须为对象数组,版本为明确字符串或整数"
)
依据 = tuple(TypeAdapter(正文依据).validate_python(s) for s in 原依据)
if 章级:
_核对章级来源(结果, 依据)
家族 = "relation" if 结果.type_id == "character_relation" else "entity"
结构 = 装配.要求故事世界().新事实结构(
身份,
结果.scope,
结果.target_ref,
绑定["schema_id"],
版本,
家族=家族,
扩展版本=绑定.get("extension_version"),
)
请求 = 事实提案请求(
work_id=结果.scope,
object_id=结果.target_ref,
expected_revision=0,
schema=结构,
content=内容,
sources=依据,
effective_from=依据行[0].get("effective_from"),
instance_family=家族,
independent=False,
knowledge_mode="fact",
)
return {
"owner": 结果.owner,
"scope": 结果.scope,
"subject_ref": 结果.target_ref,
"payload": asdict(请求),
}
def _核对章级来源(结果: 分流结果, 依据: tuple[正文依据, ...]) -> None:
if not 结果.references:
raise 迁移错误("target_source_mismatch", "旧章来源缺失")
原章 = 结果.references[0]
匹配 = [
s
for s in 依据
if s.chapter_id == 原章.get("target_ref") and str(s.revision) == str(原章.get("revision"))
]
引文 = 结果.content.get("source_hint", {}).get("evidence")
if not 匹配 or (
引文 is not None
and 引文 != ""
and not any(e.quote == 引文 for s in 匹配 for e in s.evidence)
):
raise 迁移错误("target_source_mismatch", "旧章声明与正文依据或原引文不一致")
def _准备正文请求(装配, 身份, 快照, 结果, 映射):
路由 = 映射.文件路由(快照)
if (
路由["route"] != "body_candidate"
or 结果.owner != "B05"
or 结果.state != "pending"
or 结果.disposition != "待转换"
):
raise 迁移错误("target_adapter_unavailable", "文件不属于未决正文候选")
作品 = 映射.作品(快照.source, 路由["legacy_work_id"], "author")
if 作品["author_id"] != 身份.作者:
raise 迁移错误("target_denied", "文件作品映射不属于当前作者")
t = 路由["target"]
当前 = 装配.要求正文().读取正文(身份, t["chapter_id"], 分支=t["branch_id"])
if 当前["work_id"] != 作品["target_ref"] or 结果.scope != 当前["work_id"]:
raise 迁移错误("target_scope_mismatch", "文件目标章不属于映射的作品")
if 当前["revision"] != t["base_revision"]:
raise 迁移错误("target_base_stale", "正文基线已变化,不自动更新映射")
return {
"owner": "B05",
"scope": 当前["work_id"],
"subject_ref": t["chapter_id"],
"payload": {
"source_key": 快照.source.记录键,
"source_hash": 快照.源哈希,
"mapping_hash": hashlib.sha256(稳定JSON(映射.值).encode()).hexdigest(),
"file_route": 路由,
"kind": "candidate",
**t,
"base_document_hash": 当前["document_hash"],
"draft": 转换文件正文(快照, 路由),
},
}
def _冻结正文草稿(冻结: dict) -> 正文草稿:
p = 冻结["payload"]
if (
p["kind"] != "candidate"
or p["chapter_id"] != 冻结["subject_ref"]
or any(
p[k] != p["file_route"]["target"][k]
for k in ("chapter_id", "branch_id", "base_revision")
)
or any(p[k] != p["file_route"][k] for k in ("source_key", "source_hash"))
):
raise 迁移错误("target_request_invalid", "冻结正文身份或基线与路由不一致")
草稿 = TypeAdapter(正文草稿).validate_python(p["draft"]["document"])
if (
正文结构哈希(草稿) != p["draft"]["document_hash"]
or 可见文本哈希(草稿) != p["draft"]["visible_text_hash"]
):
raise 迁移错误("target_request_invalid", "冻结正文内容与其哈希不一致")
return 草稿
def 提交目标请求(装配: 应用装配, 身份: 调用身份, 命令ID: str, 冻结: dict) -> dict:
if 冻结模式(冻结) == "link_confirmed":
raise 迁移错误("target_request_invalid", "历史关联不执行任何业务提交")
if 冻结["owner"] == "B05":
草稿 = _冻结正文草稿(冻结)
p = 冻结["payload"]
return 装配.要求正文().创建人工候选(
身份, 命令ID, p["chapter_id"], p["base_revision"], 草稿, 分支=p["branch_id"]
)
if 冻结["owner"] == "B03":
请求 = TypeAdapter(参考分析请求).validate_python(冻结["payload"])
if 请求.analysis_id != 冻结["subject_ref"] or 请求.source_id != 冻结["scope"]:
raise 迁移错误("target_request_invalid", "参考分析冻结的身份或来源已改变")
return 装配.要求资料().提出参考分析(身份, 命令ID, 请求)
if 冻结["owner"] == "B04":
请求 = TypeAdapter(方法提案请求).validate_python(冻结["payload"])
if 请求.method_id != 冻结["subject_ref"] or 请求.owner != 身份.作者:
raise 迁移错误("target_request_invalid", "方法冻结请求的身份已改变")
return 装配.要求知识方法().提出方法(身份, 命令ID, 请求)
if 冻结["owner"] == "B02":
请求 = TypeAdapter(事实提案请求).validate_python(冻结["payload"])
if (
请求.independent
or 请求.object_id != 冻结["subject_ref"]
or 请求.work_id != 冻结["scope"]
):
raise 迁移错误("target_request_invalid", "事实冻结请求的作用域或正文依赖已改变")
return 装配.要求故事世界().提出事实(身份, 命令ID, 请求)
raise 迁移错误("target_adapter_unavailable", "未登记的目标提交入口")
def 核对目标产物(装配: 应用装配, 身份: 调用身份, 冻结: dict, 回执: dict) -> list[dict]:
if not 身份.作者 or not 身份.允许写正式内容:
raise 迁移错误("target_denied", "迁移对账需要本地已认证作者")
if 冻结模式(冻结) == "link_confirmed":
raise 迁移错误("target_request_invalid", "历史关联必须同时核对台账正式源")
目标 = 原回执产物(冻结, 回执)
提案ID, 原版本 = 目标["id"], 目标["revision"]
if 冻结["owner"] == "B05":
_冻结正文草稿(冻结)
p = 冻结["payload"]
正文 = 装配.要求正文()
当前 = 正文.读取候选(身份, 提案ID, 版本=原版本)
基线 = 正文.读取正文(身份, p["chapter_id"], 分支=p["branch_id"], 版本=p["base_revision"])
草稿 = TypeAdapter(正文草稿).validate_python(当前["document"])
if any(当前[k] != p[k] for k in ("chapter_id", "branch_id", "base_revision")) or (
当前["origin"] != "author"
or 当前["revision"] != 原版本
or 当前["candidate_hash"] != 目标["candidate_hash"]
or 正文结构哈希(草稿) != p["draft"]["document_hash"]
or 可见文本哈希(草稿) != p["draft"]["visible_text_hash"]
or 基线["work_id"] != 冻结["scope"]
or 基线["document_hash"] != p["base_document_hash"]
):
raise 迁移错误("target_mismatch", "实际正文候选、基线或回执与冻结请求不同")
return [目标]
if 冻结["owner"] == "B03":
请求 = TypeAdapter(参考分析请求).validate_python(冻结["payload"])
with 装配.要求数据库().连接(只读=True) as 连:
当前 = 装配.要求资料().读取分析(连, 身份.作者, 提案ID)
源 = 装配.要求资料().读取版本依据(连, 请求.source_id, 请求.source_revision)
if (
当前["analysis_id"] != 冻结["subject_ref"]
or 当前["source_id"] != 冻结["scope"]
or 当前["origin"] != "legacy_import"
or 当前["revision"] != 原版本
or 当前["payload"]["request"] != asdict(请求)
or 当前["content_hash"] != 目标["content_hash"]
or 当前["source_content_hash"] != 源["content_hash"]
):
raise 迁移错误("target_mismatch", "实际分析版本或原来源与冻结请求不一致")
return [目标]
if 冻结["owner"] == "B04":
当前 = 装配.要求知识方法().读取方法提案(身份, 提案ID)
if 当前["method_id"] != 冻结["subject_ref"] or 当前["owner"] != 身份.作者:
raise 迁移错误("target_mismatch", "实际方法提案不属于原身份")
elif 冻结["owner"] == "B02":
当前 = 装配.要求故事世界().读取提案(身份, 提案ID)
if 当前["object_id"] != 冻结["subject_ref"] or 当前["work_id"] != 冻结["scope"]:
raise 迁移错误("target_mismatch", "实际世界提案不属于原作用域")
else:
raise 迁移错误("target_adapter_unavailable", "未登记的目标回查入口")
if 当前["revision"] < 原版本:
raise 迁移错误("target_mismatch", "实际候选版本低于原回执")
# 导入版本取S01原回执,不以作者后续编辑后的当前版本冒充导入结果。
return [目标]