feat(all): 数据库模块化拆分与版本化迁移、任务引擎命名体系收敛、物理输出校验加固与用户配置接通
- server/db: 拆 4929 行 db.rs 单体为 db/ 目录,migrations.rs 引入 PRAGMA user_version
版本化迁移运行器(M1~M13)
- 任务引擎 Phase 6/7b/7c 改名收敛:EngineStageConfig→PhaseConfig、StagePolicy→ResumePolicy、
Converged→Completed、删除 task_type 列、success_method 拆 tlusty_/synspec_ 双列、
新增 tlusty_status/synspec_status 半失败阶段守卫
- 科学正确性加固:conv_check 任意行 NaN/Inf/溢出判无效(0 行容忍)、新增 spec_is_valid
校验 SYNSPEC 脏谱、itek_history 逐次迭代全量保真、fmt_abn powf 溢出饱和
- 用户配置真正接通:tlusty_chain/tlusty_input 由死字段经 调度器→TaskSpec→executor→runner
透传生效;config 加载期 validate + deny_unknown_fields + 解析失败记 warn
- 调度修复:H1 活锁(pending_strategies 跳过已失败策略)、种子查找错误不再静默降级冷启动
- dashboard: 阶段配置面板 tlusty_stage/synspec_stage、"已完成"标签、迭代诊断展示
- docs: 新增 database_refactor_design.md,同步 database/api/PIPELINE/workflow_detail
This commit is contained in:
@@ -212,14 +212,9 @@ impl SqliteTaskQueue {
|
||||
}
|
||||
};
|
||||
|
||||
// 旧版兼容归一化(见 models.rs::normalize_compat 文档):
|
||||
// 旧 MQ 在途消息可能只含 task_type、无 tlusty_config 字段。serde default
|
||||
// 会把 tlusty_config.strategies 填为完整默认链 [cold_run, seed_step],
|
||||
// 导致旧 SeedStep 热启动消息首项被误判为 cold_run。normalize_compat
|
||||
// 据 task_type 校正首项策略。在 pop 出队后立即调用,保证节点拿到的是
|
||||
// 语义正确的策略链。对新版消息(已显式设置 tlusty_config)无副作用。
|
||||
let mut task = task;
|
||||
task.normalize_compat();
|
||||
// Phase 6(P8):task_type 兼容字段与 normalize_compat 整体废弃。
|
||||
// 升级前须确认队列为空(docs/database_refactor_design.md §8.8)——残留
|
||||
// 旧 payload 的 strategies 会按 serde default 填成默认链,无法再校正。
|
||||
|
||||
// 记录任务归属:claim 时写入领用方 node_id,供 report 阶段校验,
|
||||
// 杜绝「节点 A 领用、节点 B 上报」的跨节点伪造结果投毒。
|
||||
@@ -391,13 +386,10 @@ impl SqliteTaskQueue {
|
||||
let mut stmt = conn.prepare(
|
||||
"DELETE FROM task_queue WHERE workflow_name = ?1 AND status = 'pending' RETURNING task_id",
|
||||
)?;
|
||||
let rows = stmt.query_map(params![wf_owned], |row| row.get::<_, String>(0))?;
|
||||
let mut ids = Vec::new();
|
||||
for r in rows {
|
||||
if let Ok(id) = r {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
let ids: Vec<String> = stmt
|
||||
.query_map(params![wf_owned], |row| row.get::<_, String>(0))?
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
Ok(ids)
|
||||
})
|
||||
.await??;
|
||||
@@ -431,7 +423,7 @@ impl SqliteTaskQueue {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use common::models::{GridPointParams, TaskType};
|
||||
use common::models::GridPointParams;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// H1 修复:队列库迁移必须把历史遗留的 NULL workflow_name 行回填为 '__legacy__'
|
||||
@@ -467,7 +459,9 @@ mod tests {
|
||||
}
|
||||
|
||||
// 打开队列库(触发迁移:补列检查 + NULL workflow_name 回填)。
|
||||
let queue = SqliteTaskQueue::new(&db_path.to_string_lossy()).await.unwrap();
|
||||
let queue = SqliteTaskQueue::new(&db_path.to_string_lossy())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let wf: String = {
|
||||
let conn = queue.pool.get().unwrap();
|
||||
@@ -503,7 +497,6 @@ mod tests {
|
||||
logn: (-2.0).into(),
|
||||
logo: (-2.0).into(),
|
||||
},
|
||||
task_type: TaskType::ColdRun,
|
||||
seed_point_name: None,
|
||||
timeout_sec: 3600,
|
||||
workflow_name: Some("test_wf".to_string()),
|
||||
@@ -552,7 +545,6 @@ mod tests {
|
||||
task_id,
|
||||
point_name: params.model_name(),
|
||||
params: params.clone(),
|
||||
task_type: TaskType::ColdRun,
|
||||
seed_point_name: None,
|
||||
timeout_sec: 60,
|
||||
workflow_name: None,
|
||||
@@ -616,7 +608,6 @@ mod tests {
|
||||
task_id,
|
||||
point_name: params.model_name(),
|
||||
params: params.clone(),
|
||||
task_type: TaskType::ColdRun,
|
||||
seed_point_name: None,
|
||||
timeout_sec: 7200,
|
||||
workflow_name: Some("wf_rq".to_string()),
|
||||
@@ -650,7 +641,7 @@ mod tests {
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
claim_a.map(|(p, w)| (p, w)),
|
||||
claim_a,
|
||||
Some((params.model_name(), Some("wf_rq".to_string()))),
|
||||
"requeue 后原节点仍持归属,应放行"
|
||||
);
|
||||
@@ -690,7 +681,6 @@ mod tests {
|
||||
logn: (-2.0).into(),
|
||||
logo: (-2.0).into(),
|
||||
},
|
||||
task_type: TaskType::ColdRun,
|
||||
seed_point_name: None,
|
||||
timeout_sec: 60,
|
||||
workflow_name: Some(wf.to_string()),
|
||||
@@ -803,20 +793,18 @@ mod tests {
|
||||
assert!(queue.pop_task("n1").await.unwrap().is_none());
|
||||
}
|
||||
|
||||
/// 旧版 MQ 在途消息兼容(二次审查 critical 修复验证):
|
||||
/// 旧消息只含 task_type=seed_step、无 tlusty_config 字段。serde default 会把
|
||||
/// strategies 填为 [cold_run, seed_step],首项 cold_run 与 task_type 不符。
|
||||
/// pop_task 出队时须调用 normalize_compat() 把首项校正为 seed_step,
|
||||
/// 否则旧热启动消息会被节点误当冷启动执行。
|
||||
/// Phase 6(P8):旧版消息兼容(normalize_compat)整体废弃。升级前确认队列为空
|
||||
/// (docs/database_refactor_design.md §8.8)后,旧 payload 的 task_type 键被 serde 忽略,
|
||||
/// strategies 回落到默认链——出队仍正常,只是不再有 task_type 校正。
|
||||
#[tokio::test]
|
||||
async fn test_pop_normalizes_legacy_seed_step_message() {
|
||||
async fn test_pop_legacy_payload_without_task_type_correction() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let db_path = temp_dir.path().join("legacy.db");
|
||||
let queue = SqliteTaskQueue::new(&db_path.to_string_lossy())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 直接写一行旧版 payload(仅 task_type,无 tlusty_config/synspec_config 字段)。
|
||||
// 旧版 payload 仍携带 task_type 键(已被忽略)+ 无 tlusty_config(回落默认链)。
|
||||
let legacy_task_id = uuid::Uuid::new_v4();
|
||||
let legacy_payload = serde_json::json!({
|
||||
"task_id": legacy_task_id.to_string(),
|
||||
@@ -848,16 +836,11 @@ mod tests {
|
||||
|
||||
let popped = queue.pop_task("n1").await.unwrap();
|
||||
let task = popped.expect("旧版消息应能正常出队");
|
||||
// normalize_compat 应把 strategies 校正为 [seed_step](与 task_type 一致)。
|
||||
assert_eq!(
|
||||
task.tlusty_config.strategies,
|
||||
vec!["seed_step".to_string()],
|
||||
"旧 seed_step 消息经 normalize_compat 后首项应为 seed_step"
|
||||
);
|
||||
// 无 task_type 校正:strategies 为 serde default 默认链,首项 cold_run。
|
||||
assert_eq!(
|
||||
task.tlusty_config.current_strategy("cold_run"),
|
||||
"seed_step",
|
||||
"current_strategy 应为 seed_step(而非默认链的 cold_run)"
|
||||
"cold_run",
|
||||
"旧 payload 无显式 strategies,回落到默认链首项"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user