- src/muse 新版全模块(装配/共享/上下文/任务运行/作品规划/故事世界/正文写作/审校修订/知识方法/作者经验/效果评测/交付连载/资料研究/正式变更/元数据/接入/基础设施/编排)+ 测试树(单元/契约/集成/架构/迁移/端到端/夹具) - 129 项功能全部实现与自动验证(功能覆盖.json/矩阵),含 W31 补齐的规则与代价/节奏安排/伏笔与承诺 - 旧实现按处置清单退出(702 条中 324 删,保护合同与未迁移条目留存有据);web/app.py 旧工作台退役,新工作台为唯一写入口 - 数据库/旧库迁移:真实旧库内容批次迁移链(端点守卫/PG作品正文映射/质量资产缺省投影) - 运行手册 docs/运行手册.md;W30 本机服务阶段一已运行(infra PG 为正式内容权威) - R2 执行证据与私有运行材料在 .agents.local/改造/R2-20260909/(不入库)
105 lines
4.7 KiB
Python
105 lines
4.7 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.数据库
|
|
生成环境 = 写手测试.生成环境
|
|
|
|
|
|
def test_断源恢复模型任务只补未执行步骤并保持原预算与候选__29f005(
|
|
生成环境, 应用测试库, 新空目标库, 隔离数据库URL, tmp_path
|
|
):
|
|
env = 生成环境
|
|
config = env["装配"].配置
|
|
app = 构建默认运行(config, 计价=写手测试.合成计价())
|
|
author = env["作者"]
|
|
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, 数据库=应用测试库[用途.维护].引用, 运行用途=用途.维护)
|
|
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)),
|
|
计价=写手测试.合成计价(),
|
|
)
|
|
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
|