执行工作台 execution.constraintlab.io

三、数据任务执行工作台架构设计(data-execution-workbench)

前置结论:现有 data-workbench 的执行内核(runs/scheduler/exec/node-runs)已经写好且有 25 个测试文件覆盖大半。本设计的第一原则是迁移复用而非重写,新项目先以"搬运 + 解耦 + 补契约"为主。

1. 系统定位与边界

2. 目标目录结构(按审计结果定制,非套模板)

data-execution-workbench/
├── engine/                    # 通用执行引擎(自 data-workbench/core 迁移)
│   ├── server.ts              # HTTP API(自 core/server.ts 拆出执行相关路由)
│   ├── runs.ts                # RunStore / executeRun(原样迁移,已验证)
│   ├── scheduler.ts           # 调度器(原样迁移)
│   ├── exec.ts                # spawn 封装、进程树管理、跨平台(原样迁移)
│   ├── node-runs.ts           # 分布式节点协议(原样迁移,备用)
│   └── manifest.ts            # 【新增】项目任务清单(task manifest)加载与校验
├── projects/                  # 各项目接入配置(不含业务脚本!)
│   ├── china-newcomers/
│   │   ├── project.config.json
│   │   └── tasks.json         # 该项目可执行任务清单(见接口规范)
│   ├── bagcomply/ …           # 同构
├── shared-scripts/            # 跨项目复用的通用脚本(迁移自各站,见迁移清单)
│   └── link-check/  probe/  …
├── app/                       # 最小 UI:runs 列表 / 详情 / 手动触发(自 workbench /runs 页迁)
├── tests/                     # 随迁移的测试 + manifest 校验测试
├── state/                     # 运行记录(runs.json 等,rsync 排除项)
├── deploy/
│   ├── workbench.service      # systemd
│   ├── nginx-exec.conf        # nginx + Basic Auth
│   └── github-deploy.yml      # push → 部署(复用现有 Actions 模式)
└── package.json

要点:各站业务脚本不搬进本仓库(原则二),本仓库只存"怎么调它们"的清单;仅真正跨项目通用的脚本(如链接检查)迁入 shared-scripts/。

3. 任务执行流程

请求(数据工作台 HTTP / 调度器 / 手动UI)
  → 参数与权限校验(token + 任务白名单 + 参数 schema)
  → manifest 检查(project.tasks.json 中存在该任务且 enabled)
  → 并发/重复防护(RunStore 同项目互斥,已验证机制)
  → 记录代码版本:读项目 repoRoot 的 git rev-parse HEAD + 本仓库自身 SHA
  → spawn 执行(cwd=项目 repoRoot,超时上限,杀进程树)
  → 日志采集(全量落 state/logs/<runId>.log,runs.json 存摘要)
  → 结果校验(解析最后一行/指定文件的 stdout JSON 结果信封,见接口规范)
  → 记录保存(原子写 runs.json + 全量日志路径)
  → 回调数据工作台 POST /api/execution-results(带 runId + 信封)

审核节点:本系统不设审核——涉及发布/写正式数据的任务(如 --promote、push、publish-changes)在 manifest 中标 "sideEffects": "mutating",数据工作台收到后走其已有 items/reviews 流程决定是否放行后续任务。执行工作台只执行、不决策(原则六的分工)。

4. 与数据工作台的接口

方向 接口 说明
数据工作台 → 执行台 POST /api/runs {project, task, params} 触发执行,返回 runId
数据工作台 → 执行台 GET /api/runs/:id / GET /api/runs?project=&task=&status= 查询状态与结果
执行台 → 数据工作台 POST /api/execution-results(runId + 结果信封) 执行完成回调;失败重试 3 次,最终以数据工作台拉取兜底
现有机制复用 分布式节点回传(node-runs 协议 v2) 若数据工作台暂不改造,可先以"执行台作为 workbench 的一个执行节点"过渡,零改动接入

数据归属:执行记录(runs、全量日志)唯一归执行台;数据工作台只存 runId 引用 + 由结果派生的问题/健康状态。避免两处各存一份日志。

5. 版本与部署方案

  1. 每次执行记录双版本:项目仓库 gitSha(执行时 git rev-parse HEAD)+ 执行台自身版本(部署时写入 deploy/version.json)。runs.json 每条必含。
  2. 脏工作区防护:spawn 前检查 git status --porcelain,非空则结果信封标 "codeVersionClean": false 并在摘要中警示——直接回应 bagcomply 现状(未提交改动会被执行的风险)。
  3. 部署核验:部署脚本部署后请求 GET /api/health 返回版本号,与 gitSha 比对;失败则保留上一版(rsync 先备份到 releases/<ts>/)。
  4. 回滚:rsync 上一版目录回切 + 重启;state/ 目录独立于代码,回滚不动运行数据。
  5. 站点运营代码与网站代码分离验证:各站 scripts/ 的变更目前与网站代码同仓同 push。第一阶段不拆仓,但执行台以 gitSha + manifest hash 记录"实际执行的是哪个版本",使"部署状态≠git状态"可被审计。拆仓留待有真实痛点再做(原则五)。

6. 安全与运行约束

约束 方案
执行权限 API 走 token(沿用 X-Node-Token 机制);nginx Basic Auth 沿用
超时 manifest 每任务声明 timeoutMs(默认 10min,采集类可放宽),超时杀整棵进程树(exec.ts 已验证)
并发 同项目同任务互斥(RunStore 已验证);全局限并发数 2,防采集限速被打爆
重复执行防护 RunStore isRunning 检查 + 调度任务错过窗口不补跑(现 scheduler 行为,保留)
敏感配置 env 注入(INDEXNOW_KEY、飞书 webhook 等),服务器端 /etc/exec-workbench.env,不进仓库;先轮换 workbench.service 中历史泄露的飞书密钥【已验证泄露】
副作用分级 manifest 标 sideEffects: none | read-only | mutating;mutating 任务必须在请求中带 confirm: true 才执行
高风险操作 push / publish-changes / verify --promote 属 mutating:执行台执行后结果标记为"待人工确认",由数据工作台 items 流程收口