新增: TaskRecord加output_json产出字段+V17迁移(决策a AiNode自审闭环)
This commit is contained in:
@@ -188,6 +188,7 @@ mod tests {
|
|||||||
workflow_def_id: None,
|
workflow_def_id: None,
|
||||||
base_branch: None,
|
base_branch: None,
|
||||||
review_rounds: 0,
|
review_rounds: 0,
|
||||||
|
output_json: None,
|
||||||
created_at: "0".to_string(),
|
created_at: "0".to_string(),
|
||||||
updated_at: "0".to_string(),
|
updated_at: "0".to_string(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -340,7 +340,11 @@ pub fn allowed_columns_for(table: &str) -> Option<&'static [&'static str]> {
|
|||||||
// update_task/update_field 白名单均不含)。后人勿把 review_rounds 补进白名单,
|
// update_task/update_field 白名单均不含)。后人勿把 review_rounds 补进白名单,
|
||||||
// 否则破坏「review_rounds 唯一写入路径」收口、引入旁路写导致计数错乱。
|
// 否则破坏「review_rounds 唯一写入路径」收口、引入旁路写导致计数错乱。
|
||||||
"project_id", "title", "description", "priority", "branch_name",
|
"project_id", "title", "description", "priority", "branch_name",
|
||||||
"assignee", "workflow_def_id", "base_branch", "updated_at",
|
"assignee", "workflow_def_id", "base_branch",
|
||||||
|
// output_json:ai_execute 写产出 / ai_self_review 读产出自审 / human_review 展示对象
|
||||||
|
// (决策 a:task 中心,产出跟 task 走)。非状态机收口字段,合法可写。
|
||||||
|
"output_json",
|
||||||
|
"updated_at",
|
||||||
// TODO(B-260616-16): project_id 跨表存在性校验待 commands/task.rs 层补。
|
// TODO(B-260616-16): project_id 跨表存在性校验待 commands/task.rs 层补。
|
||||||
// 通用 CRUD 层(db repo)只懂表/列语义,不持有跨表业务约束(查 projects 表存在性)。
|
// 通用 CRUD 层(db repo)只懂表/列语义,不持有跨表业务约束(查 projects 表存在性)。
|
||||||
// project_id 当前可在白名单内改写,合法目标存在性由上层命令层校验。
|
// project_id 当前可在白名单内改写,合法目标存在性由上层命令层校验。
|
||||||
@@ -485,6 +489,7 @@ fn task_from_row(row: &Row<'_>) -> std::result::Result<TaskRecord, rusqlite::Err
|
|||||||
workflow_def_id: row.get("workflow_def_id")?,
|
workflow_def_id: row.get("workflow_def_id")?,
|
||||||
base_branch: row.get("base_branch")?,
|
base_branch: row.get("base_branch")?,
|
||||||
review_rounds: row.get("review_rounds")?,
|
review_rounds: row.get("review_rounds")?,
|
||||||
|
output_json: row.get("output_json")?,
|
||||||
created_at: row.get("created_at")?,
|
created_at: row.get("created_at")?,
|
||||||
updated_at: row.get("updated_at")?,
|
updated_at: row.get("updated_at")?,
|
||||||
})
|
})
|
||||||
@@ -745,22 +750,22 @@ impl_repo!(
|
|||||||
from_row => |row| task_from_row(row),
|
from_row => |row| task_from_row(row),
|
||||||
insert => |conn, rec| {
|
insert => |conn, rec| {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO tasks (id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, created_at, updated_at)
|
"INSERT INTO tasks (id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, created_at, updated_at)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14)",
|
||||||
params![
|
params![
|
||||||
rec.id, rec.project_id, rec.title, rec.description, rec.status, rec.priority,
|
rec.id, rec.project_id, rec.title, rec.description, rec.status, rec.priority,
|
||||||
rec.branch_name, rec.assignee, rec.workflow_def_id, rec.base_branch,
|
rec.branch_name, rec.assignee, rec.workflow_def_id, rec.base_branch,
|
||||||
rec.review_rounds, rec.created_at, rec.updated_at
|
rec.review_rounds, rec.output_json, rec.created_at, rec.updated_at
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
update => |conn, rec| {
|
update => |conn, rec| {
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE tasks SET project_id = ?1, title = ?2, description = ?3, status = ?4, priority = ?5, branch_name = ?6, assignee = ?7, workflow_def_id = ?8, base_branch = ?9, review_rounds = ?10, updated_at = ?11 WHERE id = ?12",
|
"UPDATE tasks SET project_id = ?1, title = ?2, description = ?3, status = ?4, priority = ?5, branch_name = ?6, assignee = ?7, workflow_def_id = ?8, base_branch = ?9, review_rounds = ?10, output_json = ?11, updated_at = ?12 WHERE id = ?13",
|
||||||
params![
|
params![
|
||||||
rec.project_id, rec.title, rec.description, rec.status, rec.priority,
|
rec.project_id, rec.title, rec.description, rec.status, rec.priority,
|
||||||
rec.branch_name, rec.assignee, rec.workflow_def_id, rec.base_branch,
|
rec.branch_name, rec.assignee, rec.workflow_def_id, rec.base_branch,
|
||||||
rec.review_rounds, rec.updated_at, rec.id
|
rec.review_rounds, rec.output_json, rec.updated_at, rec.id
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -769,14 +774,14 @@ impl_repo!(
|
|||||||
impl TaskRepo {
|
impl TaskRepo {
|
||||||
/// 列出未删除任务(deleted_at IS NULL)— 对标 ProjectRepo::list_active
|
/// 列出未删除任务(deleted_at IS NULL)— 对标 ProjectRepo::list_active
|
||||||
///
|
///
|
||||||
/// 显式列出 13 个 TaskRecord 列名(同 ProjectRepo::list_active 写法),
|
/// 显式列出 14 个 TaskRecord 列名(同 ProjectRepo::list_active 写法),
|
||||||
/// 不 SELECT deleted_at:TaskRecord 不带该字段,取了 from_row 会因未知列报错。
|
/// 不 SELECT deleted_at:TaskRecord 不带该字段,取了 from_row 会因未知列报错。
|
||||||
pub async fn list_active(&self) -> Result<Vec<TaskRecord>> {
|
pub async fn list_active(&self) -> Result<Vec<TaskRecord>> {
|
||||||
let conn = self.conn.clone();
|
let conn = self.conn.clone();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let guard = conn.blocking_lock();
|
let guard = conn.blocking_lock();
|
||||||
let mut stmt = guard
|
let mut stmt = guard
|
||||||
.prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, created_at, updated_at FROM tasks WHERE deleted_at IS NULL ORDER BY created_at DESC")
|
.prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, created_at, updated_at FROM tasks WHERE deleted_at IS NULL ORDER BY created_at DESC")
|
||||||
.map_err(storage_err)?;
|
.map_err(storage_err)?;
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map([], |row| task_from_row(row))
|
.query_map([], |row| task_from_row(row))
|
||||||
@@ -877,7 +882,7 @@ impl TaskRepo {
|
|||||||
}
|
}
|
||||||
// 回读更新后的记录(含新 status / 累加后的 review_rounds / 新 updated_at)。
|
// 回读更新后的记录(含新 status / 累加后的 review_rounds / 新 updated_at)。
|
||||||
let mut stmt = guard
|
let mut stmt = guard
|
||||||
.prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, created_at, updated_at FROM tasks WHERE id = ?1")
|
.prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, created_at, updated_at FROM tasks WHERE id = ?1")
|
||||||
.map_err(storage_err)?;
|
.map_err(storage_err)?;
|
||||||
let row = stmt
|
let row = stmt
|
||||||
.query_row(params![id], |row| task_from_row(row))
|
.query_row(params![id], |row| task_from_row(row))
|
||||||
@@ -898,7 +903,7 @@ impl TaskRepo {
|
|||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let guard = conn.blocking_lock();
|
let guard = conn.blocking_lock();
|
||||||
let mut stmt = guard
|
let mut stmt = guard
|
||||||
.prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, created_at, updated_at FROM tasks WHERE deleted_at IS NOT NULL ORDER BY updated_at DESC")
|
.prepare("SELECT id, project_id, title, description, status, priority, branch_name, assignee, workflow_def_id, base_branch, review_rounds, output_json, created_at, updated_at FROM tasks WHERE deleted_at IS NOT NULL ORDER BY updated_at DESC")
|
||||||
.map_err(storage_err)?;
|
.map_err(storage_err)?;
|
||||||
let rows = stmt
|
let rows = stmt
|
||||||
.query_map([], |row| task_from_row(row))
|
.query_map([], |row| task_from_row(row))
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub fn run(conn: &Connection) -> Result<()> {
|
|||||||
|
|
||||||
// 迁移步骤链: 顺序执行,跳过已应用的版本(current_version < N 才跑)。
|
// 迁移步骤链: 顺序执行,跳过已应用的版本(current_version < N 才跑)。
|
||||||
// 新增版本时,在此数组追加一项 (N, migrate_vN) 即可,无需改逻辑。
|
// 新增版本时,在此数组追加一项 (N, migrate_vN) 即可,无需改逻辑。
|
||||||
let steps: [(i32, fn(&Connection) -> Result<()>); 16] = [
|
let steps: [(i32, fn(&Connection) -> Result<()>); 17] = [
|
||||||
(1, migrate_v1),
|
(1, migrate_v1),
|
||||||
(2, migrate_v2),
|
(2, migrate_v2),
|
||||||
(3, migrate_v3),
|
(3, migrate_v3),
|
||||||
@@ -51,6 +51,7 @@ pub fn run(conn: &Connection) -> Result<()> {
|
|||||||
(14, migrate_v14),
|
(14, migrate_v14),
|
||||||
(15, migrate_v15),
|
(15, migrate_v15),
|
||||||
(16, migrate_v16),
|
(16, migrate_v16),
|
||||||
|
(17, migrate_v17),
|
||||||
];
|
];
|
||||||
|
|
||||||
for (version, migrate_fn) in steps {
|
for (version, migrate_fn) in steps {
|
||||||
@@ -295,6 +296,22 @@ fn migrate_v16(conn: &Connection) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// V17: 幂等补 tasks.output_json 列(AiNode 自审闭环产出,决策 a:task 中心)
|
||||||
|
///
|
||||||
|
/// 任务产出 JSON 字符串:ai_execute 写产出 / ai_self_review 读产出做自审 / human_review 展示对象。
|
||||||
|
/// TEXT NULL 向后兼容(老库行默认 NULL,TaskRecord 字段为 Option<String>),
|
||||||
|
/// 经通用 update_field 白名单写入(非 status 状态机收口字段,status 收口 F-03 不变)。
|
||||||
|
/// 用 PRAGMA 探测列存在性,缺失才 ALTER(同 v4/v5/v6/v8/v10/v11/v14/v15/v16 模式),对新库/老库均安全。
|
||||||
|
fn migrate_v17(conn: &Connection) -> Result<()> {
|
||||||
|
if !column_exists(conn, "tasks", "output_json") {
|
||||||
|
conn.execute("ALTER TABLE tasks ADD COLUMN output_json TEXT", [])?;
|
||||||
|
tracing::info!("v17: 补建 tasks.output_json 列(AiNode 自审闭环产出)");
|
||||||
|
}
|
||||||
|
conn.execute("INSERT INTO schema_version (version) VALUES (?)", [17])?;
|
||||||
|
tracing::info!("迁移 v17 完成");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// V1 建表 SQL
|
/// V1 建表 SQL
|
||||||
const V1_SQL: &str = "
|
const V1_SQL: &str = "
|
||||||
-- 想法表
|
-- 想法表
|
||||||
|
|||||||
@@ -66,6 +66,14 @@ pub struct TaskRecord {
|
|||||||
/// #[serde(default)] 兼容旧前端无该字段的 JSON(老任务记录默认 0)。
|
/// #[serde(default)] 兼容旧前端无该字段的 JSON(老任务记录默认 0)。
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub review_rounds: i32,
|
pub review_rounds: i32,
|
||||||
|
/// 任务产出 JSON 字符串(AiNode 自审闭环,决策 a:task 中心,产出跟 task 走)。
|
||||||
|
///
|
||||||
|
/// 三方读写约定:ai_execute 写产出 / ai_self_review 读产出做自审 / human_review 展示产出对象。
|
||||||
|
/// 经通用 update_field 写白名单(非 status 那种状态机收口字段),TEXT NULL 向后兼容。
|
||||||
|
/// #[serde(skip_serializing_if)] 无产出不序列化(省前端字段空值),#[serde(default)] 兼容旧 JSON。
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub output_json: Option<String>,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ fn task(id: &str, project_id: &str) -> TaskRecord {
|
|||||||
workflow_def_id: None,
|
workflow_def_id: None,
|
||||||
base_branch: None,
|
base_branch: None,
|
||||||
review_rounds: 0,
|
review_rounds: 0,
|
||||||
|
output_json: None,
|
||||||
created_at: now_ts(),
|
created_at: now_ts(),
|
||||||
updated_at: now_ts(),
|
updated_at: now_ts(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ pub fn build_ai_tool_registry(db: &Arc<Database>) -> AiToolRegistry {
|
|||||||
status: "todo".to_string(), priority: args["priority"].as_i64().unwrap_or(2) as i32,
|
status: "todo".to_string(), priority: args["priority"].as_i64().unwrap_or(2) as i32,
|
||||||
branch_name: None, assignee: None, workflow_def_id: None, base_branch: None,
|
branch_name: None, assignee: None, workflow_def_id: None, base_branch: None,
|
||||||
review_rounds: 0,
|
review_rounds: 0,
|
||||||
|
output_json: None,
|
||||||
created_at: now_millis(), updated_at: now_millis(),
|
created_at: now_millis(), updated_at: now_millis(),
|
||||||
};
|
};
|
||||||
let id = record.id.clone();
|
let id = record.id.clone();
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ pub async fn create_task(
|
|||||||
workflow_def_id: None,
|
workflow_def_id: None,
|
||||||
base_branch: None,
|
base_branch: None,
|
||||||
review_rounds: 0,
|
review_rounds: 0,
|
||||||
|
output_json: None,
|
||||||
created_at: now.clone(),
|
created_at: now.clone(),
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user