物理正确性校验体系(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 报错
547 lines
34 KiB
Markdown
547 lines
34 KiB
Markdown
# 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-05,subagent 对抗性审查)
|
||
|
||
| 严重度 | 发现 | 修复 |
|
||
|---|---|---|
|
||
| 【严重】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`:半失败 = Failed,reporter.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 迁移 M1(tasks 表,均在线 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:1246)SQL 与 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 — 死列清理(P5;registration_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_step,synspec 收敛点统计丢失 | **已解决(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+SYNSPEC,synspec_sec 是子集);
|
||
- `seed_point_name` 标注 synspec-only 双义。
|
||
- **前端可感知修正**(bug 级):
|
||
- "已收敛" → "已完成"(pointsTable.js:44 等所有中文标签);
|
||
- "方法"过滤器增加 synspec 策略名选项(pointsTable.js:48-51);
|
||
- CSV 列 `last_task_type` → `overall_method`(pointsTable.js:218,P9 拆分后改 `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 4(M4)与 Phase 6(M6)的 `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-05;5b 待 summary_json 数据积累后评估 |
|
||
| task_type 字段 | **全量删除**(Phase 6) | 用户决策 2026-08-05;集群无历史节点,随镜像统一重建,无旧节点兼容负担 |
|
||
| Phase 7 命名语义修正 | **立项**,先 7a(注释+前端)后 7b(改名),7c 已实施(2026-08-05) | 用户决策 2026-08-05;subagent 全库审计确认 TLUSTY-first 命名残留 |
|