Files
DCTS/docs/database_refactor_design.md
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

547 lines
34 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DCTS 数据库结构修复设计
> 本文档记录数据库结构的系统性修复方案(2026-08-05 立项)。
> 依据《docs/database.md》现状与调度/上报链路的实际数据流,覆盖迁移基础设施、
> tasks 阶段信息补全、索引、查询重写、死列清理与凭据留存决策、点级阶段化与命名语义修正。
>
> 关联文档:[database.md](./database.md)、[task_engine_decoupling_design.md](./task_engine_decoupling_design.md)、
> [dynamic_cpu_slots_design.md](./dynamic_cpu_slots_design.md)。
---
## 实施状态(2026-08-05 记录)
| Phase | 状态 | 关键改动 |
|---|---|---|
| 0 迁移基础设施 | ✅ 已实施 | `migrations.rs` + `PRAGMA user_version` 运行器,detect 守卫幂等 |
| 1 tasks 阶段信息 | ✅ 已实施 | M1 `failed_stage`/`summary_json`,结算落库 + 前端徽标 |
| 2 tasks 索引 | ✅ 已实施 | M2 `idx_tasks_wf_status_created` |
| 3 窗口函数化 | ✅ 已实施 | `ROW_NUMBER()` 重写逐点列表 + **§5.3 CTE 排序**(删 Rust sort_by |
| 4 死列清理 | ✅ 已实施 | M4 `DROP COLUMN revoked`registration_secret 留存 nodes |
| 6 删除 task_type | ✅ 已实施 | M6 `DROP COLUMN task_type`;执行链改 strategies[0] 推导 |
| 5a synspec 归因 | ✅ 已实施 | **M7**(版本号按部署序 >6`synspec_success_method` + 统计桶(含 WorkflowListStats |
| 5b 阶段状态列 | ✅ 已实施 | **M8** `tlusty_status`/`synspec_status`,结算/claim/running 同步,半失败守卫保留 tlusty 终态 |
| P9 归因拆分 | ✅ 已实施 | **M12** 新增 `tlusty_success_method` + 回填、**M13** `DROP COLUMN success_method`;整体归因改派生(前端 `tlusty ?? synspec` |
| 7a 注释+前端 | ✅ 已实施 | converged/max_relc 等注释契约;"已完成"标签;synspec 过滤器 |
| 7b 改名 | ✅ 已实施 | ChainStep/PhaseConfig/ResumePolicy/StepSummary、result_valid、trigger_tlusty_fallback、旧环境变量清理 |
| 7c converged→completed | ✅ 已实施 | **M9** 状态值迁移 + **M10** 快照列改名 + `GridPointStatus::Completed` + 前端/stats 键 |
| itek 全量保真 | ✅ 已实施 | `StepSummary.itek_history` 逐次迭代诊断随 summary_json 落库 |
| 文档同步 | ✅ 已实施 | `database.md` + `api.md` 同步新 schema/API |
> 每 Phase 合入前均通过全仓 `cargo test` + `cargo clippy -- -D warnings`。
### 审查发现与修复(2026-08-05subagent 对抗性审查)
| 严重度 | 发现 | 修复 |
|---|---|---|
| 【严重】CRITICAL | 7c 的测试断言 perl `"converged"(?!:)` 误把结算阶段值 `then_some("converged")` 改成 `"completed"`,而 claim/running 守卫仍查 `'converged'`**半失败重试时 tlusty_status 被覆盖,守卫完全失效**(147 测试未捕获,因测试断言了同样的错误值) | 结算阶段值回退 `'converged'`;修正测试断言;补**生命周期测试**(结算→claim→running→重试结算全程验证守卫保留 tlusty_status |
| 【高】HIGH | claim/running 守卫同时保留了 synspec 'failed',与设计 §7.3 打开项 #2「仅 synspec 侧流转」不符(重试期间 synspec_status 停在旧 failed | claim/running 改:tlusty 保留终态守卫 + **synspec 自由流转**为 queued/running |
| 【中】MEDIUM | `mark_grid_point_imported` 不设阶段列,导入点与正常点阶段口径不一致 | 导入置 `tlusty_status='converged'`synspec 保持 NULL |
| 【中】MEDIUM | 半失败分支隐含依赖 tlusty_enabled=true(脆弱的隐式不变量) | 加 `tlusty_enabled.then_some("converged")` 防御性守卫 |
| 【轻微】LOW | stats 内部 SQL 别名 `AS converged` 仍用旧命名 | 改名 `AS completed`(内部别名,不影响 JSON |
| 【轻微】LOW | M9 detect 数据检测在"空表"边界返回已迁移(跳过) | 接受为已知局限(FROM 保留 'converged' legacy 别名兜底) |
---
## 0. 背景与问题清单
对现有 schema`crates/server/src/db.rs` `init_tables` + `crates/mq/src/sqlite_queue.rs`)与
数据流(调度 → 队列 → 节点执行 → 上报结算)的审计,确认以下结构性问题:
| 编号 | 问题 | 严重度 | 现状 |
|---|---|---|---|
| P1 | `tasks` 表阶段结果不完整:`failed_stage``summary_json`synspec_rc/error/sec 全量)上报后即丢 | 高 | 结算 UPDATE 只落 7 个标量(db.rs:1943),阶段细节仅存磁盘 conv.json |
| P2 | `tasks` 表无 `workflow_name` 单列索引,`COUNT(*) WHERE workflow_name=?` 全表扫 | 中 | 仅有 `idx_tasks_point_wf_time(point_name, workflow_name, completed_at)` |
| P3 | 逐点列表 N+1 相关子查询(每 grid_point 行执行一次取最新任务) | 中 | db.rs:2780 / 2818 `LEFT JOIN ... (SELECT ... LIMIT 1)` |
| P4 | `registration_secret`(鉴权凭据)放在 `nodes`(运行时态表),与 `token_hash` 分属两表 | 中(已决策保留) | 审批前状态,`node_credentials.token_hash NOT NULL` 约束决定其只能存 nodes;P4 仅清 `revoked` 死列 |
| P5 | `node_credentials.revoked` 死列,新代码不读写 | 低 | db.rs:513 注释确认 |
| P6 | `grid_points.status` 单值掩盖两阶段管线:半失败点(大气收敛+光谱失败)点级只有 `failed`"大气就绪"不可查询 | 高(立项) | 阶段事实仅存于 tasks 历史 |
| P7 | 迁移机制脆弱:全部列变更在 `init_tables` 内手写 `PRAGMA table_info` + `ALTER`,无版本追踪 | 中 | 已积累 9+ 块幂等 ALTER |
| P8 | `task_type` 字段冗余:与 `tlusty_strategies[0]` 恒等,靠派发时同步维护不变量;synspec-only 场景为"假值" | 低 | 已废弃兼容字段,全量删除(Phase 6) |
| P9 | TLUSTY-first 命名残留:一批标识符保留单阶段语义,两阶段管线实现时存在语义误判风险(subagent 全库审计) | 中(立项) | `converged`/`stage`/`seed_*`/旧环境变量见 Phase 7**`success_method` 值域混用列已拆分(M12/M13** |
> **已确认不做**:`tasks` 表清理/保留策略(用户决策 2026-08-05,仅加索引)。
> **已确认不做**`grid_points.id` AUTOINCREMENT 移除(一切按 `(workflow_name, name)` 访问,表重建不值当)。
**基础约束**
- 双库分工不动:主库(状态/审计)与队列库(热路径抢占)保持分离。
- 新增列一律可空,旧节点上报不破坏结算。
- bundled SQLite 3.45+libsqlite3-sys 0.28)支持 `DROP COLUMN`、窗口函数、JSON1、`UPDATE...RETURNING`
---
## 1. 总体设计原则
1. **迁移先行**:所有结构变更走 Phase 0 建立的版本化迁移,不再新增 `init_tables` 手写 ALTER 块。
2. **可独立交付**:每 Phase 独立可上线、可回滚、带测试;全量 `cargo test` 通过才合入。
3. **兼容优先**`failed_stage` 缺省兜底 `"tlusty"`(与 api/task.rs:312 回退默认一致);新列全可空。
4. **观测不塞进热路径**:阶段细节落库在结算事务内完成,不为查询便利增加运行时开销。
---
## 2. Phase 0 — 版本化迁移基础设施
### 2.1 目标
消灭 `init_tables` 内继续堆积手写 ALTER 块(P7),为 P1–P6 提供统一的迁移通道。
### 2.2 设计
引入 `PRAGMA user_version` 驱动的迁移运行器。**V0 定义为 0**(`PRAGMA user_version` 对全新库的默认值),后续编号迁移从 1 开始:
```
Database::new
├─ init_tables() 现有幂等 bootstrap:建全表 + 既有列检测补全 → 置 user_version = V0(=0)
└─ apply_migrations() 从当前 user_version 顺序应用 M1..Mn → 每个迁移独立事务
```
- **新库**bootstrap 建表(CREATE TABLE 始终是最新形态,含后续 Phase 新增列)→ 置 `V0` → 应用 `V0+1..N`。**每个迁移自带 detect 守卫,已存在的列/索引直接跳过**,故新库上所有迁移为 no-op。
- **旧库**user_version=0 但表已存在):bootstrap 幂等跑一遍(含既有列检测补全)→ 置 `V0` → 应用后续迁移。detect 守卫保证只补缺的列/索引,数据零搬运。
- **幂等性关键**SQLite 无 `ADD COLUMN IF NOT EXISTS`,迁移必须靠 `detect` 守卫而非裸 SQL 数组实现幂等(审查 CRITICAL#3 修复——否则全新库上 bootstrap 已建新列,迁移再 ADD 会报 duplicate column 崩启动)。
- 迁移定义集中到新文件 `crates/server/src/migrations.rs`
```rust
pub struct Migration {
pub version: u32, // > V0(=0) 的顺序号(1..N
pub name: &'static str, // 便于日志与审计
pub detect: fn(&Connection) -> Result<bool>, // 该迁移是否已应用(列/索引存在性)
pub up: &[&str], // 未应用时才执行,同事务内顺序执行
}
pub const MIGRATIONS: &[Migration] = &[ /* M1, M2, M4, M6, M7(=5a) */ ];
> **迁移编号与部署顺序约束(实施修正 2026-08-05**:迁移版本号必须与 §11 部署顺序单调一致——
> 5a 部署在 Phase 6 之后,故其版本号取 **M7**(而非早期草案的 M5)。否则已升到 v6 的库会因
> `version <= current` 跳过 5a`synspec_success_method` 列永不创建。
pub fn current_version(conn: &Connection) -> u32; // PRAGMA user_version
pub fn apply_migrations(conn: &mut Connection) -> Result<()>;
```
- 运行器逻辑:读 `current_version` → 对每个 `version > current` 的迁移,先跑 `detect`
- `detect = true`(已应用,如新库 bootstrap 已建列)→ 仅推进 `user_version`,不执行 `up`
- `detect = false``BEGIN IMMEDIATE` → 执行 `up` SQL → `PRAGMA user_version = V``COMMIT`
- 中途失败不推进版本(进程启动时重试)。
- 与现有 `sqlite_queue.rs` 的迁移惯用法(PRAGMA table_info 检测)并存:队列库无用户版本迁移,维持现状;**仅主库**引入版本号。
### 2.3 代码改动
| 文件 | 改动 |
|---|---|
| `crates/server/src/migrations.rs` | 新增(迁移表 + 运行器) |
| `crates/server/src/db.rs` | `init_tables` 末尾置 `user_version = V0``Database::new` 在 bootstrap 后调用 `apply_migrations` |
### 2.4 测试
1. 新库全流程:`Database::new` → 版本 = 最新,全部迁移已应用。
2. 旧库升级:手工构造"缺若干列/索引"的旧 schema 库 → bootstrap + 迁移 → 断言列/索引/数据完整。
3. 迁移中断恢复:模拟 M2 中途失败 → user_version 停在 M1 → 重跑后 M2 成功。
---
## 3. Phase 1 — tasks 表阶段信息补全(P1)
### 3.1 目标
让"哪个阶段失败"`failed_stage`)与"完整阶段结果"`summary_json`)在 DB 可查,替代"只能翻磁盘 conv.json"。
### 3.2 数据流核实(关键前提)
节点上报 `TaskReport`models.rs:496-513)已携带全部所需字段:
- `converged` = **TLUSTY 大气收敛标志**reporter.rs:32 注释确认,非整管线);
- `status` = 整管线成败(`derive_report_status`:半失败 = Failedreporter.rs:38-45);
- `failed_stage` = `"tlusty"`/`"synspec"` 精确归因(`infer_failed_stage`reporter.rs:15-27);
- `summary_json` = Rust 侧 `ModelSummary` 序列化(含 `synspec_rc`/`synspec_error`/`synspec_sec` 与各子步骤摘要)。
服务端 `record_task_report`db.rs:1920)已持有 `&TaskReport`——**无需改签名**,只在结算 UPDATE 补两列。
> **保真边界(审查修正,2026-08 更新)**`ModelSummary`models.rs:616-641)与 `StepSummary`590-612)现已携带逐次 itek 迭代诊断数组(`StepSummary.itek_history: Vec<IterCheck>`runner.rs:425 填充),随 summary_json 全量落库(见状态表「itek 全量保真 ✅ 已实施」),不再仅存磁盘 conv.json。落库的 summary_json 与磁盘 conv.json 同源于 runner 的 `StepSummary`,含 last_iter / worst_depth / n_depths 与完整收敛轨迹。
### 3.3 迁移 M1tasks 表,均在线 ADD COLUMN
```sql
ALTER TABLE tasks ADD COLUMN failed_stage TEXT; -- "tlusty" / "synspec"
ALTER TABLE tasks ADD COLUMN summary_json TEXT; -- 完整 ModelSummary 全保真
```
### 3.4 代码改动
| 位置 | 改动 |
|---|---|
| db.rs:1943 结算 UPDATE | `SET` 增加 `failed_stage = COALESCE(?10, 'tlusty')``summary_json = ?11`;绑定 `report.failed_stage` / `report.summary_json` |
| db.rs `list_point_attempts`2848 | SELECT 增加 `failed_stage``summary_json` |
| `common/src/models.rs` `AttemptRow` | 增加 `failed_stage: Option<String>``summary_json: Option<String>``#[serde(default)]` 保 API 兼容) |
| dashboard 逐尝试列表 | 阶段徽标(TLUSTY / SYNSPEC+ synspec 错误摘要(解析 summary_json |
> **错误路径**reporter.rs:85 失败上报的 `summary_json = {"error": ...}` 非 ModelSummary——前端解析必须容错(解析失败即显示错误文本),服务端仅透传不解析。
### 3.5 测试
- 全状态往返:completed / failed / timeout / 半失败(大气成+光谱败)/ synspec-only / 旧节点(failed_stage=None → 兜底 "tlusty")。
- `record_task_report` 后 tasks 行 `failed_stage``summary_json` 与上报一致。
- `list_point_attempts` 返回新字段。
- 错误路径:summary_json 为 `{"error":...}` 时前端容错展示。
---
## 4. Phase 2 — tasks 表索引(P2,仅索引,不做清理)
### 4.1 目标
消除 `workflow_name` 单列查询的全表扫。
### 4.2 迁移 M2
```sql
CREATE INDEX IF NOT EXISTS idx_tasks_wf_status_created
ON tasks(workflow_name, status, created_at);
```
> **用户决策(2026-08-05**:不做 `tasks` 清理/保留策略。`tasks` 作为完整审计日志长期保留;
> 此索引覆盖 `COUNT(*) WHERE workflow_name=?` 及 `find_stale_pending_points` 的 JOIN 侧(按 point_name+workflow_name 定位)。
> 注意:`find_stale_pending_points` 的过滤条件(`status='pending' AND created_at < ...`,无 workflow_name 前置)**不受此索引覆盖**——该查询由 JOIN 侧既有 `idx_tasks_point_wf_time` 支撑,随日志增长扫描量线性增加,属已接受的权衡。
### 4.3 测试
- `EXPLAIN QUERY PLAN` 验证 `SELECT COUNT(*) FROM tasks WHERE workflow_name=?` 走索引扫描。
- 既有 `find_stale_pending_points` / 工作流统计测试不回归。
---
## 5. Phase 3 — 逐点列表窗口函数化(P3)
### 5.1 目标
消除 `get_workflow_points` / `get_workflow_point_row` 的逐行相关子查询(数千点 → 数千次子查询)。
### 5.2 设计
用单遍 `ROW_NUMBER()` 窗口取每点最新任务,替代 `LEFT JOIN tasks ON task_id = (SELECT ... LIMIT 1)`
```sql
SELECT * FROM (
SELECT gp.name, gp.teff, ..., gp.status, gp.tlusty_success_method, gp.synspec_success_method, gp.attempt_count,
t.max_relc, t.task_type, t.seed_point_name, t.node_id,
t.completed_at, t.error_message, t.failed_stage, t.summary_json,
COALESCE(t.elapsed_sec, gp.last_elapsed_sec) AS eff_elapsed,
ROW_NUMBER() OVER (
PARTITION BY gp.name, gp.workflow_name
ORDER BY t.completed_at IS NULL, t.completed_at DESC, t.created_at DESC
) AS rn
FROM grid_points gp
LEFT JOIN tasks t
ON t.point_name = gp.name AND t.workflow_name = gp.workflow_name
) WHERE rn = 1 AND {where_sql} ORDER BY ... {limit} {offset}
```
外层再叠加筛选/排序/分页,保持与 `get_workflow_points` 现签名(`(total, points)` + 过滤 + 分页)一致。
`get_workflow_point_row`(单点)同样改写,外层 `WHERE workflow_name=? AND name=? AND rn=1`
### 5.3 附带优化(可选)
`claim_pending_grid_points`db.rs:1246SQL 与 Rust 侧双重排序(db.rs:1282 `list.sort_by`):
`UPDATE...RETURNING` 不保序,用 CTE 让子查询排序贯穿,删掉 Rust 侧 `sort_by`
### 5.4 测试
- 用既有测试网格样本断言新旧查询**结果集逐行等价**(含:无任务点 → LEFT JOIN 为 NULL、多点多尝试 → 取最新、`completed_at NULL` 排序规则)。
- **边界回归(审查修正)**:构造"点只有未完成任务(completed_at 全 NULL"的样本,断言 ROW_NUMBER 与相关子查询取到**同一行**——窗口函数与子查询的 NULL 排序语义须显式验证等价。
- 分页 `total` 计数不变。
---
## 6. Phase 4 — 死列清理(P5registration_secret 保留在 nodes
### 6.1 目标
清除死列 `node_credentials.revoked`P5)。**`registration_secret` 不迁移**——审查确认移动它存在两个结构性障碍(见 §6.2),且保留在 nodes 语义自洽。
> **决策**:保持两表分离不合并;`registration_secret` 保留在 `nodes`,加注释消除"凭据半吊子"的困惑。
### 6.2 为什么 registration_secret 不迁移(审查 CRITICAL#1/#2
1. **数据丢失**`register_node`db.rs:589)只 INSERT `nodes`,不建 node_credentials 行;`approve_node`db.rs:618)才建。pending 节点无 node_credentials 行,`UPDATE` 迁移会静默跳过它们,`DROP COLUMN` 后凭据永久丢失且无恢复路径。
2. **schema 约束**`token_hash TEXT NOT NULL`db.rs:518+ `idx_node_credentials_token_hash` 唯一索引——为 pending 节点 INSERT 空占位会撞唯一索引;改 nullable 需整表重建。
3. **语义**registration_secret 是**审批前**一次性凭据(注册时产生、取 token 前消费),node_credentials 是**审批后** token 状态——放 nodes 与 `status='pending_approval'` 同生命周期,自洽。
### 6.3 迁移 M4(清死列)
```sql
ALTER TABLE node_credentials DROP COLUMN revoked; -- bundled SQLite 3.45+,涉及表重建,低峰窗口
```
### 6.4 代码改动
- 仅注释:`nodes.registration_secret` 列加注释"审批前一次性凭据,唯一例外;token 哈希见 node_credentials"。
- `register_node` / `approve_node` / `check_status` / `reject_node` 全链路**零改动**。
### 6.5 测试
- `revoked` 列消失,`CREATE TABLE IF NOT EXISTS` 对新库不再创建该列。
- 注册 → 审批 → 取 token 全链路回归(registration_secret 行为不变)。
---
## 7. Phase 5 — grid_points 阶段化(立项)
### 7.1 目标
解除 P6:点级 `status` 单值无法表达两阶段管线。半失败点(大气收敛 + 光谱失败)需在点级可识别"大气就绪、光谱待重试";synspec 收敛归因需点级可查。
### 7.2 阶段语义(基于已核实的上报数据)
`record_task_report` 结算时可从报告精确推导各阶段状态:
| 上报条件 | 整体 status | tlusty_status | synspec_status |
|---|---|---|---|
| `tlusty_enabled=0` | — | NULL(不适用) | 由 `synspec_rc`/`synspec_error` 定:成功→converged,失败→failed |
| `converged=true` + synspec 成功 | converged | converged | converged |
| `converged=true` + synspec 失败(半失败) | failed | **converged** | failed |
| `converged=false`tlusty 启用) | failed | failed | pending(未运行) |
- `converged` = 大气收敛标志(reporter.rs:32);synspec 成败取自 `summary_json.synspec_rc` / `synspec_error`
- 整体 `status` 仍是调度/策略/筛选的**权威状态**,阶段列是补充可查信息,不替代它。
### 7.3 分步交付
#### 5a(先做,轻量):`synspec_success_method` 归因列
```sql
ALTER TABLE grid_points ADD COLUMN synspec_success_method TEXT;
```
结算时(settlement 事务内)设置,镜像既有大气归因的 synspec 分支逻辑(db.rs:1973-1984)。
**已实施(P9 拆分,M12/M13**:原 `success_method` 值域混用列拆为 `tlusty_success_method` + `synspec_success_method`
两个阶段列并删除原列,整体归因改由消费方派生(`tlusty ?? synspec`)。
```sql
CASE WHEN synspec 成功
THEN json_extract(tasks.synspec_strategies, '$[0]')
END
```
收益:解锁"光谱以 standard 等策略收敛了多少点"的 SQL 统计。无瞬态状态同步负担。
**统计 SQL 改动归本阶段独占**db.rs:2325-2574 加 synspec 策略桶),P7a 只做前端过滤器引用,避免两阶段重复改同一处(审查 HIGH#2)。
#### 5b(再议,完整):阶段状态列
```sql
ALTER TABLE grid_points ADD COLUMN tlusty_status TEXT; -- pending/queued/running/converged/failed
ALTER TABLE grid_points ADD COLUMN synspec_status TEXT; -- pending/running/converged/failed/not_required
```
**同步触点**(与 `grid_points.status` 的既有更新点对齐,均为机械追加列):
- `claim_pending_grid_points`db.rs:1246)→ 相关阶段列置 `queued`
- `mark_grid_point_running`db.rs:1508)→ 相关阶段列置 `running`
- `record_task_report` 结算(db.rs:1994-2004)→ 按 7.2 语义表置阶段终态;
- 回退重置(scheduler.rs `trigger_strategy_fallback`)→ 半失败重试时保持 `tlusty_status=converged`,仅重置 `synspec_status`
**打开项(5b 实施前需定)**
1. `not_required`(阶段关闭)与 `NULL`(未配置)的取值约定;
2. 半失败重试期间 `tlusty_status` 是否可被后续 claim 覆盖(建议:不可,仅 synspec 侧流转);
3. `initialize_grid` 各策略(skip_converged / skip_failed / force_recompute)如何对待阶段状态(建议:以整体 `status` 为准,阶段列只读展示)。
> 建议 5b 在 5a 上线、`summary_json` 数据积累后评估——届时"阶段状态是否需要独立列,还是 join tasks 已够"有真实数据支撑。
---
## 8. Phase 6 — 删除 task_type(全量删除,P8
### 8.1 目标与前提
消除冗余字段、synspec-only 假值坑、以及"派发时同步维护 `task_type == strategies[0]` 不变量"的负担。
**前提(用户决策 2026-08-05**:集群**无历史节点**,server/node 随同一镜像统一重建,不存在旧节点反序列化兼容问题。
### 8.2 执行链改由 strategies 推导(runner + executor
现状:executor 传 `task.task_type``custom_chain` 恒为 `None`executor.rs:172-175),runner 用 task_type 选执行链(runner.rs:299-301)。
改造:executor 按 `tlusty_config.strategies[0]` 显式推导执行链并作为 `custom_chain` 传入:
```rust
// executor.rs
let chain = match task.tlusty_config.current_strategy("cold_run") {
"seed_step" => common::runner::default_seed_chain(),
_ => common::runner::default_cold_chain(),
};
runner.run_model_with_timeout(..., Some(chain), ...);
```
runner`run_model_with_timeout``task_type: TaskType` 参数改为 `current_strategy: &str`
`unwrap_or_else` 兜底改按策略串匹配;`TaskType` 枚举整体删除。
`default_cold_chain()` / `default_seed_chain()`runner.rs:16, 66)已 `pub`,无需改动。
### 8.3 models.rs
- `TaskSpec` 移除 `task_type` 字段(models.rs:398);
- 删除 `normalize_compat` 的 task_type 回填分支(models.rs:435-444);
- 删除 `TaskType` 枚举定义。
### 8.4 调度器(scheduler.rs
- 删除 `strategy_to_task_type`239-244);
- 全部 `TaskSpec { task_type: ... }` 构造(465, 776, 836)移除该字段。
### 8.5 DB 迁移 M6 + 配套查询改造
```sql
ALTER TABLE tasks DROP COLUMN task_type; -- bundled SQLite 3.45+,涉及表重建,低峰窗口
```
| 位置 | 改造 |
|---|---|
| db.rs:1979 `success_method` 归因 | `ELSE task_type``ELSE json_extract(tlusty_strategies, '$[0]')`(不变量成立时等价,从此归因全派生)。**后经 P9 拆分(M12/M13)改 `tlusty_success_method` + `synspec_success_method` 两阶段列并删原列** |
| db.rs:1777-1780 `insert_task` | 删除 `task_type_str` 解析与绑定 |
| db.rs:2848 `list_point_attempts` | SELECT 去 `task_type` |
| db.rs:1859-1866 `has_pending_tasks_for_point` | `Some(tt)` 分支 `AND task_type = ?3``AND json_extract(tlusty_strategies, '$[0]') = ?3`(生产仅传 `None`,此分支测试用) |
| models.rs `AttemptRow` / API 响应 | 去 `task_type` 字段(`#[serde(default)]` 兼容已发前端) |
### 8.6 前端
- `pointsTable.js:222``last_task_type`
- `pointPanel.js:116``task_type` 徽标;
- `overview.js:211` 注释清理。
### 8.7 测试
- 全量修正 `TaskType::ColdRun` / `TaskType::SeedStep` 引用(实际计数:scheduler.rs 18 处、db.rs 14 处 + AttemptRow 字段、api_tests.rs 20+ 处 JSON 断言、sqlite_queue.rs 7 处、runner.rs 5 处、executor.rs 2 处——编译器兜住全部,属纯工作量);
- 新增:executor 按 `strategies[0]="seed_step"` 走种子链的用例;
- `has_pending_tasks_for_point` 过滤改 `strategies[0]` 后的等价断言。
### 8.8 升级注意
队列库(`dcts_queue.db`)可能残留升级前序列化含 `task_type` 的在途 payload。新代码 serde 默认忽略未知字段可正常反序列化,但**旧 `seed_step` 消息的 `strategies` 可能被 serde default 填成完整链 `[cold_run, seed_step]` 导致误判 cold_run**——升级前确认队列为空(或停机时清空队列)。
### 8.9 收益与风险
**收益**:消除冗余列与假值坑;不再维护同步不变量;归因/过滤全部改派生口径。
**风险**:runner 执行链行为需回归(seed_step 消息必须走种子链);测试改动面大但机械。
---
## 9. Phase 7 — 命名语义修正(TLUSTY-first 残留,P9
### 9.1 背景
系统最早仅 TLUSTY(SYNSPEC 默认不可配置),演进为两阶段管线后,一批标识符保留"仅 TLUSTY"语义,
实现完整计算过程时存在语义误判风险。本 Phase 由 subagent 全库审计产出(2026-08-05),
分注释契约、前端修正、改名、清理四档。
### 9.2 最危险的 5 个语义混淆
| # | 名称 | 位置 | 问题 | 处置 |
|---|---|---|---|---|
| 1 | `converged` | models.rs:603 / reporter.rs:32 / runner.rs:605-616 | TLUSTY 启用时=大气收敛;synspec-only 被重写为管线成功;读方无法凭字段名判断语义 | 7a 注释契约 + 7b 改名 |
| 2 | `StageConfig` vs `EngineStageConfig` | runner.rs / models.rs:330 | "stage"同词两层义:TLUSTY 计算链子步骤(lte/nc/nl) vs TLUSTY/SYNSPEC 管线大阶段;易把 SYNSPEC 误加进 `stages` | 7b 改名 |
| 3 | `success_method` 值域 | db.rs:1973-1984 | synspec-only 塞入策略名(standard);统计分桶(db.rs:2325-2574)与前端过滤器仅认 cold_run/seed_stepsynspec 收敛点统计丢失 | **已解决(M12/M13 拆分 + 5a 加桶)**:拆 `tlusty_success_method`/`synspec_success_method` 两阶段列,synspec-only 不再污染大气列 |
| 4 | `seed_point_name` / `seed_atmos_path` | models.rs:399 / executor.rs:70-83 | TLUSTY seed_step=热启动种子;synspec-only=光谱输入大气来源点,双义无注释 | 7a 注释 |
| 5 | `task_type` 余光 | scheduler.rs:458 | synspec-only 固定填 cold_run,前端/CSV 误标"冷启动" | Phase 6 已覆盖;7a 处理前端残留 |
### 9.3 分步实施
**7a(先做,无破坏性)——注释契约 + 前端可感知修正**
- **注释契约**(不改名):
- `converged` 双义标注(`TaskReport.converged` = "本次结果是否可用",非"大气收敛");
- `max_relc` / `final_max_relc` 标注"仅 TLUSTY 大气迭代有效"
- `cno_sum` / `wave` 标注"TLUSTY 大气金属丰度排序"
- `elapsed_sec` vs `synspec_sec` 标注包含关系(总耗时含 TLUSTY+SYNSPECsynspec_sec 是子集);
- `seed_point_name` 标注 synspec-only 双义。
- **前端可感知修正**(bug 级):
- "已收敛" → "已完成"pointsTable.js:44 等所有中文标签);
- "方法"过滤器增加 synspec 策略名选项(pointsTable.js:48-51);
- CSV 列 `last_task_type``overall_method`pointsTable.js:218P9 拆分后改 `overallMethod(p) = tlusty ?? synspec` 派生值);
- `pointMethodBadge(a.task_type)` 改从 strategy 推导(pointPanel.js:116);
- "阶段链诊断" → "TLUSTY 收敛链诊断"pointPanel.js:152);
- "方法"过滤器的 synspec 选项数据源 = Phase 5a 落库的 `synspec_success_method`(统计 SQL 改动归 5a 独占,本阶段不再触碰 db.rs:2325-2574)。
**7b(Phase 6 后,纯改名,无行为变化)**
- `StageConfig``ChainStep``StageSummary``StepSummary`TLUSTY 子步骤层);
- `EngineStageConfig``PhaseConfig``StagePolicy``ResumePolicy`(管线大阶段层);
- `ModelSummary.stages` 加注释"TLUSTY iteration steps, NOT pipeline phases"
- `TaskReport.converged``result_valid`
- `trigger_seed_step_fallback` 删除(或改 `trigger_tlusty_fallback`);
- 旧环境变量回退清理:`CNO_PORT` / `CNO_SERVER_URL` / `DCTS_RESULTS_DIR` / `DCTS_ARCHIVE_DIR``GridConfig.results` 死字段。
**7c(可选,大迁移;**2026-08-05 已实施****
- `GridPointStatus::Converged``Completed`enum 改名 + DB 文本值 `'converged'``'completed'`(全链 SQL
字面量 + 数据迁移 **M9** + 快照列改名 **M10** + 前端/统计键)。`From<&str>` 保留 `'converged'`/`'done'`
为 legacy 别名(M9 前旧数据)。实施细节:M9 `UPDATE grid_points SET status='completed' WHERE status='converged'`
M10 `RENAME COLUMN workflow_progress_snapshots.converged → completed`stats 顶层键 `"converged"``"completed"`
`cold_run_converged`/`seed_step_converged`/`synspec_converged` 方法限定键保留)。
### 9.4 测试
- 7a:前端过滤/统计对 synspec 收敛点的等价断言;注释类无测试。
- 7b:改名后全量 `cargo test`(编译期即验证)+ `cargo clippy -- -D warnings`
- 7c:M9/M10 迁移专项测试(状态值迁移、快照列改名)+ 全量回归。
### 9.5 数据库列名审计结论
DB 列名绝大多数**如实反映存储内容**,无需改名;TLUSTY-first 残留集中在**列值**与**概念**层面而非列名。
逐表核实结论如下。
**需要改动的列(已由既有 Phase 覆盖)**
| 列 | 动作 | 对应 |
|---|---|---|
| `tasks.task_type` | 删除 | Phase 6 (M6) |
| `grid_points.success_method` | **已删除**:值域混用列拆为 `tlusty_success_method` + `synspec_success_method`M12/M13);整体归因派生 `tlusty ?? synspec` | P9 拆分 |
| `tasks.max_relc` | 加注释"仅 TLUSTY 大气迭代有效";**不改名**(改名需动全链 SQL,收益低) | Phase 7a |
**列名级残留(仅一处 + 一个耦合项)**
1. `workflow_progress_snapshots.converged` 列名:已随 7c 实施改名为 `completed`(M10)——存"管线完成点数"。
2. `grid_points.status``'converged'` 值:列名 `status` 无问题,**值**是 TLUSTY-first(暗示大气收敛,实为管线完成)。7c 已实施:值改为 `'completed'`(M9 数据迁移 + 全链 SQL 字面量 + 前端)。
**核实后无需改动的列**
- `tasks.seed_point_name`:DB 列只存 TLUSTY 热启动种子,synspec-only 派发时恒 `None`scheduler.rs:453);"双义"发生在 executor 局部变量 `seed_atmos_path`,不在 DB 列上,7a 注释即可。
- `grid_points.cno_sum` / `wave`:存的就是 TLUSTY 丰度排序量,名实相符,7a 加物理语义注释。
- `tasks.elapsed_sec` / `attempt_count` / `atmosphere_ref` / `tlusty_*` / `synspec_*` 家族:全部准确。
**判定原则**:DB 列改名成本高(迁移 + 全链 SQL + 前端 + 历史数据),仅在"列名本身误导"时才值得。本审计确认除 `snapshots.converged`(耦合项)外**无列名级误导**,其余均为列值/概念问题,走注释与派生口径解决。
---
## 10. 测试策略
沿用仓库现有惯例(db.rs / scheduler.rs 内 `#[tokio::test]` 模块 + `tests/` 集成):
1. **迁移测试**:新库全流程;旧 schema 库升级;迁移中断恢复(Phase 0)。
2. **Phase 1**`record_task_report` 全状态往返(含半失败、synspec-only、旧节点兜底)。
3. **Phase 2**:索引生效(EXPLAIN)+ 既有统计测试不回归。
4. **Phase 3**:新旧查询结果集逐行等价 + 分页计数不变。
5. **Phase 4**:新库无 `revoked` 列;注册 → 审批 → 取 token 全链路(registration_secret 行为不变)。
6. **Phase 5**:半失败点结算后 `tlusty_status=converged``synspec_status=failed`;重试期间不覆盖 `tlusty_status``synspec_success_method` 归因正确。
7. **Phase 7**7a 前端过滤/统计对 synspec 收敛点等价断言;7b 改名后编译期全量通过。
每 Phase 合入前:全量 `cargo test` + `cargo clippy -- -D warnings`
---
## 11. 部署与回滚
- **迁移安全性**P1/P2/5a 的 `ADD COLUMN` / `CREATE INDEX` 全在线,无停写窗口;
Phase 4M4)与 Phase 6M6)的 `DROP COLUMN` 涉及表重建,**部署前做手动备份**(已有每日 `backup_database` + 7 天保留兜底),在低峰窗口执行。
- **回滚**SQLite `user_version` 迁移无 down 迁移;回滚 = 从备份恢复 + 重放后续版本。`ADD COLUMN` 类变更对旧二进制无害(新列新代码写、旧代码不读)。
- **部署顺序**Phase 0 → 1 → 2 → 3 → 4 → 6 →(5a)→(5b)→(7a)→(7b),每阶段独立发版。
- Phase 6 前确认队列为空(见 §8.8);
- Phase 7a 无 schema 依赖,可与 Phase 1–4 并行实施;其统计/过滤修正与 5a 合并;
- Phase 7b 改名在 Phase 6 之后(避免与 task_type 删除的改动冲突)。
---
## 12. 决策记录
| 决策 | 结论 | 依据 |
|---|---|---|
| tasks 清理/保留策略 | **不做**(仅加索引) | 用户决策 2026-08-05:tasks 作为完整审计日志长期保留 |
| nodes / node_credentials | **不合并**registration_secret 保留在 nodes(审批前状态),仅清 `revoked` 死列 | 写频差异显著;token_hash NOT NULL + 唯一索引决定 secret 只能存 nodes(审查 CRITICAL#1/#2 |
| `node_credentials.revoked` | 删除 | 死列,新代码不读写 |
| `grid_points.id` AUTOINCREMENT | 保留 | 一切按 `(workflow_name, name)` 访问,表重建不值当 |
| Phase 5 | **立项**,先 5a 后 5b | 用户决策 2026-08-055b 待 summary_json 数据积累后评估 |
| task_type 字段 | **全量删除**Phase 6 | 用户决策 2026-08-05;集群无历史节点,随镜像统一重建,无旧节点兼容负担 |
| Phase 7 命名语义修正 | **立项**,先 7a(注释+前端)后 7b(改名),7c 已实施(2026-08-05 | 用户决策 2026-08-05subagent 全库审计确认 TLUSTY-first 命名残留 |