14 KiB
14 KiB
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/jobsGET /api/v1/ops/jobs/{job_id}GET /api/v1/ops/jobs/{job_id}/eventsPOST /api/v1/ops/jobsPOST /api/v1/ops/jobs/batchPOST /api/v1/ops/policy/previewPOST /api/v1/ops/jobs/{job_id}/approvePOST /api/v1/ops/jobs/{job_id}/cancelPOST /api/v1/ops/jobs/{job_id}/dispatch
配套对象来源:
ops_jobsops_job_stepsops_job_events
正式约束:
- 页面、CLI、Codex 不能各自定义第 2 套任务模型
playbook run只能聚合ops jobrollout只能批量生成ops jobNode Agent只能拉取和回执ops job
3. 当前实现里的 Job Object
来源:
GET /api/v1/ops/jobsGET /api/v1/ops/jobs/{job_id}- 创建 / 审批 / 取消 / 派发后的返回体中的
job
最小字段:
{
"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 当前实现中已使用的状态
当前后端已实际使用:
queuedawaiting_approvalblockedrunningsuccessfailedcancelled
这些状态已经是正式 contract 的最小闭环。
4.2 推荐终局扩展状态
终局可继续扩展为:
draftapprovedqueueddispatchingrunningverifyingpartially_succeededrollback_runningrolled_backsuccessfailedcancelledtimed_out
但要明确区分:
- 当前实际 API 已稳定承诺的,是最小状态集
- 终局扩展状态不能破坏当前状态语义
5. Execution Mode Contract
当前实现里正式出现的执行方式:
remote-agentlocal-runtimecontrol-planessh
语义必须固定:
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 至少应允许:
nodebatchnodesrollout
语义建议固定为:
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}
最小字段:
{
"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至少会先生成一个dispatchstep - 以后如果引入真正 DAG / 多步骤执行,也应继续沿用
ops_job_steps
正式约束:
step_key- 是步骤主键,不应用标题代替
status- 必须与 job 状态联动,但不要求完全相同
stdout / stderr- 是步骤级输出,不应与整条 job 的
result混成一层
- 是步骤级输出,不应与整条 job 的
8. Job Event Contract
来源:
GET /api/v1/ops/jobs/{job_id}/events
最小字段建议:
{
"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
- 是给页面、CLI、Codex 直接消费的短摘要,不要求调用方再拼接
level_label- 是面向人看的等级标签
occurred_at- 是统一观察面时间字段,允许与
created_at同值
- 是统一观察面时间字段,允许与
8.1 Event List Summary Contract
当前 GET /api/v1/ops/jobs/{job_id}/events 建议同时返回:
{
"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 明确成:
{
"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
最小请求体:
{
"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_selectorrollout_id
最小返回体:
{
"job": {},
"executed_immediately": false
}
关键语义:
executed_immediately=true- 说明任务已被控制面即时执行,而不是停留在队列
executed_immediately=false- 说明任务进入了:
queuedawaiting_approvalblocked
- 说明任务进入了:
正式要求:
- 创建接口不能只回答“建成功了”
- 必须明确回答:
- 当前状态是什么
- 是否立即执行
- 如果没执行,是在等审批、等 agent、还是被阻断
11. Batch Create Contract
来源:
POST /api/v1/ops/jobs/batch
最小请求体建议:
{
"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
}
最小返回体建议:
{
"jobs": [],
"results": []
}
正式要求:
- batch 只是创建方式,不是新的执行对象
- batch 最终仍要展开成多条
ops job - 单条失败不能让结果完全丢失,必须保留逐项结果
12. Policy Preview Contract
来源:
POST /api/v1/ops/policy/previewops_jobs.policy
这层是创建前的正式门禁对象。
当前创建任务时,后端已先做:
- 风险等级判断
- 阻断判断
- 审批要求判断
建议最小返回字段:
{
"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
最小请求体:
{
"approved_by": "admin"
}
语义:
- 仅
awaiting_approval可审批 - 审批后回到
queued
13.2 Cancel
来源:
POST /api/v1/ops/jobs/{job_id}/cancel
最小请求体:
{
"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 就都能围绕同一个中心对象协作。