- server/db: 拆 4929 行 db.rs 单体为 db/ 目录,migrations.rs 引入 PRAGMA user_version
版本化迁移运行器(M1~M13)
- 任务引擎 Phase 6/7b/7c 改名收敛:EngineStageConfig→PhaseConfig、StagePolicy→ResumePolicy、
Converged→Completed、删除 task_type 列、success_method 拆 tlusty_/synspec_ 双列、
新增 tlusty_status/synspec_status 半失败阶段守卫
- 科学正确性加固:conv_check 任意行 NaN/Inf/溢出判无效(0 行容忍)、新增 spec_is_valid
校验 SYNSPEC 脏谱、itek_history 逐次迭代全量保真、fmt_abn powf 溢出饱和
- 用户配置真正接通:tlusty_chain/tlusty_input 由死字段经 调度器→TaskSpec→executor→runner
透传生效;config 加载期 validate + deny_unknown_fields + 解析失败记 warn
- 调度修复:H1 活锁(pending_strategies 跳过已失败策略)、种子查找错误不再静默降级冷启动
- dashboard: 阶段配置面板 tlusty_stage/synspec_stage、"已完成"标签、迭代诊断展示
- docs: 新增 database_refactor_design.md,同步 database/api/PIPELINE/workflow_detail
34 KiB
DCTS 数据库结构修复设计
本文档记录数据库结构的系统性修复方案(2026-08-05 立项)。 依据《docs/database.md》现状与调度/上报链路的实际数据流,覆盖迁移基础设施、 tasks 阶段信息补全、索引、查询重写、死列清理与凭据留存决策、点级阶段化与命名语义修正。
关联文档:database.md、task_engine_decoupling_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.idAUTOINCREMENT 移除(一切按(workflow_name, name)访问,表重建不值当)。
基础约束:
- 双库分工不动:主库(状态/审计)与队列库(热路径抢占)保持分离。
- 新增列一律可空,旧节点上报不破坏结算。
- bundled SQLite 3.45+(libsqlite3-sys 0.28)支持
DROP COLUMN、窗口函数、JSON1、UPDATE...RETURNING。
1. 总体设计原则
- 迁移先行:所有结构变更走 Phase 0 建立的版本化迁移,不再新增
init_tables手写 ALTER 块。 - 可独立交付:每 Phase 独立可上线、可回滚、带测试;全量
cargo test通过才合入。 - 兼容优先:
failed_stage缺省兜底"tlusty"(与 api/task.rs:312 回退默认一致);新列全可空。 - 观测不塞进热路径:阶段细节落库在结算事务内完成,不为查询便利增加运行时开销。
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:
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→ 执行upSQL →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 测试
- 新库全流程:
Database::new→ 版本 = 最新,全部迁移已应用。 - 旧库升级:手工构造"缺若干列/索引"的旧 schema 库 → bootstrap + 迁移 → 断言列/索引/数据完整。
- 迁移中断恢复:模拟 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 补两列。
保真边界(审查修正):
ModelSummary(models.rs:599-616)与StageSummary(577-595)不含 conv.json 的逐次 itek 迭代数组——该细节在 Rust serde 往返中结构性丢失,仍仅存磁盘 conv.json。落库的 summary_json 是"阶段级摘要"(含 last_iter / worst_depth / n_depths 等部分迭代诊断),非原始全量。若需完整逐次迭代入库,须给StageSummary加#[serde(flatten)]保留字段(暂缓,先文档化此边界)。
3.3 迁移 M1(tasks 表,均在线 ADD COLUMN)
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
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):
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)
- 数据丢失:
register_node(db.rs:589)只 INSERTnodes,不建 node_credentials 行;approve_node(db.rs:618)才建。pending 节点无 node_credentials 行,UPDATE迁移会静默跳过它们,DROP COLUMN后凭据永久丢失且无恢复路径。 - schema 约束:
token_hash TEXT NOT NULL(db.rs:518)+idx_node_credentials_token_hash唯一索引——为 pending 节点 INSERT 空占位会撞唯一索引;改 nullable 需整表重建。 - 语义:registration_secret 是审批前一次性凭据(注册时产生、取 token 前消费),node_credentials 是审批后 token 状态——放 nodes 与
status='pending_approval'同生命周期,自洽。
6.3 迁移 M4(清死列)
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 归因列
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)。
CASE WHEN synspec 成功
THEN json_extract(tasks.synspec_strategies, '$[0]')
END
收益:解锁"光谱以 standard 等策略收敛了多少点"的 SQL 统计。无瞬态状态同步负担。 统计 SQL 改动归本阶段独占(db.rs:2325-2574 加 synspec 策略桶),P7a 只做前端过滤器引用,避免两阶段重复改同一处(审查 HIGH#2)。
5b(再议,完整):阶段状态列
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 实施前需定):
not_required(阶段关闭)与NULL(未配置)的取值约定;- 半失败重试期间
tlusty_status是否可被后续 claim 覆盖(建议:不可,仅 synspec 侧流转); 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 传入:
// 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 + 配套查询改造
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_secvssynspec_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 前旧数据)。实施细节:M9UPDATE grid_points SET status='completed' WHERE status='converged'; M10RENAME 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 |
列名级残留(仅一处 + 一个耦合项):
workflow_progress_snapshots.converged列名:已随 7c 实施改名为completed(M10)——存"管线完成点数"。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/ 集成):
- 迁移测试:新库全流程;旧 schema 库升级;迁移中断恢复(Phase 0)。
- Phase 1:
record_task_report全状态往返(含半失败、synspec-only、旧节点兜底)。 - Phase 2:索引生效(EXPLAIN)+ 既有统计测试不回归。
- Phase 3:新旧查询结果集逐行等价 + 分页计数不变。
- Phase 4:新库无
revoked列;注册 → 审批 → 取 token 全链路(registration_secret 行为不变)。 - Phase 5:半失败点结算后
tlusty_status=converged、synspec_status=failed;重试期间不覆盖tlusty_status;synspec_success_method归因正确。 - 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 命名残留 |