feat(上下文): 复利机制批次3——参考改写扩展选段授权链,偏好与参考材料进冻结快照并受依赖复检
This commit is contained in:
parent
8ad28be021
commit
02063c8454
@ -20814,6 +20814,14 @@
|
||||
"type": "array",
|
||||
"title": "Protected Ranges",
|
||||
"default": []
|
||||
},
|
||||
"references": {
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array",
|
||||
"title": "References",
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
|
||||
@ -20,6 +20,7 @@
|
||||
| 读取方法评测材料 | 维护事务、作者、具体确认版本ID | 核原确认内容、版本与证据,按S04 generation/evaluation投影;只形成B10受批准的资料快照,不启用方法或写生产消费 |
|
||||
| 核对方法评测投影 | 同用途元数据连接和已封存材料 | 重验内容/结构/字段策略哈希;评测不直接读正式方法表 |
|
||||
| 读取消费统计 | 作者、方法版本范围 | 按版本聚合的实际消费计数,供 B09 偏好重排的强信号路;只读,不改消费台账,不把被检索或任务成功当作消费 |
|
||||
| 读取参考材料 | 作者、确切方法版本清单、两种用途 | 当前且已确认或启用版本的参考材料渲染与哈希,供参考改写冻结与依赖复检;不查启用凭据、不写消费 |
|
||||
|
||||
所有接口在本模块的接口.py 暴露。输入输出类型由同一公开表面导出,外部不导入私有模型或 SQL。
|
||||
|
||||
|
||||
@ -237,6 +237,7 @@ class 上下文服务:
|
||||
含目录: bool = False,
|
||||
含声音: bool = False,
|
||||
含偏好: bool = False,
|
||||
参考版本: tuple[str, ...] = (),
|
||||
) -> dict:
|
||||
return 授权任务范围(
|
||||
self.数据库,
|
||||
@ -251,6 +252,7 @@ class 上下文服务:
|
||||
含目录=含目录,
|
||||
含声音=含声音,
|
||||
含偏好=含偏好,
|
||||
参考版本=参考版本,
|
||||
)
|
||||
|
||||
def 读取范围(self, 任务ID: str) -> dict:
|
||||
@ -276,6 +278,7 @@ class 上下文服务:
|
||||
核对声音装配(范围, 装配, 绑定, 声音)
|
||||
核对偏好装配(范围, 装配, 绑定, 核对范围偏好(连, 范围))
|
||||
方法 = 核对范围方法(连, 范围)
|
||||
from muse.上下文.参考材料 import 核对范围参考
|
||||
from muse.上下文.材料装配 import 按键读取材料
|
||||
|
||||
入选方法 = [条 for 条 in 方法 if 条["材料键"] in 装配.入选来源]
|
||||
@ -283,6 +286,12 @@ class 上下文服务:
|
||||
实际材料 = 按键读取材料(装配)
|
||||
if any(实际材料.get(条["材料键"]) != 条["材料"] for 条 in 入选方法):
|
||||
raise 上下文错误("CONTEXT_INPUT_INVALID", "入选方法正文与授权材料不一致")
|
||||
参考 = 核对范围参考(连, 范围)
|
||||
入选参考 = [条 for 条 in 参考 if 条["材料键"] in 装配.入选来源]
|
||||
if 入选参考:
|
||||
实际材料 = 按键读取材料(装配)
|
||||
if any(实际材料.get(条["材料键"]) != 条["材料"] for 条 in 入选参考):
|
||||
raise 上下文错误("CONTEXT_INPUT_INVALID", "入选参考正文与授权材料不一致")
|
||||
新建 = 上下文存储(连, self.数据库.用途).写入快照(
|
||||
{
|
||||
"snapshot_id": 身份,
|
||||
|
||||
@ -285,6 +285,24 @@ def 核对依赖(
|
||||
锁定.append(
|
||||
依赖锁定(条["preference_id"], "preference", str(条["revision"]), 偏好结构, 偏好投影)
|
||||
)
|
||||
|
||||
from muse.上下文.参考材料 import 核对范围参考
|
||||
|
||||
try:
|
||||
参考 = 核对范围参考(连, 冻结范围)
|
||||
except 上下文错误 as 错误:
|
||||
变化项.append(错误.说明)
|
||||
else:
|
||||
for 条 in 参考:
|
||||
锁定.append(
|
||||
依赖锁定(
|
||||
条["version_id"],
|
||||
"reference",
|
||||
str(条["version"]),
|
||||
条["schema_hash"],
|
||||
条["projection_version"],
|
||||
)
|
||||
)
|
||||
return 依赖状态(有效=not 变化项, 锁定依据=tuple(锁定), 变化项=tuple(变化项))
|
||||
|
||||
|
||||
|
||||
89
src/muse/上下文/参考材料.py
Normal file
89
src/muse/上下文/参考材料.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""B04参考方法材料的确切投影;参考是作者明确挑选的来源,不是作品绑定。
|
||||
|
||||
参考改写合同见模块设计/B06-审校修订.md「参考改写(选段改写链路扩展)」:
|
||||
挑选发生在流程发起前,选中的方法版本作为授权输入进入冻结范围;
|
||||
材料是可选材料语义,漂移即整体拒绝,不部分放行。
|
||||
"""
|
||||
|
||||
from muse.上下文.模型 import 上下文错误, 来源绑定
|
||||
|
||||
_身份字段 = (
|
||||
"version_id",
|
||||
"method_id",
|
||||
"version",
|
||||
"材料键",
|
||||
"材料哈希",
|
||||
"schema_hash",
|
||||
"projection_version",
|
||||
)
|
||||
|
||||
|
||||
def 冻结参考身份(条: dict) -> dict:
|
||||
"""范围冻结只保留身份与哈希;材料文本不入范围,读取时经B04现取。"""
|
||||
return {键: 条[键] for 键 in _身份字段}
|
||||
|
||||
|
||||
def 校验参考节(范围: dict) -> None:
|
||||
参考 = 范围.get("references")
|
||||
if 参考 is None:
|
||||
return
|
||||
if not isinstance(参考, list):
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", "冻结参考清单必须是列表")
|
||||
已见: set[str] = set()
|
||||
for 条 in 参考:
|
||||
if not isinstance(条, dict) or any(
|
||||
not isinstance(条.get(键), str) or not 条[键]
|
||||
for 键 in (
|
||||
"version_id",
|
||||
"method_id",
|
||||
"材料键",
|
||||
"材料哈希",
|
||||
"schema_hash",
|
||||
"projection_version",
|
||||
)
|
||||
):
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", "冻结参考缺少版本或材料身份")
|
||||
if type(条.get("version")) is not int or 条["version"] < 1:
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", "冻结参考版本必须为正整数")
|
||||
if 条["version_id"] in 已见 or 条["材料键"] != "参考方法:" + 条["version_id"]:
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", "冻结参考重复或材料键不匹配")
|
||||
已见.add(条["version_id"])
|
||||
|
||||
|
||||
def 核对范围参考(连, 范围: dict, *, 读取预算=None) -> list[dict]:
|
||||
"""按冻结身份现读当前材料并逐项核对;任一漂移即整体拒绝。"""
|
||||
校验参考节(范围)
|
||||
参考 = 范围.get("references")
|
||||
if not 参考:
|
||||
return []
|
||||
from muse.知识方法.接口 import 方法错误, 读取参考材料
|
||||
|
||||
try:
|
||||
当前 = 读取参考材料(
|
||||
连,
|
||||
tuple(条["version_id"] for 条 in 参考),
|
||||
作者=范围["authorized_by"],
|
||||
内容用途=范围["content_purpose"],
|
||||
运行用途=范围["run_purpose"],
|
||||
读取预算=读取预算,
|
||||
)
|
||||
except 方法错误 as 错:
|
||||
raise 上下文错误("SOURCE_STALE", f"参考方法不可用:{错.说明}") from 错
|
||||
按版本 = {条["version_id"]: 条 for 条 in 当前}
|
||||
结果 = []
|
||||
for 条 in 参考:
|
||||
现 = 按版本.get(条["version_id"])
|
||||
if 现 is None:
|
||||
raise 上下文错误("SOURCE_STALE", f"参考方法 {条['version_id']} 已不在当前清单")
|
||||
if any(
|
||||
现[键] != 条[键] for 键 in ("version", "材料哈希", "schema_hash", "projection_version")
|
||||
):
|
||||
raise 上下文错误("SOURCE_STALE", f"参考方法 {条['version_id']} 材料或投影已漂移")
|
||||
结果.append({**条, "材料": 现["材料"], "title": 现["title"], "type_id": 现["type_id"]})
|
||||
return 结果
|
||||
|
||||
|
||||
def 参考绑定(条: dict) -> 来源绑定:
|
||||
return 来源绑定(
|
||||
条["version_id"], str(条["version"]), 条["schema_hash"], 条["projection_version"]
|
||||
)
|
||||
@ -16,6 +16,7 @@ from muse.上下文.冻结快照 import (
|
||||
文本哈希,
|
||||
输入哈希,
|
||||
)
|
||||
from muse.上下文.参考材料 import 参考绑定, 校验参考节, 核对范围参考
|
||||
from muse.上下文.回放材料 import 历史版本选择, 回放材料选择, 核对回放字段策略, 读取回放材料
|
||||
from muse.上下文.声音材料 import 核对范围声音
|
||||
from muse.上下文.探索回放 import (
|
||||
@ -80,6 +81,9 @@ __all__ = [
|
||||
"核对范围声音",
|
||||
"核对范围偏好",
|
||||
"偏好绑定",
|
||||
"核对范围参考",
|
||||
"参考绑定",
|
||||
"校验参考节",
|
||||
"核对方法消费材料",
|
||||
"上下文快照",
|
||||
"上下文错误",
|
||||
|
||||
@ -51,10 +51,15 @@ def 授权任务范围(
|
||||
含目录: bool = False,
|
||||
含声音: bool = False,
|
||||
含偏好: bool = False,
|
||||
参考版本: tuple[str, ...] = (),
|
||||
) -> dict:
|
||||
"""按作者身份冻结历史文稿、目标章细纲与事实系统版本;两种用途随范围绑定。"""
|
||||
if not 作品ID:
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", "任务范围必须绑定明确作品")
|
||||
if isinstance(参考版本, str) or not isinstance(参考版本, tuple):
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", f"参考版本必须以元组显式声明:{参考版本!r}")
|
||||
if len(参考版本) > 8:
|
||||
raise 上下文错误("CONTEXT_SCOPE_INVALID", "参考方法版本不超过8个")
|
||||
if 身份.作者 is None or 身份.用途 is not 数据库.用途:
|
||||
raise 上下文错误("SCOPE_DENIED", "任务范围授权需要与装配用途一致的作者身份")
|
||||
校验截止(截止)
|
||||
@ -126,8 +131,25 @@ def 授权任务范围(
|
||||
else None
|
||||
)
|
||||
偏好 = 偏好身份(读取偏好投影(连, 身份.作者, 作品ID, 身份.任务)) if 含偏好 else []
|
||||
参考: list[dict] = []
|
||||
if 参考版本:
|
||||
from muse.上下文.参考材料 import 冻结参考身份
|
||||
from muse.知识方法.接口 import 读取参考材料
|
||||
|
||||
参考 = [
|
||||
冻结参考身份(条)
|
||||
for 条 in 读取参考材料(
|
||||
连,
|
||||
参考版本,
|
||||
作者=身份.作者,
|
||||
内容用途=内容用途,
|
||||
运行用途=运行用途,
|
||||
读取预算=用量.预留来源,
|
||||
)
|
||||
]
|
||||
来源集 = [d["document_id"] for d in 文稿]
|
||||
来源集.extend(x["preference_id"] for x in 偏好)
|
||||
来源集.extend(条["version_id"] for 条 in 参考)
|
||||
if 声音 is not None:
|
||||
来源集.append(声音["version_id"])
|
||||
来源集.extend(条["binding_id"] for 条 in 方法)
|
||||
@ -167,6 +189,8 @@ def 授权任务范围(
|
||||
范围["preference_task"] = 身份.任务
|
||||
if 含方法:
|
||||
范围["methods"] = 方法
|
||||
if 参考:
|
||||
范围["references"] = 参考
|
||||
if 细纲 is not None:
|
||||
范围["target_plan"] = 细纲
|
||||
if 事实 is not None:
|
||||
|
||||
@ -8,6 +8,7 @@ from muse.上下文.偏好材料 import (
|
||||
校验偏好节,
|
||||
核对偏好装配,
|
||||
)
|
||||
from muse.上下文.参考材料 import 校验参考节
|
||||
from muse.上下文.声音材料 import (
|
||||
校验声音节,
|
||||
核对声音装配,
|
||||
@ -77,6 +78,23 @@ def _校验冻结输入(
|
||||
条["projection_version"],
|
||||
):
|
||||
raise 上下文错误("SOURCE_STALE", "方法版本或材料哈希与授权不一致")
|
||||
参考集 = {条["version_id"]: 条 for 条 in 范围.get("references", [])}
|
||||
参考键 = {条["材料键"] for 条 in 参考集.values()}
|
||||
if any(键.startswith("参考方法:") and 键 not in 参考键 for 键 in 装配.入选来源):
|
||||
raise 上下文错误("SCOPE_DENIED", "装配包含未授权参考方法材料")
|
||||
入选参考 = {vid for vid, 条 in 参考集.items() if 条["材料键"] in 装配.入选来源}
|
||||
实绑参考 = {条.来源ID for 条 in 绑定 if 条.来源ID in 参考集}
|
||||
if 入选参考 != 实绑参考:
|
||||
raise 上下文错误("CONTEXT_INPUT_INVALID", "入选参考与冻结版本绑定不一致")
|
||||
for 绑 in 绑定:
|
||||
if 绑.来源ID in 参考集:
|
||||
条 = 参考集[绑.来源ID]
|
||||
if (绑.数据版本, 绑.结构哈希, 绑.投影版本) != (
|
||||
str(条["version"]),
|
||||
条["schema_hash"],
|
||||
条["projection_version"],
|
||||
):
|
||||
raise 上下文错误("SOURCE_STALE", "参考版本或材料哈希与授权不一致")
|
||||
目录源 = 范围.get("directory_source")
|
||||
if 目录源:
|
||||
for 项 in 绑定:
|
||||
@ -159,11 +177,13 @@ def 恢复冻结范围(数据库: 数据库工厂, 任务: 任务快照) -> dict
|
||||
or 事实 is not None
|
||||
or 目录源 is not None
|
||||
or 范围.get("methods")
|
||||
or 范围.get("references")
|
||||
or "voice" in 范围
|
||||
):
|
||||
_校验用途绑定(范围, 固定)
|
||||
校验声音节(范围.get("voice"))
|
||||
校验偏好节(范围)
|
||||
校验参考节(范围)
|
||||
if 范围.get("preference_task") not in (None, 任务.任务ID):
|
||||
raise 上下文错误("SCOPE_DENIED", "偏好绑定的任务不是当前任务")
|
||||
身份集 = 范围.get("source_ids")
|
||||
@ -200,6 +220,7 @@ def 恢复冻结范围(数据库: 数据库工厂, 任务: 任务快照) -> dict
|
||||
方法ID.add(条["binding_id"])
|
||||
基础 = {项["document_id"] for 项 in 文稿} | 方法ID
|
||||
基础.update(x["preference_id"] for x in 范围.get("preferences", []))
|
||||
基础.update(x["version_id"] for x in 范围.get("references", []))
|
||||
if 范围.get("voice") is not None:
|
||||
基础.add(范围["voice"]["version_id"])
|
||||
if 目录源 is not None:
|
||||
|
||||
@ -6,4 +6,6 @@ expand在原场景内补动作、对话和感官细节;compress删去冗余但
|
||||
|
||||
原文、附近语境、作者指示与已确认声音由上下文提供。保留人物、数量、时间、推测情态、引文和保护表达,不以其他修改抵消某一处损失。改变因果或事实需要作者另行明确判断,不能自称已通过语义核验。
|
||||
|
||||
上下文若提供“参考方法:”前缀的材料,那是作者为本次改写明确挑选的参考范式:只借鉴其节奏、技法与细节处理方式,结合修订目标、人物与声音重新原创;不照抄参考材料的句子,不把其中的人名、地名、专有名词或数字带入选段,也不因参考材料而扩大改写范围。没有参考材料时不虚构参考。
|
||||
|
||||
只返回输出合同允许的字段。位置和候选身份由服务端决定,不编造诊断、调用回执或作者批准。
|
||||
|
||||
@ -87,6 +87,7 @@ class 选段写作授权:
|
||||
purpose: str
|
||||
selections: tuple[原文选段, ...]
|
||||
protected_ranges: tuple[原文选段, ...] = ()
|
||||
references: tuple[str, ...] = ()
|
||||
|
||||
def __post_init__(self):
|
||||
if self.revision < 1 or not self.purpose.strip() or not 1 <= len(self.selections) <= 32:
|
||||
@ -95,6 +96,12 @@ class 选段写作授权:
|
||||
raise 正文错误("选段改写需明确作品、章节及分支")
|
||||
if len({s.selection_id for s in self.selections}) != len(self.selections):
|
||||
raise 正文错误("圈选不能重复")
|
||||
if len(self.references) > 8:
|
||||
raise 正文错误("参考方法不超过8个")
|
||||
if len(set(self.references)) != len(self.references):
|
||||
raise 正文错误("参考方法版本不能重复")
|
||||
if not all(type(r) is str and r.strip() for r in self.references):
|
||||
raise 正文错误("参考方法需要非空版本身份")
|
||||
|
||||
|
||||
def 准备选段改写(连, 作者, 授权: 选段写作授权):
|
||||
|
||||
@ -13,6 +13,7 @@ from muse.正式变更.接口 import 作者动作, 变更命令, 正式变更服
|
||||
from muse.知识方法.存储 import 方法存储
|
||||
from muse.知识方法.方法卡 import (
|
||||
列出方法族类型,
|
||||
读取参考材料,
|
||||
读取方法检索候选,
|
||||
读取方法检索资料,
|
||||
读取方法检索身份,
|
||||
@ -304,6 +305,7 @@ __all__ = [
|
||||
"消费输入",
|
||||
"读取方法检索资料",
|
||||
"读取方法检索候选",
|
||||
"读取参考材料",
|
||||
"读取方法检索身份",
|
||||
"读取方法索引维护资料",
|
||||
"读取版本材料大小",
|
||||
|
||||
@ -9,6 +9,7 @@ from __future__ import annotations
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from muse.元数据.接口 import 元数据服务, 投影值, 投影字段, 授权摘要, 校验值, 选择实例维护者
|
||||
from muse.正式变更.接口 import 固定哈希
|
||||
@ -201,6 +202,97 @@ def 读取方法检索候选(
|
||||
}
|
||||
|
||||
|
||||
def 读取参考材料(
|
||||
连,
|
||||
version_ids: tuple[str, ...],
|
||||
*,
|
||||
作者: str,
|
||||
内容用途: str,
|
||||
运行用途: str,
|
||||
读取预算=None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""按确切版本清单渲染参考材料;只接受作者自有、当前且已确认或启用的版本。
|
||||
|
||||
参考是作者明确挑选的来源,不是作品绑定:不查启用凭据、不写消费记录;
|
||||
供参考改写的冻结与依赖复检使用,材料与哈希形状与绑定材料同构。
|
||||
"""
|
||||
from muse.元数据.接口 import 元数据服务, 元数据错误, 投影值, 投影字段
|
||||
from muse.正式变更.接口 import 固定哈希
|
||||
from muse.知识方法.模型 import 证据引用
|
||||
from muse.知识方法.证据反例 import 核对证据
|
||||
|
||||
if isinstance(version_ids, str) or not isinstance(version_ids, (tuple, list)):
|
||||
raise 方法错误("METHOD_INPUT_INVALID", "参考版本清单必须是元组或列表")
|
||||
存储 = 方法存储(连)
|
||||
已见: set[str] = set()
|
||||
材料: list[dict[str, Any]] = []
|
||||
for 原始 in version_ids:
|
||||
vid = str(原始)
|
||||
if not vid.strip():
|
||||
raise 方法错误("METHOD_INPUT_INVALID", "参考版本需要非空身份")
|
||||
try:
|
||||
UUID(vid)
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
raise 方法错误("METHOD_NOT_FOUND", f"参考方法 {vid} 已不存在") from None
|
||||
if vid in 已见:
|
||||
raise 方法错误("METHOD_INPUT_INVALID", f"参考版本重复声明:{vid}")
|
||||
已见.add(vid)
|
||||
头 = 存储.读取检索版本头(vid)
|
||||
if 头 is None:
|
||||
raise 方法错误("METHOD_NOT_FOUND", f"参考方法 {vid} 已不存在")
|
||||
if 头["owner"] != 作者:
|
||||
raise 方法错误("SCOPE_DENIED", "参考方法不属于当前作者")
|
||||
if 头["version"] != 头["current_version"]:
|
||||
raise 方法错误("METHOD_VERSION_STALE", "参考版本不是方法当前版本")
|
||||
if 头["state"] not in {"confirmed", "enabled"}:
|
||||
raise 方法错误("METHOD_STATE_UNAVAILABLE", "参考方法当前状态不可用")
|
||||
if 读取预算 is not None:
|
||||
读取预算(读取版本材料大小(连, vid))
|
||||
版本, 证据 = 读取版本材料(连, vid)
|
||||
if 版本["proposal_id"] is None:
|
||||
raise 方法错误("EVIDENCE_INVALID", "方法版本缺少确认提案,只能历史回查")
|
||||
核对证据(
|
||||
连,
|
||||
作者,
|
||||
tuple(证据引用(kind=证["kind"], **证["ref"], note=证["note"]) for 证 in 证据),
|
||||
)
|
||||
元数据 = 元数据服务(连)
|
||||
try:
|
||||
结构 = 元数据.读取有效结构(版本["schema_id"], 版本["schema_version"])
|
||||
if 结构.effective_schema_hash != 版本["effective_schema_hash"]:
|
||||
raise 方法错误("SOURCE_STALE", "方法版本的结构依据已变化")
|
||||
投影 = 投影字段(
|
||||
结构,
|
||||
元数据.当前策略(版本["type_id"]),
|
||||
方法授权(版本["owner"]),
|
||||
字段用途名="aiContext",
|
||||
内容用途=内容用途,
|
||||
运行用途=运行用途,
|
||||
)
|
||||
内容 = 投影值(投影, 版本["content"])
|
||||
except 元数据错误 as 错:
|
||||
raise 方法错误("SOURCE_STALE", f"方法字段用途或结构不可用:{错}") from 错
|
||||
if not 内容:
|
||||
raise 方法错误("SOURCE_STALE", "该方法没有允许当前模型用途的内容字段")
|
||||
文本 = 渲染方法材料({**版本, "content": 内容}, 证据)
|
||||
材料.append(
|
||||
{
|
||||
"材料键": "参考方法:" + vid,
|
||||
"method_id": str(版本["method_id"]),
|
||||
"version_id": vid,
|
||||
"version": int(头["version"]),
|
||||
"type_id": 版本["type_id"],
|
||||
"title": 版本["title"],
|
||||
"材料": 文本,
|
||||
"材料哈希": 固定哈希([文本]),
|
||||
"schema_hash": 结构.effective_schema_hash,
|
||||
"projection_version": 投影.projection_version,
|
||||
"参数上限": 版本["参数上限"],
|
||||
}
|
||||
)
|
||||
return 材料
|
||||
|
||||
|
||||
def 读取方法检索身份(连, *, 作者: str | None = None, 数量: int = 5001) -> tuple[str, ...]:
|
||||
"""当前可发现版本的轻量清单;不展开正文、提案或来源证据。"""
|
||||
return tuple(
|
||||
|
||||
@ -10,8 +10,12 @@ from uuid import uuid4
|
||||
|
||||
from muse.上下文.接口 import (
|
||||
依赖重验检查,
|
||||
偏好绑定,
|
||||
参考绑定,
|
||||
投影版本,
|
||||
来源绑定,
|
||||
核对范围偏好,
|
||||
核对范围参考,
|
||||
核对范围声音,
|
||||
装配材料,
|
||||
)
|
||||
@ -107,6 +111,8 @@ def 发起模型修订(app, 身份, 命令ID, 授权: 模型修订授权 | 选
|
||||
),
|
||||
含目录=True,
|
||||
含声音=True,
|
||||
含偏好=True,
|
||||
参考版本=tuple(授权.references) if isinstance(授权, 选段写作授权) else (),
|
||||
)
|
||||
策略 = 角色策略目录.从发布包()
|
||||
request = 任务请求(
|
||||
@ -167,7 +173,10 @@ def 登记审校修订(登记, app, 计价):
|
||||
_, _, _, text = 核对修订任务(连, ctx.任务)
|
||||
scope = 上下文.恢复冻结范围(ctx.任务)
|
||||
voice = 核对范围声音(连, scope)
|
||||
偏好材料 = 核对范围偏好(连, scope)
|
||||
参考材料 = 核对范围参考(连, scope)
|
||||
材料 = {"修订目标": json.dumps(text, ensure_ascii=False, sort_keys=True)}
|
||||
必读 = ["修订目标"]
|
||||
doc = scope["documents"][0]
|
||||
bindings = [
|
||||
来源绑定(
|
||||
@ -179,6 +188,7 @@ def 登记审校修订(登记, app, 计价):
|
||||
]
|
||||
if voice is not None:
|
||||
材料[voice["材料键"]] = voice["材料"]
|
||||
必读.append(voice["材料键"])
|
||||
bindings.append(
|
||||
来源绑定(
|
||||
voice["version_id"],
|
||||
@ -187,7 +197,17 @@ def 登记审校修订(登记, app, 计价):
|
||||
voice["projection_version"],
|
||||
)
|
||||
)
|
||||
组装 = 装配材料(材料, tuple(材料), 字节上限=65536)
|
||||
for 条 in 偏好材料:
|
||||
材料[条["材料键"]] = 条["材料"]
|
||||
必读.append(条["材料键"])
|
||||
bindings.append(偏好绑定(条))
|
||||
# 参考方法是可选材料:超预算记省略原因,不拒绝整轮修订。
|
||||
for 条 in 参考材料:
|
||||
材料[条["材料键"]] = 条["材料"]
|
||||
组装 = 装配材料(材料, tuple(必读), 字节上限=65536)
|
||||
for 条 in 参考材料:
|
||||
if 条["材料键"] in 组装.入选来源:
|
||||
bindings.append(参考绑定(条))
|
||||
snap = 上下文.冻结上下文(ctx.任务.任务ID, 组装, tuple(bindings))
|
||||
return 步骤结果({"snapshot_id": snap.快照ID}, 检查点={"snapshot_id": snap.快照ID})
|
||||
|
||||
|
||||
77
tests/契约/test_参考改写合同.py
Normal file
77
tests/契约/test_参考改写合同.py
Normal file
@ -0,0 +1,77 @@
|
||||
"""参考改写授权合同:references 有界、唯一、非空;旧授权载荷向后兼容。"""
|
||||
|
||||
import pytest
|
||||
from pydantic import TypeAdapter, ValidationError
|
||||
|
||||
from muse.共享.错误 import Muse错误
|
||||
from muse.正文写作.接口 import 原文选段, 选段写作授权
|
||||
|
||||
|
||||
def _选段():
|
||||
return 原文选段("p1", "0" * 64, 0, 4, "雨声很轻")
|
||||
|
||||
|
||||
def _授权(**changes):
|
||||
base = {
|
||||
"work_id": "w1",
|
||||
"chapter_id": "c1",
|
||||
"branch_id": "main",
|
||||
"revision": 1,
|
||||
"document_hash": "a" * 64,
|
||||
"mode": "rewrite",
|
||||
"purpose": "改为机甲近身刀法",
|
||||
"selections": (_选段(),),
|
||||
}
|
||||
return 选段写作授权(**{**base, **changes})
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10008",
|
||||
environment="离线",
|
||||
given="固定选段授权与参考方法版本清单",
|
||||
when="构造带 references 的选段写作授权",
|
||||
then=["参考清单有界、唯一、非空;默认空清单向后兼容"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
)
|
||||
def test_参考清单有界唯一非空且默认兼容__a10008():
|
||||
assert _授权().references == ()
|
||||
assert _授权(references=("v1", "v2")).references == ("v1", "v2")
|
||||
with pytest.raises(Muse错误):
|
||||
_授权(references=tuple(f"v{i}" for i in range(9)))
|
||||
with pytest.raises(Muse错误):
|
||||
_授权(references=("v1", "v1"))
|
||||
with pytest.raises(Muse错误):
|
||||
_授权(references=("v1", " "))
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10009",
|
||||
environment="离线",
|
||||
given="不含 references 字段的旧授权载荷",
|
||||
when="经公开边界重建授权",
|
||||
then=["旧载荷按空参考清单兼容;注入未知字段拒绝"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
)
|
||||
def test_旧授权载荷兼容且未知字段拒绝__a10009():
|
||||
adapter = TypeAdapter(选段写作授权)
|
||||
payload = {
|
||||
"work_id": "w1",
|
||||
"chapter_id": "c1",
|
||||
"branch_id": "main",
|
||||
"revision": 1,
|
||||
"document_hash": "a" * 64,
|
||||
"mode": "rewrite",
|
||||
"purpose": "改为机甲近身刀法",
|
||||
"selections": [
|
||||
{
|
||||
"paragraph_id": "p1",
|
||||
"paragraph_hash": "0" * 64,
|
||||
"start": 0,
|
||||
"end": 4,
|
||||
"original": "雨声很轻",
|
||||
}
|
||||
],
|
||||
}
|
||||
assert adapter.validate_python(payload).references == ()
|
||||
with pytest.raises(ValidationError):
|
||||
adapter.validate_python({**payload, "references": ["v1"], "unknown_field": True})
|
||||
145
tests/用例清单.json
145
tests/用例清单.json
@ -27096,6 +27096,151 @@
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10008",
|
||||
"environment": "离线",
|
||||
"given": "固定选段授权与参考方法版本清单",
|
||||
"when": "构造带 references 的选段写作授权",
|
||||
"then": [
|
||||
"参考清单有界、唯一、非空;默认空清单向后兼容"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
"file": "tests/契约/test_参考改写合同.py",
|
||||
"symbol": "test_参考清单有界唯一非空且默认兼容__a10008",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_参考改写合同.py::test_参考清单有界唯一非空且默认兼容__a10008"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10009",
|
||||
"environment": "离线",
|
||||
"given": "不含 references 字段的旧授权载荷",
|
||||
"when": "经公开边界重建授权",
|
||||
"then": [
|
||||
"旧载荷按空参考清单兼容;注入未知字段拒绝"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
"file": "tests/契约/test_参考改写合同.py",
|
||||
"symbol": "test_旧授权载荷兼容且未知字段拒绝__a10009",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/契约/test_参考改写合同.py::test_旧授权载荷兼容且未知字段拒绝__a10009"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"离线防护"
|
||||
],
|
||||
"markers": []
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10010",
|
||||
"environment": "隔离PG与合成HTTP提供方",
|
||||
"given": "带参考版本与活跃偏好的选段改写授权",
|
||||
"when": "发起并执行受控修订流程",
|
||||
"then": [
|
||||
"偏好与参考材料入选装配并绑定进快照;写手输入携带材料"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
"file": "tests/集成/test_参考改写.py",
|
||||
"symbol": "test_偏好与参考材料进快照与写手输入__a10010",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_参考改写.py::test_偏好与参考材料进快照与写手输入__a10010"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"参考环境",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"生成环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10011",
|
||||
"environment": "隔离PG与合成HTTP提供方",
|
||||
"given": "携带不存在参考版本的选段授权",
|
||||
"when": "发起受控修订",
|
||||
"then": [
|
||||
"授权范围阶段快速拒绝,不创建任务"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
"file": "tests/集成/test_参考改写.py",
|
||||
"symbol": "test_未知参考版本在发起时拒绝__a10011",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_参考改写.py::test_未知参考版本在发起时拒绝__a10011"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"参考环境",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"生成环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-w32-a10012",
|
||||
"environment": "隔离PG与合成HTTP提供方",
|
||||
"given": "已冻结的参考改写任务",
|
||||
"when": "参考方法推进新版本后读取快照",
|
||||
"then": [
|
||||
"旧参考版本不再当前,快照标注失效且要求重新授权"
|
||||
],
|
||||
"contract": "docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
"file": "tests/集成/test_参考改写.py",
|
||||
"symbol": "test_参考版本漂移使快照失效__a10012",
|
||||
"parameter_ids": [],
|
||||
"node_ids": [
|
||||
"tests/集成/test_参考改写.py::test_参考版本漂移使快照失效__a10012"
|
||||
],
|
||||
"fixtures": [
|
||||
"request",
|
||||
"tmp_path",
|
||||
"tmp_path_factory",
|
||||
"内置种子方案",
|
||||
"内置结构测试库",
|
||||
"参考环境",
|
||||
"数据库底座",
|
||||
"测试资源接缝",
|
||||
"源码资源",
|
||||
"生成环境",
|
||||
"离线防护",
|
||||
"隔离数据库URL"
|
||||
],
|
||||
"markers": [
|
||||
"数据库"
|
||||
]
|
||||
},
|
||||
{
|
||||
"case_id": "NC-work-current-binding",
|
||||
"environment": "隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程",
|
||||
|
||||
183
tests/集成/test_参考改写.py
Normal file
183
tests/集成/test_参考改写.py
Normal file
@ -0,0 +1,183 @@
|
||||
"""参考改写真实链路:授权携带参考版本,偏好与参考材料进冻结快照;漂移即失效。"""
|
||||
|
||||
import json
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
import test_上下文快照与索引写入 as 方法测试
|
||||
import test_两阶段写手 as 写手测试
|
||||
import test_模型修订任务 as 修订测试
|
||||
|
||||
from muse.任务运行.接口 import 任务状态
|
||||
from muse.作者经验.接口 import 偏好范围, 偏好请求
|
||||
from muse.共享.错误 import Muse错误
|
||||
from muse.启动 import 装配审校修订
|
||||
from muse.正文写作.接口 import 原文选段, 文本节点, 正文草稿, 段落, 段落哈希, 选段写作授权
|
||||
from muse.资料研究.接口 import 导入请求, 资料服务
|
||||
|
||||
pytestmark = pytest.mark.数据库
|
||||
生成环境 = 修订测试.生成环境
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def 参考环境(生成环境):
|
||||
env = 生成环境
|
||||
装配 = env["装配"]
|
||||
装配审校修订(
|
||||
装配, 写手测试.合成计价(), Path(__file__).parents[2] / "配置/流程模板/审校修订.yaml"
|
||||
)
|
||||
doc = 正文草稿(
|
||||
(段落("p1", (文本节点("机甲的刀锋嗡鸣。"),)), 段落("p2", (文本节点("火花四散。"),)))
|
||||
)
|
||||
env["正文"].保存人工(env["作者"], "ref-seed", "ch-4", 0, doc)
|
||||
saved = env["正文"].读取正文(env["作者"], "ch-4")
|
||||
|
||||
方法 = 装配.要求知识方法()
|
||||
with 方法.数据库.连接() as 连:
|
||||
来源 = 资料服务().导入(
|
||||
连,
|
||||
env["作者"].作者,
|
||||
导入请求(
|
||||
kind="reference",
|
||||
title="机甲打斗参考",
|
||||
origin="seed://ref-method",
|
||||
content="机甲近身刀法借势卸力,受击反馈优先于光效描写。",
|
||||
authorized_uses=("analysis",),
|
||||
),
|
||||
)
|
||||
请求 = 方法测试._提案(str(uuid4()), "机甲近身刀法", str(来源.source_id), owner=env["作者"].作者)
|
||||
回执 = 方法.提出方法(env["作者"], str(uuid4()), 请求)
|
||||
方法测试._确认(方法, env["作者"], 回执["target_ref"])
|
||||
版本 = 方法.读取方法详情(env["作者"], 请求.method_id)["versions"][0]
|
||||
|
||||
经验 = 装配.要求作者经验()
|
||||
经验.保存偏好(
|
||||
env["作者"],
|
||||
str(uuid4()),
|
||||
偏好请求(
|
||||
preference_id=str(uuid4()),
|
||||
topic="打斗描写",
|
||||
text="重视受击反馈,不堆砌形容词。",
|
||||
quote="今后重视受击反馈。",
|
||||
scope=偏好范围(),
|
||||
),
|
||||
)
|
||||
|
||||
选段 = 原文选段("p2", 段落哈希(doc.paragraphs[1]), 0, 5, "火花四散。")
|
||||
授权 = 选段写作授权(
|
||||
"gen-work",
|
||||
"ch-4",
|
||||
"main",
|
||||
saved["revision"],
|
||||
saved["document_hash"],
|
||||
"rewrite",
|
||||
"改为机甲近身刀法,参考所选方法",
|
||||
(选段,),
|
||||
(),
|
||||
references=(str(版本["version_id"]),),
|
||||
)
|
||||
env.update(
|
||||
授权=授权,
|
||||
选段=选段,
|
||||
参考版本=str(版本["version_id"]),
|
||||
参考方法ID=请求.method_id,
|
||||
来源ID=str(来源.source_id),
|
||||
)
|
||||
return env
|
||||
|
||||
|
||||
def _快照ID(task):
|
||||
return next(
|
||||
s["checkpoint"]["snapshot_id"]
|
||||
for s in task.步骤
|
||||
if s.get("checkpoint") and "snapshot_id" in s["checkpoint"]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10010",
|
||||
environment="隔离PG与合成HTTP提供方",
|
||||
given="带参考版本与活跃偏好的选段改写授权",
|
||||
when="发起并执行受控修订流程",
|
||||
then=["偏好与参考材料入选装配并绑定进快照;写手输入携带材料"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
)
|
||||
def test_偏好与参考材料进快照与写手输入__a10010(参考环境):
|
||||
env = 参考环境
|
||||
tid = 修订测试.发起(env, command="ref-revision-1")
|
||||
env["剧本"].append(
|
||||
{
|
||||
"类型": "文本",
|
||||
"文本": {
|
||||
"edits": [
|
||||
{
|
||||
"selection_id": env["选段"].selection_id,
|
||||
"action": "replace",
|
||||
"replacement": "刀锋擦过装甲,反震顺着掌心爬上来。",
|
||||
"reason": "合成参考改写",
|
||||
}
|
||||
]
|
||||
},
|
||||
}
|
||||
)
|
||||
task = 修订测试.跑(env, tid)
|
||||
assert task.状态 == 任务状态.已完成
|
||||
输入 = json.loads(env["收到"][-1]["input"])
|
||||
assert "重视受击反馈,不堆砌形容词。" in 输入["装配材料"]
|
||||
|
||||
快照 = env["装配"].要求上下文().读取快照(_快照ID(task))
|
||||
assert 快照.失效说明 is None
|
||||
assert "参考方法:" + env["参考版本"] in 快照.装配["入选"]
|
||||
assert any(str(绑.来源ID) == env["参考版本"] for 绑 in 快照.来源绑定)
|
||||
result = env["装配"].要求审校().读取模型修订(env["作者"], tid)
|
||||
assert result["source_kind"] == "selection" and result["outcome"] == "candidate"
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10011",
|
||||
environment="隔离PG与合成HTTP提供方",
|
||||
given="携带不存在参考版本的选段授权",
|
||||
when="发起受控修订",
|
||||
then=["授权范围阶段快速拒绝,不创建任务"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
)
|
||||
def test_未知参考版本在发起时拒绝__a10011(参考环境):
|
||||
env = 参考环境
|
||||
env["授权"] = replace(env["授权"], references=("不存在的版本",))
|
||||
with pytest.raises(Muse错误):
|
||||
修订测试.发起(env, command="ref-revision-bad")
|
||||
|
||||
|
||||
@pytest.mark.case_id(
|
||||
"NC-w32-a10012",
|
||||
environment="隔离PG与合成HTTP提供方",
|
||||
given="已冻结的参考改写任务",
|
||||
when="参考方法推进新版本后读取快照",
|
||||
then=["旧参考版本不再当前,快照标注失效且要求重新授权"],
|
||||
contract="docs/系统架构/新版设计/模块设计/B06-审校修订.md#参考改写选段改写链路扩展",
|
||||
)
|
||||
def test_参考版本漂移使快照失效__a10012(参考环境):
|
||||
env = 参考环境
|
||||
tid = 修订测试.发起(env, command="ref-revision-drift")
|
||||
svc = env["装配"].任务运行
|
||||
claim = svc.领取步骤("revision-worker", ["revision.freeze"])
|
||||
assert claim is not None
|
||||
svc.执行一步(claim)
|
||||
快照ID = _快照ID(svc.读取任务(tid))
|
||||
|
||||
方法 = env["装配"].要求知识方法()
|
||||
新请求 = 方法测试._提案(
|
||||
str(uuid4()),
|
||||
"机甲近身刀法二版",
|
||||
env["来源ID"],
|
||||
owner=env["作者"].作者,
|
||||
方法ID=env["参考方法ID"],
|
||||
)
|
||||
回执 = 方法.提出方法(env["作者"], str(uuid4()), 新请求)
|
||||
方法测试._确认(方法, env["作者"], 回执["target_ref"])
|
||||
|
||||
快照 = env["装配"].要求上下文().读取快照(快照ID)
|
||||
assert 快照.失效说明 and "参考方法" in 快照.失效说明
|
||||
assert "需重新授权" in 快照.失效说明
|
||||
@ -114,7 +114,7 @@ export function Ui选段改写({
|
||||
pending.current = {
|
||||
command_id: crypto.randomUUID(),
|
||||
config_id: config,
|
||||
request: { ...target, mode, purpose, selections, protected_ranges },
|
||||
request: { ...target, mode, purpose, selections, protected_ranges, references: [] },
|
||||
};
|
||||
}
|
||||
const { data } = await 客户端.POST("/api/v1/revision-tasks", {
|
||||
|
||||
@ -7114,6 +7114,11 @@ export interface components {
|
||||
* @default []
|
||||
*/
|
||||
protected_ranges: components["schemas"]["muse____________________22"][];
|
||||
/**
|
||||
* References
|
||||
* @default []
|
||||
*/
|
||||
references: string[];
|
||||
};
|
||||
/** _定位陈述 */
|
||||
muse______________________20: {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user