fix(chain): 修评审 CRITICAL+HIGH — 链网关按 session 重建/日志脱敏/resume 原子化+所有权/死导入

- CRITICAL #1:链 write/review 节点经 gateway_builder 按节点自建 session 现建网关,
  usage_ledger sink 绑活 session,随节点 commit 持久化;run_chain_job 不再转发请求网关
  (其 session 在 BackgroundTask 跑时已关闭,记账行被静默丢弃)。新增 get_chain_gateway_builder
  缝(仿 digest builder),get_chain_gateway 退化为纯 503 凭据预检。守不变量 #1。
- HIGH #2:chain_runner 失败日志不再记 str(exc)(可能含 key/连接串/LLM 输出),改记
  _classify_job_error 脱敏文案 + exc_type(设计 §5)。
- HIGH #3:resume 端点原子抢占 awaiting→running(JobRepo.claim_awaiting_to_running 条件
  UPDATE),抢不到 → 409,防并发 resume 双 Command(resume) 损坏图。
- HIGH #4:resume 校验 job.project_id == project_id(JobView 新增 project_id),不匹配 → 404。
- HIGH #5:resume 返回新 ChainResumeAccepted{job_id,chain_key},去掉无意义哨兵 start/count=0。
- HIGH #6:删 nodes.py 死导入 extract_conflicts(import + __all__)。
- 测试 #7:e2e 断言链跑后 usage_ledger 有行(#1 回归守卫)+ chapter_reviews 每章一行;
  新增 claim 原子抢占单测 + resume 跨项目 404 / 并发 409 端点测。
This commit is contained in:
Yaojia Wang
2026-06-23 18:04:10 +02:00
parent 061792db1c
commit d1ea83b191
13 changed files with 316 additions and 37 deletions

View File

@@ -414,7 +414,13 @@ class FakeJobRepo:
self.rows: dict[uuid.UUID, JobView] = {}
async def create(self, project_id: uuid.UUID | None, kind: str) -> JobView:
view = JobView(id=uuid.uuid4(), kind=kind, status=STATUS_QUEUED, progress=0)
view = JobView(
id=uuid.uuid4(),
project_id=project_id,
kind=kind,
status=STATUS_QUEUED,
progress=0,
)
self.rows[view.id] = view
return view
@@ -442,6 +448,15 @@ class FakeJobRepo:
self.rows[job_id] = view
return view
async def claim_awaiting_to_running(self, job_id: uuid.UUID) -> JobView | None:
"""原子抢占 awaiting→running内存替身仅当现态 awaiting 才抢到,否则 None"""
current = self.rows.get(job_id)
if current is None or current.status != STATUS_AWAITING:
return None
view = current.model_copy(update={"status": STATUS_RUNNING})
self.rows[job_id] = view
return view
async def fail(self, job_id: uuid.UUID, error: str) -> JobView:
view = self.rows[job_id].model_copy(update={"status": STATUS_FAILED, "error": error})
self.rows[job_id] = view