feat(上下文): 复利机制批次3——参考改写扩展选段授权链,偏好与参考材料进冻结快照并受依赖复检

This commit is contained in:
zizi 2026-09-19 00:14:41 +08:00
parent 8ad28be021
commit 02063c8454
18 changed files with 709 additions and 2 deletions

View File

@ -20814,6 +20814,14 @@
"type": "array", "type": "array",
"title": "Protected Ranges", "title": "Protected Ranges",
"default": [] "default": []
},
"references": {
"items": {
"type": "string"
},
"type": "array",
"title": "References",
"default": []
} }
}, },
"additionalProperties": false, "additionalProperties": false,

View File

@ -20,6 +20,7 @@
| 读取方法评测材料 | 维护事务、作者、具体确认版本ID | 核原确认内容、版本与证据,按S04 generation/evaluation投影;只形成B10受批准的资料快照,不启用方法或写生产消费 | | 读取方法评测材料 | 维护事务、作者、具体确认版本ID | 核原确认内容、版本与证据,按S04 generation/evaluation投影;只形成B10受批准的资料快照,不启用方法或写生产消费 |
| 核对方法评测投影 | 同用途元数据连接和已封存材料 | 重验内容/结构/字段策略哈希;评测不直接读正式方法表 | | 核对方法评测投影 | 同用途元数据连接和已封存材料 | 重验内容/结构/字段策略哈希;评测不直接读正式方法表 |
| 读取消费统计 | 作者、方法版本范围 | 按版本聚合的实际消费计数,供 B09 偏好重排的强信号路;只读,不改消费台账,不把被检索或任务成功当作消费 | | 读取消费统计 | 作者、方法版本范围 | 按版本聚合的实际消费计数,供 B09 偏好重排的强信号路;只读,不改消费台账,不把被检索或任务成功当作消费 |
| 读取参考材料 | 作者、确切方法版本清单、两种用途 | 当前且已确认或启用版本的参考材料渲染与哈希,供参考改写冻结与依赖复检;不查启用凭据、不写消费 |
所有接口在本模块的接口.py 暴露。输入输出类型由同一公开表面导出,外部不导入私有模型或 SQL。 所有接口在本模块的接口.py 暴露。输入输出类型由同一公开表面导出,外部不导入私有模型或 SQL。

View File

@ -237,6 +237,7 @@ class 上下文服务:
含目录: bool = False, 含目录: bool = False,
含声音: bool = False, 含声音: bool = False,
含偏好: bool = False, 含偏好: bool = False,
参考版本: tuple[str, ...] = (),
) -> dict: ) -> dict:
return 授权任务范围( return 授权任务范围(
self.数据库, self.数据库,
@ -251,6 +252,7 @@ class 上下文服务:
含目录=含目录, 含目录=含目录,
含声音=含声音, 含声音=含声音,
含偏好=含偏好, 含偏好=含偏好,
参考版本=参考版本,
) )
def 读取范围(self, 任务ID: str) -> dict: def 读取范围(self, 任务ID: str) -> dict:
@ -276,6 +278,7 @@ class 上下文服务:
核对声音装配(范围, 装配, 绑定, 声音) 核对声音装配(范围, 装配, 绑定, 声音)
核对偏好装配(范围, 装配, 绑定, 核对范围偏好(连, 范围)) 核对偏好装配(范围, 装配, 绑定, 核对范围偏好(连, 范围))
方法 = 核对范围方法(连, 范围) 方法 = 核对范围方法(连, 范围)
from muse.上下文.参考材料 import 核对范围参考
from muse.上下文.材料装配 import 按键读取材料 from muse.上下文.材料装配 import 按键读取材料
入选方法 = [条 for 条 in 方法 if 条["材料键"] in 装配.入选来源] 入选方法 = [条 for 条 in 方法 if 条["材料键"] in 装配.入选来源]
@ -283,6 +286,12 @@ class 上下文服务:
实际材料 = 按键读取材料(装配) 实际材料 = 按键读取材料(装配)
if any(实际材料.get(条["材料键"]) != 条["材料"] for 条 in 入选方法): if any(实际材料.get(条["材料键"]) != 条["材料"] for 条 in 入选方法):
raise 上下文错误("CONTEXT_INPUT_INVALID", "入选方法正文与授权材料不一致") raise 上下文错误("CONTEXT_INPUT_INVALID", "入选方法正文与授权材料不一致")
参考 = 核对范围参考(连, 范围)
入选参考 = [条 for 条 in 参考 if 条["材料键"] in 装配.入选来源]
if 入选参考:
实际材料 = 按键读取材料(装配)
if any(实际材料.get(条["材料键"]) != 条["材料"] for 条 in 入选参考):
raise 上下文错误("CONTEXT_INPUT_INVALID", "入选参考正文与授权材料不一致")
新建 = 上下文存储(连, self.数据库.用途).写入快照( 新建 = 上下文存储(连, self.数据库.用途).写入快照(
{ {
"snapshot_id": 身份, "snapshot_id": 身份,

View File

@ -285,6 +285,24 @@ def 核对依赖(
锁定.append( 锁定.append(
依赖锁定(条["preference_id"], "preference", str(条["revision"]), 偏好结构, 偏好投影) 依赖锁定(条["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(变化项)) return 依赖状态(有效=not 变化项, 锁定依据=tuple(锁定), 变化项=tuple(变化项))

View 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"]
)

View File

@ -16,6 +16,7 @@ from muse.上下文.冻结快照 import (
文本哈希, 文本哈希,
输入哈希, 输入哈希,
) )
from muse.上下文.参考材料 import 参考绑定, 校验参考节, 核对范围参考
from muse.上下文.回放材料 import 历史版本选择, 回放材料选择, 核对回放字段策略, 读取回放材料 from muse.上下文.回放材料 import 历史版本选择, 回放材料选择, 核对回放字段策略, 读取回放材料
from muse.上下文.声音材料 import 核对范围声音 from muse.上下文.声音材料 import 核对范围声音
from muse.上下文.探索回放 import ( from muse.上下文.探索回放 import (
@ -80,6 +81,9 @@ __all__ = [
"核对范围声音", "核对范围声音",
"核对范围偏好", "核对范围偏好",
"偏好绑定", "偏好绑定",
"核对范围参考",
"参考绑定",
"校验参考节",
"核对方法消费材料", "核对方法消费材料",
"上下文快照", "上下文快照",
"上下文错误", "上下文错误",

View File

@ -51,10 +51,15 @@ def 授权任务范围(
含目录: bool = False, 含目录: bool = False,
含声音: bool = False, 含声音: bool = False,
含偏好: bool = False, 含偏好: bool = False,
参考版本: tuple[str, ...] = (),
) -> dict: ) -> dict:
"""按作者身份冻结历史文稿、目标章细纲与事实系统版本;两种用途随范围绑定。""" """按作者身份冻结历史文稿、目标章细纲与事实系统版本;两种用途随范围绑定。"""
if not 作品ID: if not 作品ID:
raise 上下文错误("CONTEXT_SCOPE_INVALID", "任务范围必须绑定明确作品") 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 数据库.用途: if 身份.作者 is None or 身份.用途 is not 数据库.用途:
raise 上下文错误("SCOPE_DENIED", "任务范围授权需要与装配用途一致的作者身份") raise 上下文错误("SCOPE_DENIED", "任务范围授权需要与装配用途一致的作者身份")
校验截止(截止) 校验截止(截止)
@ -126,8 +131,25 @@ def 授权任务范围(
else None else None
) )
偏好 = 偏好身份(读取偏好投影(连, 身份.作者, 作品ID, 身份.任务)) if 含偏好 else [] 偏好 = 偏好身份(读取偏好投影(连, 身份.作者, 作品ID, 身份.任务)) if 含偏好 else []
参考: list[dict] = []
if 参考版本:
from muse.上下文.参考材料 import 冻结参考身份
from muse.知识方法.接口 import 读取参考材料
参考 = [
冻结参考身份(条)
for 条 in 读取参考材料(
连,
参考版本,
作者=身份.作者,
内容用途=内容用途,
运行用途=运行用途,
读取预算=用量.预留来源,
)
]
来源集 = [d["document_id"] for d in 文稿] 来源集 = [d["document_id"] for d in 文稿]
来源集.extend(x["preference_id"] for x in 偏好) 来源集.extend(x["preference_id"] for x in 偏好)
来源集.extend(条["version_id"] for 条 in 参考)
if 声音 is not None: if 声音 is not None:
来源集.append(声音["version_id"]) 来源集.append(声音["version_id"])
来源集.extend(条["binding_id"] for 条 in 方法) 来源集.extend(条["binding_id"] for 条 in 方法)
@ -167,6 +189,8 @@ def 授权任务范围(
范围["preference_task"] = 身份.任务 范围["preference_task"] = 身份.任务
if 含方法: if 含方法:
范围["methods"] = 方法 范围["methods"] = 方法
if 参考:
范围["references"] = 参考
if 细纲 is not None: if 细纲 is not None:
范围["target_plan"] = 细纲 范围["target_plan"] = 细纲
if 事实 is not None: if 事实 is not None:

View File

@ -8,6 +8,7 @@ from muse.上下文.偏好材料 import (
校验偏好节, 校验偏好节,
核对偏好装配, 核对偏好装配,
) )
from muse.上下文.参考材料 import 校验参考节
from muse.上下文.声音材料 import ( from muse.上下文.声音材料 import (
校验声音节, 校验声音节,
核对声音装配, 核对声音装配,
@ -77,6 +78,23 @@ def _校验冻结输入(
条["projection_version"], 条["projection_version"],
): ):
raise 上下文错误("SOURCE_STALE", "方法版本或材料哈希与授权不一致") 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") 目录源 = 范围.get("directory_source")
if 目录源: if 目录源:
for 项 in 绑定: for 项 in 绑定:
@ -159,11 +177,13 @@ def 恢复冻结范围(数据库: 数据库工厂, 任务: 任务快照) -> dict
or 事实 is not None or 事实 is not None
or 目录源 is not None or 目录源 is not None
or 范围.get("methods") or 范围.get("methods")
or 范围.get("references")
or "voice" in 范围 or "voice" in 范围
): ):
_校验用途绑定(范围, 固定) _校验用途绑定(范围, 固定)
校验声音节(范围.get("voice")) 校验声音节(范围.get("voice"))
校验偏好节(范围) 校验偏好节(范围)
校验参考节(范围)
if 范围.get("preference_task") not in (None, 任务.任务ID): if 范围.get("preference_task") not in (None, 任务.任务ID):
raise 上下文错误("SCOPE_DENIED", "偏好绑定的任务不是当前任务") raise 上下文错误("SCOPE_DENIED", "偏好绑定的任务不是当前任务")
身份集 = 范围.get("source_ids") 身份集 = 范围.get("source_ids")
@ -200,6 +220,7 @@ def 恢复冻结范围(数据库: 数据库工厂, 任务: 任务快照) -> dict
方法ID.add(条["binding_id"]) 方法ID.add(条["binding_id"])
基础 = {项["document_id"] for 项 in 文稿} | 方法ID 基础 = {项["document_id"] for 项 in 文稿} | 方法ID
基础.update(x["preference_id"] for x in 范围.get("preferences", [])) 基础.update(x["preference_id"] for x in 范围.get("preferences", []))
基础.update(x["version_id"] for x in 范围.get("references", []))
if 范围.get("voice") is not None: if 范围.get("voice") is not None:
基础.add(范围["voice"]["version_id"]) 基础.add(范围["voice"]["version_id"])
if 目录源 is not None: if 目录源 is not None:

View File

@ -6,4 +6,6 @@ expand在原场景内补动作、对话和感官细节;compress删去冗余但
原文、附近语境、作者指示与已确认声音由上下文提供。保留人物、数量、时间、推测情态、引文和保护表达,不以其他修改抵消某一处损失。改变因果或事实需要作者另行明确判断,不能自称已通过语义核验。 原文、附近语境、作者指示与已确认声音由上下文提供。保留人物、数量、时间、推测情态、引文和保护表达,不以其他修改抵消某一处损失。改变因果或事实需要作者另行明确判断,不能自称已通过语义核验。
上下文若提供“参考方法:”前缀的材料,那是作者为本次改写明确挑选的参考范式:只借鉴其节奏、技法与细节处理方式,结合修订目标、人物与声音重新原创;不照抄参考材料的句子,不把其中的人名、地名、专有名词或数字带入选段,也不因参考材料而扩大改写范围。没有参考材料时不虚构参考。
只返回输出合同允许的字段。位置和候选身份由服务端决定,不编造诊断、调用回执或作者批准。 只返回输出合同允许的字段。位置和候选身份由服务端决定,不编造诊断、调用回执或作者批准。

View File

@ -87,6 +87,7 @@ class 选段写作授权:
purpose: str purpose: str
selections: tuple[原文选段, ...] selections: tuple[原文选段, ...]
protected_ranges: tuple[原文选段, ...] = () protected_ranges: tuple[原文选段, ...] = ()
references: tuple[str, ...] = ()
def __post_init__(self): def __post_init__(self):
if self.revision < 1 or not self.purpose.strip() or not 1 <= len(self.selections) <= 32: if self.revision < 1 or not self.purpose.strip() or not 1 <= len(self.selections) <= 32:
@ -95,6 +96,12 @@ class 选段写作授权:
raise 正文错误("选段改写需明确作品、章节及分支") raise 正文错误("选段改写需明确作品、章节及分支")
if len({s.selection_id for s in self.selections}) != len(self.selections): if len({s.selection_id for s in self.selections}) != len(self.selections):
raise 正文错误("圈选不能重复") 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 准备选段改写(连, 作者, 授权: 选段写作授权): def 准备选段改写(连, 作者, 授权: 选段写作授权):

View File

@ -13,6 +13,7 @@ from muse.正式变更.接口 import 作者动作, 变更命令, 正式变更服
from muse.知识方法.存储 import 方法存储 from muse.知识方法.存储 import 方法存储
from muse.知识方法.方法卡 import ( from muse.知识方法.方法卡 import (
列出方法族类型, 列出方法族类型,
读取参考材料,
读取方法检索候选, 读取方法检索候选,
读取方法检索资料, 读取方法检索资料,
读取方法检索身份, 读取方法检索身份,
@ -304,6 +305,7 @@ __all__ = [
"消费输入", "消费输入",
"读取方法检索资料", "读取方法检索资料",
"读取方法检索候选", "读取方法检索候选",
"读取参考材料",
"读取方法检索身份", "读取方法检索身份",
"读取方法索引维护资料", "读取方法索引维护资料",
"读取版本材料大小", "读取版本材料大小",

View File

@ -9,6 +9,7 @@ from __future__ import annotations
import json import json
from dataclasses import asdict from dataclasses import asdict
from typing import Any from typing import Any
from uuid import UUID
from muse.元数据.接口 import 元数据服务, 投影值, 投影字段, 授权摘要, 校验值, 选择实例维护者 from muse.元数据.接口 import 元数据服务, 投影值, 投影字段, 授权摘要, 校验值, 选择实例维护者
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, ...]: def 读取方法检索身份(连, *, 作者: str | None = None, 数量: int = 5001) -> tuple[str, ...]:
"""当前可发现版本的轻量清单;不展开正文、提案或来源证据。""" """当前可发现版本的轻量清单;不展开正文、提案或来源证据。"""
return tuple( return tuple(

View File

@ -10,8 +10,12 @@ from uuid import uuid4
from muse.上下文.接口 import ( from muse.上下文.接口 import (
依赖重验检查, 依赖重验检查,
偏好绑定,
参考绑定,
投影版本, 投影版本,
来源绑定, 来源绑定,
核对范围偏好,
核对范围参考,
核对范围声音, 核对范围声音,
装配材料, 装配材料,
) )
@ -107,6 +111,8 @@ def 发起模型修订(app, 身份, 命令ID, 授权: 模型修订授权 | 选
), ),
含目录=True, 含目录=True,
含声音=True, 含声音=True,
含偏好=True,
参考版本=tuple(授权.references) if isinstance(授权, 选段写作授权) else (),
) )
策略 = 角色策略目录.从发布包() 策略 = 角色策略目录.从发布包()
request = 任务请求( request = 任务请求(
@ -167,7 +173,10 @@ def 登记审校修订(登记, app, 计价):
_, _, _, text = 核对修订任务(连, ctx.任务) _, _, _, text = 核对修订任务(连, ctx.任务)
scope = 上下文.恢复冻结范围(ctx.任务) scope = 上下文.恢复冻结范围(ctx.任务)
voice = 核对范围声音(连, scope) voice = 核对范围声音(连, scope)
偏好材料 = 核对范围偏好(连, scope)
参考材料 = 核对范围参考(连, scope)
材料 = {"修订目标": json.dumps(text, ensure_ascii=False, sort_keys=True)} 材料 = {"修订目标": json.dumps(text, ensure_ascii=False, sort_keys=True)}
必读 = ["修订目标"]
doc = scope["documents"][0] doc = scope["documents"][0]
bindings = [ bindings = [
来源绑定( 来源绑定(
@ -179,6 +188,7 @@ def 登记审校修订(登记, app, 计价):
] ]
if voice is not None: if voice is not None:
材料[voice["材料键"]] = voice["材料"] 材料[voice["材料键"]] = voice["材料"]
必读.append(voice["材料键"])
bindings.append( bindings.append(
来源绑定( 来源绑定(
voice["version_id"], voice["version_id"],
@ -187,7 +197,17 @@ def 登记审校修订(登记, app, 计价):
voice["projection_version"], 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)) snap = 上下文.冻结上下文(ctx.任务.任务ID, 组装, tuple(bindings))
return 步骤结果({"snapshot_id": snap.快照ID}, 检查点={"snapshot_id": snap.快照ID}) return 步骤结果({"snapshot_id": snap.快照ID}, 检查点={"snapshot_id": snap.快照ID})

View 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})

View File

@ -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", "case_id": "NC-work-current-binding",
"environment": "隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程", "environment": "隔离 PostgreSQL;结构升级为明确的已完成状态夹具,不代替作者升级旅程",

View 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 快照.失效说明

View File

@ -114,7 +114,7 @@ export function Ui选段改写({
pending.current = { pending.current = {
command_id: crypto.randomUUID(), command_id: crypto.randomUUID(),
config_id: config, 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", { const { data } = await 客户端.POST("/api/v1/revision-tasks", {

View File

@ -7114,6 +7114,11 @@ export interface components {
* @default [] * @default []
*/ */
protected_ranges: components["schemas"]["muse____________________22"][]; protected_ranges: components["schemas"]["muse____________________22"][];
/**
* References
* @default []
*/
references: string[];
}; };
/** _定位陈述 */ /** _定位陈述 */
muse______________________20: { muse______________________20: {