实现侧: - 上下文:任务范围拆分为 范围校验/范围授权;索引按可发现口径重建、索引新鲜度改对称差;依赖校验统一快照漂移说明。 - 知识方法:方法与材料读取口径统一;超限方法材料按可选省略,核对路径不再二次计费;删除无合同的读时重算。 - 任务运行:新增 context.usage/tool.denied 事件类型;连接池常驻并在装配生命周期内开关;调用结算与核对分列。 - 效果评测/审校修订/交付连载/作者经验/作品规划:凭据冻结、标定消费、导出补证、事实引文核对等收尾修复。 - 资源加载:能力正文不再夹带索引用的导航注记(该注记此前进入角色与技能的模型提示)。 - 元数据:受保护骨架与代码保护属性对齐;字段校验与内置结构口径同步。 - 基础设施:环境预检进入装配生命周期;数据库连接运行期字段不参与相等比较;索引指纹归一化 jsonb 浮点。 - 删除被替代实现:7 份旧提示词模板与空壳 资料来源 读取器。 用例侧: - 用例身份与导航元信息迁移;夹具补生命周期、同库暴露与模板封存; - 本轮定向修复:方法材料省略、事实引文、迁移回执、额度与暂停用例、慢用例超时预算等。
118 lines
5.6 KiB
Python
118 lines
5.6 KiB
Python
"""隔离模型任务在原库断开后,从完整备份续接已批准任务且不重发旧回合。"""
|
|
|
|
from dataclasses import replace
|
|
from datetime import UTC, datetime, timedelta
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
import psycopg
|
|
import pytest
|
|
import test_两阶段写手 as 写手测试
|
|
from psycopg import sql
|
|
from psycopg.conninfo import conninfo_to_dict, make_conninfo
|
|
|
|
from muse.任务运行.接口 import 任务预算计划, 角色预算, 预算管理, 额度策略
|
|
from muse.共享.调用身份 import 用途
|
|
from muse.启动 import 构建默认运行
|
|
from muse.基础设施.备份恢复 import 创建备份, 恢复备份
|
|
from muse.编排.生成正文 import 发起生成正文
|
|
from muse.编排.运行装配 import 推进作者任务
|
|
from muse.配置 import 数据库引用
|
|
|
|
pytestmark = pytest.mark.数据库
|
|
生成环境 = 写手测试.生成环境
|
|
|
|
|
|
@pytest.mark.case_id(
|
|
"NC-w29-29f005",
|
|
environment="隔离PG",
|
|
given="显式作者、隔离配置、合成数据与已知发布构建",
|
|
when="经真实维护、业务或浏览器入口执行并读取持久结果",
|
|
then=["断源恢复模型任务只补未执行步骤并保持原预算与候选"],
|
|
contract="docs/系统架构/新版设计/迁移与验收.md#备份与恢复入口",
|
|
)
|
|
def test_断源恢复模型任务只补未执行步骤并保持原预算与候选__29f005(
|
|
生成环境, 新空目标库, 隔离数据库URL, tmp_path
|
|
):
|
|
env = 生成环境
|
|
config = env["装配"].配置
|
|
app = 构建默认运行(config, 计价=写手测试.合成计价())
|
|
author = env["作者"]
|
|
# 第二个装配另有常驻池,必须自己进入生命周期。
|
|
with app.生命周期():
|
|
original = app.要求正文().读取正文(author, "ch-3")
|
|
task_id = 发起生成正文(
|
|
app,
|
|
author,
|
|
"model-backup",
|
|
"new_chapter",
|
|
work_id="gen-work",
|
|
chapter_id="ch-3",
|
|
配置ID="gen-config",
|
|
)["task_id"]
|
|
预算管理(env["库"], "synthetic").登记策略(额度策略("synthetic", "1", Decimal("20"), 40))
|
|
app.任务运行.批准作者预算(
|
|
task_id,
|
|
author.作者,
|
|
任务预算计划(
|
|
Decimal("10"),
|
|
(角色预算("writer", 8, 8, Decimal("1")),),
|
|
"synthetic-backup-budget",
|
|
datetime.now(UTC) + timedelta(minutes=10),
|
|
),
|
|
)
|
|
budget = app.任务运行.读取作者预算(task_id, author.作者)
|
|
env["剧本"].extend(
|
|
写手测试.正常剧本(
|
|
[
|
|
"雨点敲着铁皮屋顶,林深把退回的信压在掌心。",
|
|
"他沿跳板走向仓库,门后传来一声应答。",
|
|
]
|
|
)
|
|
)
|
|
assert 推进作者任务(app, author.作者, task_id)["executed_step"]["step_id"] == "预组装"
|
|
assert 推进作者任务(app, author.作者, task_id)["executed_step"]["step_id"] == "受限探索"
|
|
assert len(env["收到"]) == 4
|
|
source_steps = app.任务运行.读取任务(task_id).步骤
|
|
backup_config = replace(config, 数据库=env["库组"][用途.维护].引用, 运行用途=用途.维护)
|
|
archive = 创建备份(backup_config, tmp_path / "backup")
|
|
source_name = conninfo_to_dict(Path(config.数据库.位置).read_text())["dbname"]
|
|
with psycopg.connect(隔离数据库URL, autocommit=True) as admin:
|
|
admin.execute(
|
|
sql.SQL("ALTER DATABASE {} ALLOW_CONNECTIONS false").format(
|
|
sql.Identifier(source_name)
|
|
)
|
|
)
|
|
# 停用本例生成的旧暂存目录,恢复必须使用新目录;供应方凭据仍在受控存储。
|
|
raw = Path(config.原文暂存)
|
|
raw.rename(tmp_path / "offline-original-raw")
|
|
restored_raw = tmp_path / "restored-raw"
|
|
restore_config = replace(
|
|
backup_config,
|
|
数据库=新空目标库.工厂.引用,
|
|
原文暂存=str(restored_raw),
|
|
)
|
|
restored = 恢复备份(tmp_path / "backup", restore_config, archive["backup_id"])
|
|
assert restored["status"] == "verified" and not restored["services_started"]
|
|
params = conninfo_to_dict(Path(新空目标库.工厂.引用.位置).read_text())
|
|
params["user"] = "muse_app"
|
|
secret = tmp_path / "restored-app.txt"
|
|
secret.write_text(make_conninfo(**params))
|
|
secret.chmod(0o600)
|
|
fresh = 构建默认运行(
|
|
replace(config, 数据库=数据库引用("受控存储", str(secret)), 原文暂存=str(restored_raw)),
|
|
计价=写手测试.合成计价(),
|
|
)
|
|
with fresh.生命周期():
|
|
assert fresh.任务运行.读取作者预算(task_id, author.作者) == budget
|
|
assert fresh.任务运行.读取任务(task_id).步骤 == source_steps
|
|
assert len(env["收到"]) == 4
|
|
for expected in ("冻结回放", "无工具写作", "检查与候选"):
|
|
result = 推进作者任务(fresh, author.作者, task_id)
|
|
assert result["executed_step"]["step_id"] == expected
|
|
assert result["state"] == "completed" and len(env["收到"]) == 5
|
|
final = next(s["result"] for s in result["steps"] if s["step_id"] == "检查与候选")
|
|
candidate = fresh.要求正文().读取候选(author, final["candidate_id"])
|
|
assert candidate["origin"] == "model" and candidate["decision"] == "undecided"
|
|
assert fresh.要求正文().读取正文(author, "ch-3") == original
|