Files
DCTS/docs/workflow_detail_design.md
T
fmq 43b82b1ae2 feat(all): 物理正确性五重硬门槛、输入文件结构化与 fort.55 错位修复、conv 诊断 DB 化与阶段归因修复、ORELAX 收敛修复与导入工具下线
物理正确性校验体系(common/conv_check.rs +494 行)
- 新增 5 类硬门槛:能量守恒(.6)、温度结构(.7)、emflux 积分校验(.emflux,含全 NaN 判失败)、假收敛排查(itek 轨迹首末比)、b 因子合理性(.bfac)
- runner 在 TLUSTY 阶段结束后执行全部校验,任一失败判 final_converged=false
- GridConfig 新增 8 个可配阈值,经 scheduler→executor→runner 全链路透传

输入文件配置结构化重构(config.rs +1453 行)
- TlustyInput 拆为 dot5/nst 分层结构,字段名严格映射 tlusty208.f READ 语句;SynspecInput 重构为 9 个 Fort55Line 子结构体
- 移除 ChainStep.metals 字段,元素集改由 dot5.atoms/ions 显式声明(gen_input5/nst_writer 同步重写为三源融合 / 分层覆盖)
- fort.55 修复行结构 bug:补全分子表行(7→9 行),IDSTD 50→0 错位修正(影响全部光谱线强归一化,需重算 SYNSPEC 阶段)

conv 诊断 DB 化与阶段归因修复(server)
- 单点详情 conv 面板从磁盘 conv.json 改读 DB grid_points.summary_json;grid_points 新增 summary_json/last_elapsed_sec 两列(旧库幂等 ALTER)
- record_task_report 阶段归因列加 CASE 守卫 + clear_synspec 对称处理,修复 synspec-only/TLUSTY-only 重跑污染统计
- 新增 summary_merge.rs 点级增量合并,避免重跑覆盖诊断字段

收敛性 ORELAX 修复与 seed_chain 可配(sdB_cno.yaml + node)
- nl 阶段加 orelax=0.5、seed_nc 加 orelax=0.3,阻尼中温区 relc 振荡发散
- seed_chain 块可配,executor 优先采用用户配置而非内置默认链

导入工具下线
- 删除 import_results 客户端工具及 Windows 推送脚本;移除 /admin/import_seed 端点
- 改为服务端临时 migrate_conv 端点(扫 conv.json 增量合并入库,迁移后可删)

文档与分析
- 新增 1305 失败点根因分析、fort.14 全 NaN 物理含义分析两份深度文档
- spectrum_correctness_analysis 两次修订标注已修复项;fetch_results.sh 修 trap RETURN 的 set -u 报错
2026-08-09 12:09:48 +08:00

27 KiB
Raw Blame History

工作流执行可视化 —— 功能重设计文档

目标:把"恒星大气网格工作流"面板从当前的 3 个动作(查看 YAML / 启动 / 删除) 升级为完整的 执行可观测台:执行进度、逐网格点执行详情、收敛判定、 冷启动 vs 种子步进归因、参数空间冷启动成功图谱,以及失败点重试。

原则:数据已经在库里大半,缺口主要是 HTTP 暴露 + 少量持久化 + 全新前端 UI。 零新增前端依赖(不引入图表库),沿用现有 ESM + 原生 DOM + 设计令牌风格。

⚠️ 实现状态(2026-08 更新,与当前 dashboard 实现的差异)

本文为设计文档,部分设计已被后续实现与 task_engine_decoupling_design.md 的 引擎面板设计覆盖,按现状对齐如下:

  • 已实现:详情页 4 个 Tab(概览 overview / 逐点表 pointsTable / 平行集合分析 parSets / 点详情面板 pointPanel)、进度曲线与 ETA、停滞检测横幅、CSV 导出、 workflow_progress_snapshots 表、YAML 编辑器(yamlEditor,双模式/语法高亮/运行中 锁定/脏状态守卫)、引擎配置面板(wfEnginePanelTLUSTY/SYNSPEC 双阶段卡、enabled/ policy/strategies 编辑、折叠跨轮询保持)。
  • 已变更(设计被覆盖):首页卡片操作按钮与详情页顶部控制条按钮已全部移除 操作收敛至详情页内嵌引擎面板(见 task_engine_decoupling_design.md §6);Tab 3 由 2D 热力矩阵改为 Parallel Sets 平行集合图(§2.2 Tab 3 描述过时);Tab 1 方法归因 行已移除(cold/seed 计数在点表与 parSets 中可用)。
  • 未实现:§2.2/§2.4 的「重试失败点」按钮与 POST .../points/retry 端点 (前后端均未实现)。
  • 方法归因imported 不再作为独立收敛途径(导入点按 seed_step 统计), 大气方法过滤白名单 cold_run / seed_step(映射 tlusty_success_method);另有 synspec_only 光谱专用点过滤器(tlusty IS NULL AND synspec IS NOT NULL)。

1. 现状盘点(带代码证据)

1.1 已经持久化、可直接用的数据

数据 位置 说明
网格点 6 维参数、cno_sumwave grid_points 表(db.rs:246-263 workflow_name 分区,复合唯一 (workflow_name, name)
点状态 pending/queued/running/completed/failedcompleted 由 7c 改名自 converged grid_points.status GridPointStatusmodels.rs:235-243
大气成功手段 cold_run / seed_step / imported grid_points.tlusty_success_method TLUSTY 阶段收敛时由成功任务的 tlusty_strategies[0] 派生回填(Phase 6 起 task_type 列已删除);TLUSTY 禁用(synspec-only)为 NULL。光谱阶段另存 synspec_success_methodPhase 5a,取 synspec_strategies[0])。整体归因由消费方派生 tlusty ?? synspecP9 拆分,原 success_method 值域混用列已删)
重试次数 grid_points.attempt_count 每次回报 +1db.rs:1089-1095
每次尝试的收敛指标 tasks.max_relc 收敛判据 max_relc < chmax(默认 0.001conv_check.rs:105-109
每次尝试的种子来源 tasks.seed_point_name 派发时写入(scheduler.rs:194-205
每次尝试的方法 / 错误 / 节点 / 完成时间 tasks.tlusty_strategies[0] / error_message / node_id / completed_at 一个点多行(每次尝试一行),工作流删除前长期保留;方法取自策略链首项(task_type 列已删)
全局种子库 seeds 表 + data/seeds/<name>/<name>.7 仅收敛且无 NaN 的点入库(task.rs:187-207
逐阶段完整诊断lte→nc→nl / seed_nc→nl data/seeds/<name>/conv.json ModelSummarymodels.rs:376-391):每阶段 converged / best_max_relc / chmax / elapsed_sec、总耗时、synspec_sec、所用种子。成功与失败的点都会写task.rs:178-184
按工作流的统计聚合 db.get_grid_summary_stats(Some(wf)) 已实现且有单测db.rs:1582-1639),但没有任何 HTTP 端点调用它

1.2 缺口(需要补的)

# 缺口 影响 补法
G1 无逐点 HTTP 端点 前端拿不到任何点级数据 新增 /workflows/:name/points
G2 无按工作流的统计端点(get_grid_summary_stats 未暴露;/api/status 只有全局) 卡片/详情无法显示每工作流进度 新增 /workflows/:name/stats;列表接口内联计数
G3 queued 被并进 pendingdb.rs:1596,1612 无法区分"排队中"与"未入队" 新端点拆开统计
G4 单点耗时/迭代数未进库(TaskReport.elapsed_sec 收到即丢;last_iterStepSummary 前被丢弃) 列表无耗时列、无 ETA 迁移补列 + runner 携带 last_iterPhase 3
G5 无进度时间序列 画不出"进度-时间"曲线 新表 workflow_progress_snapshotsPhase 3
G6 失败点无法重试(start 只重置 queueddb.rs:886-899 失败点永久卡死 新增 retry 端点(复用 reset_specific_grid_points_to_pending 思路)
G7 conv.json 不提供下载/查看 逐阶段诊断无法展示 点详情端点读盘解析返回
G8 前端无任何进度/详情 UI;无 tab 组件 —— 全新 UI

1.3 领域语义(设计依据)

  • 冷启动 vs 种子步进不是用户选的,是调度器按"种子可用性"自动决定的 派发时 find_best_seed_from_db 命中(同族精确匹配优先,否则全局加权距离 ≤ 3.0, seed_finder.rs:13-38)→ SeedStep(热启动链 seed_nc→nl),否则 ColdRunlte→nc→nl)。
  • wave = 难度波次:按 cno_sum 升序分组,"先易后难",让早期收敛点充当种子。 所以"哪些参数冷启动能成功" ≈ 低 cno_sum / 中低温区的点;高温 He-poor 区几乎全靠种子步进。
  • 还有一次性失败回退:冷启动发散且存在邻居种子时,自动以 seed_step 重投一次 scheduler.rs:270-350,受 seed_step_fallback 配置与 has_seed_step_attempt 一次性闸门约束)。 因此 tlusty_success_method='seed_step' 的点可能是"冷启动失败后被救回"的——该归因需从 tasks 多行历史还原。
  • 第三类 imported:历史 run_grid.py 结果导入(db.rs:966-981),UI 需单独呈现为"历史导入"。
  • 收敛判据:末次迭代最差深度点的最大相对修正 max_relc < chmax(默认 1e-3), 且大气 NaN 占比 ≤ 10%(conv_check.rs:127-149)。

2. 功能设计(信息架构)

2.1 工作流卡片(改造现有 workflows.js 卡片)

┌─────────────────────────────────────────────┐
│ sdB_cno                          [运行中]    │  ← 现有:名称/描述/状态徽章
│ sdB 6维恒星大气模型计算网格                   │
│ ▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░  42%            │  ← 新增:收敛进度条 + 百分比
│ 181/432 收敛 · 12 种子步进 · 3 失败 · 8 运行 │  ← 新增:一行计数摘要
│ [进入详情 →]            [查看YAML] [暂停] [删除] │  ← 整卡头部可点进详情页
└─────────────────────────────────────────────┘
  • 卡片名/头部区域是 <a href="#/workflows/sdB_cno">;操作按钮 stopPropagation 保持独立语义。

  • 进度数据来自 列表接口内联计数(见 §3.2),不额外轮询。

  • 状态徽章扩充:initializing 显示"初始化中"warning 琥珀),paused 显示"已暂停"secondary), 不再把三者都归为"闲置"。

2.2 工作流详情页(独立页面,hash 路由 #/workflows/:name

信息架构决策:详情内容量(三 Tab + 可过滤点表 + 热力矩阵 + 逐点诊断)远超模态框承载, 且需要深链/返回键/长时间监控驻留。因此采用独立视图页面 + 零依赖 hash 路由, 而非宽模态。入口:卡片整卡或"详情"按钮 → location.hash = '#/workflows/<name>'

页面骨架(顶部常驻"任务控制条",是全页最先映入眼帘的部分——直接呈现这个网格的实时生命体征):

← 返回控制台        sdB_cno   [运行中]          [启动/暂停] [重试失败] [查看YAML] [删除]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░  ← 分段进度条:绿=收敛 红=失败 琥珀=运行 蓝=排队 灰=待定
432 网格点 · 181 收敛(42%) · 8 运行 · 40 排队 · 3 失败 · 冷启动 169 / 种子步进 12 · ETA ≈ 2h40m
  • 分段进度条按状态占比着色拼接,5s 轮询下各段宽度此消彼长——进度是"看得见在动"的。
  • 计数用 tabular-num 防跳动;数字变化可加轻量过渡。
  • 工作流不存在(hash 指向已删除的名字)→ 空态卡 + 返回按钮。

Tab 1 — 执行概览

  • 4 个 metric tile(复用 .metric-card):网格总数 / 已收敛 / 失败 / 运行中+排队。
  • 方法归因:冷启动收敛 X · 种子步进收敛 Y · 历史导入 Z · 失败 N(彩色计数行)。
  • wave(难度波次)分布:每波次 converged/total 的小条形列表——直观看到"难度推进到哪了"。
  • 最近动态流:最近 12 条尝试回报(点名 / 方法徽章 / 结果 / max_relc / 节点 / 完成时间), 轮询下新条目从顶部淡入——数据就是 points 端点 sort=last_completed_at&order=desc&limit=12,零新增端点。
  • ETA(Phase 3):按平均单点耗时 × 剩余点数 ÷ 集群空闲槽位估算。

Tab 2 — 网格点明细

  • 过滤器:状态(全部/排队/运行/收敛/失败)、方法(全部/冷启动/种子步进/导入)、wave 下拉、点名搜索框。
  • 表格(复用 .data-table + 关键行增量刷新,仿 nodesTable.js):
来源
点名(mono 省略,title 全文) name
Teff / logg / logHe / CNO和 6 维参数
wave wave
状态 badge:收敛=online 绿 / 失败=danger 红 / 运行=warning 琥珀 / 排队=info 蓝 / 待定=secondary
方法 badge:冷启动=蓝(blueprint/ 种子步进=紫(purple/ 导入=secondary——刻意区别于状态色
max_relc tabular-num,科学计数;失败点显示最后一次尝试值
尝试数 attempt_count>1 且收敛 = "被救回",加 ↻ 角标)
耗时 Phase 3 起有值;之前用 completed_at-created_at 粗估并标 ~
操作 [查看] → 点详情
  • 分页:limit=100 + offset,服务端排序(默认 wave→cno_sum→teff)。
  • 刷新策略:打开弹窗时拉一次;Tab 激活期间每 5s 自动刷新(可手动刷新按钮强制); 弹窗关闭即停。不污染全局轮询。

Tab 3 — 收敛性分析(冷启动图谱)

  • 核心问题:"哪些参数范围内冷启动能成功"。
  • 6 维空间降到 2D 热力矩阵:用户从下拉选 X 轴 / Y 轴(默认 X=Teff、Y=cno_sum); 其余维度若为多值则提供切片选择器(固定某维取值)。
  • 单元格着色(CSS grid,无图表库):
    • 🟩 冷启动收敛(converged && method=cold_run
    • 🟪 种子步进收敛(seed_step
    • 历史导入(imported
    • 🟥 失败(failed
    • 🟨 运行/排队中
    • ░ 未开始(pending 且从未尝试)
  • 矩阵下方:图例 + 聚合结论条("冷启动成功率 62%268/432);Teff≥60000 区间冷启动成功率 0%,全部由种子步进救回 41 个、失败 3 个")。
  • 单元格 hover 显示点名与 max_relc;点击 → 同 Tab 2 的点详情。

2.3 网格点详情(右侧滑入面板 slide-over,不再模态套模态)

点表格/热力图/动态流中任一点名 → 右侧滑入 480px 面板(.point-panel,背景遮罩 + Esc/点击遮罩关闭, 复用焦点圈管理;独立页面上滑入面板比二级模态更轻、视线不跳转)。

  • 头部:点名(mono)+ 状态/方法徽章 + [复制点名]。
  • 尝试历史表tasks 多行):# / 方法 / 种子来源(点名,可点击跳查该点)/ max_relc / 结果 / 节点 / 完成时间 / 错误摘要。 这一张表直接还原"冷启动失败 → 种子步进救回"的完整剧情。
  • 逐阶段链诊断(读 conv.json):每阶段一张小卡: lte grey start · nc ⚠️ max_relc=0.957(不要求收敛)· nl max_relc=6.9e-3 / 17 iter / 612s 种子步进链则显示 seed_nc → nl,并标注种子点名与总耗时、synspec 耗时、NaN 标志。
  • 失败点:突出显示最后一次 error_messagecode-pre-wraptextContent 渲染防 XSS)。

2.4 视图与操作逻辑

路由(新增 src/router.js,约 70 行,零依赖):

hash 视图 挂载动作 卸载动作
#/(或空/未知) 控制台首页(现有布局) 渲染骨架 + startPolling() 停全局轮询
#/workflows/:name 工作流详情页 渲染页面骨架 + 拉 stats/points + 启作用域轮询 停作用域轮询
  • window.addEventListener('hashchange', render);首次加载按当前 hash 渲染。
  • 登录门禁不变:无 token 时任何 hash 都先展示登录遮罩,登录成功后按 hash 进入对应视图。
  • 全局 header/footer 保持常驻;视图内容渲染进 #view-root 容器(index.html 结构调整: 现有 main.main-container 内容收敛为首页视图模板)。
  • 返回键/深链/刷新天然可用(hash 不触发整页刷新)。

交互一览

操作 前端 后端
进详情页 hash 切换 → 按需 fetch stats + points(第1页) ——
切 Tab 委托 data-wf-tab;切到"明细/分析"时惰性加载 ——
过滤/翻页/排序 change/click → 重新 fetch(带 query 端点支持参数
自动刷新 详情页挂载期间 5s 拉 stats(控制条+概览常驻刷新);明细 Tab 激活时同步刷新表格 ——
点详情面板 行内 [查看]/点名链接 → 拉 points/:point → 滑入面板;种子来源点名可跳查(换载面板内容) ——
重试失败点 showConfirm → POST → toast → 刷新 stats/points 新端点,仅 running 状态允许
返回首页 "← 返回控制台"链接 / 浏览器返回 ——
首页卡片 整卡点击进详情;卡片上保留 启动/暂停/删除 快捷操作 + 查看YAML(仍为模态) ——

3. 后端设计

3.1 新增端点(全部挂在 /api/workflows/:name/... 下)

鉴权:required_rolepath.starts_with("/workflows/") 已映射 Adminmod.rs:92-95), 子路由无需改鉴权矩阵。统一返回 {success, message, data} 信封(与现有 workflow API 一致)。 点名参数复用现有字符集白名单校验(task.rs:133-146 同款),防路径穿越。

GET /api/workflows/:name/stats —— 每工作流进度(G2/G3

复用并增强 get_grid_summary_stats(Some(wf))拆开 pending/queued,补方法与波次维度:

{
  "success": true, "message": "ok",
  "data": {
    "name": "sdB_cno", "status": "running",
    "total": 432,
    "pending": 120, "queued": 40, "running": 8, "completed": 261, "failed": 3,
    "cold_run_converged": 220, "seed_step_converged": 41,
    "waves": [ {"wave": 0, "total": 108, "completed": 108, "failed": 0}, ... ],
    "avg_point_sec": 740.0, "eta_sec": 9600
  }
}
  • avg_point_sec / eta_secPhase 3 有 elapsed_sec 列后精确计算; 之前用 tasks.completed_at - tasks.created_at 均值近似(含排队等待,偏大,标注近似)。
  • SQL 走现有覆盖索引 idx_grid_points_wf_status,单次聚合扫描,5s 轮询无压力。

GET /api/workflows/:name/points —— 逐点列表(G1

参数:statusmethodwaveq(点名子串)、sortwave|teff|max_relc|attempts,默认 wave)、 orderlimit(≤500,默认 100)、offset

{
  "success": true, "message": "ok",
  "data": {
    "total": 432,
    "points": [
      {
        "name": "t60000_g5.0_he-2_c-4_n-4_o-4",
        "teff": 60000, "logg": 5.0, "loghe": -2, "logc": -4, "logn": -4, "logo": -4,
        "cno_sum": -12, "wave": 0,
        "status": "completed", "tlusty_success_method": "seed_step", "synspec_success_method": "standard", "attempt_count": 2,
        "last_max_relc": 0.00043,
        "seed_point_name": "t60000_g5.0_he2_c-4_n-4_o-4",
        "node_id": "node-a1b2", "last_completed_at": "2026-07-30T11:12:00Z",
        "last_elapsed_sec": 126.4
      }
    ]
  }
}
  • "最近一次尝试"用关联子查询取 tasks 最新行:
SELECT gp.*, t.max_relc AS last_max_relc,
       t.seed_point_name, t.node_id, t.completed_at AS last_completed_at,
       t.error_message AS last_error
FROM grid_points gp
LEFT JOIN tasks t ON t.task_id = (
    SELECT t2.task_id FROM tasks t2
    WHERE t2.point_name = gp.name AND t2.workflow_name = gp.workflow_name
    ORDER BY t2.completed_at IS NULL, t2.completed_at DESC, t2.created_at DESC
    LIMIT 1)
WHERE gp.workflow_name = ?1 [AND ...filters]
ORDER BY gp.wave, gp.cno_sum, gp.teff
LIMIT ? LIMIT OFFSET ?;
  • 配套新索引:CREATE INDEX idx_tasks_point_wf_time ON tasks(point_name, workflow_name, completed_at); (迁移中补;432 点规模下无索引也能跑,加索引是为大网格兜底。)
  • 备选(若大网格性能不够):把 last_* 字段反规范化进 grid_points,在 record_task_report 里顺手写—— 但默认不做,避免迁移面扩大;先用 JOIN,压测后再议。

GET /api/workflows/:name/points/:point —— 单点详情 + 阶段诊断(G7

{
  "success": true, "message": "ok",
  "data": {
    "point": { /*   的单点字段 */ },
    "attempts": [
      {"task_id": "...", "seed_point_name": null,                // Phase 6 起无 task_type 字段
       "status": "failed", "max_relc": 954000.0, "atmosphere_has_nan": true,
       "node_id": "node-a1b2", "error_message": "nl stage diverged...",
       "failed_stage": "tlusty", "summary_json": "{...}",
       "created_at": "...", "completed_at": "...", "elapsed_sec": 1800.0},
      {"task_id": "...", "seed_point_name": "t60000_...",        // 第二次尝试(seed_step 热启动救回)
       "status": "completed", "max_relc": 0.00043, "failed_stage": null, ...}
    ],
    "conv": { /* data/seeds/<name>/conv.json 解析后的 ModelSummary;文件不在则 null */ }
  }
}
  • conv 读盘路径严格拼为 seeds_dir/<point>/conv.json(单层目录,point 先过白名单 + canonicalize 归属兜底),解析失败/不存在 → null(前端降级为"诊断文件不可用")。 把整文件塞进列表接口(体积控制)。

POST /api/workflows/:name/points/retry —— 失败点重试(G6

请求体:{"names": ["t80000_..."], "all_failed": true}(二选一)。

  • 前置:工作流必须 running(否则 400,提示"请先启动工作流"——避免重置后无调度器消费的僵尸态)。
  • 逻辑:新 db 函数 reset_failed_points(wf, names|all) UPDATE grid_points SET status='pending', tlusty_success_method=NULL, synspec_success_method=NULL WHERE workflow_name=? AND status='failed' [AND name IN (...)] 随后立即触发一次 schedule_pending_tasks()(不必等 30s tick)。
  • 重试后的方法仍由调度器按种子可用性自动决定(大概率 seed_step,因为此时种子池已更丰富)—— 这与领域语义一致,前端文案如实说明:"重试的点将由调度器自动选择冷启动或种子步进"。

⑤ 列表接口内联计数(G2,卡片进度条用)

GET /api/workflowsWorkflowSummary 追加:

{"name": "...", "status": "...", "description": "...",
 "created_at": "...", "updated_at": "...",
 "stats": {"total": 432, "completed": 261, "failed": 3, "running": 8,
           "cold_run_converged": 220, "seed_step_converged": 41}}
  • 实现:list_workflows 里对 grid_points 做一次 GROUP BY workflow_name 聚合再 map 合并(单条 SQL,无 N+1)。
  • 未启动(无网格点)的工作流 stats: null,前端不显示进度条。

3.2 持久化变更(Phase 3,最小迁移)

Phase 1/2 零迁移——全部基于现有列。以下为 Phase 3 的可选增强:

  1. grid_points 加列:elapsed_sec REAL(最近一次成功/失败尝试的墙钟耗时)。 在 record_task_report 写入——TaskReport.elapsed_sec 本来就在手(models.rs:318),当前被丢弃。
  2. StepSummary 携带 last_iter / worst_depth / n_depthsmodels.rs:590-612 扩字段, runner.rs:302-317ConvCheckResult 已有值,只是没搬过去)→ conv.json 与点详情获得迭代数。
  3. 新表 workflow_progress_snapshots(workflow_name TEXT, ts DATETIME, pending INT, queued INT, running INT, completed INT, failed INT):由 main.rs:111-197 的 30s 后台循环顺手写一行; 保留策略:仅当计数相对上次快照有变化才写;定期清理 24h 前的行。供"进度-时间"曲线与精确 ETA。

3.3 明确不做 / 保持现状

  • 改 pull 调度模型、不改 wave 语义、不改种子匹配算法——只读取与展示。
  • 引入 WebSocket/SSE:5s 轮询 + 详情弹窗作用域轮询足够,复杂度与收益不匹配。
  • /api/seed/:name 开 Admin 权限(它是 Node 角色的二进制通道);前端只需 conv.json 的 JSON 内容,走 ③ 即可。
  • seeds 表/.7 文件不暴露列表下载(超出本次范围,且属 Node 域)。

3.4 安全清单(对照全局安全规约)

  • 所有新端点经现有 auth_middleware(Admin 角色)+ 全局限流;无新增鉴权旁路。
  • 点名/工作流名全部过字符集白名单([A-Za-z0-9._-]);conv.json 读盘路径不接受 ../绝对路径。
  • 无新增密钥;错误信息用现有 AppErrorInternal 不回显细节,error.rs:28-34)。
  • 前端所有服务端字符串经 escapeHtml(列表/表格)或 textContentconv.json、错误文本)渲染。
  • retry 端点为写操作:仅 Admin、受限于 running 状态、参数白名单,无注入面(参数化查询)。

4. 前端实现设计

4.1 新增/改动文件

文件 改动
src/router.js(新) hash 路由:parseHash(){view:'home''workflow', name?}hashchange 监听 + 首载渲染;视图挂载/卸载生命周期钩子(停/启作用域轮询);登录门禁判断
src/views/home.js(新) index.htmlmain.main-container 的首页内容收敛为模板函数 + 挂载逻辑(metric 卡/节点表/工作流卡列表),startPolling() 在此触发
src/views/workflowDetail.js(新) 详情页:任务控制条(分段进度条/计数/ETA/操作)+ Tab 容器;概览(tiles/方法归因/wave 分布/最近动态流)、点表格(关键行签名 diff,仿 nodesTable)、过滤器、热力矩阵(CSS grid 单元格)、点详情滑入面板。所有插值 escapeHtml;作用域 5s 轮询自管
src/api.js 新增 fetchWorkflowStatsApi(name)fetchWorkflowPointsApi(name, params)fetchPointDetailApi(name, point)retryPointsApi(name, body);全部经 apiFetch + encodeURIComponent,返回 raw Response
src/components/workflows.js 卡片加进度条/计数/状态徽章扩充;头部包成进详情的锚链接;读取列表内联 stats;空 stats 降级
src/main.js 瘦身为全局接线:登录/主题/登出/全局模态(create-wf / view-yaml / reissue / confirm/ header 刷新;视图级委托移入各 view
src/state.js 全局轮询仅服务首页;视图切换时由 router 停启
index.html main 收敛为 <div id="view-root">(视图由 JS 渲染);保留登录遮罩/header/footer/全局模态/toast;新增 #point-panel 滑入面板骨架
src/style.css 新组件:.wf-status-strip(控制条 + 分段进度条 .seg-progress 五色段)、.wf-tabstab 栏,双主题)、.method-badge.cold/.seed/.imported(蓝/紫/中性,区别于状态色)、.heatmap/.heatmap-cell(状态色填充 + hover)、.stage-chip(阶段诊断小卡)、.point-panel(右侧滑入 + 遮罩)、.activity-feed(动态流条目淡入);全部走设计令牌 + prefers-reduced-motion 降级

4.2 必须遵循的现有约定(探查结论)

  1. 事件一律 data-* 委托,不挂逐按钮监听(main.js:377-388 模式)。
  2. 服务端字符串进 innerHTML 前必过 escapeHtml;整块文本用 textContent
  3. 模态:静态骨架 + setupFocusTrapclose 按钮类 modal-close(否则 Escape 映射失效,modal.js:21)。
  4. 破坏性操作先 showConfirm,反馈只走 showToast,变更后 fetchAllData() 立即刷新。
  5. 轮询下渲染:行/卡 keyed by data-* + 签名 diff + 高频单元格 textContent 原位刷新(nodesTable.js:34-54 模式),空态写入做 trim 比较防抖。
  6. 样式只用令牌;状态复用 .status-badge 变体;进度复用 .progress-bar-track/-fill;表格复用 .data-table + .col-* + .tabular-num
  7. 工作流标识是 name(无数字 id);后端状态含 initializing,前端要显式映射。

5. 分期实施计划

Phase 1 — 进度可见(MVP,零迁移)

  • 后端:stats 端点(拆 queued+ points 端点(JOIN 最近尝试 + 新索引)+ 列表内联 stats
  • 前端:hash 路由 + 视图化重构 + 卡片进度条/计数/徽章 + 工作流详情页(任务控制条 + 「概览」「网格点明细」两 Tab + 过滤器/分页 + 最近动态流)。
  • 验收:深链 #/workflows/sdB_cno 打开详情页,能看到 432 点逐行状态、每点方法/max_relc/尝试数/种子来源,进度条随轮询而动。

Phase 2 — 诊断与归因

  • 后端:points/:point 端点(尝试历史 + conv.json 解析)+ retry 端点 + db reset_failed_points
  • 前端:点详情滑入面板(尝试历史 + 阶段链诊断 + 错误)+「收敛性分析」热力 Tab + 「重试失败点」按钮。
  • 验收:能回答"这个点为什么失败/被谁救回",能看到冷启动成功参数区,能一键重试失败点。

Phase 3 — 时序与精算(可选增强)

  • 后端:迁移(elapsed_sec 列、StepSummary.last_iter+ workflow_progress_snapshots + ETA 精算。
  • 前端:概览 ETA 精确化 + 进度-时间 sparkline + 点表耗时列去"~"。

6. 待确认的开放问题

  1. Phase 范围:三期全做,还是先交付 Phase 1+2?
  2. 「重试失败点」是否需要(它会触发真实计算任务)?默认仅 running 时可用。
  3. 热力矩阵默认轴 Teff × cno_sum 是否符合你的物理直觉?(sdB_cno 当前网格 logg/logHe/CNO 各只有单值,实际是 Teff 一维——多值网格下矩阵才有意义。)
  4. conv.json 之外是否还需要在点详情里提供逐阶段 .err/fort.9 原始文件下载(目前节点归档 data/result 有 LRU 200 上限,服务端只有 conv.json)?