修复: CR-01审查待修项①类7项(CSS补testing/blocked徽章+CAS防回收站推进+DRY+注释+测试改名+advancing接文案)+新增待审查.md审查队列(职责分离:主会话写/审查agent读)+todo销账

This commit is contained in:
2026-06-16 03:12:24 +08:00
parent 38c7180365
commit 4dc5219a3c
6 changed files with 116 additions and 12 deletions

View File

@@ -20,7 +20,7 @@ use df_storage::db::Database;
use df_storage::models::TaskRecord;
use df_workflow::node::{Node, NodeContext, NodeOutput, NodeResult, NodeSchema};
use crate::task_state_machine::{can_transition, is_regression, is_valid_state};
use crate::task_state_machine::{can_transition, is_regression, is_valid_state, ALL_STATES};
/// 推进任务到目标状态(核心逻辑,DAG 节点与 IPC 入口共用)。
///
@@ -45,8 +45,9 @@ pub async fn advance_task_atomic(
// 1. target 合法性(7 态之一)。先于读库校验:即便任务不存在,也先拒绝非法状态值。
if !is_valid_state(target_status) {
return Err(df_core::error::Error::Validation(format!(
"非法 target_status {:?},合法值: todo/in_progress/in_review/testing/done/blocked/cancelled",
target_status
"非法 target_status {:?},合法值: {}",
target_status,
ALL_STATES.join("/")
)));
}
@@ -295,15 +296,15 @@ mod tests {
}
#[tokio::test]
async fn concurrent_cas_change_detected() {
// 模拟 TOCTOU:推进前已被旁路改 status。CAS 的 expected 与库内不符 → None → 报错
async fn advance_succeeds_after_prior_status_change() {
// 前序旁路改 status 后,推进链基于「当前」status 判定合法转换并正常写入
// 注:这并非 CAS 并发失败场景(真 CAS 失败由 cas_returns_none_when_status_mismatch 覆盖),
// 而是验证读后改路径在 from=当前库内 status 时正常推进。
let repo = setup().await;
repo.insert(rec("t1", "todo")).await.unwrap();
// 旁路把 status 改成 in_progress(模拟另一路并发推进)
repo.update_field("t1", "status", "in_progress").await.unwrap();
// 此时读出来是 in_progress,推进到 in_review 合法的——这测的不是 CAS 失败,
// 而是验证「读后改」路径在 status 一致时正常。CAS 失败路径靠 advance_status_atomic
// 自身的 None 返回覆盖(下方 cas_returns_none_when_status_mismatch 单测)。
// 读出来是 in_progress,推进到 in_review 合法 → 正常成功
let r = advance_task_atomic(&repo, "t1", "in_review").await.unwrap();
assert_eq!(r.status, "in_review");
}

View File

@@ -325,6 +325,10 @@ pub fn allowed_columns_for(table: &str) -> Option<&'static [&'static str]> {
"tasks" => &[
// id/created_at 不列入:主键与创建时间不可通过通用 update_field 改写
// (防篡改主键/伪造创建时间/跨项目移动)。
// review_rounds 不列入:它仅 advance_status_atomic 退回转换
// (in_review→in_progress / testing→in_review)时原子 +1(收口:仅该专用路径可改,
// update_task/update_field 白名单均不含)。后人勿把 review_rounds 补进白名单,
// 否则破坏「review_rounds 唯一写入路径」收口、引入旁路写导致计数错乱。
"project_id", "title", "description", "status", "priority", "branch_name",
"assignee", "workflow_def_id", "base_branch", "updated_at",
// TODO(B-260616-16): project_id 跨表存在性校验待 commands/task.rs 层补。
@@ -846,12 +850,14 @@ impl TaskRepo {
tokio::task::spawn_blocking(move || {
let guard = conn.blocking_lock();
// CAS:WHERE id AND status=expected 锁定当前态;affected==0 即并发已改动。
// deleted_at IS NULL:回收站任务(soft_delete 设了 deleted_at)CAS 必败→affected=0,
// 返回 None,杜绝回收站任务被推进(D-02 软删语义收口,一处关闭)。
let sql = if bump_rounds {
"UPDATE tasks SET status = ?1, review_rounds = review_rounds + 1, updated_at = ?2 \
WHERE id = ?3 AND status = ?4"
WHERE id = ?3 AND status = ?4 AND deleted_at IS NULL"
} else {
"UPDATE tasks SET status = ?1, updated_at = ?2 \
WHERE id = ?3 AND status = ?4"
WHERE id = ?3 AND status = ?4 AND deleted_at IS NULL"
};
let affected = guard
.execute(sql, params![new_status, now, id, expected])