Files
DCTS/docs/api.md
T
fmq cd370d88e7 feat(all): 任务引擎双阶段解耦、僵尸涡旋修复、动态 CPU 配额与前端详情页重构
将 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 笔误
2026-08-04 23:40:52 +08:00

47 KiB
Raw Blame History

DCTS (Distributed Computing TLUSTY/SYNSPEC) API 文档

本文档由源代码自动提取并整理,详细说明了 DCTS 分布式恒星大气网格计算系统 服务端 (dcts_server) 提供的所有 RESTful API 接口规范、数据结构定义、鉴权机制、错误码及 curl 调用示例。


目录 (Table of Contents)

  1. 通用说明与鉴权机制
  2. 数据结构与类型定义 (Rust & TypeScript Schema)
  3. 计算节点管理 API (Node Management)
  4. 任务调度与结果上报 API (Task Processing)
  5. 种子文件管理 API (Seed Management)
  6. 静态资源与数据下载 API (Data Assets)
  7. 系统状态监控 API (System Status)
  8. 工作流管理 API (Workflow CRUD & Execution)
  9. 错误处理与状态码汇总

1. 通用说明与鉴权机制

1.1 服务端信息

  • 默认服务地址: http://127.0.0.1:8090 (端口可通过 --port / DCTS_PORT 环境变量配置)
  • 传输协议: HTTP / HTTPS
  • 默认请求/响应格式: application/json (部分文件下载接口为 application/octet-stream,任务上报为 multipart/form-data)

1.2 鉴权机制与权限控制矩阵 (auth_middleware & RBAC)

服务端基于角色访问控制 (RBAC) 划分三种请求鉴权级别:

  1. Admin 角色:具有系统管理权限(工作流 CRUD/起停、节点凭据审批/重发/停用/启用、系统恢复)。在 Request Header 中需携带:
    Authorization: Bearer <DCTS_ADMIN_TOKEN>
    # 或
    x-api-key: <DCTS_ADMIN_TOKEN>
    
  2. Node 角色:仅限 Worker 节点运行态调用(心跳/抢占任务/汇报/下载数据)。携带管理员审批颁发的专属节点 Token:
    Authorization: Bearer <NODE_SPECIFIC_TOKEN>
    
  3. Public 角色:无需鉴权直接访问(如 /login 管理登录、/node/register 提交申请、/node/check_status 轮询审批、/healthz 健康检查)。

Tip

防侧信道保护:底层调用 Constant-Time 等时比较机制 (Sha256 摘要匹配),彻底规避侧信道攻击(Side-Channel Attack)。

1.3 限流机制与错误模型 (Rate Limiting & Unified Error)

  • API 限流 (Rate Limiting):服务端对敏感接口(如 /login/node/register)应用了滑动窗口sliding window)请求限流器(rate_limit.rs)。超出速率上限时返回 429 Too Many Requests
  • 统一错误格式 (AppError):所有 RESTful API 的错误响应均格式化为标准 JSON:
    {
      "success": false,
      "message": "错误原因详细说明",
      "data": null
    }
    

2. 数据结构与类型定义 (Rust & TypeScript Schema)

2.1 6维网格点参数 (GridPointParams)

定义恒星大气模型的 6 维大气参数:温度 $T_{\text{eff}}$、重力加速度 $\log g$、以及元素丰度 \log(N_{\text{He}}/N_{\text{H}}), \log(N_{\text{C}}/N_{\text{H}}), \log(N_{\text{N}}/N_{\text{H}}), $\log(N_{\text{O}}/N_{\text{H}})$。

  • Rust 定义 (models.rs):

    // 每个轴值携带数值与 YAML 源书写文本,使命名严格忠于源精度。
    #[derive(Debug, Clone, PartialEq)]
    pub struct GridAxisValue { /* 内部:value: f64 + text: 源文本 */ }
    
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
    pub struct GridPointParams {
        pub teff: GridAxisValue,   // 有效温度 (K), e.g. 35000.0
        pub logg: GridAxisValue,   // 表面重力加速度对数 (cgs), e.g. 5.5
        pub loghe: GridAxisValue,  // 氦丰度对数, e.g. -1.0
        pub logc: GridAxisValue,   // 碳丰度对数, e.g. -2.0
        pub logn: GridAxisValue,   // 氮丰度对数, e.g. -2.0
        pub logo: GridAxisValue,   // 氧丰度对数, e.g. -2.0
    }
    

    serde 行为GridAxisValue 序列化为纯数值(f64),下游 JSON 消费者无感; 反序列化时优先捕获 YAML/JSON 标量原文。命名精度model_name() 直接拼接各轴 源书写文本——配置里多少位小数就多少位(logg: 5.0g5.0teff: 20000t20000), 与旧版 Python gen_input5.model_name 逐字符一致,保证历史数据可迁移。详见 GridConfig::from_yaml_strYAML 源文本捕获路径)。

  • TypeScript 类型声明:

    export interface GridPointParams {
      teff: number;
      logg: number;
      loghe: number;
      logc: number;
      logn: number;
      logo: number;
    }
    

2.2 任务规格 (TaskSpec)

服务端派发给 Worker 节点的单个计算任务定义。

  • Rust 定义 (models.rs):

    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct TaskSpec {
        pub task_id: Uuid,
        pub point_name: String,
        pub params: GridPointParams,
        pub task_type: TaskType,             // ColdRun | SeedStep(兼容字段 = strategies[0]
        pub seed_point_name: Option<String>, // 步进种子点名称(如适用)
        pub timeout_sec: u64,
        pub workflow_name: Option<String>,   // 多工作流分区键
        pub wave: i32,                        // 难度波次
        pub tlusty_config: EngineStageConfig,  // TLUSTY 阶段配置(enabled/policy/strategies
        pub synspec_config: EngineStageConfig, // SYNSPEC 阶段配置
        pub synspec_params: Option<serde_json::Value>, // SYNSPEC 数值参数(波长范围等)
        pub atmosphere_ref: Option<String>,   // 显式大气来源点(仅 SYNSPEC-only 场景)
    }
    

    EngineStageConfig(阶段独立配置,见 task_engine_decoupling_design.md §3):

    pub struct EngineStageConfig {
        pub enabled: bool,
        pub policy: String,          // skip_converged | force_recompute | skip_failed
        pub strategies: Vec<String>, // 策略链:["cold_run","seed_step"],失败回退弹首项
    }
    
  • TypeScript 类型声明:

    export type TaskType = 'cold_run' | 'seed_step';
    
    export interface EngineStageConfig {
      enabled: boolean;
      policy: string;
      strategies: string[];
    }
    
    export interface TaskSpec {
      task_id: string;
      point_name: string;
      params: GridPointParams;
      task_type: TaskType;
      seed_point_name?: string | null;
      timeout_sec: number;
      workflow_name?: string | null;
      wave: number;
      tlusty_config: EngineStageConfig;
      synspec_config: EngineStageConfig;
      synspec_params?: Record<string, unknown> | null;
      atmosphere_ref?: string | null;
    }
    

2.3 任务上报报告 (TaskReport)

Worker 节点向服务端上报的任务计算结果。

  • Rust 定义 (models.rs):

    #[derive(Debug, Clone, Serialize, Deserialize)]
    pub struct TaskReport {
        pub task_id: Uuid,
        pub point_name: String,
        #[serde(default)]
        pub params: Option<GridPointParams>,
        pub node_id: String,
        pub status: TaskStatus, // Pending | Running | Completed | Failed | Timeout
        pub converged: bool,
        pub max_relc: Option<f64>,
        pub atmosphere_has_nan: bool,
        pub elapsed_sec: f64,
        pub error_message: Option<String>,
        pub summary_json: String,
        pub failed_stage: Option<String>, // "tlusty" | "synspec":失败阶段归因,决定弹哪条策略链
    }
    
    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
    #[serde(rename_all = "snake_case")]
    pub enum TaskStatus {
        Pending,
        Running,
        Completed,
        Failed,
        Timeout,
    }
    
  • TypeScript 类型声明:

    export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'timeout';
    
    export interface TaskReport {
      task_id: string;
      point_name: string;
      params?: GridPointParams;
      node_id: string;
      status: TaskStatus;
      converged: boolean;
      max_relc?: number | null;
      atmosphere_has_nan: boolean;
      elapsed_sec: number;
      error_message?: string | null;
      summary_json: string;
      failed_stage?: 'tlusty' | 'synspec' | null;
    }
    

2.4 节点信息与请求模型 (NodeRegisterRequest / NodeHeartbeatRequest)

  • Rust 定义 (models.rs):

    pub struct NodeRegisterRequest {
        pub node_id: String,
        pub max_slots: i32,
    }
    
    pub struct NodeHeartbeatRequest {
        pub node_id: String,
        pub active_slots: i32,
        pub cpu_usage: f32,
        pub memory_usage: f32,
    }
    
    pub struct NodeInfo {
        pub node_id: String,
        pub max_slots: i32,
        pub active_slots: i32,
        pub status: String,
        pub cpu_usage: f32,
        pub memory_usage: f32,
        pub last_heartbeat: DateTime<Utc>,
    }
    
  • TypeScript 类型声明:

    export interface NodeRegisterRequest {
      node_id: string;
      max_slots: number;
    }
    
    export interface NodeHeartbeatRequest {
      node_id: string;
      active_slots: number;
      cpu_usage: number;
      memory_usage: number;
    }
    
    export interface NodeInfo {
      node_id: string;
      max_slots: number;
      active_slots: number;
      status: 'online' | 'offline' | 'pending_approval' | 'disabled' | 'rejected';
      cpu_usage: number;
      memory_usage: number;
      last_heartbeat: string;
    }
    

    节点 status 取值: 'online'(在线分发中)、'offline'(心跳超时离线)、'pending_approval'(待管理员审批)、'disabled'(管理员手动停用——在线但不分发任务,详见 /disable)。


2.5 工作流响应模型与请求体 (CreateWorkflowRequest / ApiResponse<T>)

  • Rust 定义 (workflow.rs):

    pub struct CreateWorkflowRequest {
        pub name: String,
        pub description: Option<String>,
        pub config_yaml: String,
    }
    
    pub struct ApiResponse<T> {
        pub success: bool,
        pub message: String,
        pub data: Option<T>,
    }
    
  • TypeScript 类型声明:

    export interface CreateWorkflowRequest {
      name: string;
      description?: string | null;
      config_yaml: string;
    }
    
    export interface ApiResponse<T = unknown> {
      success: boolean;
      message: string;
      data?: T | null;
    }
    
    export interface WorkflowSummary {
      name: string;
      description?: string | null;
      status: 'idle' | 'initializing' | 'running' | 'paused' | 'completed';
      created_at: string;
      updated_at: string;
      stats?: WorkflowListStats | null;  // 内联网格点聚合计数(未启动为 null,见 §8.11)
    }
    
    export interface WorkflowListStats {
      total: number;
      converged: number;
      failed: number;
      running: number;
      cold_run_converged: number;
      seed_step_converged: number;
    }
    
    export interface WorkflowItem {
      name: string;
      description?: string | null;
      config_yaml: string;
      status: 'idle' | 'running' | 'paused' | 'completed';
      created_at: string;
      updated_at: string;
    }
    

3. 计算节点管理 API (Node Management)

处理 Worker 节点的注册登录与定期心跳保活。

3.1 注册计算节点 (POST /api/node/register)

  • 处理函数: register_node
  • 函数签名:
    pub async fn register_node(
        State(state): State<AppState>,
        auth_node: Option<Extension<AuthenticatedNode>>,
        Json(req): Json<NodeRegisterRequest>
    ) -> impl IntoResponse
    
  • 鉴权: 否(免凭据申请,提交后进入 pending_approval 待管理员审批)
  • 请求 Header: Content-Type: application/json
  • 请求 Body:
    {
      "node_id": "node-worker-01",
      "max_slots": 8
    }
    
  • 响应 Schema:
    • 200 OK (新节点申请提交成功,待审批):
      {
        "status": "pending_approval",
        "message": "节点注册申请已成功提交!请在管理 Dashboard 控制台上点击【同意接入】授权该节点",
        "node_token": null,
        "registration_secret": "<一次性凭据>"
      }
      

      registration_secret:节点注册时下发的一次性凭据,节点须在后续 /api/node/check_status 时回传,才能取走待发 token(防止仅知 node_id 的攻击者抢先冒领)。

    • 200 OK (已授权节点带专属 token 刷新配置):
      {
        "status": "approved",
        "message": "节点配置更新成功",
        "node_token": null
      }
      
  • curl 示例:
    curl -X POST http://localhost:8090/api/node/register \
      -H "Content-Type: application/json" \
      -d '{
        "node_id": "node-worker-01",
        "max_slots": 8
      }'
    

3.2 节点心跳保活 (POST /api/node/heartbeat)

  • 处理函数: heartbeat_node
  • 函数签名:
    pub async fn heartbeat_node(
        State(state): State<AppState>,
        Extension(auth_node): Extension<AuthenticatedNode>,
        Json(req): Json<NodeHeartbeatRequest>,
    ) -> impl IntoResponse
    
  • 鉴权: 是(Node 角色必填;req.node_id 必须与鉴权身份一致,否则 403
  • 请求 Header: Content-Type: application/json
  • 请求 Body:
    {
      "node_id": "node-worker-01",
      "active_slots": 2,
      "cpu_usage": 45.2,
      "memory_usage": 30.8
    }
    
  • 响应 Schema:
    • 200 OK (成功):
      {
        "status": "ok",
        "admin_max_slots": 4
      }
      

      admin_max_slots:管理员强制的并发槽位配额(null = 无限制,沿用物理 max_slots)。 节点据此动态调整本地 effective_max_slots(见 dynamic_cpu_slots_design.md)。

    • 200 OK (失败):
      {
        "status": "error",
        "message": "节点未找到或心跳更新失败"
      }
      
  • curl 示例:
    curl -X POST http://localhost:8090/api/node/heartbeat \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer secret_token" \
      -d '{
        "node_id": "node-worker-01",
        "active_slots": 2,
        "cpu_usage": 45.2,
        "memory_usage": 30.8
      }'
    

3.3 节点凭据与审批管理 API (Admin 角色)

1. 列出全部节点及凭据状态 (GET /api/admin/nodes)

  • 权限: Admin 角色
  • 响应 (200 OK):
    {
      "success": true,
      "message": "成功获取节点列表",
      "data": [
        {
          "node_id": "node-worker-01",
          "status": "online",
          "token_status": "active",
          "token_issued_at": "2026-07-28T12:00:00Z",
          "admin_max_slots": 4
        }
      ]
    }
    

    token_status 取值:active(有效)/ none(无凭据记录)。token 失效靠重发覆盖 hash 实现,不存在「已吊销」中间态。 admin_max_slots:管理员强制并发槽位配额(null = 无限制),Dashboard 据此渲染节点配额状态。

2. 同意节点接入申请 (POST /api/admin/nodes/:node_id/approve)

  • 权限: Admin 角色
  • 说明: 批准处于待审批状态的 Node,并生成该节点的专属 Token。

3. 拒绝节点接入申请 (POST /api/admin/nodes/:node_id/reject)

  • 权限: Admin 角色
  • 说明: 拒绝处于待审批状态的 Node 接入。

4. 重新颁发节点专属 Token (POST /api/admin/nodes/:node_id/reissue)

  • 权限: Admin 角色
  • 说明: 作废旧 Token(hash 被覆盖,立即失效)并重新生成新 Token 文本返回。新明文同时暂存到服务端,节点可通过 /node/check_status 自动拉取(限 1 天内有效),或由管理员手动同步到节点本地 .node_token

5. 停用节点 (POST /api/admin/nodes/:node_id/disable)

  • 权限: Admin 角色
  • 说明: 手动停用一个处于 online/offline 状态的节点。停用后节点保持在线心跳(Dashboard 仍可见其存活),但 claim 不再向其分发任务,Worker 收到 {"status":"disabled"} 后会拉长轮询、空闲待命,可随时调用 enable 恢复。
  • 状态语义: 节点 status 切为 disabled。与「重发 Token」(凭据失效、Worker 强制退出重注册)不同——停用仅是运维意图,不退出 Worker 进程、不动凭据。
  • 响应:
    • 200 OK: {"success": true, "message": "节点 '...' 已停用,不再分发任务"}
    • 409 Conflict: 节点当前状态不支持停用(仅 online/offline 可停用,pending_approval/disabled 返回此码)。
  • curl 示例:
    curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/disable \
      -H "Authorization: Bearer <ADMIN_TOKEN>"
    

6. 重新启用节点 (POST /api/admin/nodes/:node_id/enable)

  • 权限: Admin 角色
  • 说明: 重新启用被手动停用(disabled)的节点。状态切为 offline,靠节点下一次心跳自然翻为 online 后即恢复分发任务——既能自愈,又不会对真实离线的节点虚报在线。
  • 响应:
    • 200 OK: {"success": true, "message": "节点 '...' 已重新启用,将在下一次心跳后恢复分发任务"}
    • 409 Conflict: 节点当前状态不支持启用(仅 disabled 可启用)。
  • curl 示例:
    curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/enable \
      -H "Authorization: Bearer <ADMIN_TOKEN>"
    

7. 调整节点并发配额 (POST /api/admin/nodes/:node_id/quota)

  • 权限: Admin 角色
  • 说明: 动态调整节点并发槽位配额上限(不重启 Worker 进程,经下一次心跳响应下发生效)。传 null 解除限制,恢复物理 max_slots。详见 dynamic_cpu_slots_design.md
  • 请求 Body:
    { "admin_max_slots": 4 }
    
  • 响应:
    • 200 OK: {"success": true, "message": "节点配额已更新"}
  • curl 示例:
    curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/quota \
      -H "Authorization: Bearer <ADMIN_TOKEN>" -H "Content-Type: application/json" \
      -d '{"admin_max_slots": 4}'
    

4. 任务调度与结果上报 API (Task Processing)

支持 Worker 节点抢占式领用任务与计算结果(含种子 .7 文件)上传。

4.1 领用计算任务 (POST /api/task/claim)

  • 处理函数: claim_task
  • 函数签名:
    pub async fn claim_task(
        State(state): State<AppState>,
        Extension(auth_node): Extension<AuthenticatedNode>,
    ) -> impl IntoResponse
    
  • 鉴权: 是(Node 角色必填;服务端据注入身份校验节点是否被停用)
  • 请求 Body: 无
  • 响应 Schema:
    • 200 OK (有可计算任务):
      {
        "status": "ok",
        "task": {
          "task_id": "550e8400-e29b-41d4-a716-446655440000",
          "point_name": "t35000_g5.5_he-1_c-2_n-2_o-2",
          "params": {
            "teff": 35000.0,
            "logg": 5.5,
            "loghe": -1.0,
            "logc": -2.0,
            "logn": -2.0,
            "logo": -2.0
          },
          "task_type": "cold_run",
          "seed_point_name": null,
          "timeout_sec": 7200
        }
      }
      
    • 200 OK (当前队列为空):
      {
        "status": "empty",
        "task": null
      }
      
    • 200 OK (节点被管理员手动停用,不再分发任务):
      {
        "status": "disabled",
        "task": null
      }
      

      Worker 收到此响应后保持存活、拉长轮询(每 60s)空闲待命,不会退出进程(区别于 401/403 的 Token 失效语义)。管理员调用 /enable 后下一次轮询即恢复领用。

    • 500 Internal Server Error:
      {
        "status": "error",
        "message": "领用任务失败: <error_details>"
      }
      
  • curl 示例:
    curl -X POST http://localhost:8090/api/task/claim \
      -H "Authorization: Bearer secret_token"
    

4.2 上报任务结果与种子文件 (POST /api/task/report)

  • 处理函数: report_task
  • 函数签名:
    pub async fn report_task(
        State(state): State<AppState>,
        Extension(auth_node): Extension<AuthenticatedNode>,
        mut multipart: Multipart,
    ) -> impl IntoResponse
    
  • 鉴权: 是(Node 角色必填;服务端据注入身份做任务归属校验 verify_task_claim,防跨节点伪造)
  • 请求格式: multipart/form-data
    • Part report: JSON 字符串 (映射为 TaskReport)
    • Part seed_file (可选): 二进制数据 (收敛网格点的 .7 大气结构种子文件)
  • 响应 Schema:
    • 200 OK (成功 / 幂等重放):
      {
        "status": "ok",
        "message": "上报成功"
      }
      

      幂等重放:已结算任务(终态守卫已吸收)的重复/迟到上报仍返回 200,并补写种子,不重复结算。

    • 400 Bad Request (缺少 report 字段):
      {
        "status": "error",
        "message": "请求中缺少 report 字段"
      }
      
    • 400 Bad Request (参数格式错误):
      {
        "status": "error",
        "message": "无法解析 params 或 summary_json"
      }
      
    • 409 Conflict (任务归属校验失败): 任务被其它节点重新领用、或队列中已无该 task_id(已被结算清理)。此时返回冲突,不写库。
  • 说明:
    • converged == trueatmosphere_has_nan == false 且包含 seed_file 时,服务端会将种子原子写入 seeds_dir/<point_name>/<point_name>.7 并记入 seeds 表(源精度 point_name)。
    • 失败上报(未收敛 / 失败 / 超时)且 state_changed == true 时,服务端才触发策略链回退trigger_strategy_fallback,按 failed_stage 弹 TLUSTY 或 SYNSPEC 策略链);被终态守卫吸收的重复失败(state_changed=false)不再触发,杜绝重复派发涡旋(2026-08-02 修复)。
  • curl 示例:
    curl -X POST http://localhost:8090/api/task/report \
      -H "Authorization: Bearer secret_token" \
      -F 'report={
        "task_id": "550e8400-e29b-41d4-a716-446655440000",
        "point_name": "t35000_g5.5_he-1_c-2_n-2_o-2",
        "node_id": "node-worker-01",
        "status": "completed",
        "converged": true,
        "max_relc": 0.00008,
        "atmosphere_has_nan": false,
        "elapsed_sec": 142.5,
        "error_message": null,
        "summary_json": "{\"name\":\"t35000_g5.5_he-1_c-2_n-2_o-2\",\"params\":{\"teff\":35000.0,\"logg\":5.5,\"loghe\":-1.0,\"logc\":-2.0,\"logn\":-2.0,\"logo\":-2.0},\"stages\":[],\"converged\":true,\"elapsed_sec\":142.5,\"atmosphere_has_nan\":false}"
      };type=application/json' \
      -F 'seed_file=@/path/to/t35000_g5.5_he-1_c-2_n-2_o-2.7'
    

4.3 历史种子批量导入 (POST /api/admin/import_seed)

把旧版单机 run_grid.py 产物(conv.json + .7 大气文件)批量回灌进 DCTS。与 /task/report 的关键区别:跳过任务归属校验(历史数据无领用语义),直接幂等落库。供 tools/import_results 调用。

  • 处理函数: import_seed
  • 鉴权: Admin 角色admin token 或登录 session
  • 请求格式: multipart/form-data
    • Part report: 旧版 conv.json原文 JSON(映射为 ModelSummary,服务端解析出 name / params / converged / final_max_relc
    • Part seed_file: 二进制数据(.7 大气种子文件;收敛点必传)
    • Part success_method (可选): 文本 cold_run / seed_step。由 tools/import_results 依据旧 conv.json 的 stages 是否含 seed_nc 判定后设置,决定导入点最终归因(缺省按 seed_step 统计)
  • Query 参数: workflow(可选,默认 imported):目标工作流名,种子导入到该工作流的 grid_points
  • 命名保真: point_name 取旧 conv.jsonname 字段(源精度真名,如 t20000_g5.0_...),逐字符落库(磁盘目录、grid_points.nameseeds.point_name),与旧版 Python gen_input5.model_name 完全一致。
  • 幂等: ON CONFLICT DO NOTHING upsert grid_pointsconv.json.7 原子覆盖写,可重复运行。
  • 响应 Schema:
    • 200 OK:
      {
        "status": "ok",
        "point_name": "t20000_g5.0_he-2_c-4_n-4_o-4",
        "converged": true,
        "max_relc": 0.000321
      }
      
    • 400 Bad Request: 缺少 report / conv.json 解析失败 / 非法网格点名称
    • 401 Unauthorized: 缺少或无效 admin 凭据
  • curl 示例:
    curl -X POST "http://localhost:8090/api/admin/import_seed?workflow=sdB_cno" \
      -H "Authorization: Bearer <admin_token>" \
      -F 'report=@/path/to/old_results/t20000_g5.0_he-2_c-4_n-4_o-4/conv.json;type=application/json' \
      -F 'seed_file=@/path/to/old_results/t20000_g5.0_he-2_c-4_n-4_o-4/t20000_g5.0_he-2_c-4_n-4_o-4.7'
    
  • 批量导入工具: import_results 自动扫描结果目录、校验无 NaN 且收敛、逐点调用本端点(同时把完整产物树迁移到 data/result/):
    cargo run -p import_results -- --dir <旧 results 根目录> \
      --config workflows/sdB_cno.yaml \
      --server http://127.0.0.1:8090 --workflow sdB_cno --token <admin_token>
    

5. 种子文件管理 API (Seed Management)

提供在网格计算过程中相近网格点间传递与下载 TLUSTY fort.7 大气结构二进制种子文件的功能。

5.1 下载网格点种子文件 (GET /api/seed/:name)

  • 处理函数: download_seed
  • 函数签名:
    pub async fn download_seed(
        State(state): State<AppState>,
        AxumPath(name): AxumPath<String>,
    ) -> Response
    
  • 鉴权: 是 (若配置 Token)
  • 路径参数:
    • name: 网格点名称 (例如: t35000_g5.5_he-1_c-2_n-2_o-2)
  • 安全检查: 防止路径穿越攻击,校验参数中不可包含 ../\
  • 响应 Header:
    • Content-Type: application/octet-stream
    • Content-Disposition: attachment; filename="<name>.7"
  • 状态码与响应体:
    • 200 OK: 返回文件二进制流
    • 400 Bad Request: "非法的种子名称参数"
    • 404 Not Found: "请求的种子文件不存在"
    • 500 Internal Server Error: "无法打开种子文件"
  • curl 示例:
    curl -X GET http://localhost:8090/api/seed/t35000_g5.5_he-1_c-2_n-2_o-2 \
      -H "Authorization: Bearer secret_token" \
      --output t35000_g5.5_he-1_c-2_n-2_o-2.7
    

6. 静态资源与数据下载 API (Data Assets)

供 Worker 节点下载执行 TLUSTY / SYNSPEC 所需的原子数据文件和线表。

6.1 下载任意数据资源文件 (GET /api/data/file/*filename)

  • 处理函数: download_single_data_file
  • 函数签名:
    pub async fn download_single_data_file(
        AxumPath(filename): AxumPath<String>
    ) -> axum::response::Response
    
  • 鉴权: 是 (若配置 Token)
  • 路径参数:
    • filename: 文件相对名称 (例如: he2.dat)
  • 响应 Header:
    • Content-Type: application/octet-stream
    • Content-Disposition: attachment; filename="<filename>"
  • 状态码与响应体:
    • 200 OK: 返回数据文件二进制流
    • 400 Bad Request: "无效的数据文件名"
    • 404 Not Found: "资源数据文件不存在"
    • 500 Internal Server Error: "无法读取资源数据文件"
  • curl 示例:
    curl -X GET http://localhost:8090/api/data/file/he2.dat \
      -H "Authorization: Bearer secret_token" \
      --output he2.dat
    

6.2 下载主光谱线表文件 (GET /api/data/linelist)

  • 处理函数: download_linelist
  • 函数签名:
    pub async fn download_linelist() -> axum::response::Response
    
  • 鉴权: 是 (若配置 Token)
  • 响应: 默认定位并流式返回 assets/gfVIS99.dat 文件。
  • 状态码: 200 OK (或 404 Not Found / 500 Internal Server Error)
  • curl 示例:
    curl -X GET http://localhost:8090/api/data/linelist \
      -H "Authorization: Bearer secret_token" \
      --output gfVIS99.dat
    

7. 系统状态监控 API (System Status)

实时监控分布式计算集群节点活跃度与计算槽位利用率。

7.1 获取集群整体状态 (GET /api/status)

  • 处理函数: get_status
  • 函数签名:
    pub async fn get_status(State(state): State<AppState>) -> impl IntoResponse
    
  • 鉴权: 是 (若配置 Token)
  • 响应 Schema (200 OK):
    {
      "status": "online",
      "nodes_online": 2,
      "total_active_slots": 4,
      "total_max_slots": 16,
      "nodes": [
        {
          "node_id": "node-worker-01",
          "max_slots": 8,
          "active_slots": 2,
          "status": "online",
          "cpu_usage": 45.2,
          "memory_usage": 30.8,
          "last_heartbeat": "2026-07-27T16:55:00.000Z"
        }
      ],
      "grid_stats": {
        "total": 512,
        "pending": 210,
        "queued": 60,
        "running": 32,
        "converged": 260,
        "failed": 10,
        "cold_run_converged": 200,
        "seed_step_converged": 60
      }
    }
    

    grid_stats全部工作流的合计(跨工作流全局聚合)。多工作流并发运行时,此处展示所有 工作流 grid_points 的汇总进度;queuedpending 分开计数(多工作流分区新语义), cold_run_converged / seed_step_converged 为收敛手段归因;如需查看单个工作流的进度, 可读取该工作流各自的 grid_points 统计(Database::get_grid_summary_stats(Some(workflow_name)))。

  • curl 示例:
    curl -X GET http://localhost:8090/api/status \
      -H "Authorization: Bearer secret_token"
    

7.2 轻量级健康检查端点 (GET /healthz)

  • 鉴权: 否 (Public 免鉴权,专用于 Docker / K8s / Caddy 探针)
  • 响应 (200 OK):
    {
      "status": "ok"
    }
    
  • curl 示例:
    curl -i http://localhost:8090/healthz
    

8. 工作流管理 API (Workflow CRUD & Execution)

管理恒星大气网格计算工作流 YAML 配置的增删改查、启动与暂停控制。

8.1 获取工作流列表 (GET /api/workflows)

  • 处理函数: list_workflows
  • 函数签名:
    pub async fn list_workflows(State(state): State<AppState>) -> impl IntoResponse
    
  • 鉴权: 是 (若配置 Token)
  • 说明: 返回工作流轻量级元数据列表(包含 namedescriptionstatuscreated_atupdated_at)。如需获取具体工作流的 YAML 配置详情,请调用 GET /api/workflows/:name
  • 响应 Schema (200 OK):
    {
      "success": true,
      "message": "成功获取工作流列表",
      "data": [
        {
          "name": "sdB_cno",
          "description": "sdB CNO 6D Stellar Atmosphere Grid",
          "status": "idle",
          "created_at": "2026-07-27T08:00:00Z",
          "updated_at": "2026-07-27T08:00:00Z"
        }
      ]
    }
    
  • curl 示例:
    curl -X GET http://localhost:8090/api/workflows \
      -H "Authorization: Bearer secret_token"
    

8.2 创建或保存工作流 (POST /api/workflows / PUT /api/workflows/:name)

  • 处理函数: save_workflow
  • 函数签名:
    pub async fn save_workflow(
        State(state): State<AppState>,
        Json(req): Json<CreateWorkflowRequest>,
    ) -> impl IntoResponse
    
  • 鉴权: 是 (若配置 Token)
  • 请求 Body:
    {
      "name": "sdB_cno_custom",
      "description": "自定义 6维 网格计算工作流",
      "config_yaml": "grid:\n  teff: [35000, 36000]\n  logg: [5.5, 6.0]\n  loghe: [-1.0]\n  logc: [-2.0]\n  logn: [-2.0]\n  logo: [-2.0]\nchain:\n  - label: LTE_START\n    lte: T\n    niter: 30\n"
    }
    
  • 响应 Schema:
    • 200 OK (成功保存):
      {
        "success": true,
        "message": "工作流 'sdB_cno_custom' 保存成功",
        "data": null
      }
      
    • 400 Bad Request (YAML 格式不合法):
      {
        "success": false,
        "message": "无效的 YAML 配置: invalid syntax at line 2...",
        "data": null
      }
      
  • curl 示例:
    curl -X POST http://localhost:8090/api/workflows \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer secret_token" \
      -d '{
        "name": "sdB_cno_custom",
        "description": "自定义 6维 网格计算工作流",
        "config_yaml": "grid:\n  teff: [35000, 36000]\n  logg: [5.5, 6.0]\n  loghe: [-1.0]\n  logc: [-2.0]\n  logn: [-2.0]\n  logo: [-2.0]\nchain:\n  - label: LTE_START\n    lte: T\n    niter: 30\n"
      }'
    

8.3 获取特定工作流详情 (GET /api/workflows/:name)

  • 处理函数: get_workflow
  • 函数签名:
    pub async fn get_workflow(
        State(state): State<AppState>,
        AxumPath(name): AxumPath<String>,
    ) -> impl IntoResponse
    
  • 鉴权: 是 (若配置 Token)
  • 路径参数: name (工作流唯一名称,如 sdB_cno)
  • 响应 Schema:
    • 200 OK (成功):
      {
        "success": true,
        "message": "成功获取工作流详情",
        "data": {
          "name": "sdB_cno",
          "description": "sdB CNO 6D Stellar Atmosphere Grid",
          "config_yaml": "...",
          "status": "idle",
          "created_at": "2026-07-27T08:00:00Z",
          "updated_at": "2026-07-27T08:00:00Z"
        }
      }
      
    • 404 Not Found (不存在):
      {
        "success": false,
        "message": "工作流 'unknown_wf' 未找到",
        "data": null
      }
      
  • curl 示例:
    curl -X GET http://localhost:8090/api/workflows/sdB_cno \
      -H "Authorization: Bearer secret_token"
    

8.4 删除工作流 (DELETE /api/workflows/:name)

  • 处理函数: delete_workflow
  • 函数签名:
    pub async fn delete_workflow(
        State(state): State<AppState>,
        AxumPath(name): AxumPath<String>,
    ) -> impl IntoResponse
    
  • 鉴权: 是 (若配置 Token)
  • 响应 Schema (200 OK):
    {
      "success": true,
      "message": "工作流 'sdB_cno_custom' 已删除",
      "data": null
    }
    
  • curl 示例:
    curl -X DELETE http://localhost:8090/api/workflows/sdB_cno_custom \
      -H "Authorization: Bearer secret_token"
    

8.5 启动工作流 (POST /api/workflows/:name/start)

  • 处理函数: start_workflow
  • 函数签名:
    pub async fn start_workflow(
        State(state): State<AppState>,
        AxumPath(name): AxumPath<String>,
    ) -> impl IntoResponse
    
  • 说明: 以原子 CAStransition_workflow_to_initializing)抢占启动权,随后异步 spawn 网格初始化:解析 YAML 的 6 维网格点、按 policy 决定点状态(跳过收敛/打回失败)、展开并计算保序难度 Wave、把 pending 点推入 MQ 开启节点调度。接口先返回 200,后台推进为 running
  • 响应 Schema:
    • 200 OK (成功启动,后台初始化中):
      {
        "success": true,
        "message": "工作流 'sdB_cno' 已进入后台异步建立与挂载流程",
        "data": null
      }
      
    • 400 Bad Request (重复启动):
      {
        "success": false,
        "message": "工作流 'sdB_cno' 已处于运行状态,无需重复启动",
        "data": null
      }
      
    • 409 Conflict (并发启动,初始化抢占失败):
      {
        "success": false,
        "message": "工作流 'sdB_cno' 初始化抢占挂起异常,请稍后重试",
        "data": null
      }
      
    • 404 Not Found:
      {
        "success": false,
        "message": "工作流 'sdB_cno' 未找到",
        "data": null
      }
      
  • curl 示例:
    curl -X POST http://localhost:8090/api/workflows/sdB_cno/start \
      -H "Authorization: Bearer secret_token"
    

8.6 暂停工作流 (POST /api/workflows/:name/stop)

  • 处理函数: stop_workflow
  • 函数签名:
    pub async fn stop_workflow(
        State(state): State<AppState>,
        AxumPath(name): AxumPath<String>,
    ) -> impl IntoResponse
    
  • 响应 Schema (200 OK):
    {
      "success": true,
      "message": "工作流 'sdB_cno' 已暂停",
      "data": null
    }
    
  • curl 示例:
    curl -X POST http://localhost:8090/api/workflows/sdB_cno/stop \
      -H "Authorization: Bearer secret_token"
    

8.7 工作流执行统计 (GET /api/workflows/:name/stats)

  • 处理函数: get_workflow_statscrates/server/src/api/workflow.rs
  • 权限: Admin/workflows/* 前缀统一映射)
  • 用途: 详情页"任务控制条 + 执行概览"数据源。在 get_grid_summary_stats(Some(wf)) 之上追加难度波次分布与近似 ETA。pendingqueued 分开计数(与 /api/status 的全局口径不同,后者由前端合并展示)。
  • 路径参数: :name — 工作流名(白名单 [A-Za-z0-9._-]{1,64});未知工作流 → 404
  • 响应 Schema (200 OK):
    {
      "success": true,
      "message": "成功获取工作流统计",
      "data": {
        "name": "sdB_cno",
        "status": "running",
        "total": 432,
        "pending": 120,
        "queued": 40,
        "running": 8,
        "converged": 261,
        "failed": 3,
        "cold_run_converged": 220,
        "seed_step_converged": 41,
        "waves": [
          { "wave": 0, "total": 108, "converged": 108, "failed": 0 }
        ],
        "avg_point_sec": 740.5,
        "eta_sec": 9620.0
      }
    }
    

    收敛手段归因仅 cold_run_converged / seed_step_converged 两字段;无独立 imported_converged——历史导入点统一按 seed_step 途径计入(success_method 语义见 §8.8)。 avg_point_sec = AVG(COALESCE(tasks.elapsed_sec, created_at→completed_at 时间戳差))—— 优先用 Worker 回报的精确墙钟(不含排队等待),历史无 elapsed_sec 的行回退时间戳差近似; eta_sec = avg_point_sec × (total - converged - failed) ÷ 在线节点总槽位(并发感知; 无在线节点按串行兜底);无历史数据时二者为 null


8.8 工作流网格点列表 (GET /api/workflows/:name/points)

  • 处理函数: get_workflow_pointscrates/server/src/api/workflow.rs

  • 权限: Admin

  • 用途: 详情页"网格点明细"表与"收敛性分析"Parallel Sets 平行集合图)、概览"最近动态"流的数据源。 每行附带最近一次尝试信息(tasks 关联子查询取最新行,从未派发过的点 last_* 为 null)。

  • 查询参数(全部可选,枚举值白名单校验,非法 → 400:

    参数 取值 默认
    status pending/queued/running/converged/failed 不过滤
    method cold_run/seed_step(白名单不含 imported,传入即 400 不过滤
    wave 整数波次 不过滤
    q 点名子串(LIKE 通配符已转义) 不过滤
    sort wave/teff/max_relc/attempts/last_completed_at wave
    order asc/desc asc
    limit 1500(超出钳位) 100
    offset ≥0 0
  • 响应 Schema (200 OK):

    {
      "success": true,
      "message": "成功获取工作流网格点列表",
      "data": {
        "total": 432,
        "points": [
          {
            "name": "t60000_g5.0_he-2_c-4_n-4_o-4",
            "teff": 60000.0, "logg": 5.0, "loghe": -2.0,
            "logc": -4.0, "logn": -4.0, "logo": -4.0,
            "cno_sum": -12.0, "wave": 0,
            "status": "converged",
            "success_method": "seed_step",
            "attempt_count": 2,
            "last_max_relc": 0.00043,
            "last_task_type": "seed_step",
            "seed_point_name": "t60000_g5.0_he2_c-4_n-4_o-4",
            "node_id": "node-a1b2",
            "last_completed_at": "2026-07-30 11:12:00",
            "last_error": null
          }
        ]
      }
    }
    

8.9 网格点详情 (GET /api/workflows/:name/points/:point)

  • 处理函数: get_workflow_point_detailcrates/server/src/api/workflow.rs
  • 权限: Admin
  • 用途: 点详情滑入面板——尝试历史(还原"冷启动失败 → 种子步进救回"剧情)
    • conv.json 逐阶段诊断。
  • 路径参数: :point — 点名白名单 [A-Za-z0-9._-+@](非空、拒前导 .、≤128, 即路径穿越前置闸门);conv.json 读盘前再经 canonicalize 归属校验。非法点名 → 400 点不存在 → 404
  • 响应 Schema (200 OK):
    {
      "success": true,
      "message": "成功获取网格点详情",
      "data": {
        "point": { "...": "同 8.8 单行" },
        "attempts": [
          {
            "task_id": "uuid",
            "task_type": "cold_run",
            "seed_point_name": null,
            "status": "failed",
            "max_relc": 954000.0,
            "atmosphere_has_nan": false,
            "node_id": "node-a1b2",
            "error_message": "nl stage diverged",
            "created_at": "2026-07-30 09:00:00",
            "completed_at": "2026-07-30 09:30:00"
          },
          { "task_type": "seed_step", "status": "completed", "...": "第二次尝试(救回)" }
        ],
        "conv": {
          "converged": true,
          "final_max_relc": 0.00043,
          "final_chmax": 0.001,
          "seed": "data/seeds/<seed_name>/<seed_name>.7",
          "atmosphere_has_nan": false,
          "synspec_rc": 0,
          "synspec_sec": 3.1,
          "elapsed_sec": 126.4,
          "stages": [
            { "label": "seed_nc", "chmax": 0.001, "lte": false, "converged": false, "best_max_relc": 0.02, "elapsed_sec": 62.4, "note": null },
            { "label": "nl", "chmax": 0.001, "lte": false, "converged": true, "best_max_relc": 0.00043, "elapsed_sec": 64.0, "note": null }
          ]
        }
      }
    }
    

    conv 来自 seeds_dir/<point>/conv.jsonModelSummary 结构,见 crates/common/src/models.rs)。文件缺失/解析失败时为 null(仍返回 200, 前端降级显示"诊断文件不可用")。


8.10 工作流进度时间序列 (GET /api/workflows/:name/progress)

  • 处理函数: get_workflow_progresscrates/server/src/api/workflow.rs
  • 权限: Admin
  • 用途: 详情页概览的进度曲线(sparkline)、经验速率 ETA 与停滞预警数据源。 快照由服务端后台循环(~30s)对每个运行中工作流写入 workflow_progress_snapshots 表,计数无变化不落库(去重防膨胀),保留期 7 天自动清理。
  • 查询参数: hours(时间窗口,默认 24,钳位 1–168)。
  • 响应 Schema (200 OK):
    {
      "success": true,
      "message": "成功获取进度时间序列",
      "data": {
        "hours": 24,
        "series": [
          { "ts": "2026-07-31 08:00:00", "total": 432, "pending": 120, "queued": 40,
            "running": 8, "converged": 261, "failed": 3 }
        ],
        "rate_per_hour": 12.5,
        "now": "2026-07-31T09:00:00Z",
        "done_rate_per_hour": 11.0,
        "rate_span_hours": 23.5,
        "stalled_minutes": 0.0
      }
    }
    

    series 超 300 条自动降采样(首末点保留);rate_per_hour = 窗口首末 converged 增量 ÷ 时长(快照不足 2 条为 null);now = 服务端当前 UTC 时刻(前端锚定曲线右缘); done_rate_per_hour = 终态完成速率(用于 ETA);rate_span_hours = 速率统计实际时间跨度; stalled_minutes = 终态数(converged+failed)最后一次增长至窗口末端的分钟数(前端 >10 分钟触发停滞预警)。


8.11 工作流列表内联统计(GET /api/workflows 响应增强)

WorkflowSummary 新增 stats 字段(单条 GROUP BY 聚合回填,无 N+1): 工作流尚无网格点(未启动)时为 null,前端据此不渲染卡片进度条。

{
  "success": true,
  "message": "成功获取工作流列表",
  "data": [
    {
      "name": "sdB_cno",
      "description": "sdB 6维恒星大气模型计算网格",
      "status": "running",
      "created_at": "2026-07-30 10:00:00",
      "updated_at": "2026-07-30 11:00:00",
      "stats": {
        "total": 432,
        "converged": 261,
        "failed": 3,
        "running": 8,
        "cold_run_converged": 220,
        "seed_step_converged": 41
      }
    }
  ]
}

9. 错误处理与状态码汇总

HTTP 状态码 触发场景说明 响应格式 核心原因与解决建议
200 OK 请求正常处理 JSON / Binary Stream 操作成功执行。
400 Bad Request 参数校验失败、缺失关键字段、YAML 格式错误或枚举白名单拒绝 application/json / Plain Text 检查请求 JSON 结构,验证 YAML 配置语法,确认 method/sort/order 等枚举在合法取值内。
401 Unauthorized 鉴权失败或缺失 Authorization Header application/json 确认环境变量配置,并在 Request Header 中包含正确的 Token。
403 Forbidden 身份校验失败(心跳 node_id 与鉴权身份不匹配、跨点上报拒绝、角色权限不足) application/json 确认节点 token 与上报身份一致,检查 RBAC 角色。
404 Not Found 资源、种子文件或工作流不存在 application/json 校验请求 URL 中的资源文件名或工作流 name 是否拼写无误。
409 Conflict 状态冲突:节点停用/启用状态不支持、start 初始化抢占失败、report 任务归属校验失败 application/json 检查目标状态是否合法,或稍后重试。
429 Too Many Requests API 请求超出速率限制(滑动窗口限流生效) application/json 降低请求频率;限流为滑动窗口实现(rate_limit.rs),非漏桶。
500 Internal Server Error 服务端数据库错误、I/O 打开失败或队列异常 application/json 检查服务端日志以进一步厘清 SQLite 锁冲突、磁盘空间或资源路径问题。

文档生成于 2026-07-272026-08-04 更新以对齐阶段独立配置 / 多工作流隔离 / 节点配额 / 失败阶段归因。