feat: add ops center and node onboarding flow

This commit is contained in:
Your Name
2026-04-18 23:52:51 +08:00
parent 246838ae4c
commit b9c29481b5
142 changed files with 89727 additions and 186 deletions

View File

@@ -0,0 +1,740 @@
# domainCheck Ops Job Contract
## 1. 目标
这份文档用于冻结 `ops job` 的正式 contract。
它是整套海外单脑控制面里最核心的执行对象。
正式定义:
> `ops job` 是一条可审计、可编排、可派发、可回执、可取消的标准运维任务记录。
它不是:
- 一条 shell 命令
- 一个页面按钮点击结果
- 一个仅供 agent 消费的内部对象
它应该同时服务于:
- 页面按钮
- CLI
- 海外 Codex 驾驶员
- Node Agent
- Release / Rollout
- Playbook Run
也就是说:
- `playbook run`
- 是编排聚合对象
- `rollout`
- 是发布推进对象
- `ops job`
- 是真正的执行颗粒度对象
---
## 2. 入口接口
当前已落地的主入口:
- `GET /api/v1/ops/jobs`
- `GET /api/v1/ops/jobs/{job_id}`
- `GET /api/v1/ops/jobs/{job_id}/events`
- `POST /api/v1/ops/jobs`
- `POST /api/v1/ops/jobs/batch`
- `POST /api/v1/ops/policy/preview`
- `POST /api/v1/ops/jobs/{job_id}/approve`
- `POST /api/v1/ops/jobs/{job_id}/cancel`
- `POST /api/v1/ops/jobs/{job_id}/dispatch`
配套对象来源:
- `ops_jobs`
- `ops_job_steps`
- `ops_job_events`
正式约束:
- 页面、CLI、Codex 不能各自定义第 2 套任务模型
- `playbook run` 只能聚合 `ops job`
- `rollout` 只能批量生成 `ops job`
- `Node Agent` 只能拉取和回执 `ops job`
---
## 3. 当前实现里的 Job Object
来源:
- `GET /api/v1/ops/jobs`
- `GET /api/v1/ops/jobs/{job_id}`
- 创建 / 审批 / 取消 / 派发后的返回体中的 `job`
最小字段:
```json
{
"id": 101,
"job_code": "ops-20260418064500-a1b2c3",
"action": "logs.collect",
"target_type": "node",
"target_node_code": "mainland-worker-01",
"target_node_codes": [
"mainland-worker-01"
],
"status": "queued",
"status_label": "排队中",
"execution_mode": "remote-agent",
"execution_mode_label": "Node Agent",
"requested_by": "web-ui/ops-center",
"payload": {},
"metadata": {},
"result": {},
"summary": "目标节点 mainland-worker-01 / 发起 web-ui/ops-center / 方式 Node Agent",
"summary_text": "目标节点 mainland-worker-01 / 发起 web-ui/ops-center / 方式 Node Agent",
"error_message": "",
"created_at": "2026-04-18 06:45:00",
"started_at": "",
"finished_at": "",
"updated_at": "2026-04-18 06:45:00",
"risk_level": "medium",
"approval_required": false,
"approval_status": "approved",
"approval_status_label": "已审批",
"approved_by": "",
"approved_at": "",
"blocked_reason": "",
"cancellation_reason": "",
"dispatched_at": "",
"target_selector": {},
"policy": {},
"rollout_id": 0,
"steps_total": 0,
"steps_running": 0,
"steps_success": 0,
"steps_failed": 0,
"steps_terminal": 0,
"steps_loaded": false,
"focus_ref": {
"kind": "ops_job",
"job_id": 101,
"job_code": "ops-20260418064500-a1b2c3",
"action": "logs.collect",
"target_node_code": "mainland-worker-01"
},
"steps": []
}
```
正式要求:
- `job_code`
- 是跨页面、CLI、事件流的稳定任务标识
- `action`
- 是执行语义主键,不应用 `title` 或自然语言代替
- `target_type`
- 定义任务作用范围,不应只靠是否传了 `target_node_code` 来猜
- `execution_mode`
- 是正式执行方式,不是附属备注
- `policy`
- 是创建时冻结的策略快照,不应由页面二次推导
- `status_label / approval_status_label`
- 是给页面、CLI、Codex 直接消费的人类可读标签
- `summary_text`
- 是任务摘要 contract不要求调用方再用状态和节点自己拼一句中文
- `steps_total / steps_running / steps_success / steps_failed / steps_terminal`
- 是任务步骤聚合统计,避免前端和 CLI 再自行遍历 steps 统计
- `focus_ref`
- 是统一定位对象后续页面、CLI、Codex 应优先消费它,而不是自己重组跳转参数
---
## 4. Job 状态机
### 4.1 当前实现中已使用的状态
当前后端已实际使用:
- `queued`
- `awaiting_approval`
- `blocked`
- `running`
- `success`
- `failed`
- `cancelled`
这些状态已经是正式 contract 的最小闭环。
### 4.2 推荐终局扩展状态
终局可继续扩展为:
- `draft`
- `approved`
- `queued`
- `dispatching`
- `running`
- `verifying`
- `partially_succeeded`
- `rollback_running`
- `rolled_back`
- `success`
- `failed`
- `cancelled`
- `timed_out`
但要明确区分:
- 当前实际 API 已稳定承诺的,是最小状态集
- 终局扩展状态不能破坏当前状态语义
---
## 5. Execution Mode Contract
当前实现里正式出现的执行方式:
- `remote-agent`
- `local-runtime`
- `control-plane`
- `ssh`
语义必须固定:
### 5.1 `remote-agent`
- 默认正式执行方式
- 由目标节点 Node Agent 拉取并执行
### 5.2 `local-runtime`
- 仅用于当前节点本机即时执行
- 主要用于本机 runtime 动作
### 5.3 `control-plane`
- 由海外控制面直接执行
- 适用于控制面内生动作
### 5.4 `ssh`
- 只作为救援和过渡执行器
- 不能成为大规模正式发布的默认方式
正式约束:
- 发布默认优先 `remote-agent`
- `ssh` 只应是兜底链路
- 页面、CLI、Codex 不应再自行创造第 5 种 execution mode
---
## 6. Target Type Contract
当前 contract 至少应允许:
- `node`
- `batch`
- `nodes`
- `rollout`
语义建议固定为:
### 6.1 `node`
- 单节点任务
### 6.2 `batch` / `nodes`
- 多节点批量任务
- 当前通常通过 `/ops/jobs/batch` 创建
### 6.3 `rollout`
- 该任务属于某轮 release rollout 推进的一部分
正式要求:
- 如果任务来自 rollout必须能通过 `rollout_id` 回溯
- 如果任务来自 playbook应通过 `metadata` 标出 run 归属
---
## 7. Job Step Contract
来源:
- `GET /api/v1/ops/jobs/{job_id}`
最小字段:
```json
{
"id": 201,
"step_key": "dispatch",
"title": "调度动作",
"node_code": "mainland-worker-01",
"status": "queued",
"stdout": "",
"stderr": "",
"result": {},
"created_at": "2026-04-18 06:45:00",
"started_at": "",
"finished_at": "",
"updated_at": "2026-04-18 06:45:00"
}
```
当前实现说明:
- 现阶段每条 `ops job` 至少会先生成一个 `dispatch` step
- 以后如果引入真正 DAG / 多步骤执行,也应继续沿用 `ops_job_steps`
正式约束:
- `step_key`
- 是步骤主键,不应用标题代替
- `status`
- 必须与 job 状态联动,但不要求完全相同
- `stdout / stderr`
- 是步骤级输出,不应与整条 job 的 `result` 混成一层
---
## 8. Job Event Contract
来源:
- `GET /api/v1/ops/jobs/{job_id}/events`
最小字段建议:
```json
{
"id": 301,
"event_key": "job-event:301",
"job_id": 101,
"step_id": 201,
"node_code": "mainland-worker-01",
"client_event_id": "evt-001",
"event_type": "job_dispatch_requested",
"level": "info",
"level_label": "信息",
"message": "任务等待 node-agent 拉取: 101",
"summary": "任务等待 node-agent 拉取: 101",
"payload": {},
"has_payload": false,
"occurred_at": "2026-04-18 06:45:03",
"created_at": "2026-04-18 06:45:03"
}
```
当前代码里事件来源包括:
- job created
- job blocked
- job awaiting approval
- job approved
- job cancelled
- job dispatch requested
- job executed locally / over ssh / on control plane
- agent start / complete / custom events
正式要求:
- `events` 是任务时间线,不是调试附属品
- 页面、CLI、Codex 都应从这里读“任务做到哪一步了”
- 不应重新从 stdout/stderr 推断关键状态
补充约束:
- `event_key`
- 是事件行的稳定前端 / CLI 标识
- `summary`
- 是给页面、CLI、Codex 直接消费的短摘要,不要求调用方再拼接 `message`
- `level_label`
- 是面向人看的等级标签
- `occurred_at`
- 是统一观察面时间字段,允许与 `created_at` 同值
### 8.1 Event List Summary Contract
当前 `GET /api/v1/ops/jobs/{job_id}/events` 建议同时返回:
```json
{
"summary": {
"job_id": 101,
"returned_total": 12,
"level_counts": {
"info": 8,
"warning": 3,
"error": 1
},
"event_type_counts": {
"job_created": 1,
"job_dispatch_requested": 1,
"executor_received": 1
},
"node_counts": {
"mainland-worker-01": 12
},
"latest_at": "2026-04-18 06:45:08",
"filters": {
"limit": 50
}
}
}
```
正式要求:
- 事件接口不只给一串数组
- 至少要给:
- 当前返回了多少条
- 等级分布
- 事件类型分布
- 最近时间
- 这样页面、CLI、Codex 才能先判断“有没有异常事件”再决定是否展开明细
---
## 9. List Contract 与 Compact 模式
来源:
- `GET /api/v1/ops/jobs`
当前实现特点:
- 默认 `list_ops_jobs(..., compact=True)`
- 返回 compact job
- `steps` 被置空
- `payload / metadata / result` 会被压缩
因此建议把列表 contract 明确成:
```json
{
"jobs": [
{
"id": 101,
"job_code": "ops-20260418064500-a1b2c3",
"action": "logs.collect",
"status": "queued",
"execution_mode": "remote-agent",
"requested_by": "web-ui/ops-center",
"payload": {
"tail_lines": 120
},
"metadata": {},
"result": {},
"result_summary": {},
"is_compact": true,
"steps": []
}
]
}
```
正式要求:
- 列表页默认只承诺 compact 视图
- 详情页再承诺 full job + steps
- 页面不应假设列表结果天然带完整 steps
---
## 10. Create Contract
来源:
- `POST /api/v1/ops/jobs`
最小请求体:
```json
{
"action": "logs.collect",
"target_type": "node",
"target_node_code": "mainland-worker-01",
"execution_mode": "remote-agent",
"requested_by": "web-ui/ops-center",
"payload": {
"tail_lines": 120
},
"metadata": {
"source": "ops-center"
},
"auto_approve": false,
"run_now": true
}
```
当前实现还支持:
- `target_selector`
- `rollout_id`
最小返回体:
```json
{
"job": {},
"executed_immediately": false
}
```
关键语义:
- `executed_immediately=true`
- 说明任务已被控制面即时执行,而不是停留在队列
- `executed_immediately=false`
- 说明任务进入了:
- `queued`
- `awaiting_approval`
- `blocked`
正式要求:
- 创建接口不能只回答“建成功了”
- 必须明确回答:
- 当前状态是什么
- 是否立即执行
- 如果没执行,是在等审批、等 agent、还是被阻断
---
## 11. Batch Create Contract
来源:
- `POST /api/v1/ops/jobs/batch`
最小请求体建议:
```json
{
"template_key": "diagnostics.collect",
"target_node_codes": [
"mainland-worker-01",
"mainland-worker-02"
],
"execution_mode": "remote-agent",
"requested_by": "web-ui/ops-center",
"payload": {},
"metadata": {},
"auto_approve": true
}
```
最小返回体建议:
```json
{
"jobs": [],
"results": []
}
```
正式要求:
- batch 只是创建方式,不是新的执行对象
- batch 最终仍要展开成多条 `ops job`
- 单条失败不能让结果完全丢失,必须保留逐项结果
---
## 12. Policy Preview Contract
来源:
- `POST /api/v1/ops/policy/preview`
- `ops_jobs.policy`
这层是创建前的正式门禁对象。
当前创建任务时,后端已先做:
- 风险等级判断
- 阻断判断
- 审批要求判断
建议最小返回字段:
```json
{
"blocked": false,
"blocking_reasons": [],
"approval_required": true,
"approval_reasons": [
"当前目标包含 control 节点"
],
"warnings": [],
"recommendations": [],
"risk_level": "high",
"target_summary": {},
"batch_plan": {},
"cluster_guardrails": {},
"operational_readiness": {}
}
```
正式要求:
- 页面、CLI、Codex 都应复用同一份 policy preview
- 不能各自再写一套“能不能执行”的判断逻辑
---
## 13. Approval / Cancel / Dispatch Contract
### 13.1 Approve
来源:
- `POST /api/v1/ops/jobs/{job_id}/approve`
最小请求体:
```json
{
"approved_by": "admin"
}
```
语义:
-`awaiting_approval` 可审批
- 审批后回到 `queued`
### 13.2 Cancel
来源:
- `POST /api/v1/ops/jobs/{job_id}/cancel`
最小请求体:
```json
{
"cancelled_by": "admin",
"reason": "本轮灰度暂停"
}
```
语义:
- `success / failed / cancelled` 不可取消
- 取消后 job 进入 `cancelled`
- steps 同步进入 `cancelled` 或保持终态
### 13.3 Dispatch
来源:
- `POST /api/v1/ops/jobs/{job_id}/dispatch`
语义:
- `queued` 才可派发
- `control-plane`
- 立即由控制面执行
- `local-runtime`
- 当前节点本机即时执行
- `ssh`
- 由 SSH 执行器执行
- `remote-agent`
- 保持 `queued`,等待对应 agent 拉取
这点必须明确:
> dispatch 不等于一定会立刻变成 running。
对于 `remote-agent`,它可能只是:
- 写入事件
- 保持排队
- 等节点稍后 pull
---
## 14. 与 Playbook / Rollout / Agent 的边界
### 14.1 与 Playbook
- `playbook run`
- 负责展开、聚合、重跑、取消整轮编排
- `ops job`
- 负责单条执行记录
### 14.2 与 Rollout
- `rollout`
- 负责发布批次推进
- `ops job`
- 负责每个节点每步的实际任务
### 14.3 与 Node Agent
- 控制面创建 `ops job`
- agent 拉取 `ops job`
- agent 回写 `ops_job_events`
- agent 推进 job / step 状态
### 14.4 与 Observability
- `ops_observability_contract`
- 看现场事实
- `ops_job_contract`
- 看执行动作本身
这两层不能混:
- 观察面告诉你“谁在跑、谁异常”
- 任务面告诉你“这一条任务如何被创建、审批、派发、执行、取消”
---
## 15. 终局约束
后续不应再回退到下面几种模式:
### 1. 页面点按钮直接跑 shell
错误。
正确方式:
- 页面创建 `ops job`
### 2. Codex 直接 SSH 改现场
错误。
正确方式:
- Codex 创建 `ops job` / `playbook run`
### 3. 发布系统不经过任务层
错误。
正确方式:
- rollout 批量生成 `ops job`
### 4. 死信治理不留痕
错误。
正确方式:
- `flush / replay / discard` 都生成正式 `ops job`
所以可以把这份文档看成整套系统的“执行订单母本”。
后续只要这份 contract 稳住页面、CLI、Codex、Agent、Release、Playbook 就都能围绕同一个中心对象协作。