feat(all): seed_step_stab 稳定化种子链与同族种子轮换、ladder/自适应 Teff/丰度轴延拓三级回退与梯级种子入库复用、热启动首末比豁免、workflow 完成翻转回退链阻塞修复、大气收敛权威统计与 M14 回填
收敛攻坚(81/400 失败点根因与实测,见 docs/failed81_cno_seed_popzer_dpsilg_2026_08_18.md): - runner: 新增 seed_step_stab 策略链——同物理族(同 Teff/logg/logHe)CNO 邻居种子 + DPSILG=3.0 λ 算子欠松弛 + POPZER=1E-10 微布居置零(联动 POPZR2/RADZER 同值 + NITZER=1),针对 20kK He 富大气 He I/II 电离前沿布居极限环 - runner: 三级链内延拓回退(主链与 nl_direct 全败后自动触发): · 固定 ladder 步进——plan_ladder_steps 按归一化间隔选轴,Δlogg≤0.25/ΔTeff≤2.5kK,≤4 步 · 自适应 Teff 延拓——步长 1250K 起、成功 ×1.5 恢复、失败二分至 25K 折叠墙,≤48 步 · C/N 丰度轴延拓——高温域(Teff>30kK)专用,严格同族种子沿 C(优先)/N 轴 0.2 dex 起步 延拓阶段 NITER 下限提至 300(慢收敛 waypoint 迭代饥饿误判修复) - runner: 稳定化多档回退(DPSILG/POPZER 三档互补,联合回收 41/65); 域门控 Teff≤30kK——高温高金属域实测旋钮致散(17 拍爆至 4e16),域外跳过 - 梯级种子持久化: 收敛中间模型登记 ladder_seeds 随上报落 server seeds 表 (任务失败也上传,合成名 _ladder 与真实网格点零冲突),簇内相邻失败点自动复用 调度与执行: - scheduler: 策略解析新增 seed_step_stab 臂——find_exact_family_seed_from_db 严格 同物理族判定(不做 global 退化,防 ladder 中间种子 ΔTeff≤5000K 误命中), 排除本点历史已用种子实现重试轮换;链在 stab 耗尽时轮换未试过同族邻居重派 - executor: seed_step_stab 补种子下载(漏列曾致 78 任务假失败,seed_nc 无 fort.8 崩溃); Teff>30kK 域外自动降级普通种子链 收敛判据: - conv_check: 热启动豁免——首拍 max_relc<1(种子已近解)时首末比 1e3 判据数学上 不可达,豁免后交五重物理硬门槛裁决(修复 nl_ladder 0.038→6e-4 物理全过被误杀); 冷启动仍受判据门控 workflow 生命周期: - workflows/tasks: 完成 flip 增加「未消费回退链」阻塞子句——failed 点策略链未耗尽 或链尾 seed_step_stab 尚有未试过同族种子时不得置 completed(修复最后活跃点 cold 失败上报抢先 flip、still_running 守卫拦截后续策略永不派发);按 failed_stage 归因 (synspec 失败行只看 synspec 链,防 stale 审计副本永久卡死)+ json_valid 脏行防护 统计与前端: - 统计新增权威口径 tlusty_converged(不按策略拆)与 seed_step_stab_converged 分项, 前端详情页色带/概览卡消费权威总数并新增稳定化青色段(修复 stab 收敛点漏计, 生产 9137/9216 差额);M14 迁移回填历史 completed 点的 tlusty_status 文档: - 新增 failed81 POPZER/DPSILG 制胜配方根因分析、Windows 节点经跳板 RDP 运维手册; failed400 增补 ladder 生产化实现与第二轮 121 残点实测矩阵
This commit is contained in:
@@ -710,6 +710,12 @@ pub struct WorkflowListStats {
|
||||
pub running: i64,
|
||||
pub cold_run_converged: i64,
|
||||
pub seed_step_converged: i64,
|
||||
/// TLUSTY 阶段收敛总数(tlusty_status='converged')——权威口径,语义同
|
||||
/// WorkflowStats.tlusty_converged(2026-08-25 补,见彼处注释)。
|
||||
#[serde(default)]
|
||||
pub tlusty_converged: i64,
|
||||
#[serde(default)]
|
||||
pub seed_step_stab_converged: i64,
|
||||
/// TLUSTY 阶段发散点数(tlusty_status='failed')。
|
||||
pub tlusty_failed: i64,
|
||||
/// SYNSPEC 阶段收敛点数(synspec_status='converged')。
|
||||
@@ -754,6 +760,15 @@ pub struct WorkflowStats {
|
||||
pub failed: i64,
|
||||
pub cold_run_converged: i64,
|
||||
pub seed_step_converged: i64,
|
||||
/// TLUSTY 阶段收敛总数(tlusty_status='converged',不按 method 拆分)——权威口径。
|
||||
/// 前端「N 大气收敛」应消费此字段;cold/seed/stab 为策略细分。2026-08-25 补:
|
||||
/// 此前前端用 cold+seed 之和,seed_step_stab(2026-08-18 引入)收敛点被漏计
|
||||
/// (生产 9137/9216 差额主因)。
|
||||
#[serde(default)]
|
||||
pub tlusty_converged: i64,
|
||||
/// TLUSTY 阶段以 seed_step_stab 策略收敛的点数(稳定化/waypoint 种子链)。
|
||||
#[serde(default)]
|
||||
pub seed_step_stab_converged: i64,
|
||||
/// TLUSTY 阶段发散点数(tlusty_status='failed')。
|
||||
pub tlusty_failed: i64,
|
||||
/// SYNSPEC 阶段收敛点数(synspec_status='converged')。
|
||||
@@ -1199,6 +1214,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
};
|
||||
db.upsert_point_summary(&summary.name, "wf_a", &summary, "seed_step")
|
||||
@@ -1215,12 +1231,16 @@ mod tests {
|
||||
assert_eq!(all["completed"], 1);
|
||||
assert_eq!(all["cold_run_converged"], 0);
|
||||
assert_eq!(all["seed_step_converged"], 1);
|
||||
// tlusty_converged(2026-08-25):不按 method 拆分的权威总数。
|
||||
assert_eq!(all["tlusty_converged"], 1);
|
||||
assert_eq!(all["seed_step_stab_converged"], 0);
|
||||
// 单工作流 wf_a:1 pending + 1 seed_step 收敛
|
||||
let a = db.get_grid_summary_stats(Some("wf_a")).await.unwrap();
|
||||
assert_eq!(a["total"], 2);
|
||||
assert_eq!(a["pending"], 1);
|
||||
assert_eq!(a["queued"], 0);
|
||||
assert_eq!(a["seed_step_converged"], 1);
|
||||
assert_eq!(a["tlusty_converged"], 1);
|
||||
// 不存在的工作流:0
|
||||
let none = db
|
||||
.get_grid_summary_stats(Some("nonexistent"))
|
||||
@@ -1264,6 +1284,120 @@ mod tests {
|
||||
assert_eq!(m.unwrap().name, exact.model_name());
|
||||
}
|
||||
|
||||
/// find_exact_family_seed_from_db 严格同物理族判定(2026-08-19 生产修复回归):
|
||||
/// ladder 中间种子(如 teff=22500)即便 CNO 距离为 0,也因 Teff 不同被排除;
|
||||
/// 同 Teff/logg/logHe 的 CNO 邻居正常命中;排除目标自身名。
|
||||
#[tokio::test]
|
||||
async fn test_find_exact_family_seed_strict_same_family() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::new(&temp_dir.path().join("seed_stab_db.db").to_string_lossy())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let target = GridPointParams {
|
||||
teff: 20000.0.into(),
|
||||
logg: 6.5.into(),
|
||||
loghe: 2.0.into(),
|
||||
logc: (-4.0).into(),
|
||||
logn: (-3.0).into(),
|
||||
logo: (-4.0).into(),
|
||||
};
|
||||
// ladder 中间种子:同 logg/logHe、CNO 完全一致,但 teff=22500 —— 必须排除。
|
||||
let ladder = GridPointParams {
|
||||
teff: 22500.0.into(),
|
||||
logg: 6.5.into(),
|
||||
loghe: 2.0.into(),
|
||||
logc: (-4.0).into(),
|
||||
logn: (-3.0).into(),
|
||||
logo: (-4.0).into(),
|
||||
};
|
||||
// 合法 CNO 邻居:同 Teff/logg/logHe,Δo=1。
|
||||
let cno_neighbor = GridPointParams {
|
||||
teff: 20000.0.into(),
|
||||
logg: 6.5.into(),
|
||||
loghe: 2.0.into(),
|
||||
logc: (-4.0).into(),
|
||||
logn: (-3.0).into(),
|
||||
logo: (-3.0).into(),
|
||||
};
|
||||
db.insert_seed(&ladder, "/tmp/ladder.7").await.unwrap();
|
||||
db.insert_seed(&cno_neighbor, "/tmp/neighbor.7").await.unwrap();
|
||||
|
||||
let m = db
|
||||
.find_exact_family_seed_from_db(&target, &[target.model_name()])
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(m.is_some(), "应命中 CNO 邻居");
|
||||
assert_eq!(
|
||||
m.unwrap().name,
|
||||
cno_neighbor.model_name(),
|
||||
"不得选 teff 不同的 ladder 中间种子(即便其 CNO 距离为 0)"
|
||||
);
|
||||
|
||||
// 无严格同族候选时返回 None(不退化到 global)。
|
||||
let lonely = GridPointParams {
|
||||
teff: 30000.0.into(),
|
||||
logg: 5.0.into(),
|
||||
loghe: (-2.0).into(),
|
||||
logc: (-2.0).into(),
|
||||
logn: (-2.0).into(),
|
||||
logo: (-2.0).into(),
|
||||
};
|
||||
let none = db
|
||||
.find_exact_family_seed_from_db(&lonely, &[lonely.model_name()])
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(none.is_none());
|
||||
}
|
||||
|
||||
|
||||
/// 种子轮换回归(2026-08-20 修复):find_exact_family_seed_from_db 排除列表
|
||||
/// 应使调用方在重试间轮换到下一个未试过的同族 CNO 邻居;全部排除后返回 None。
|
||||
#[tokio::test]
|
||||
async fn test_find_exact_family_seed_rotation() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::new(&temp_dir.path().join("seed_rot_db.db").to_string_lossy())
|
||||
.await
|
||||
.unwrap();
|
||||
let base = |logc: f64, logn: f64, logo: f64| GridPointParams {
|
||||
teff: 20000.0.into(),
|
||||
logg: 6.0.into(),
|
||||
loghe: 2.0.into(),
|
||||
logc: logc.into(),
|
||||
logn: logn.into(),
|
||||
logo: logo.into(),
|
||||
};
|
||||
let target = base(-3.0, -3.0, -1.0);
|
||||
let n1 = base(-3.0, -2.0, -1.0);
|
||||
let n2 = base(-3.0, -3.0, -2.0);
|
||||
db.insert_seed(&n1, "/tmp/rot_n1.7").await.unwrap();
|
||||
db.insert_seed(&n2, "/tmp/rot_n2.7").await.unwrap();
|
||||
|
||||
let self_name = target.model_name();
|
||||
// 首次:选距离最近的未排除邻居。
|
||||
let first = db
|
||||
.find_exact_family_seed_from_db(&target, &[self_name.clone()])
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("应命中 CNO 邻居");
|
||||
// 排除首个后:轮换到另一个邻居。
|
||||
let second = db
|
||||
.find_exact_family_seed_from_db(&target, &[self_name, first.name.clone()])
|
||||
.await
|
||||
.unwrap()
|
||||
.expect("排除首个邻居后应轮换到第二个邻居");
|
||||
assert_ne!(first.name, second.name);
|
||||
// 全部排除后:None(终态 failed,不死循环)。
|
||||
let none = db
|
||||
.find_exact_family_seed_from_db(
|
||||
&target,
|
||||
&[target.model_name(), first.name, second.name],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(none.is_none(), "邻居全部试过后应返回 None");
|
||||
}
|
||||
|
||||
/// "种子回退仅一次"守卫语义(2026-08-02 涡旋事故定稿):has_seed_step_attempt
|
||||
/// 统计**一切** seed_step 行(含 pending)。pending 行在新架构下只有两种来源:
|
||||
/// (a) 真在途(排队/已领用)——计数它正是对在途回退的去重,挡住救援途中迟到失败
|
||||
@@ -3265,6 +3399,347 @@ mod tests {
|
||||
assert_eq!(item.status, "completed");
|
||||
}
|
||||
|
||||
/// 完成 flip 的「未消费回退链」阻塞回归(2026-08-22 修复):
|
||||
/// 最后一个活跃点失败时,其最新 failed 行策略链弹掉失败首项后仍有顺位
|
||||
/// (长度 >1)→ workflow 不得置 completed(否则 trigger_strategy_fallback
|
||||
/// 的 still_running 守卫拦截,链上 seed_step/seed_step_stab 永不派发;
|
||||
/// 生产实证 sdB_cno/t60000_g5.0_he-4_c-4_n-4_o-1,8-22 04:00:50)。
|
||||
/// 链耗尽(长度 1)后放行翻转。
|
||||
#[tokio::test]
|
||||
async fn test_workflow_flip_blocked_by_unconsumed_fallback_chain() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::new(
|
||||
&temp_dir
|
||||
.path()
|
||||
.join("flip_block_db.db")
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let params = GridPointParams {
|
||||
teff: 60000.0.into(),
|
||||
logg: 5.0.into(),
|
||||
loghe: (-4.0).into(),
|
||||
logc: (-4.0).into(),
|
||||
logn: (-4.0).into(),
|
||||
logo: (-1.0).into(),
|
||||
};
|
||||
let name = params.model_name();
|
||||
db.upsert_workflow("wf_fb2", None, "config", "idle")
|
||||
.await
|
||||
.unwrap();
|
||||
db.update_workflow_status("wf_fb2", "running").await.unwrap();
|
||||
db.upsert_grid_point(¶ms, 0, "wf_fb2").await.unwrap();
|
||||
// 点终态 failed、无 pending/queued/running——旧条件已满足 flip。
|
||||
db.update_grid_status(&name, GridPointStatus::Failed, "wf_fb2")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 最新 failed 行携带 3 顺位链(cold_run 刚失败,尚余 seed_step/seed_step_stab)。
|
||||
let t1 = uuid::Uuid::new_v4();
|
||||
db.insert_task(&common::models::TaskSpec {
|
||||
task_id: t1,
|
||||
point_name: name.clone(),
|
||||
params: params.clone(),
|
||||
timeout_sec: 7200,
|
||||
workflow_name: Some("wf_fb2".to_string()),
|
||||
wave: 0,
|
||||
tlusty_config: PhaseConfig {
|
||||
strategies: vec![
|
||||
"cold_run".to_string(),
|
||||
"seed_step".to_string(),
|
||||
"seed_step_stab".to_string(),
|
||||
],
|
||||
..PhaseConfig::default_tlusty()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
let pool = db.pool.clone();
|
||||
let tid = t1.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let conn = pool.get().unwrap();
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'failed', completed_at = datetime('now') \
|
||||
WHERE task_id = ?1",
|
||||
params![tid],
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
db.sync_all_running_workflows_completion().await.unwrap();
|
||||
let wf = db.get_workflow("wf_fb2").await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
wf.status, "running",
|
||||
"存在未消费回退链的 failed 点时,workflow 不应翻转 completed"
|
||||
);
|
||||
|
||||
// 回退推进到链尾(更新的 failed 行仅剩单顺位)→ 阻塞解除,翻转放行。
|
||||
let t2 = uuid::Uuid::new_v4();
|
||||
db.insert_task(&common::models::TaskSpec {
|
||||
task_id: t2,
|
||||
point_name: name.clone(),
|
||||
params: params.clone(),
|
||||
timeout_sec: 7200,
|
||||
workflow_name: Some("wf_fb2".to_string()),
|
||||
wave: 0,
|
||||
tlusty_config: PhaseConfig {
|
||||
strategies: vec!["seed_step_stab".to_string()],
|
||||
..PhaseConfig::default_tlusty()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
let pool = db.pool.clone();
|
||||
let tid = t2.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let conn = pool.get().unwrap();
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'failed', completed_at = datetime('now'), \
|
||||
created_at = datetime('now', '+1 hour') WHERE task_id = ?1",
|
||||
params![tid],
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
db.sync_all_running_workflows_completion().await.unwrap();
|
||||
let wf2 = db.get_workflow("wf_fb2").await.unwrap().unwrap();
|
||||
assert_eq!(
|
||||
wf2.status, "completed",
|
||||
"链耗尽(长度 1)后应恢复完成翻转"
|
||||
);
|
||||
}
|
||||
|
||||
/// 完成 flip 的「种子轮换待执行」阻塞回归(2026-08-22 审查补充):
|
||||
/// 链尾 [seed_step_stab] 失败 + 存在未试过的同物理族干净种子 → 不翻转
|
||||
/// (否则 rotation 臂被 still_running 守卫吞掉,waypoint 只有一次机会);
|
||||
/// 同族种子全部用过后放行翻转。
|
||||
#[tokio::test]
|
||||
async fn test_workflow_flip_blocked_by_seed_rotation_pending() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::new(
|
||||
&temp_dir
|
||||
.path()
|
||||
.join("flip_rot_db.db")
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let params = GridPointParams {
|
||||
teff: 60000.0.into(),
|
||||
logg: 5.0.into(),
|
||||
loghe: (-4.0).into(),
|
||||
logc: (-4.0).into(),
|
||||
logn: (-4.0).into(),
|
||||
logo: (-1.0).into(),
|
||||
};
|
||||
let name = params.model_name();
|
||||
db.upsert_workflow("wf_rot", None, "config", "idle")
|
||||
.await
|
||||
.unwrap();
|
||||
db.update_workflow_status("wf_rot", "running").await.unwrap();
|
||||
db.upsert_grid_point(¶ms, 0, "wf_rot").await.unwrap();
|
||||
db.update_grid_status(&name, GridPointStatus::Failed, "wf_rot")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 链尾 [seed_step_stab] 失败行。
|
||||
let t1 = uuid::Uuid::new_v4();
|
||||
db.insert_task(&common::models::TaskSpec {
|
||||
task_id: t1,
|
||||
point_name: name.clone(),
|
||||
params: params.clone(),
|
||||
timeout_sec: 7200,
|
||||
workflow_name: Some("wf_rot".to_string()),
|
||||
wave: 0,
|
||||
tlusty_config: PhaseConfig {
|
||||
strategies: vec!["seed_step_stab".to_string()],
|
||||
..PhaseConfig::default_tlusty()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
let pool = db.pool.clone();
|
||||
let tid = t1.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let conn = pool.get().unwrap();
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'failed', completed_at = datetime('now') \
|
||||
WHERE task_id = ?1",
|
||||
params![tid],
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// 无同族种子 → 无可轮换 → 放行翻转。
|
||||
db.sync_all_running_workflows_completion().await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_workflow("wf_rot").await.unwrap().unwrap().status,
|
||||
"completed",
|
||||
"无同族候选种子时链尾失败应放行翻转"
|
||||
);
|
||||
|
||||
// 重启场景:workflow 回 running,注入一个未用过的同族干净种子。
|
||||
db.update_workflow_status("wf_rot", "running").await.unwrap();
|
||||
let neighbor = GridPointParams {
|
||||
logn: (-3.489798).into(),
|
||||
..params.clone()
|
||||
};
|
||||
db.insert_seed_named(
|
||||
"t60000_g5_he-4_c-4_n-3.489798_o-1_ladder",
|
||||
&neighbor,
|
||||
"/tmp/seed.7",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
db.sync_all_running_workflows_completion().await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_workflow("wf_rot").await.unwrap().unwrap().status,
|
||||
"running",
|
||||
"存在未试过的同族种子时,链尾 seed_step_stab 失败应阻塞翻转(rotation 待执行)"
|
||||
);
|
||||
|
||||
// 轮换消费该种子(任务行携带 seed_point_name)→ 候选耗尽 → 放行。
|
||||
let t2 = uuid::Uuid::new_v4();
|
||||
db.insert_task(&common::models::TaskSpec {
|
||||
task_id: t2,
|
||||
point_name: name.clone(),
|
||||
params: params.clone(),
|
||||
seed_point_name: Some("t60000_g5_he-4_c-4_n-3.489798_o-1_ladder".to_string()),
|
||||
timeout_sec: 7200,
|
||||
workflow_name: Some("wf_rot".to_string()),
|
||||
wave: 0,
|
||||
tlusty_config: PhaseConfig {
|
||||
strategies: vec!["seed_step_stab".to_string()],
|
||||
..PhaseConfig::default_tlusty()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
let pool = db.pool.clone();
|
||||
let tid = t2.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let conn = pool.get().unwrap();
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'failed', completed_at = datetime('now'), \
|
||||
created_at = datetime('now', '+1 hour') WHERE task_id = ?1",
|
||||
params![tid],
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
db.sync_all_running_workflows_completion().await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_workflow("wf_rot").await.unwrap().unwrap().status,
|
||||
"completed",
|
||||
"同族种子全部用过(无新候选)后应放行翻转"
|
||||
);
|
||||
}
|
||||
|
||||
/// 完成 flip 的阶段归因回归(2026-08-22 审查补充):synspec 失败行的
|
||||
/// tlusty_strategies 是完整审计副本(scheduler.rs synspec 回退刻意保留),
|
||||
/// 阻塞判定必须按 failed_stage='synspec' 只看 synspec 链——否则 synspec
|
||||
/// 工作流会因 stale 审计副本永久卡 running。
|
||||
#[tokio::test]
|
||||
async fn test_workflow_flip_stage_attribution_ignores_tlusty_audit_copy() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let db = Database::new(
|
||||
&temp_dir
|
||||
.path()
|
||||
.join("flip_syn_db.db")
|
||||
.to_string_lossy(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let params = GridPointParams {
|
||||
teff: 30000.0.into(),
|
||||
logg: 5.0.into(),
|
||||
loghe: (-2.0).into(),
|
||||
logc: (-2.0).into(),
|
||||
logn: (-2.0).into(),
|
||||
logo: (-2.0).into(),
|
||||
};
|
||||
let name = params.model_name();
|
||||
db.upsert_workflow("wf_syn2", None, "config", "idle")
|
||||
.await
|
||||
.unwrap();
|
||||
db.update_workflow_status("wf_syn2", "running").await.unwrap();
|
||||
db.upsert_grid_point(¶ms, 0, "wf_syn2").await.unwrap();
|
||||
db.update_grid_status(&name, GridPointStatus::Failed, "wf_syn2")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// synspec 失败行:synspec 链已耗尽(长度 1),但 tlusty 链是完整审计
|
||||
// 副本(长度 3)——阶段归因下不应阻塞。
|
||||
let t1 = uuid::Uuid::new_v4();
|
||||
db.insert_task(&common::models::TaskSpec {
|
||||
task_id: t1,
|
||||
point_name: name.clone(),
|
||||
params: params.clone(),
|
||||
timeout_sec: 7200,
|
||||
workflow_name: Some("wf_syn2".to_string()),
|
||||
wave: 0,
|
||||
tlusty_config: PhaseConfig {
|
||||
strategies: vec![
|
||||
"cold_run".to_string(),
|
||||
"seed_step".to_string(),
|
||||
"seed_step_stab".to_string(),
|
||||
],
|
||||
..PhaseConfig::default_tlusty()
|
||||
},
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
{
|
||||
let pool = db.pool.clone();
|
||||
let tid = t1.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let conn = pool.get().unwrap();
|
||||
conn.execute(
|
||||
"UPDATE tasks SET status = 'failed', failed_stage = 'synspec', \
|
||||
synspec_strategies = '[\"standard\"]', completed_at = datetime('now') \
|
||||
WHERE task_id = ?1",
|
||||
params![tid],
|
||||
)
|
||||
.unwrap();
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
db.sync_all_running_workflows_completion().await.unwrap();
|
||||
assert_eq!(
|
||||
db.get_workflow("wf_syn2").await.unwrap().unwrap().status,
|
||||
"completed",
|
||||
"synspec 链已耗尽的失败行,其 tlusty 审计副本不应阻塞完成翻转"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_take_pending_node_token_atomic() {
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
@@ -3695,6 +4170,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
};
|
||||
let report1 = TaskReport {
|
||||
@@ -3761,6 +4237,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
};
|
||||
let report2 = TaskReport {
|
||||
@@ -3927,6 +4404,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
};
|
||||
let report1 = TaskReport {
|
||||
@@ -3984,6 +4462,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: Some("synspec 失败".into()),
|
||||
};
|
||||
let report2 = TaskReport {
|
||||
@@ -4108,6 +4587,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
};
|
||||
let report1 = TaskReport {
|
||||
@@ -4183,6 +4663,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
};
|
||||
let report2 = TaskReport {
|
||||
|
||||
Reference in New Issue
Block a user