fix(backlog): 修评审 3 HIGH — resume chain_key 真值 / 模板空白校验 / 续写链 resume E2E

1. resume_chain 回报真 chain_key:从 awaiting job.result 读回(run 时 _chain_result
   持久化),不再按 job.kind 推断(kind 恒为通用常量 "chain",从不在 SUPPORTED_CHAINS
   → 总错回退 draft_volume)。continue_volume 的 resume 回执现报真值。
2. 模板 title/body 拒纯空白:StringConstraints(strip_whitespace, min_length=1),
   "   " strip 后为空 → 422。
3. 续写链 resume 路径 E2E:continue_volume 第 1 章冲突 → interrupt → awaiting →
   resume 带裁决 → done,断言 resume 回执 / job result chain_key="continue_volume"
   (回归守卫 #1)。

测试:+resume continue_volume 单测(chain_key 真值)+2 模板空白 422 单测 +1 续写链
resume E2E。重生成 TS 客户端(仅 description 文案变化)。
This commit is contained in:
Yaojia Wang
2026-06-23 20:45:06 +02:00
parent 1a402f5ccc
commit 1921b037a0
7 changed files with 166 additions and 11 deletions

View File

@@ -215,7 +215,9 @@ async def resume_chain(
f"job {job_id} 已被并发续跑抢占或非 awaiting_input 态,不可重复续跑",
)
chain_key = claimed.kind if claimed.kind in SUPPORTED_CHAINS else "draft_volume"
# chain_key 真值run 时 interrupt 把图 state 的 chain_key 持久进 job.result_chain_result
# 读回它,而非按 job.kind 推断kind 恒为通用常量 JOB_KIND_CHAIN从不在 SUPPORTED_CHAINS
chain_key = _chain_key_from_result(claimed.result)
accept_op = build_accept_op(
session_factory=session_factory,
@@ -251,6 +253,17 @@ async def resume_chain(
return ChainResumeAccepted(job_id=job_id, chain_key=chain_key)
def _chain_key_from_result(result: dict[str, Any] | None) -> str:
"""从 awaiting job 的 result 取真 chain_keyrun 时由 _chain_result 持久化)。
缺失/非受支持 key旧行/异常)→ 回退 "draft_volume"(保守默认,不崩)。
"""
key = (result or {}).get("chain_key")
if isinstance(key, str) and key in SUPPORTED_CHAINS:
return key
return "draft_volume"
def _chapter_repo_factory(session: AsyncSession) -> object:
"""链节点review 重读草稿)按 session 建 chapter draft repo。"""
from ww_core.domain.chapter_repo import SqlChapterRepo