三、数据任务执行工作台架构设计(data-execution-workbench)
前置结论:现有 data-workbench 的执行内核(runs/scheduler/exec/node-runs)已经写好且有 25 个测试文件覆盖大半。本设计的第一原则是迁移复用而非重写,新项目先以"搬运 + 解耦 + 补契约"为主。
1. 系统定位与边界
- 独立仓库
data-execution-workbench,独立部署,独立二级域名(候选exec.constraintlab.io,【待核实】DNS 与证书后再定——审计确认现网仅 workbench.constraintlab.io 在用,无冲突)。 - 只做三件事:① 托管/编排各项目运营代码的执行 ② 提供统一任务接口与执行记录 ③ 面向数据工作台输出结构化结果。
- 不做:业务规则(留在各站)、问题状态管理/健康展示/审核(留在数据工作台)。
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. 版本与部署方案
- 每次执行记录双版本:项目仓库 gitSha(执行时
git rev-parse HEAD)+ 执行台自身版本(部署时写入deploy/version.json)。runs.json 每条必含。 - 脏工作区防护:spawn 前检查
git status --porcelain,非空则结果信封标"codeVersionClean": false并在摘要中警示——直接回应 bagcomply 现状(未提交改动会被执行的风险)。 - 部署核验:部署脚本部署后请求
GET /api/health返回版本号,与 gitSha 比对;失败则保留上一版(rsync 先备份到releases/<ts>/)。 - 回滚:rsync 上一版目录回切 + 重启;state/ 目录独立于代码,回滚不动运行数据。
- 站点运营代码与网站代码分离验证:各站
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 流程收口 |