将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。
引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
(mark_grid_point_running 仅 pending/queued→running;
record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照
僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复
动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效
科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)
前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画
文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
23 KiB
任务失败判定逻辑调研:Tlusty&Synspec收敛性判断LUSTY / SYNSPEC 退出码可靠性与兜底机制
调研日期:2026-08-04 范围:
crates/common/src/runner.rs、crates/node/src/reporter.rs、crates/common/src/conv_check.rs的任务成败判定逻辑,对照 TLUSTY 208 / SYNSPEC 54 源码(tlusty/tlusty208.f、synspec/synspec54.f) 与 gfortran 运行时退出码行为。 方法:源码级静态分析 + gfortran 实测(退出码、运行时错误)+ 三方独立复核。 本文仅记录调研结论,不含修复实现。
0. TL;DR
调度系统通过子进程退出码 rc 判定 TLUSTY / SYNSPEC 成败,但 gfortran 下裸 STOP 默认返回 rc=0,而两个 Fortran 程序的几乎所有错误路径用的都是裸 STOP 或 call quit(内部亦为裸 stop)。因此 rc 本身不可靠。
当前判定能"基本不出错",靠的是 rc 之外的兜底:
- TLUSTY:
rc==0 && fort.7 存在才进入check_fort9(max_relc < chmax)+atmosphere_has_nan双重否决。这三重条件对"显式发散 STOP"和"硬崩溃(信号)"覆盖可靠。 - SYNSPEC:仅靠
synspec_rc==0,下游零内容校验,是当前系统最大的科学正确性风险。
复核(含 gfortran 运行时错误实测)发现两类此前未点出的风险:
- gfortran 运行时错误默认返回 rc=0 且静默(实数 IEEE 异常、数组越界),生产编译未启用
-fcheck/-ffpe-trap。 - fort.9 缺失分支无条件判收敛,且写入虚构的
best_max_relc=0.0,会污染种子选择并向相邻网格点扩散。
修复优先级收敛为:SYNSPEC .spec 内容校验 > 收紧 atmosphere_has_nan 阈值 > 修正 fort.9 缺失分支 > 补齐 SOLVES/RYBSOL 发散路径归因。
1. 现状:失败判定的四层结构
node 端任务失败判定从底向上分四层:
execute_task (executor.rs) ← 前置 IO 失败 → 直接返回 Err
└─ run_model_with_timeout ← 计算失败 → 产出 ModelSummary(converged 字段)
└─ derive_report_status ← 半失败判定 → Completed / Failed
└─ infer_failed_stage ← 失败归因 → tlusty / synspec
第一层:execute_task(executor.rs:11)—— 前置 IO 失败
直接返回 Err、进不到计算:
| 失败情形 | 处理 | 行号 |
|---|---|---|
| 原子数据文件拉取失败 | warn 但继续(非致命) |
executor.rs:46-50 |
| 种子大气下载失败(HTTP 非 2xx / 网络) | bail! 返回 Err |
executor.rs:114-137 |
| 沙盒目录创建失败 | ? 返回 Err |
executor.rs:57 |
返回 Err 时,worker.rs:456 转为 String,reporter 走 Err(e) 分支(reporter.rs:78-89):
status=Failed, converged=false, error_message=e。
第二层:run_model_with_timeout(runner.rs:232)—— 计算过程收敛判定
核心,final_converged 在此累积,分三块:
① TLUSTY 阶段链收敛判定(runner.rs:402-469)
每个阶段执行后,只有 rc==0 && fort.7 存在 才进入收敛检查,否则该阶段 converged=false:
// runner.rs:402
if rc == 0 && fort7.is_file() {
if fort9.is_file() {
let res = check_fort9(&fort9, eff_chmax);
stage_summary.converged = res.converged; // max_relc < chmax
} else {
// NITER=0 grey start without fort.9 ← 见 §4 漏洞 2
stage_summary.converged = true;
stage_summary.best_max_relc = Some(0.0);
}
} else {
stage_summary.note = Some(format!("tlusty rc={} or missing fort.7", rc));
}
check_fort9 的收敛判据(conv_check.rs:163):
converged = max_relc.is_finite() && max_relc < chmax
其中 max_relc 是 fort.9 最后一次迭代中所有深度点里最大的相对修正绝对值。关键防御:
chmax ≤ 0 或非有限→ 直接判失败并报错(conv_check.rs:58,防误配)- fort.9 无有效迭代数据 →
max_relc=Infinity,判失败(conv_check.rs:127) - 发散值(NaN/Inf/无-E 记数法
-1.35+118)→is_finite()=false→ 判失败(conv_check.rs:160) - fort.9 缺失但 rc=0 → 视为"NITER=0 grey start",判收敛(runner.rs:419)—— 见 §4 漏洞 2
阶段间联动:若某阶段 require_converged=true 且未收敛 → break,中止后续所有阶段(runner.rs:463)。
final_converged 取最后执行阶段的值。
② 大气 NaN 强制否决(runner.rs:488-491)
let atmo_has_nan = atmosphere_has_nan(&final_7);
if atmo_has_nan { final_converged = false; }
atmosphere_has_nan(conv_check.rs:190)检测大气文件里 NaN/Inf/********(Fortran 字段溢出),
超 10% 行命中即判无效。此步会推翻前面收敛结论 —— 见 §4 漏洞 3 的阈值盲区。
③ SYNSPEC 阶段(runner.rs:498-603)
SYNSPEC 不直接改 final_converged,记录到 synspec_rc / synspec_error:
fort.8(大气输入)复制失败 → 记synspec_error,跳过(runner.rs:508)fort.55(控制卡)写入失败 → 记synspec_error,跳过(runner.rs:531)- synspec 进程 rc≠0 或超时(上限
min(600, timeout_sec))→ 记synspec_rc(runner.rs:568-575)
仅 SYNSPEC 场景(tlusty 关闭)的收敛重判(runner.rs:611-616):
final_converged = (synspec_rc == 0)。
第三层:derive_report_status(reporter.rs:38)—— 半失败 = 失败
let synspec_failed = synspec_error.is_some() || matches!(synspec_rc, Some(rc) if rc != 0);
if s.converged && !synspec_failed { TaskStatus::Completed } else { TaskStatus::Failed }
旧逻辑 if converged { Completed } 只看大气收敛,TLUSTY 收敛但 SYNSPEC 失败时 converged 仍为 true
→ 误报成功 → 服务端吸收为终态。新逻辑:大气好 + 光谱坏 = 整体失败,让服务端弹 synspec 链重试。
converged 字段仍按大气真实状态上报(为 true 时服务端仍存 .7 作种子)。
第四层:infer_failed_stage(reporter.rs:15)—— 失败归因
| 条件 | 归因 |
|---|---|
converged=false + tlusty 启用 |
tlusty(含级联) |
converged=false + tlusty 关闭 |
synspec |
converged=true 但 synspec 出错/rc≠0 |
synspec |
| 全部成功 | None |
2. rc==0 的真实含义与不可靠性
2.1 gfortran 退出码实测(实证)
在 /tmp 用最小 Fortran 程序实测(gfortran 15.2.0,-fno-automatic -O3 等价于生产编译环境):
| Fortran 语句 / 错误类型 | 触发方式 | 退出码 rc | runner 能否抓住 |
|---|---|---|---|
裸STOP |
stop |
0 | ❌ 靠 fort.9 兜 |
STOP 'msg' |
stop 'x' |
0 | ❌ 靠 fort.9 兜 |
STOP 1(带数字) |
stop 1 |
1 | ✅(TLUSTY 未用此形式) |
自然END |
主程序结束 | 0 | — |
实数除零1.0/0.0 |
IEEE 默认 | 0 | ❌ 产 Inf,无陷阱 |
0.0/0.0 |
IEEE 默认 | 0 | ❌ 产 NaN |
实数溢出1e30*1e30 |
IEEE 默认 | 0 | ❌ 产 Inf |
sqrt(-1) / log(0) |
IEEE 默认 | 0 | ❌ 产 NaN/-Inf |
整数除零 1/0 |
SIGFPE | 136(128+8) | ✅ |
| 数组越界(默认) | 无-fcheck |
0,静默写越界内存 | ❌ |
数组越界(-fcheck=bounds) |
启用检查 | 2 | ✅ |
READ 遇 EOF(无end= 守卫) |
运行时 | 2 | ✅ |
READ 类型不匹配(无err= 守卫) |
运行时 | 2 | ✅ |
| 栈溢出 / 段错误 | SIGSEGV | 139(128+11) | ✅ |
生产编译命令(deployment.md:86,90)为
gfortran -fno-automatic -O3,未启用-fcheck=bounds与-ffpe-trap=invalid,zero,overflow。因此表中所有 rc=0 的静默失败在生产二进制中真实存在。
2.2 核心矛盾
Fortran 的 STOP 语句默认返回 0,而 TLUSTY/SYNSPEC 几乎所有错误处理用的都是裸 STOP 或 call quit(内部裸 stop)。导致进程异常终止的退出码 = 正常完成的退出码 = 0。
- TLUSTY
subroutine quit(tlusty208.f:29946-29955)末行裸stop。 - SYNSPEC
subroutine quit(synspec54.f:12014-12021)末行裸stop。 - 两者全文 80+ 处
STOP/call quit,错误路径几乎全走裸STOP。
3. 源码级失败路径分析
3.1 TLUSTY:三重兜底对"显式发散"覆盖可靠
主循环结构(tlusty208.f:1-62):
10 ITER=ITER+1
CALL RESOLV ← 形式解;内部 CALL OUTPUT(:3807/:3828) 每次迭代写 fort.7
IF(LFIN) GO TO 20 ← 上一轮设了 LFIN 就跳出
IF(IACC.GT.0) CALL ACCEL2
IF(IFRYB.EQ.0) THEN
IF(NN.GT.MSMX) THEN
CALL SOLVE ← 含发散 STOP(:14703)
ELSE
CALL SOLVES ← 含发散 STOP(:15050)
END IF
ELSE
CALL RYBSOL ← 含发散 STOP(:47585-47590) ← 修正点:第三条路径
END IF
GO TO 10
20 STOP ← 主程序正常结束,裸 STOP → rc=0
END
关键事实:
- fort.7 每次迭代都写:
CALL OUTPUT(tlusty208.f:3807, 3828)在 RESOLV 内,每次迭代执行。 - 发散 STOP 在三处平行求解器内(不止 SOLVE):
SOLVE:14701-14706(CHMX > 1.D16→ 裸 STOP)SOLVES:15047-15051(同逻辑)RYBSOL/RYBCHN :47585-47590(同逻辑,IFRYB≠0时走此路) 三处发散检查都在PRCHAN/RYBCHN 写完 fort.9 之后,故发散 STOP 时 fort.7 与 fort.9 均已落盘。
- 达到 NITER 未收敛:
LFIN=ABS(CHMX).LE.CHMAX.OR.ITER.GE.NITER(:14728),走主程序正常 END,rc=0,fort.9 含未收敛的 max_relc。
TLUSTY 失败场景对照:
| 场景 | Fortran 处理 | rc | 写 fort.7 | 写 fort.9 | runner 判定 | 是否合适 |
|---|---|---|---|---|---|---|
| 正常收敛 | 主程序 END | 0 | ✅ | ✅ | check_fort9 → converged | ✅ |
| 达到 NITER 未收敛 | LFIN,END |
0 | ✅ | ✅ | check_fort9 → max_relc≥chmax → 未收敛 | ✅ |
| 数值发散 CHMX>1e16 | SOLVE/SOLVES/RYBSOL 内裸 STOP | 0 | ✅ | ✅ | check_fort9 → 发散值未收敛 | ✅ |
| 输入错误(call quit) | 裸 stop | 0 | ❌ | ❌ | rc=0 但 fort.7 缺失 → 失败 | ✅ |
| temp 越界 | stop 'partf; temp...'(:44624) |
0 | ❌ | ❌ | fort.7 缺失 → 失败 | ✅ |
结论:rc 虽不可靠,但 rc==0 + fort.7 存在 + check_fort9 + atmosphere_has_nan 四重条件对"显式 STOP 类发散"和"硬崩溃(信号)"覆盖可靠 —— 这是一个巧合但有效的设计。
3.2 SYNSPEC:仅靠 rc,基本裸奔
主程序结构(synspec54.f:1-174): 主循环 :10 标签到 :150 IF(IBLANK.LT.NBLANK) GO TO 10,
CALL OUTPRI(:143)写谱输出(unit 7 谱 / 17 连续谱 / 16 部分等宽 / 6 诊断)且在循环内、多次追加写(无 REWIND)。
主程序以自然 END(:174)结束,正常路径 rc=0。
call quit / STOP 相对 OUTPRI 的时序分类(复核产出):
| 归类 | 数量 | 代表行号 | 风险 |
|---|---|---|---|
| OUTPRI 之前(START 阶段输入校验) | 多数 | :726, :808, :937, :1062, :1856, :2229, :12168, :12224, :12252, :12323, :12324, :16745, :19455, :19601 | 低 —— 谱未生成,靠"文件缺失"兜得住 |
| OUTPRI 之后 / 之间(主循环内 RESOLV/RESOLW/OPAC 调用链) | 6 处 | :4203, :8208, :9935, :18863, :18865, :19891 | 高 —— 脏数据风险 |
| 死代码 / 坏码 | 3 处 | :17414stop23(坏码)、:17851 CHCKAB(调用被注释)、:18383 MOLSET(从未调用) |
无 |
脏数据风险的精确触发条件:仅在 NBLANK>1 或分子/集合重处理(synspec54.f:152-162)导致前序迭代已写 fort.7 之后,后续迭代在 INISET/OPAC/RESOLW/SETRAY 等处中途 call quit → 残缺谱已落盘 + rc=0 → 被误判成功。单集合单次运行(NBLANK=1)所有 quit 都在首次 OUTPRI 之前,无脏数据。
SYNSPEC 失败场景对照:
| 场景 | Fortran 处理 | rc | runner 判定 | 是否合适 |
|---|---|---|---|---|
| 正常完成 | 主程序 END(:174) | 0 | synspec_rc=0 → 成功 | ✅ |
| 输入数据错误(OUTPRI 前 quit) | 裸 stop | 0 | 谱未生成 → .spec 缺失(runner.rs:579 is_file 不成立)→ 未产谱 | ⚠️ 靠文件缺失 |
| 计算中途错误(OUTPRI 后 quit,多轮场景) | 裸 stop | 0 | synspec_rc=0 →误判成功 | ❌ 严重 |
4. 复核发现的漏洞(按严重度排序)
以下为源码级静态分析与 gfortran 实测、经三方独立复核确认的风险。
漏洞 1(最严重):SYNSPEC 产出的 .spec 全链路零内容校验
runner 对 SYNSPEC 产出(runner.rs:578-599)只有 is_file() 存在性检查 + rename/copy,无任何内容校验:
if model_dir.join("fort.7").is_file() {
let _ = tokio::fs::rename(..., ... ".spec").await; // 不查内容
}
下游全部不拦截:
executor.rs:191-215:只读.7(种子),从不碰.spec。result_filter.rs:64-110:纯文件名后缀白名单,spec在白名单内(result_filter.rs:28),0 字节.spec与干净的 50MB.spec同等归档。derive_report_status(reporter.rs:38-45):只看synspec_rc与synspec_error,不看产物。
完整穿透链:SYNSPEC call quit(rc=0)+ 写出含 NaN/Inf/0 字节的 .spec → is_file() 成立 → rename 成 .spec →
final_converged = rc==0(runner.rs:612)→ synspec_failed=false(reporter.rs:39)→ Completed →
脏谱归档、服务端吸收为终态、不重试。没有任何一道防线检查 .spec 内容。
漏洞 2(严重):fort.9 缺失分支无条件判收敛 + 虚构 best_max_relc=0.0
runner.rs:417-421,当 rc==0 && fort.7 存在 但 fort.9 不存在时:
} else {
// NITER=0 grey start without fort.9
stage_summary.converged = true; // 无条件判收敛
stage_summary.best_max_relc = Some(0.0); // 虚构"完美收敛"值
stage_summary.note = Some("NITER=0 grey start".to_string());
}
- 注释"NITER=0 grey start"是断言而非检查,代码未校验本次 stage 的
niter配置是否真为 0。 best_max_relc = Some(0.0)进入final_max_relc(runner.rs:458-459)→ 上报(reporter.rs:73)。若服务端用 max_relc 选种子,会把误判模型当"完美收敛"优先选作下一轮种子,污染向相邻网格点扩散。- 合法 grey start(
lte/nc阶段,require_converged=false,runner.rs:23/38)与致命崩溃共用同一段无校验代码,无法区分。
触发条件:rc==0(gfortran 裸 STOP 或运行时静默错误)+ fort.7 已写 + fort.9 缺失。严重度比漏洞 1 低,因为多数发散情况 fort.9 会写出含发散值的行,反而触发 check_fort9 的 is_finite()=false 判不收敛。
漏洞 3(严重):gfortran 运行时错误默认 rc=0 且静默
见 §2.1 实测表。在生产编译(无 -fcheck/-ffpe-trap)下:
- 实数 IEEE 异常(除零、溢出、NaN 产生)→ rc=0,无陷阱,静默产出 NaN/Inf。
- 数组越界(无
-fcheck=bounds)→ rc=0,静默写越界内存,可能产出物理上错误但无 NaN 标记的大气。
这两类若发生在 RESOLV 写完 fort.7(tlusty208.f:3828)之后、SOLVE 写 fort.9 之前 → fort.7 在、fort.9 不在 → 命中漏洞 2 → 误判收敛。atmosphere_has_nan 能否兜住取决于越界是否恰好产生 NaN/Inf 字面值;若只是静默改写相邻内存,此防线无效。
漏洞 4(中):atmosphere_has_nan 的 10% 阈值对物理产物过松
conv_check.rs:212:(bad_lines as f64) > (total_lines as f64 * 0.1),严格大于。
典型 .7 文件 60-100 个深度行,阈值 6-10 行:
| 场景 | bad/total | 判定 | 物理正确性 |
|---|---|---|---|
| 全 NaN(彻底发散) | 100% | true → 否决 | 正确 |
| 6/100 行 NaN(表层发散) | 6% | false → 放过 | 错误 |
| 5/60 行 NaN(典型深度网格) | 8.3% | false → 放过 | 错误 |
| 单行 NaN | 1% | false → 放过 | 错误 |
物理上任意一个深度点的温度/密度是 NaN,整个大气就不可用(积分会传播 NaN)。10% 是统计学语义,与大气可用性的布尔语义不匹配。表层(深度浅、温度高)最易数值发散,往往只有最外几层出 NaN —— 正好落在盲区。
补充盲区:regex (?i)(\bnan\b|\binf(?:inity)?\b|\*{3,})(conv_check.rs:199)只匹配字面 NaN/Inf/***;若 Fortran 把发散值写成接近 f64 上限但未溢出的普通科学记数法(如 1.0E+308),不命中任何模式。
漏洞 5(低):infer_failed_stage / 日志归因对 RYBSOL 路径不完整
发散 STOP 在 SOLVE / SOLVES / RYBSOL 三处(§3.1),但失败 note 统一记 tlusty rc=0 or missing fort.7(runner.rs:429),丢失"为何未收敛"的线索。对走 RYBSOL(IFRYB≠0)的发散,归因信息不完整。
漏洞 6(低):仅 SYNSPEC 场景 final_converged 重判只看 rc
runner.rs:611-616,TLUSTY 关闭的纯复算场景,final_converged = (synspec_rc == 0),与漏洞 1 同源,场景更窄。
5. 被排除的伪风险
超时 / shutdown 后半成品 fort.7 进 check_fort9 误判收敛 —— 不存在。
run_child_async_with_timeout 在超时/shutdown 时返回 Err(runner.rs:151, 156)→ 上层 match rc = -1
(runner.rs:380-383 / 568-574)。于是 rc==0 && fort.7 存在 前置门不成立 → 进 else 分支 →
converged 保持 false → check_fort9 根本不调用。SYNSPEC 超时同理 → synspec_rc=-1 → 判失败。
超时路径是可靠的。
6. 修复优先级(仅建议,不含实现)
| 优先级 | 漏洞 | 建议 | 改动范围 |
|---|---|---|---|
| P0 | 漏洞 1 | SYNSPEC.spec 内容校验:NaN/Inf/*** + 行数下限 + 非全零,命中则置 synspec_rc 为非零或设 synspec_error |
runner.rs(SYNSPEC 产出后,runner.rs:585 附近) |
| P1 | 漏洞 4 | 收紧物理产物 NaN 阈值:产物准入用 0 行容忍(任意一行 NaN 即否决),而非 10% | conv_check.rs:212 或新增严格版函数 |
| P1 | 漏洞 2 | runner.rs:417-421:至少把best_max_relc 从 Some(0.0) 改为 None(消除污染扩散);理想是校验 stage 的 niter 配置才走该分支 |
runner.rs:417-421 |
| P2 | 漏洞 5 | 补齐 SOLVES / RYBSOL 发散路径的归因,失败 note 区分求解器类型 | runner.rs:429 |
| P3 | 漏洞 3 | 生产编译加-fcheck=bounds -ffpe-trap=invalid,zero,overflow,让数组越界与 IEEE 异常返回 rc≠0(性能换正确性) |
deployment.md 编译命令 |
7. 参考资料
- 源码:
tlusty/tlusty208.f(主程序 :1-62;发散 STOP :14703/:15050/:47585;quit :29946-29955; PRCHAN :22876-22944;RYBCHN :47445-47598;RESOLV :3724-3903;OUTPUT :14055 起)synspec/synspec54.f(主程序 :1-174;OUTPRI :3343-3458;quit :12014-12021; 脏数据风险点 :4203/:8208/:9935/:18863/:18865/:19891)
- 判定逻辑:
crates/common/src/runner.rs(run_model_with_timeout :232-682;漏洞 1/2/5/6 所在)crates/node/src/reporter.rs(derive_report_status :38-45;infer_failed_stage :15-27)crates/common/src/conv_check.rs(check_fort9 :54-175;atmosphere_has_nan :190-213)
- 编译配置:
docs/deployment.md:86,90(确认生产编译无-fcheck/-ffpe-trap) - 相关设计:
docs/task_engine_decoupling_design.md(阶段独立配置、半失败判定语义)