Files
getDomain/docs/24_domainCheck_NodeAgent协议与ReleaseHub设计.md
2026-04-18 23:52:51 +08:00

4422 lines
122 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 24 domainCheck Node Agent 协议与 Release Hub 设计
如果当前已经进入“把方案真正落地到上线前收口”的阶段,建议配套阅读:
- `docs/25_domainCheck_海外单脑控制面上线收口总表.md`
- `docs/schemas/ops_driver_contract.md`
- `docs/schemas/ops_stack_diagnosis_contract.md`
## 一、Node Agent 为什么是核心
终局架构里,大陆节点不应该再是“需要人工登录的机器”,而应该是:
> 受控执行节点
每个节点通过 `domaincheck-node-agent` 接入海外控制面,承担:
- 注册
- 心跳
- 拉取任务
- 执行任务
- 回传结果
- 回传日志
- 回传诊断包
这样后台按钮和 Codex 都不用再直接碰节点 shell。
补一个很关键的落地约束:
- OpsCenter 顶部的“总检入口”现在应正式优先消费 `GET /api/v1/ops/go-live-summary`
- `GET /api/v1/ops/stack-diagnosis`
- 保留为更细的可解释总检层
- 用于继续下钻 `issues / operator_decision / next_actions / recommended_commands`
- CLI 总检脚本也应优先消费同一个 contract
- 海外 Codex 驾驶员后续默认也先看这一份总检结果
也就是说Node Agent、Release Hub、页面按钮、Codex 驾驶员,不再各自拼一套“当前现场判断”,而是统一先看:
> go-live summary
然后再根据 `stack-diagnosis / overview / release launchpad` 继续下钻。
这里还要再固定一个新口径,避免页面和运维动作继续混用“可联调”和“可发版”:
- `go_live_status`
- 回答“当前是否已经进入统一收口、联调、复核车道”
- 允许出现 `attention`
- `publish_status / publish_ready`
- 回答“当前是否已经满足正式发版门禁”
- 必须明确区分
- `blocked`
- `attention`
- `ready`
这里再补一条已经收口成代码规则、后续不要再放松的判断:
- 如果目标节点的 Node Agent `delivery_queue` 仍然存在 `dead_letter`
- 这不是普通 warning
-`publish_status` 必须按 `blocked` 处理
-`default_rollout_gate` 也必须按阻断处理
- 如果只是 `retrying`
- 可以继续保留为 `warning / attention`
- 但不应该再被当成“完全干净的发布现场”
原因很简单:
- `dead_letter` 说明控制面到节点的执行回执链路已经出现语义失败
- 这时候页面如果还显示“可发布”,就会制造最危险的假阳性
- 所以 `go_live_status` 可以是 `attention`
-`publish_status` 必须更严格,直接阻断正式发版
这里还要补一条已经落成代码的事件语义,后续不要再退回“任务成功就算一切成功”的旧口径:
- 如果 Node Agent 在执行 `jobs/{job_id}/start` 时,开始回执投递失败
- 允许节点继续本地执行
- 不允许因为这一次投递失败就直接中断真实任务
- 但控制面必须把这类现场显性化为单独问题,而不是静默吞掉
- activity stream 中对应 `ops_job` 应升级为 `attention`
- 保留原始 `job_status`
- 并暴露
- `start_delivery_state`
- `start_delivery_error`
- `source_focus_ref`
- `ops overview / go-live summary`
- 也必须把它当成正式关注项
- 不能只在活动流里可见、但首页总检仍显示“可直接上线”
- 当前口径应是
- `go_live_status = attention`
- `publish_status = attention`
- 默认下一步指向 `focus_latest_job_events`
- `stack-diagnosis`
- 也必须把这类现场提升为可操作 issue
- 默认建议动作应该回到 `focus_latest_job_events`
这里再固定一个动作层约束,避免后面 UI / CLI / Codex 又各自猜入口:
- 如果活动焦点已经明确落到
- `source_focus_ref.kind = ops_job_event`
- 或者问题本身就是“开始回执异常”
- 那么默认焦点动作不应继续使用泛化的 `focus_activity_item`
- 而应直接升级为
- `focus_latest_job_events`
原因是:
- `focus_activity_item` 适合“先进入这条活动对应的详情”
- `focus_latest_job_events` 适合“问题已经明确在 job event 级别”
- 对开始回执异常这类问题,继续停在 activity 级别只会多一跳,降低定位效率
原因也很直接:
- 这类问题不代表节点没执行
- 但代表控制面和节点现场之间已经出现“开始态失联”
- 如果不显性化,页面会以为“任务没问题,只是还没刷新”
- 实际上这是自动化链路一致性已经开始打折的早期信号
也就是说:
- “总检入口”不只告诉你系统是不是在健康收口
- 还必须同时告诉你“现在能不能正式发布”
后续页面、CLI、Codex、按钮动作都应该优先消费这两个层级而不是再各自推导一套“看起来差不多能上”的临时判断。
这里再补一个已经落地的收口原则:
- `GET /api/v1/ops/stack-diagnosis` 现在不只返回传统的
- `issues`
- `recommended_actions`
- `next_step`
- 还会正式返回
- `diagnosis.operator_decision`
- `diagnosis.next_actions`
- `diagnosis.recommended_commands`
这三层的定位要固定下来:
- `operator_decision`
先回答“当前主处理车道是什么”
例如:`observability / node_handover / release / ops_jobs / steady`
- `next_actions`
直接给出主动作和复核动作的下一跳命令
- `recommended_commands`
把节点现场日志、接管、Release Hub、driver-resolve 等常用 drill-down 统一收口成可复用命令集
这样页面顶部“当前主决策”卡、CLI 的 `check_ops_center_stack.sh`、海外 Codex 驾驶员,就都不该再各自重新推导“当前应该先看日志还是先接管还是先看发布门禁”,而是优先共享后端总检 contract。
这里再补一条已经收口成正式操作约束、后续不要再退回去的口径:
- 如果总检已经识别出
- `runtime_build_schema_stale`
- `runtime_route_surface_incomplete`
- `runtime_build_info_unavailable`
- 页面、CLI、Codex 驾驶员的第一反应不应该再是“给我一条 `systemctl restart domaincheck-api`
- 而应该统一提升为标准恢复入口
- `runtime-refresh-recover`
- `go-live-recover`
这两个入口的语义要固定:
- `runtime-refresh-recover`
- 回答“当前运行中的 API 是否还是旧 schema / 旧路由面”
- 给出固定恢复链
- 再输出下一跳复检命令
- `go-live-recover`
-`runtime-refresh-recover`
- `stack-diagnosis`
- `stack-next`
- `go-live-check`
- `doctor`
这条联调收口链直接串起来
也就是说:
- 原子 systemd 重启仍然只是底层执行事实
- 对页面按钮、CLI、Codex 来说,正式入口必须是组合恢复动作
- 这样后续总检摘要、主决策卡、推荐命令集,才不会再次退化成“看见 schema 漂移就手工重启”
再往前一层,现在还应该固定:
- `GET /api/v1/ops/driver-feed`
- 返回 `go_live_summary`
- `GET /api/v1/ops/driver-feed`
- 继续返回 `automation_coverage`
- `GET /api/v1/ops/codex-brief`
- 返回 `go_live_summary`
- `GET /api/v1/ops/codex-brief`
- 继续返回 `automation_coverage`
也就是说:
- 页面顶部先看 `go-live-summary`
- Codex 驾驶员先看 `codex-brief.go_live_summary`
- CLI 总检先看 `go-live-check`
- 真正需要解释“为什么阻断 / 下一步为什么是这个动作”时,再回退到 `stack-diagnosis`
同时还要固定一条“自动化覆盖率”口径避免后续页面、CLI、Codex 又各自推导一套“现在已经自动化到什么程度”:
- `driver_feed.automation_coverage`
- `codex_brief.automation_coverage`
至少需要稳定包含:
- `automation_level_counts`
- `recommendation_counts`
- `executor_kind_counts`
- `safe_auto_total`
- `guarded_auto_total`
- `mixed_total`
- `ui_only_total`
- `blocked_total`
- `preview_only_total`
- `backend_handled_total`
- `execution_ready_total`
- `human_dependency_total`
- `launch_status`
- `launch_ready`
其中语义固定为:
- `preview_only_total`
统计那些已经后端化、但本质仍属于“只读预览 / 聚焦 / 审阅”的动作
- `backend_handled_total`
统计已经脱离人工页面点击、可以直接走后端 contract 的动作
- `execution_ready_total`
统计 `auto_execute + confirm_then_execute`
- `human_dependency_total`
统计 `resolve_first + open_ui + blocked`
- `launch_status`
作为“自动化层面的收口判断”
取值固定为 `ready / attention / blocked`
- `launch_ready`
只回答“从自动化执行角度看,当前是否已经接近无人值守可上线”
这样才是完整的:
- 一份稳定收口摘要
- 一份可解释总检
- 一组可执行下钻命令
---
## 二、当前已经落下的第一版协议骨架
当前后端已经补出这些接口骨架:
- `POST /api/v1/ops/agent/tokens`
- `POST /api/v1/ops/agent/bootstrap-plan`
- `POST /api/v1/ops/agent/register`
- `POST /api/v1/ops/agent/heartbeat`
- `POST /api/v1/ops/agent/pull`
- `POST /api/v1/ops/agent/jobs/{job_id}/start`
- `POST /api/v1/ops/agent/jobs/{job_id}/complete`
- `POST /api/v1/ops/agent/jobs/{job_id}/events`
这几类接口已经足够表达“节点是怎么接控制面的”。
当前仓库里也已经补出第一版 agent 运行骨架:
- `domain-api/app/node_agent.py`
- `domain-api/deploy/systemd/domain-node-agent.service`
- `domain-api/deploy/multi-region/build_node_agent_bootstrap_plan.sh`
- `domain-api/deploy/multi-region/templates/domaincheck-node-agent.env.example`
也就是说,这已经不是纯文档方案,而是开始具备正式执行器入口了。
另外现在控制面已经不只是“签发一枚 token”而是可以直接生成完整接入方案
- 后端通过 `POST /api/v1/ops/agent/bootstrap-plan`
- 一次返回:
- token
- env 内容
- 安装命令块
- 启动检查命令
这一步很关键,因为它把“接管新节点”从零散知识点,收敛成了控制面里的标准对象。
控制面页面现在也已经补上了第一版“可视化接管态”:
- `GET /api/v1/ops/nodes`
不再只返回“已纳管节点”,也会把“集群可见但未纳管”的节点一起带回来
- `GET /api/v1/ops/nodes/{node_code}/handover`
可以直接拿到单节点接管详情、阻断项、下一步建议和 bootstrap 默认参数
- `POST /api/v1/ops/nodes/{node_code}/handover/bootstrap-plan`
可以按节点当前上下文直接生成新的 bootstrap 方案,而不是让页面或脚本自己再拼 `node_region / node_role`
- `GET /api/v1/ops/nodes/{node_code}/onboarding`
现在除了返回 `onboarding_stage / acceptance / recommended_actions`,还应返回统一的 `recovery_decision`
- `POST /api/v1/ops/nodes/{node_code}/onboarding/recovery/preview`
应作为 `node-recover` 的正式 preview 入口
- `POST /api/v1/ops/nodes/{node_code}/onboarding/recovery/execute`
应作为 `node-recover confirm` 的正式 execute 入口
- 节点会被明确区分为:
- `已接管`
- `仅运行态在线`
- `待接入`
- `心跳过期`
- `Token 过期 / 已禁用`
这意味着海外控制面第一次真正具备了“先看见,再接管”的统一入口,而不是只有接入完成后才能看见节点。
这里补一个非常关键的收口原则:
- `recovery_decision` 必须由后端统一生成
- 前端、CLI、Codex 驾驶员不再各自重新推导“下一步该 bootstrap 还是该 acceptance”
- 它至少要固定返回:
- `action`
- `label`
- `summary`
- `command_hint`
- `window`
- `stage_code`
- `acceptance_status_code`
当前口径应固定为:
- 节点仍处于
- `pending_bootstrap`
- `runtime_only`
- `stale`
- `agent_pending`
- 以及其他接入中状态
时,优先收口为 `bootstrap_run`
- 节点真正进入
- `acceptance_ready`
- 或 Agent 已在线
时,才切到 `run_acceptance`
这样 `node-recover`、OpsCenter“自动收口建议”、海外 Codex 驾驶员三边看到的恢复结论才会完全一致。
这里还要补一条很关键的执行约束避免页面、CLI、Codex 后面又各自补出“双发一次”的隐藏 bug
- `node-recover`
- preview 时优先命中 `onboarding/recovery/preview`
- confirm 时优先命中 `onboarding/recovery/execute`
- 如果控制面已经成功命中正式 `recovery/execute`
- 就表示后端已经完成“选择动作 + 发起执行”
- CLI / 页面 / Codex 不允许再额外补跑一次
- `node-bootstrap-run`
-`node-acceptance-run`
- 只有在 recovery endpoint 不存在、退回兼容视图时
- CLI 才可以根据 `recovery_decision / stage_code / acceptance_status_code`
再本地降级决定下一跳
也就是说,正式协议下:
- 动作选择权在后端 recovery endpoint
- CLI 和页面只是展示恢复结论与执行回执
- 兼容模式下才允许本地推断
这样才能避免后续接管链路出现:
- preview 看的是一套
- execute 走的是另一套
- CLI 还再补跑一次底层动作
一旦出现这种双发结构,节点接管、验收、发布回执都会失真,这在上线期是不能接受的。
接管通过后,控制面也不应该只是停在“状态已通过”这一步,而是要立刻进入正式运维动作:
- 单节点执行标准巡检
- 批量对“真正参与检测节点”发起巡检
- 批量对“在线未参与节点”发起巡检
- 节点定向进入 Release / Rollout
其中“标准巡检”已经统一成固定序列:
- `health.snapshot`
- `logs.collect`
- `diagnostics.collect`
后续无论是 Codex 驾驶员还是后台按钮,都应该优先复用这条标准序列,而不是再回到临时 shell 排障。
在控制面页面上,这些状态不应该只是文字摘要,还应该继续收口成“驾驶建议卡”:
- 如果存在有效执行节点但尚未接管,优先进入接管动作
- 如果存在参与检测节点但现场日志还不可见,优先开启关键日志回传
- 如果存在真正参与检测节点,优先执行标准巡检
- 如果存在在线未参与节点,优先排查接单/待命问题
- 当接管和巡检链路稳定后,再推进 Release / Rollout
这样 Node Agent 协议、Ops Job 模型和页面按钮才会是同一个体系,而不是三套彼此分裂的运维逻辑。
这里的驾驶动作命名也要尽量收敛成通用语义,而不是绑定页面分组:
- `run_standard_inspection`
- `open_worker_logs`
- `open_diagnostics`
这样同一套动作才能在:
- 驾驶建议卡
- 后台按钮
- Codex 自动驾驶
- 后续自动化编排
之间稳定复用。
再进一步为了避免前端按钮、Codex 驾驶员、临时脚本各自维护一套“多步动作串联逻辑”,控制面里还需要再上一层:
- `ops playbook`
它的职责不是直接执行 shell而是把一个高层运维意图展开为一组标准 `ops jobs`
例如:
- `inspection.standard`
- `health.snapshot`
- `logs.collect(worker)`
- `diagnostics.collect`
- `scene.logs.key`
- `logs.collect(worker, 120)`
- `scene.logs.full`
- `logs.collect(worker, 300)`
- `diagnostics.collect(300)`
这样:
- 后端推荐的是 playbook
- 前端按钮点的是 playbook
- Codex 驾驶员调用的也是 playbook
- 真正落库和回执的仍然是 `ops jobs`
整个模型会更稳定,也更适合后面持续扩机器。
并且这层已经开始真正进入页面主流程:
- OpsCenter 已能列出 playbook
- OpsCenter 已能预览 playbook 展开结果
- “标准巡检”已优先直接调用 `inspection.standard`
- “接管后一键验收”已优先直接调用 `onboarding.acceptance`
也就是说,控制面 UI、Codex 驾驶员、后端编排这三层终于开始共用同一套多步动作模型。
同样OpsCenter 不应该只会发任务,还要能直接承接执行现场:
-`participating_nodes``non_participating_nodes` 一起并入运维视图
-`log_sync.enabled / mode / source_nodes / last_line` 一起并入运维视图
- 并允许控制面直接切换远端日志回传模式
这样 Codex 驾驶员和人工按钮都会在同一页看到:
- 谁正在跑
- 谁在线但没跑
- 日志有没有回来
- 要不要临时切到全量回传
并且这层不能只停留在页面按钮,还要提供固定 drill-down 命令:
- `bash domain-api/deploy/multi-region/drive_ops_center.sh scene-node-log <mainland_base_url> <node_code> [limit] [key|full]`
- `bash domain-api/deploy/multi-region/check_node_scene_log.sh <mainland_base_url> <node_code> [limit] [key|full]`
这样海外控制面、CLI 和 Codex 驾驶员在看到“参与检测但缺样本”时,不需要再人工猜下一步看哪台机器,而是直接按节点下钻现场日志。
这里再补一个已经落地的恢复流原则:
-`stack-diagnosis` / `ops overview` 判断为
- `remote_log_sync_disabled`
- 或“已有参与节点但现场日志仍不可见”
- CLI、页面按钮、Codex 驾驶员都不应该再只给“去看日志”这种观察型建议
- 而应该优先给出:
> `log-sync-recover`
也就是:
- 先执行 `enable_log_sync_key``enable_log_sync_full`
- 再自动复核 `execution_scene.log_sync`
- 再自动给出下一跳 drill-down
当前海外单入口已经有这一条组合命令:
```bash
bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-recover
bash domain-api/deploy/multi-region/drive_ops_center.sh log-sync-recover http://127.0.0.1:8100 full confirm cli/ops
```
它不是“额外的便捷脚本”,而是终局驾驶 contract 的一个关键落地点:
- 页面按钮可直接复用
- Codex 驾驶员可直接复用
- `stack-diagnosis / doctor / stack-next` 也应该优先推荐它
这样“恢复可见性”就不再是人工脑补的一串步骤,而是正式的一跳动作。
Node Agent 接管链路也应采用同样原则:
-`stack-diagnosis` 判断主车道已经进入 `node_handover`
- 或发现 `managed_nodes_agent_pending`
- 不应该只给出零散的
- `node-handover`
- `node-bootstrap-plan`
- `doctor`
- 而应该优先给出一条组合恢复流:
> `agent-gap-recover`
它的目标不是“替代真正的节点落地”,而是把海外控制面需要做的这几步先标准化:
1. 识别首个接管缺口节点
2. 输出该节点当前 handover 状态
3. 生成该节点 bootstrap plan
4. 给出后续检查命令
当前海外单入口已经支持:
```bash
bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-check
bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-recover
bash domain-api/deploy/multi-region/drive_ops_center.sh agent-gap-export
```
而且这条链路也已经具备版本兼容能力:
- 如果当前控制面 API 还没有节点级 `/handover`
- 退回 `stack-diagnosis`
- 如果当前控制面 API 还没有节点级 `/handover/bootstrap-plan`
- 退回通用 `/ops/agent/bootstrap-plan`
这样“节点接管”就不再只是页面里的一个弹窗动作而是正式进入海外单入口、CLI 总检、Codex 驾驶员都可复用的统一恢复模型。
其中:
- `agent-gap-recover`
- 负责把首个缺口节点的状态、bootstrap plan、下一跳建议串成一条恢复流
- `agent-gap-export`
- 负责把首个缺口节点的接管材料直接导出成目录
- 默认应至少包含:
- `stack-first-gap-handover`
- `agent-gap-check`
- `node-onboarding`
- `node-bootstrap-plan`
- `node-bootstrap-preview`
- `node-acceptance-plan`
- `manifest.json`
这样海外控制面就不只是“告诉你下一步做什么”,而是可以直接产出一份可执行、可交接、可归档的节点接管包。
另外,`node-bootstrap-plan` 的 condensed summary 还应该主动区分两类情况:
- 仓库代码本身还不支持最新 bootstrap 字段
- 仓库代码已经支持,但运行中的 API 还没重启到最新版本
后一类在联调里非常常见,所以 summary 必须直接给出:
- `repo_capability.supports_install_command_block`
- `repo_capability.runtime_may_need_restart`
- `recommended_commands.restart_api_if_runtime_stale`
这样一眼就能判断,这次缺口到底是“代码没写到位”,还是“代码已经在仓库里,但线上进程还是旧的”。
这个判断还必须继续上提到总检 contract
- 一旦 `stack-diagnosis` 识别出 `runtime_build_schema_stale`
- 默认 `next_step.action_code`
- 必须优先收敛为 `api-restart`
- `diagnosis.operator_decision.lane`
- 必须优先收敛为 `runtime_recovery`
因为这类问题的本质不是“节点还没接管”,而是“控制面 API 还是旧 schema / 旧路由面”。
如果这里不先做 API 重启,而是继续把人带去执行 `bootstrap_run / fix_managed_nodes`
- 只会让真正问题继续被掩盖
- 页面、CLI、Codex 也会一起被带偏
另外bootstrap 计划和安装脚本都必须兼容多种部署目录布局:
- `${ROOT_DIR}/domainCheck/...`
- `${ROOT_DIR}/domain-api/...`
- `${ROOT_DIR}/deploy/...`
原因很现实:
- 开发仓库里脚本位于 `domain-api/deploy/...`
- 实际上线包可能已经把 API 内容平铺到 `domainCheck/...`
如果 bootstrap 计划只写死其中一种路径,就会在“控制面判定缺口正确,但落地脚本直接找不到 install script”这个位置翻车。
同样,这个 drill-down 也应该进入 `doctor-export`
-`stack-diagnosis` 已经给出 `node_scene_log` 焦点
- 导出包自动追加 `15_scene_node_log_<node_code>.txt`
- `manifest.json.summary.scene_log_reports` 也同步记录这些节点级现场日志附件
并且这里的焦点来源现在也应该统一成后端总检 contract
- `diagnosis.issues[*].focus_ref`
- `diagnosis.next_step.focus_ref`
- `diagnosis.operator_decision.next_focus`
只要其中任一层已经明确给出 `node_scene_log`,诊断包就应自动把该节点现场样本带上,而不是再由导出脚本自行重新推导“哪台机器值得看”。
这样海外 Codex、后台自动驾驶和人工交接看到的不是一句“建议去看某台节点”而是一份已经带着关键现场样本的诊断包。
再往前一步,驾驶建议本身也不应只存在于前端页面,而应成为 ops overview contract 的一部分:
- 后端返回 `driver_recommendations`
- 每条建议附带 `action_code`
- 前端只做动作映射,不再重新计算优先级
继续往下,`driver_recommendations` 也不能只给“同一组 node_codes + 同一份 payload”。
因为真实场景里经常会出现:
- 主动作是 `创建 Rollout`
- 次动作是 `打开点状灰度模板`
- 两个动作对应的目标节点、release_id、预填参数并不相同
所以 contract 还要支持:
- `primary_node_codes`
- `secondary_node_codes`
- `primary_action_payload`
- `secondary_action_payload`
这样海外控制面、Codex 驾驶员、CLI 推荐执行器才能真正做到“同一张建议卡,主次动作各走各的参数”,而不是被迫回退成前端手写分支。
这样未来换成 Codex 自动驾驶或脚本编排时,也能直接复用同一套推荐逻辑。
并且这套 contract 不该只停在“给建议”,而要继续补到“统一执行入口”:
- `POST /api/v1/ops/driver-actions/preview`
- `POST /api/v1/ops/driver-actions/execute`
- 能后端化的动作先统一走这里
- 仍需人工选择、弹窗补参数的动作,再回退到前端交互
其中 `preview` 这层尤其关键,因为它负责把:
- execution chain
- target api / method
- 主次动作 request payload
- runbook resolve 结果
统一冻结成后端 contract。
这样页面、CLI 和海外 Codex 驾驶员都不需要自己重新拼 request preview也不会再出现“页面自己猜 sequence -> action 映射”的口径漂移。
现在这条链路继续往下已经开始接入:
- `GET /api/v1/ops/playbooks`
- `GET /api/v1/ops/playbook-runs`
- `GET /api/v1/ops/playbook-runs/{run_code}`
- `GET /api/v1/ops/playbook-runs/{run_code}/events`
- `GET /api/v1/ops/activity-stream`
- `POST /api/v1/ops/playbook-runs/{run_code}/rerun`
- `POST /api/v1/ops/playbook-runs/{run_code}/cancel`
- `POST /api/v1/ops/playbooks/preview`
- `POST /api/v1/ops/playbooks/execute`
也就是说,驾驶动作的后端执行入口不再只是“直接发一个动作模板”,而是可以把多步标准动作整体展开。
这样整条链路才是:
- 后端推荐
- 后端标准动作执行
- 前端只做展示和少量交互兜底
再进一步,控制面还要具备“巡检结果收口视图”:
- 一个节点最近的 `health.snapshot`
- 一个节点最近的 `logs.collect`
- 一个节点最近的 `diagnostics.collect`
要能在页面里按节点重新聚合,而不是让操作者自己翻几十条 `ops job` 去拼结果。
这一步很关键,因为它决定了:
- Codex 驾驶员读取的是“节点收口状态”
- 而不是一堆离散任务记录
现在这层也不该继续停留在前端临时聚合,而应该进入正式后端协议:
- `GET /api/v1/ops/overview` 返回 `inspection`
- `GET /api/v1/ops/inspection-overview` 返回独立巡检收口视图
- 页面只负责展示收口排序、异常优先级、Worker 日志口径都以后端为准
并且这层 contract 不能只停留在“返回一句总结”,还要正式结构化:
- `problem_kind`
- `problem_label`
- `problem_level`
- `recommended_action_code`
- `ui_intent`
这样页面筛选、Codex 自动驾驶、后续策略编排才不需要再从自然语言里反推“这是失败、执行中还是巡检缺口”。
同理,动作模板 contract 也不能继续停留在“只有 text / number / select”
- `boolean`
- `textarea`
- `text_list`
这三类字段是运维动作真正高频的表达方式:
- `boolean` 用来表达是否自动审批、是否自动回滚、是否切 current
- `textarea` 用来承载 artifact URL、长文本备注、临时参数块
- `text_list` 用来承载服务列表、健康检查 URL 列表、节点列表
否则发布、回滚、批量诊断这些动作永远只能靠前端写死表单,无法真正回收到统一模板层。
尤其 `deploy.release` 这类动作,应该正式进入模板目录,而不是只存在于 rollout 内部:
- `deploy.release.control`
- `deploy.release.worker`
- `deploy.release.custom`
这样单节点灰度验证、点状修复、正式 rollout 之前的预演,才能统一落到同一个 `ops job` 体系里。
模板动作再往前还要补一层正式能力:
- `action template preview`
- `batch policy preview`
也就是在真正创建 `ops jobs` 之前,控制面先基于:
- 目标节点集合
- 当前 execution mode
- 动作 payload
- 集群参与态 / 忙碌态 / control / effective worker 身份
给出统一预检结论:
- `blocked`
- `approval_required`
- `warnings`
- `recommendations`
这样发布、停 Worker、重启 API 这些高风险动作,就不会变成“点了按钮才知道会出事”,而是先看到:
- 哪些节点被阻断
- 哪些节点建议审批
- 哪些只是需要关注
这层能力既服务于页面按钮,也服务于后续的海外 Codex 驾驶员,因为两边都应该调用同一份 policy preview contract而不是各自写一套风险判断。
在 playbook 这一层,还要继续补一条非常关键的 contract
- 一次 playbook 执行 = 一次 `playbook run`
- 每个 run 都要有自己的 `run_code`
- 每个子任务都写入统一的 playbook metadata
- 控制面能够把这些子任务重新聚合成:
- run 总状态
- step 状态
- 目标节点集合
- 最近可追事件的任务入口
这一步的意义是把“多步动作”从前端临时串联,正式升级成可审计、可追溯、可回放的运维对象。
继续补齐 run 级动作之后,控制面就可以直接:
- 对 playbook run 做状态筛选
- 按分组收窄到 onboarding / diagnostics / scene
- 按 run_code、创建来源、目标节点做关键词检索
- 直接查看某轮 run 的 focus summary
- 直接看到 problem steps / active steps
也就是说,控制面针对 playbook run 的 contract 已经不再只是“能聚合”,而是进一步升级成“能定位焦点、能快速追问题”。
再往前一步playbook run 还必须具备“整轮事件视角”:
- 不是只查某一条 job 的事件
- 而是能把同一 `run_code` 下面所有 job 的事件重新聚合
- 并补齐:
- `step_key`
- `step_title`
- `job_code`
- `job_status`
- `target_node_code`
这样海外控制面的详情抽屉才能真正像“远端驾驶舱”,因为它看到的是整轮执行过程,而不是零散子任务的碎片。
现在这层 contract 还要进一步冻结成“前端不再自行翻译”的正式字段:
- `playbook run`
- `status_label`
- `summary`
- `summary_text`
- `steps_total`
- `steps_success`
- `steps_running`
- `steps_problem`
- `steps_terminal`
- `focus_ref`
- `playbook run.steps[]`
- `status_label`
- `summary`
- `summary_text`
- `target_node_codes`
- `focus_ref`
- `playbook run events`
- `level_label`
- `job_status_label`
- `summary`
- `summary_text`
- `event_key`
- `occurred_at`
- `focus_ref`
- `source_focus_ref`
- `playbook run events.summary`
- `returned_total`
- `event_type_counts`
- `job_status_counts`
- `filters.limit`
也就是说,从这一步开始:
- 前端不再自己把 `running` 翻译成“收口中”
- 前端不再自己拼“当前焦点摘要”
- 前端也不再把“播放焦点”和“远端执行焦点”混成一个对象
这里还要再补一条非常重要的消费规则:
- `playbook run events.focus_ref`
- 始终用于定位到本轮 playbook run 的正式落点
- `playbook run events.source_focus_ref`
- 始终保留远端 Agent / ops job 原始焦点
- 允许携带:
- `job_id`
- `job_code`
- `event_key`
- `step_key`
- 其他执行侧专有定位字段
这样控制面详情抽屉、Codex 驾驶员和后续 CLI 就不会再遇到一个老问题:
- 页面需要跳到“这一轮 playbook 的哪个步骤”
- 但排障又需要知道“远端 Agent 具体卡在了哪条事件”
这两种焦点必须并存,而不能互相覆盖。
- 前端不再自己推断“应该跳到哪一轮 / 哪一步 / 哪条事件”
而是统一消费控制面已经产出的结构化别名字段。
同样,驾驶建议层也开始和 playbook run 正式打通:
- recommendation 不再只推荐“去看哪个节点”
- 而是可以直接推荐“先处理哪轮异常编排”
- 并携带:
- `run_code`
- `focus_step_key`
- `focus_step_title`
这样 recommendation -> playbook run detail -> playbook run events 才形成一条真正闭环的驾驶链路。
继续往前收一层recommendation 也不该只看 playbook run而要继续消费统一 activity stream
- 如果最近存在失败 / 阻断 / 已取消的 standalone `ops job`
- 或存在 `awaiting_approval``halted``completed_with_issues` 的 rollout
- recommendation 应直接把这条 activity 提升成驾驶建议
- 并通过统一 `ui_intent` 把前端直接送到:
- `job_events`
- `rollout_jobs`
这样 recommendation 才真正具备“先聚焦异常活动,再决定是否补动作”的现场驾驶能力。
再继续往下一层,控制面还需要统一的“最近活动入口”:
- 不只是 playbook run
- 也包括 standalone ops jobs
- 以及 rollout 推进记录
所以活动流 contract 也应该成为正式后端协议:
- `GET /api/v1/ops/activity-stream`
- 每条 activity 带:
- `kind`
- `activity_key`
- `status`
- `status_label`
- `occurred_at`
- `summary`
- `summary_text`
- `target_node_codes`
- `ui_intent`
- `focus_ref`
其中 `ui_intent` 不是让前端自己猜跳哪,而是由后端直接声明:
- `playbook_run_detail`
- `job_events`
- `rollout_jobs`
这样后端不仅能告诉前端“这里有一条异常活动”,还能直接告诉它“应该落到哪一个处理界面”。
这样海外控制面、后台按钮和 Codex 驾驶员才能共享一套“最近活动 -> 正确落点”的跳转 contract。
并且这层不要再停在“页面展示友好字段”,而要正式成为统一驾驶对象:
- `status_label`
- 直接给页面、CLI、Codex 看的人类可读状态
- `summary_text`
- 直接给驾驶建议和摘要面板消费的短摘要
- `target_node_codes`
- 不让调用方再从自然语言里猜目标节点
- `focus_ref`
- 让页面、CLI、Codex 都能用同一份定位对象跳到:
- `playbook run`
- `ops job`
- `rollout`
- `execution scene`
这样后续不管是海外 Codex 驾驶员、CLI 还是后台按钮,都能把 activity 当成正式 contract而不是“页面 table 的一行数据”。
同样,`activity-stream` 里的 `ops_job` 项也要冻结一条优先级规则:
- 如果 job 本身已经带 `result.summary_text`
- activity 的 `summary_text` 优先使用它
- 如果 job result 里已经带 `focus_ref`
- activity 的 `focus_ref` 优先吸收它
- 只有在没有明确执行结果摘要时
- 才回退到“目标节点 / 发起人 / 执行方式”这类模板化描述
原因很简单:
- 模板摘要适合“任务刚创建”
- 结果摘要才适合“任务已经执行过,且远端已经给出结论”
否则页面上会长期出现这种低信噪比口径:
- `目标节点 xxx / 发起 web-ui / 方式 远端 Agent`
但真正更有价值的应该是:
- `远端 Agent 已回传 120 行 Worker 日志,可直接继续分析`
这一点对海外 Codex 驾驶员尤其关键,因为它读取 activity-feed 时,优先需要的是“执行结论”,而不是“模板元信息”。
这条规则继续往下也要落到 Codex brief
- `codex-brief.entries`
- 仍然是可执行主线
- `codex-brief.activity_focus`
- 则是可观察焦点
- `codex-brief.scene_log_observation`
- 则是节点级现场日志观察摘要
- 如果当前没有动作主线,但已有观察焦点
- `codex-brief.focus` 允许回退到首条 `activity_focus`
- 但推荐行为只能是 `open_ui`
- 不允许伪装成 `auto_execute`
- 如果当前没有动作主线、也没有合适的 activity focus但现场日志已经识别出明确目标节点
- `codex-brief.focus` 允许继续回退到 `scene_log_observation.focus_ref`
- 这样海外 Codex 会直接落到 `node_scene_log`
这样海外 Codex 在“现场正在变化,但暂时没有标准动作要打”的时候,仍然能稳定给出:
- 先盯哪一条
- 回到哪个正式落点
- 为什么现在是观察优先而不是执行优先
接下来 driver-feed / runbook 这一层也必须正式冻结:
- `driver_feed.entries[*]`
- `focus_ref`
- `primary_focus_ref`
- `secondary_focus_ref`
- `runbook_sequences[*]`
- `focus_ref`
- `primary_focus_ref`
- `secondary_focus_ref`
也就是说:
- driver-feed 不再只返回“动作码 + 按钮文案”
- runbook 也不再只返回“主次动作”
- 而是必须同时告诉页面、CLI、Codex
- 这条主线整体应该聚焦哪里
- 主动作对应哪个正式落点
- 次动作对应哪个正式落点
这样后端才能真正消灭“前端根据 action_code 猜页面落点”的分叉逻辑。
这一层现在还要继续补一条非常实用的分流规则:
- `driver-feed.entries`
- 仍然表示“驾驶动作主线”
- `driver-feed.activity_focus`
- 单独表示“观察与跟踪焦点”
- 不再把观察项硬塞成一条可执行建议
- `driver-feed.scene_log_observation`
- 单独表示“当前远端日志回传是否已经形成可下钻的节点级现场日志”
也就是说以后海外控制面和 Codex 同时会拿到两类对象:
- 一类是可以继续 preview / resolve / execute 的动作主线
- 一类是当前值得优先盯住的现场对象
- 例如:
- 正在跑的 `ops job`
- 未收口的 `playbook run`
- 当前 `execution scene`
- 远端 `log sync`
这样不会再出现一个老问题:
- 页面把“值得先看”误渲染成“可以直接执行”
- Codex 又把“先看现场”误读成“可以自动发动作”
同样的冻结规则还要继续往下压到驾驶执行链:
- `driver-actions/preview`
- `focus_ref`
- `primary_focus_ref`
- `secondary_focus_ref`
- `primary.focus_ref`
- `secondary.focus_ref`
- `driver-actions/resolve`
- `preview_request.focus_ref`
- `preview_request.primary_focus_ref`
- `preview_request.secondary_focus_ref`
- `gate.focus_ref`
- 顶层 `focus_ref`
- `codex-actions/resolve`
- `selected_entry.focus_ref`
- `preview_request.focus_ref`
- `gate.focus_ref`
- 顶层 `focus_ref`
只有这样页面、CLI、海外 Codex 驾驶员在 preview / resolve / execute 三段链路里,才不会重新猜“当前应该回看哪个 release、哪个 rollout、哪个 execution scene”。这也是后续 NodeAgent、ReleaseHub、OpsCenter 可以共享同一份驾驶协议的关键。
进一步说,`activity-stream` 不只是最近事件列表,还应该是正式可筛选的观察面:
- `kind`
- `status`
- `query`
这些过滤条件必须后端支持,而不是只在前端本地过滤。这样未来:
- 海外 Codex 驾驶员
- CLI 检查脚本
- 后台页面
三者都可以复用同一套活动流收口逻辑。
继续升级之后,`driver action` 的协议也不该停在“一个 action_code”
- 应返回:
- `handled`
- `mode`
- `ui_intent`
- 其中 `ui_intent` 用来表达:
- 应打开哪个详情页
- 应聚焦哪一轮 playbook run
- 应进入哪个发布入口
- 应打开哪个节点接管入口
这一步的价值很大,因为它意味着:
- recommendation 的解释权回到后端
- 前端变成“执行/跳转承载层”
- 后续如果换成海外 Codex 驾驶员,仍然可以直接复用同一份 intent contract
- 对某一轮编排做统一重跑
- 对某一轮编排做统一取消
- 不需要再由前端把同一批子任务逐条循环处理
这正是“海外控制面统一驾驶,大陆节点被托管执行”的关键基础。
同理execution scene contract 也不该只是“参与节点数组 + 待命节点数组”,而要继续收成更明确的运行口径:
- `dispatch_active_nodes`
- `recent_only_nodes`
- `standby_nodes`
- `load_syncing_nodes`
以及日志回传本身的正式状态:
- `disabled`
- `waiting_sample`
- `partial_coverage`
- `healthy`
- `full_capture`
并且要直接返回:
- 覆盖了多少参与节点
- 哪些参与节点还没回传样本
- 当前推荐动作文案
这样页面、Codex 驾驶员和后续自动化脚本都能共享同一套“现场观察 -> 判断缺口 -> 执行动作”的协议,而不是各自再做一次前端猜测。
这样未来新增:
- Codex 自动巡检
- 后台按钮巡检
- 节点接管后的首轮验收
- 发布后的批次验收
都能直接复用同一个 inspection contract。
在收口视图之上,还应该继续有“异常节点优先队列”:
- 先处理失败 / 阻断 / 取消
- 再处理仍在执行中的节点
- 再处理尚未形成完整巡检记录的节点
另外标准巡检里的 `logs.collect` 应明确限定为 Worker 日志收口,不应把 Node Agent 的辅助排障日志混成同一类结果。
当前第一版 agent 执行器已经覆盖:
- `service.start`
- `service.stop`
- `service.restart`
- `service.status`
- `health.snapshot`
- `logs.collect`
- `diagnostics.collect`
- `deploy.release`
后续继续增强时,只需要在这条主链路上加能力,不需要重换架构。
---
## 三、Agent 标准执行流程
### 1. 签发 token
控制面给节点签发一枚 agent token。
### 2. 节点注册
节点启动后调用:
- `register`
上报:
- `node_code`
- `region`
- `role`
- `agent_version`
- `capabilities`
- `labels`
- `hostname`
- `ip`
### 3. 节点心跳
节点周期调用:
- `heartbeat`
控制面更新:
- `last_seen_at`
- agent 元数据
- 节点在线状态依据
### 4. 节点拉任务
节点调用:
- `pull`
控制面返回分配给该节点的 `ops job`
### 5. 节点开工
节点拿到 job 后先调用:
- `jobs/{id}/start`
这一步不是可有可无,而是为了把“任务已派发”和“任务已真实开始执行”区分开。
### 6. 节点完工
执行结束后调用:
- `jobs/{id}/complete`
上报:
- `success / failed / partially_succeeded`
- `stdout`
- `stderr`
- `result json`
### 7. 节点事件流
执行过程中可持续调用:
- `jobs/{id}/events`
把中间事件推回控制面。
---
## 四、为什么要有 Release Hub
因为正式环境不应该继续依赖:
- 节点自己 `git pull`
- 工作区状态不确定
- root/www 用户混跑
正式版必须把“版本”变成平台中的一级对象。
也就是:
> 先有 Release再有 Deploy
---
## 五、当前已经落下的 Release Hub 骨架
当前后端已经补出:
- `POST /api/v1/ops/releases`
- `GET /api/v1/ops/releases`
- `GET /api/v1/ops/releases/latest`
- `GET /api/v1/ops/releases/{id}`
- `POST /api/v1/ops/releases/{id}/activate`
- `POST /api/v1/ops/releases/{id}/rollouts`
- `GET /api/v1/ops/rollouts/{id}`
- `GET /api/v1/ops/rollouts/{id}/jobs`
- `POST /api/v1/ops/rollouts/{id}/advance`
并落了两类核心对象:
- `ops_releases`
- `ops_release_rollouts`
这意味着后面发布系统可以正式围绕:
- 版本号
- 渠道
- commit sha
- artifact url
- checksum
- 激活版本
来运作。
但是 Release Hub 还不能只回答“有没有版本”,还要回答:
> 现在这版到底能不能发
因此控制面里还需要一个正式门禁对象:
- `release_hub.default_rollout_gate`
它至少要返回:
- 默认 Release
- 默认 Rollout 目标节点
- 门禁状态:
- `missing_release`
- `release_not_ready`
- `artifact_missing`
- `no_targets`
- `blocked`
- `attention`
- `ready`
- 阻断原因
- 告警原因
- 推荐动作
- `remote_agent_ready_nodes / nodes_total`
- `inspection_healthy_nodes / nodes_total`
这样首页驾驶建议、Release Hub 顶部摘要、Codex 自动驾驶、真正发 Rollout 前的 preview才能统一读同一份门禁判断。
---
## 六、现在这版 rollout 已经不只是“建记录”
当前 rollout 已经补上三类关键能力:
- 分批推进
- rollout 与 ops job 关联
- 执行结果自动反推 rollout 状态
也就是说,控制面不再是一次性给所有节点平铺发任务,而是可以表达:
- 第一批先发 `1` 台 canary
- 健康后再发后续批次
- 某批失败则暂停
- 人工确认后继续下一批
当前 rollout 状态会根据 job 结果自动进入:
- `planned`
- `running`
- `awaiting_approval`
- `ready_for_next_batch`
- `halted`
- `completed`
- `completed_with_issues`
这已经非常接近正式发布系统的骨架,而不是临时脚本。
---
## 七、终局版发布链路
推荐的正式发布链路:
1. 海外控制面构建 release
2. 写入 `ops_releases`
3. 标记某个 channel 的 active release
4. 后台点击“部署 release”
5. 生成 `ops job`
6. node-agent 拉取任务
7. 下载 artifact
8. 校验 checksum
9. 切换版本
10. 重启服务
11. 健康检查
12. 成功或回滚
当前后端也已经补出:
- Release 对象
- Release 激活
- Rollout 对象
- `release -> rollout -> deploy jobs` 分批生成
- rollout 推进接口
- 节点执行结果回写后自动刷新 rollout 总结
这意味着后续真正接发布包仓库时,只需要把 artifact 构建和分发接入,不需要再重新设计控制面对象模型。
---
## 八、Node Agent 侧的发布保险丝
当前 `deploy.release` 执行链路已经补上:
- 下载 artifact
- checksum 校验
- 解压到 `releases/<version>`
- 切换 `current` 软链
- 重启指定服务
- 执行健康检查
- 健康失败时回滚到旧版本
健康检查目前支持:
- `systemctl is-active`
- HTTP URL 探测
并且 agent 会把关键事件回推控制面,例如:
- `deploy_download_started`
- `deploy_checksum_verified`
- `deploy_current_switched`
- `deploy_health_failed`
- `deploy_rollback_completed`
这套事件流很重要,因为后面后台和 Codex 都是靠这条链路看懂“节点到底做到了哪一步”。
所以事件 contract 也要继续冻结,而不能只返回一行原始 message
- 每条 job event 至少应带:
- `event_key`
- `event_type`
- `level`
- `level_label`
- `summary`
- `occurred_at`
- `ui_intent`
- 任务事件接口本身还要有 `summary`
- `returned_total`
- `level_counts`
- `event_type_counts`
- `node_counts`
- `latest_at`
这样海外控制面打开“任务事件流”时,先看到的是结构化执行回放,而不是先读一大串原始日志文本。
---
## 九、为什么这套设计后续最省心
因为它把三件事情分清楚了:
- `release` 是版本对象
- `ops job` 是动作对象
- `node agent` 是执行对象
后面不管节点从 `3` 台增加到 `30` 台,还是 Codex 接管更多动作,这三层都不用推翻。
---
## 十、接管不是终点,要有“验收后直接进入正式运维”的桥
实际落地里最容易断掉的一环,不是 token也不是 heartbeat而是
> 节点明明已经接进来了,但接下来该做什么,操作员还得自己切页面、自己填 node_code、自己判断先巡检还是先发布。
这会把接管成功后的效率重新打回人工模式。
所以控制面里要把 Node Agent 接管链路拆成三段:
### 1. 接入观察
用于回答:
- token 是否有效
- node agent 是否已经 register
- heartbeat 是否稳定
- 当前是 `未纳管 / 待接入 / 已接管 / 心跳过期`
### 2. 标准验收
接管完成后,不应该直接默认“节点已经可用”,而是先跑一轮固定验收动作:
1. `health.snapshot`
2. `service.status(domaincheck-node-agent)`
3. `logs.collect(domaincheck-node-agent)`
4. `service.status(domaincheck-worker)`
5. `logs.collect(domaincheck-worker)`
这轮验收的意义不是“多做几步”,而是把最常见的接管隐患一次性暴露出来:
- 服务虽然启动了,但 register 没成功
- heartbeat 有,但 pull / complete 异常
- worker 在线,但 Redis / DB / proxy 配置不对
- systemd 看似 active但日志里已经反复报错
### 3. 正式运维入口
如果标准验收通过,控制面不能只显示一个“已通过”标签,而要直接给出两类后续入口:
- `进入巡检模式`
默认锁定当前节点,直接预填 `diagnostics.collect`
- `进入发布模式`
默认锁定当前节点,直接预填目标 release 和单节点 rollout
这样节点接管就形成了完整闭环:
> 看见节点 -> 纳管 -> 签发接入方案 -> 接入观察 -> 标准验收 -> 巡检 / 发布
而不是“接进来之后又回到人工记忆和手填”。
当前前端已经按这个思路落了一版:
- 接管弹窗里增加“接管验收结论”
- 可一键跑标准验收
- 验收通过后,直接出现:
- `进入巡检模式`
- `进入发布模式`
- 节点会自动锁定为当前接管节点
- 发布会默认带上当前选中的 release没有 release 时直接提示先创建
并且 OpsCenter 现在不再只是“有按钮”,而是开始把 contract 直接展示到关键弹窗里:
- `生成 Node Agent 接入方案`
- 明确显示当前使用 `ops_agent_protocol`
- 展示主入口、schema 文档、发现入口、执行链路
- `创建 Release`
- 明确显示当前使用 `release_hub_contract`
- 展示 release 主入口、launchpad、schema 文档、发布链路
- `创建 Rollout`
- 明确显示当前使用 `release_hub_contract`
- 展示 rollout 主入口、launchpad、execution_mode、执行链路
- `驾驶建议 / 驾驶主线 / 自动驾驶判断`
- 可直接打开“驾驶动作契约预览”
- 展示 `ops_driver_contract`、目标 API、执行链路、request preview
- `overview / driver-feed` 已下沉到后端 `driver-actions/resolve` / `driver-actions/execute-resolved`
- `driver-actions/preview` 保留为基础契约预览与兜底入口
- `codex-focus-preview` / `codex-focus-run` 进一步下沉到后端 `codex-actions/resolve` / `codex-actions/execute`
- 把“这次点击到底会调用什么”从黑盒按钮变成可审阅对象
命令行侧也开始走同一条 contract 预览链:
- `bash domain-api/deploy/multi-region/drive_ops_center.sh contracts`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feed`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-preview`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-run`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-preview ...`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-resolve ...`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-run ...`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-preview ...`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh runbook-run ...`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-preview`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-run`
这样海外主机上即使不打开页面,也能先看到统一 contract再决定要不要执行。
这里还要再收一条非常重要的约束:
- `codex-focus-preview / codex-focus-run`
- 不能只认 `codex-brief.entries`
- 当当前没有动作主线、但存在 `codex-brief.activity_focus`
- 应允许选中观察焦点
- 并统一收口为 `focus_activity_item`
- 其 gate 必须落到 `open_ui`
- 不能伪装成 `auto_execute`
并且这条命令链现在已经补上了真正面向“单脑驾驶”的默认入口:
- `driver-feed`
- 看当前驾驶主线
- `driver-focus-preview`
- 默认消费 `driver-feed.top_recommendation`
- 没传 `entry_key` 时,不再要求人工先抄 `action_code`
- 如果显式传入的是 `activity_focus.key`
- 应自动收口为 `focus_activity_item`
- 只做“进入工作区”的观察焦点,不升级成自动执行
- `driver-focus-run`
- 先按同一条 focus 生成 `resolve / execute-resolved` 请求
- 自动兼容:
- 普通 `driver_action`
- `runbook_sequence`
- `secondary`
- `confirm`
这样后续海外 Codex 驾驶员拿到的就不是“很多离散脚本”,而是一条稳定的动作主线:
1.`driver-feed`
2.`top_recommendation`
3.`preview`
4. 再按门禁 `run`
并且 `codex-focus-run` 已经开始真正落地“驾驶门禁”:
- `safe_auto`
- 直接执行
- `guarded_auto`
- 默认不执行,只输出审阅结果
- 只有显式 `confirm` 才放行
- `resolve_first / open_ui / blocked`
- 一律不执行,只给出原因和目标入口
现在这层门禁已经不再由 CLI 本地解释,而是由后端统一返回:
- `selected_entry`
- `preview`
- `gate`
- `execution_result`
这样后续海外 Codex 驾驶员就不是“会调接口的脚本”,而是严格受同一份后端 contract 和门禁规则约束。
这样后续不管是人工点击、CLI 脚本调用,还是海外 Codex 驾驶员接管,都不会再出现“页面这样理解、后端那样理解、节点执行又是第三套”的口径漂移。
Release Hub 自己也要走同一套“标准动作对象”思路,而不是继续靠页面里临时拼表单。
当前已经开始往这条线上收:
- `发节点任务` 不再只是一个通用入口,而是正式区分:
- `deploy.release.control`
- `deploy.release.worker`
- `deploy.release.custom`
- 同时命令行侧的 `check_release_hub.sh` 也会直接输出:
- release 模板摘要
- 控制面批量预检
- Worker 批量预检
- 混合节点预检
这样页面按钮、海外 Codex 驾驶员、CLI 自检脚本三者看到的就是同一套发布口径,而不是三套不同的发布逻辑。
这一步非常关键,因为它把:
- Node Agent 接管
- Ops Job
- Release Hub
第一次真正接成了一条连续工作流。
---
## 十一、正式协议收口原则
为了让后续开发不再反复改口径,这套体系必须先明确 6 条协议原则:
### 1. 前后端与 Codex 共用同一份 contract
同一个动作、同一个节点、同一轮 rollout必须由后端返回统一结构
- 页面直接展示
- Codex 直接判断
- CLI 直接检查
不能让三边各自“再推导一次”。
### 2. 所有远控动作都要先变成对象
最终要统一成:
- `release`
- `rollout`
- `ops job`
- `playbook run`
- `activity`
而不是:
- 页面点按钮直接跑 shell
- Codex 临时拼命令
- 节点本地脚本自己决定做什么
### 3. Node Agent 只执行已声明动作
Agent 不应该接受任意 shell 文本,而应该只接受标准动作:
- `service.start`
- `service.stop`
- `service.restart`
- `service.status`
- `health.snapshot`
- `logs.collect`
- `diagnostics.collect`
- `deploy.release`
这样控制面才能做审计、门禁、批量预检和回滚。
### 4. 所有对象都必须能审计
至少都要带:
- `created_at`
- `updated_at`
- `requested_by`
- `target_node_code / target_nodes`
- `status`
- `result`
后面不管是人、后台还是 Codex 都要能回放过程。
### 5. 协议必须支持“逐步增强”
当前已经落下的是第一版骨架,所以协议要区分:
- `当前已实现字段`
- `后续建议补充字段`
这样可以先稳定主链路,再逐步增强,而不是一开始就推翻。
### 6. API 返回必须统一包裹
当前所有接口都已经走:
```json
{
"code": 0,
"message": "ok",
"data": {}
}
```
所以后面所有新增接口也应继续遵守:
- `code=0` 表示成功
- `code=1` 表示业务失败
- 具体错误语义继续放进 `message` 和后续建议补的 `detail_code`
---
## 十二、Node Agent HTTP 协议
### 1. 公共约束
#### 请求头
Agent 相关接口统一使用:
- `Content-Type: application/json`
- `X-Domaincheck-Agent-Token: <token>`
其中:
- `tokens`
- `bootstrap-plan`
是控制面主动签发,不要求 agent token。
而以下接口要求带 token
- `register`
- `heartbeat`
- `pull`
- `jobs/{id}/start`
- `jobs/{id}/complete`
- `jobs/{id}/events`
#### 公共节点载荷
当前代码里的 `_base_payload()` 已经收敛出最小节点信息:
- `node_code`
- `region`
- `role`
- `title`
- `hostname`
- `ip`
- `agent_version`
- `capabilities`
- `labels`
- `metadata.service_names`
- `metadata.delivery_queue`
这意味着后续页面、Codex 和 agent 日志里看到的节点身份,应该都围绕这组字段统一。
其中 `metadata.delivery_queue` 当前已经是正式 contract而不是预留字段。它至少会带
- `state`
- `label`
- `reason`
- `pending_count`
- `dead_letter_count`
- `last_flush_at`
- `oldest_pending_at`
- `oldest_dead_letter_at`
也就是说heartbeat 不只是在汇报“Agent 还在线”,也在汇报:
> 当前本地是否有待补发回执、是否已经出现死信、最近一次补发冲刷是否成功
---
### 2. 签发 Agent Token
`POST /api/v1/ops/agent/tokens`
#### 请求体
```json
{
"node_code": "mainland-worker-02",
"issued_by": "web-ui",
"expires_in_hours": 72,
"metadata": {
"source": "ops-center"
}
}
```
#### 当前返回字段
- `token`
- `token_preview`
- `record_id`
- `node_code`
- `expires_at`
- `created_at`
#### 设计要求
这一步只做“签发”,不做安装、不做纳管、不做启动。
也就是说:
- token 是凭证对象
- bootstrap plan 才是接入方案对象
---
### 3. 生成 Bootstrap Plan
`POST /api/v1/ops/agent/bootstrap-plan`
#### 请求体
```json
{
"node_code": "mainland-worker-02",
"node_region": "mainland",
"node_role": "worker",
"issued_by": "web-ui",
"expires_in_hours": 72,
"control_plane_base_url": "https://ops.example.com",
"root_dir": "/opt/domaincheck",
"metadata": {
"source": "ops-center"
}
}
```
#### 当前返回字段
当前后端 `build_node_agent_bootstrap_plan()` 已返回:
- token 相关字段
- `control_plane_base_url`
- `bootstrap_plan.node_code`
- `bootstrap_plan.node_region`
- `bootstrap_plan.node_role`
- `bootstrap_plan.root_dir`
- `bootstrap_plan.env_file`
- `bootstrap_plan.service_name`
- `bootstrap_plan.service_file`
- `bootstrap_plan.install_script_path`
- `bootstrap_plan.env_content`
- `bootstrap_plan.command_lines`
- `bootstrap_plan.command_block`
- `bootstrap_plan.health_checks`
- `bootstrap_plan.health_check_block`
- `bootstrap_plan.bootstrap_script_name`
- `bootstrap_plan.bootstrap_script_path`
- `bootstrap_plan.bootstrap_script_content`
- `bootstrap_plan.bootstrap_run_script_block`
#### 正式定位
`bootstrap_plan` 不只是“给一段命令”,它是:
> 控制面签发给目标节点的一份标准接管方案
后续:
- 页面按钮
- Codex 驾驶员
- CLI 辅助脚本
都应该复用这一个对象,不再自己拼接 env 或 shell。
---
### 4. Agent Register
`POST /api/v1/ops/agent/register`
#### 请求体
```json
{
"node_code": "mainland-worker-02",
"region": "mainland",
"role": "worker",
"title": "mainland-worker-02",
"hostname": "host-a",
"ip": "10.0.0.12",
"agent_version": "0.1.0",
"capabilities": [
"service.start",
"service.stop",
"service.restart",
"service.status",
"health.snapshot",
"logs.collect",
"diagnostics.collect",
"deploy.release"
],
"labels": {},
"metadata": {
"service_names": {
"api": "domaincheck-api",
"worker": "domaincheck-worker",
"sync_agent": "domaincheck-sync-agent",
"node_agent": "domaincheck-node-agent"
}
}
}
```
#### 当前返回字段
- `node_code`
- `expires_at`
- `capabilities`
#### 后端当前行为
当前 `agent_register()` 会:
- 校验 token 与 `node_code`
-`_upsert_agent_runtime()`
- 更新 `ops_managed_nodes`
所以 register 的实际语义是:
> 让节点正式进入“已接管候选 / 已纳管”目录
---
### 5. Agent Heartbeat
`POST /api/v1/ops/agent/heartbeat`
#### 请求体
当前与 register 共用同一份最小节点载荷。
#### 当前返回字段
- `node_code`
- `server_time`
- `expires_at`
#### 协议语义
heartbeat 不是简单“在线 ping”而是
- 刷新 last seen
- 刷新节点身份与 service_names
- 维持控制面里 Node Agent 在线态
- 同步回执队列现场快照
#### 当前已落地的队列快照字段
当前 heartbeat 已经会通过 `metadata.delivery_queue` 上报:
- `state`
- `healthy`
- `retrying`
- `dead_letter`
- `pending_count`
- `dead_letter_count`
- `last_flush_at`
- `oldest_pending_at`
- `oldest_pending_request_id`
- `oldest_pending_kind`
- `oldest_dead_letter_at`
- `oldest_dead_letter_request_id`
- `oldest_dead_letter_kind`
#### 后续建议补充字段
后续建议 heartbeat 再扩:
- `active_jobs`
- `disk_free_mb`
- `load_average`
- `python_version`
其中 `local_queue_depth` 这一类信息,当前已经由 `delivery_queue` 快照承接,不再是空白能力。
---
### 6. Agent Pull
`POST /api/v1/ops/agent/pull?limit=1`
#### 请求体
```json
{
"node_code": "mainland-worker-02"
}
```
#### 当前后端行为
当前 `agent_pull_jobs()` 只派发:
- `target_node_code = 当前节点`
- `status = queued`
- `execution_mode = remote-agent`
并在派发时自动改成:
- `ops_jobs.status = dispatching`
- `ops_job_steps.status = dispatching`
然后写入事件:
- `agent_dispatched`
#### 当前返回字段
```json
{
"jobs": [
{
"id": 123,
"job_code": "ops-xxx",
"action": "health.snapshot",
"target_node_code": "mainland-worker-02",
"status": "dispatching",
"execution_mode": "remote-agent",
"payload": {},
"metadata": {},
"policy": {},
"steps": []
}
],
"count": 1
}
```
#### 正式约束
后续就算扩到批量拉取,也应继续遵守:
- agent 只能拿到属于自己的 job
- agent 不负责选择 job
- 调度权始终在控制面
---
### 7. 标记任务开始
`POST /api/v1/ops/agent/jobs/{job_id}/start`
#### 请求体
```json
{
"node_code": "mainland-worker-02"
}
```
#### 当前后端行为
当前 `agent_mark_job_started()` 会:
-`ops_jobs.status` 改成 `running`
- 把同 job 的 steps 改成 `running`
- 写入 `agent_started`
#### 设计要求
这一步必须保留,不能省略。
因为:
- `dispatching` 表示控制面已派发
- `running` 表示节点已真实开始执行
这两者对排障非常重要。
---
### 8. 标记任务完成
`POST /api/v1/ops/agent/jobs/{job_id}/complete`
#### 请求体
```json
{
"node_code": "mainland-worker-02",
"client_request_id": "complete-ops-123-1",
"status": "success",
"stdout": "",
"stderr": "",
"result": {
"summary": "ok"
},
"error_message": ""
}
```
#### 当前允许状态
- `success`
- `failed`
- `partially_succeeded`
#### 当前后端行为
`agent_complete_job()` 会:
- 校验 job 属于该节点
- 基于 `client_request_id` 做完成回执幂等
- 更新 `ops_jobs.status`
- 写入 `result_json`
- 回写 `error_message`
- 更新全部 `ops_job_steps`
- 写入 `agent_completed`
- 如果 job 属于 rollout自动刷新 rollout 汇总
#### 正式约束
节点只负责回写:
- 结构化结果
- stdout/stderr
- 失败原因
不负责自己修改 rollout 策略、release 状态或下个批次。
这些都必须仍由控制面决定。
#### 当前已落地的幂等键
当前 `complete` 已支持:
- `client_request_id`
控制面会把它落到:
- `ops_jobs.last_agent_complete_request_id`
因此同一个完成回执即使因为网络抖动被重复补发,也不会再次制造副作用。
---
### 9. 上报中间事件
`POST /api/v1/ops/agent/jobs/{job_id}/events`
#### 请求体
```json
{
"node_code": "mainland-worker-02",
"client_event_id": "event-ops-123-checksum-1",
"step_id": 0,
"event_type": "deploy_checksum_verified",
"level": "info",
"message": "checksum ok",
"payload": {
"checksum": "sha256:..."
}
}
```
#### 当前后端行为
`agent_append_job_event()` 会:
- 校验 token 和 node_code
- 基于 `client_event_id` 做事件幂等
- 追加结构化事件
- 返回 `event_id`
#### 正式要求
中间事件不能只传纯文本,至少还要带:
- `event_type`
- `level`
- `message`
- `payload`
因为后面:
- 页面活动流
- rollout 详情
- playbook run events
- Codex 驾驶员
都依赖结构化事件,而不是只靠日志文本猜。
#### 当前已落地的事件去重约束
当前事件表已经有:
- `ops_job_events.client_event_id`
- `uq_ops_job_events_job_client_event`
所以同一个事件即使被 Agent 重放,也应继续表现为:
- 控制面接收成功
- 不重复制造第二条事件
---
## 十三、Node Agent 状态机
### 1. Token 状态机
- `issued`
- `disabled`
- `expired`
当前代码里 `ops_node_tokens` 已具备:
- `is_enabled`
- `expires_at`
- `last_used_at`
所以 token 生命周期应该正式解释成:
- `issued + enabled + 未过期`:可用
- `issued + disabled`:不可用
- `issued + 已过期`:不可用
---
### 2. 节点接管状态机
页面与控制面应该统一理解为:
- `未纳管`
- `待接入`
- `已接管`
- `仅运行态在线`
- `心跳过期`
- `token 过期 / 已禁用`
建议正式映射为:
- `discovered`
- `pending_bootstrap`
- `agent_online`
- `runtime_only`
- `stale`
- `token_invalid`
前端显示名可以中文化,但后端内部最好固定英文状态码。
---
### 3. Job 状态机
当前主链路已经形成:
- `queued`
- `dispatching`
- `running`
- `success`
- `failed`
- `partially_succeeded`
正式语义应固定为:
- `queued`:已创建,尚未派发
- `dispatching`:控制面已派发给节点,但节点尚未确认开工
- `running`:节点已开始执行
- `success`:完成且成功
- `failed`:完成且失败
- `partially_succeeded`:部分成功,需要人工关注
这套状态机后面不能再随意改名否则活动流、巡检收口、rollout 汇总都会被打散。
---
## 十四、`ops job` 的 Agent 执行载荷标准
这部分后续建议直接以 [ops_job_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_job_contract.md:1) 为任务对象母本,再由 [ops_agent_protocol.md](/www/wwwroot/getDomain/docs/schemas/ops_agent_protocol.md:1) 继续冻结 Agent 视角下的拉取、开工、完工与事件回执协议。
### 1. Agent 可见 job 最小字段
Agent 从 `pull` 接口拿到 job 时,最少应该依赖这些字段:
- `id`
- `job_code`
- `action`
- `target_node_code`
- `execution_mode`
- `payload`
- `metadata`
- `policy`
其他字段如:
- `requested_by`
- `risk_level`
- `approval_status`
- `rollout_id`
虽然不是最小执行必需,但建议一起保留,便于未来 agent 诊断与本地审计。
### 2. 结构化动作必须优先
当前 agent 已按动作名分发:
- `supports_structured_action(action)`
- `execute_structured_action(...)`
- `execute_release_action(...)`
这条设计必须坚持:
- 先结构化动作
- 再结构化 payload
- 最后才在执行器内部落成本地命令
不能反过来回到“控制面下发整段 shell”。
### 3. `deploy.release` 的 payload 基线
当前 release 执行器已经支持的正式意图至少包括:
- artifact 下载
- checksum 校验
- 解压到 `releases/<version>`
- 切换 `current`
- 重启服务
- 健康检查
- 失败回滚
因此 `deploy.release` 的 payload 应长期固定围绕:
- `release_version`
- `artifact_url`
- `checksum`
- `restart_services`
- `health_check_urls`
- `health_check_timeout_seconds`
- `health_check_retries`
- `health_check_interval_seconds`
- `rollback_on_failure`
- `switch_current`
这套字段已经足够支撑正式 release 部署。
### 4. `logs.collect` 的口径
标准巡检里的 `logs.collect` 应优先解释为:
> 收集目标服务日志,而不是执行任意日志命令
正式建议字段:
- `service_name`
- `lines`
- `include_agent_logs`
- `since_seconds`
并且默认仍要优先收口:
- `domaincheck-worker`
- `domaincheck-node-agent`
而不是允许页面任意填系统命令。
---
## 十五、Release Hub 正式契约
### 1. Release 对象
当前 `ops_releases` 已具备:
- `release_version`
- `channel`
- `commit_sha`
- `status`
- `artifact_url`
- `checksum`
- `notes`
- `metadata`
- `created_by`
- `created_at`
- `activated_at`
现在正式 contract 还需要进一步固定这些别名字段:
- `status_label`
- `summary`
- `summary_text`
- `focus_ref`
正式上应把 Release 理解为:
> 一份不可变版本记录
也就是说:
- release 创建后,不应该允许直接把它重新指向另一份 artifact
- 同一 `release_version` 只对应一份发布物
### 2. Rollout 对象
当前 `ops_release_rollouts` 已具备:
- `release_id`
- `rollout_code`
- `target_selector`
- `target_nodes`
- `policy`
- `status`
- `batch_cursor`
- `batches_total`
- `jobs_total`
- `jobs_created`
- `result_summary`
而 rollout 这一层也不该再只返回“原始推进计数”,还应稳定返回:
- `status_label`
- `target_node_codes`
- `summary`
- `summary_text`
- `focus_ref`
所以 rollout 的正式定义应为:
> 某个 release 在某批目标节点上的一次分批推进计划
### 3. `default_rollout_gate`
这层是 Release Hub 最关键的后端 contract必须作为正式对象保留。
至少应返回:
- `status`
- `status_label`
- `status_type`
- `summary`
- `summary_text`
- `release`
- `execution_mode`
- `execution_mode_label`
- `target_node_codes`
- `default_target_node_codes`
- `blocking_reasons`
- `warning_reasons`
- `recommendations`
- `operational_readiness.summary`
- `operational_readiness.rows`
- `focus_ref`
#### 推荐状态码
- `missing_release`
- `release_not_ready`
- `artifact_missing`
- `no_targets`
- `blocked`
- `attention`
- `ready`
这组状态码后面必须同时服务:
- 首页驾驶建议
- Release 页面门禁卡
- Rollout 预检
- Codex 自动驾驶
### 4. `release_launchpad`
除了默认门禁外,还应保留更偏“驾驶舱”的发布聚合对象:
- `latest_package`
- `latest_release`
- `worker_rollout_preview`
- `control_rollout_preview`
- `launchpad_status`
其中 `launchpad_status` 至少应回答:
- 当前应该先补什么
- 推荐动作码是什么
- 推荐执行方式是什么
- 是先看 Worker 预案还是先看 Control 预案
- 当前是否还存在接入缺口
- 首个缺口节点是谁
- 这个缺口是
- `bootstrap_pending`
- 还是 `acceptance_ready`
并且这层也应该直接给出:
- `summary_text`
- `focus_ref`
- `recommended_target_node_code`
- `recommended_recovery_label`
- `recommended_recovery_summary`
- `onboarding_bootstrap_pending_nodes`
- `onboarding_acceptance_ready_nodes`
其中 `focus_ref` 不该只是“回到 release hub”这么模糊而应该允许继续携带
- `section`
- `mode`
这样页面、CLI、Codex 都不需要自己再推断“该跳回 release hub 的哪个区域”。
这里再固定一个已经收口成代码字段的判断口径:
- 如果 `launchpad_status.recommended_action_code` 已经落在
- `bootstrap_run`
- `run_acceptance`
- 那么页面、CLI、Codex 不应再只显示笼统的“待补执行面”
- 而应直接把以下字段作为主提示:
- `recommended_target_node_code`
- `recommended_recovery_label`
- `recommended_recovery_summary`
- `onboarding_bootstrap_pending_nodes`
- `onboarding_acceptance_ready_nodes`
原因是:
- 真正的阻断并不总是“发布包有问题”
- 很多时候是“发布包已经好了,但节点还没接管完”
- 如果 launchpad 只返回一个大而化之的 `attention`
前端和 Codex 还得再各自遍历 gap rows 去猜
- 这会重新制造多套判断逻辑
所以现在要把 launchpad 的缺口口径固定成:
- `launchpad_status`
直接给驾驶层结论
- `gap_rows`
继续给详情层做下钻
而不是反过来让驾驶层去解析详情层。
当前仓库里这套骨架已经存在,所以后续不要再新造第四套发布摘要模型。
---
## 十六、Rollout 状态机
当前 rollout 状态已经接近正式版,建议固定为:
- `planned`
- `running`
- `awaiting_approval`
- `ready_for_next_batch`
- `halted`
- `completed`
- `completed_with_issues`
### 正式语义
- `planned`:已创建,尚未开始发批次
- `running`:当前批次已发出,正在等待 job 结果
- `awaiting_approval`:下一步推进需要人工确认
- `ready_for_next_batch`:当前批次通过,可进入后续批次
- `halted`:由于失败、阻断或人工暂停而停止
- `completed`:全部批次完成且整体健康
- `completed_with_issues`:全部批次结束,但存在失败或告警
后续不建议再引入新的近义状态名,否则页面和 Codex 会反复适配。
---
## 十七、错误码、幂等与重试规则
### 1. `detail_code` 已经进入 Agent / Ops 主链路
现在统一响应体已经有:
- `code`
- `message`
- `data`
而且 `detail_code` 已经开始进入顶层或 `data` 中,成为控制面和 Agent 都能消费的结构化错误语义。
这层现在的意义不再是“未来建议”,而是:
> 不再靠中文 message 猜失败原因而是让页面、Codex 和 Node Agent 共用同一份错误码
当前主链路里的错误返回,已经会继续补:
- `detail_code`
例如:
- `agent_token_invalid`
- `agent_token_expired`
- `agent_token_node_mismatch`
- `ops_job_not_owned_by_agent`
- `ops_job_invalid_status_transition`
- `release_checksum_mismatch`
- `release_health_check_failed`
这样页面和 Codex 就不用再从中文 message 里反推错误类型。
### 2. 幂等 contract
当前已经正式落地两条关键幂等线:
- `complete`
- 使用 `client_request_id`
- 回写到 `ops_jobs.last_agent_complete_request_id`
- `events`
- 使用 `client_event_id`
- 通过 `uq_ops_job_events_job_client_event` 去重
这意味着 Agent 在遇到控制面瞬断、网络抖动或重试补发时,已经具备:
- 回执不重入
- 事件不重复落库
后续仍建议继续保持幂等的动作有:
以下动作应该尽量幂等:
- `register`
- `heartbeat`
- `bootstrap-plan`
- `activate release`
- `rollout preview`
也就是重复请求最多刷新状态,不应制造重复副作用。
### 3. Agent 重试与本地补发规则
建议正式约束,并且当前主链路已经落下一版:
- `register` 失败:指数退避重试
- `heartbeat` 失败:短退避重试,不立即退出
- `pull` 失败:进入轮询退避
- `start/complete/events` 失败:本地保留待补发队列
其中现在已经正式落地的是:
- `complete`
- `events`
这两类回执在失败时会进入 Agent 本地待补发队列,而不是直接丢失。
### 4. 当前已经落地的本地待补发 / 死信队列
当前 `domain-api/app/node_agent.py` 已经具备:
- 本地 `pending` 队列
- 本地 `dead-letter` 队列
- 定时 `_flush_delivery_queue()`
- 对永久性 `detail_code` 的死信分流
也就是说Agent 当前不是“请求失败就算了”,而是已经形成:
1. 优先即时发送
2. 失败则本地排队
3. 下个 tick 自动重放
4. 如果命中永久性错误,则进入死信
这层能力当前主要覆盖:
- `jobs/{id}/complete`
- `jobs/{id}/events`
### 5. 控制面现在如何看见队列问题
这条链路现在也已经不只存在于 Agent 本地。
当前控制面会把 heartbeat 里的 `metadata.delivery_queue` 收口成:
- 节点行字段
- `delivery_queue_state`
- `delivery_queue_label`
- `delivery_queue_reason`
- `delivery_queue_pending_count`
- `delivery_queue_dead_letter_count`
- 节点汇总字段
- `queue_retrying_nodes`
- `queue_dead_letter_nodes`
- `queue_pending_records`
- `queue_dead_letter_records`
并且驾驶建议已经开始优先提升这类问题:
- 如果存在死信节点,优先建议先处理 Agent 死信
- 如果存在待重试积压,优先建议先观察回执积压
所以这已经不是“隐藏在节点本地目录里的运维细节”,而是正式进入海外控制面的驾驶 contract。
---
## 十八、终局验收标准
当这套体系真正算“上线可用”,至少要满足以下 10 条:
### 1. 新增节点不再需要人工拼命令
控制面能够直接生成:
- token
- env
- bootstrap script
- health check block
### 2. 海外控制面能先看见节点,再决定是否接管
不是只有接入成功后才看见机器。
### 3. 接管完成后可直接跑标准验收
而不是再去手工 systemctl / journalctl。
### 4. 标准巡检统一走 `ops job` / `playbook run`
而不是页面每个按钮都各做一套逻辑。
### 5. 真正参与检测节点和在线未参与节点口径统一
页面、后端、Codex 必须读同一份 execution scene。
### 6. 远端日志回传状态正式结构化
至少统一成:
- `disabled`
- `waiting_sample`
- `partial_coverage`
- `healthy`
- `full_capture`
### 7. 发布必须先有 Release再有 Rollout
不能回退到节点直接 `git pull`
### 8. Rollout 必须能分批、暂停、继续、回滚
而不是一次性把所有节点推平。
### 9. 页面按钮和 Codex 都只创建对象,不直接执行 shell
最终:
- 页面创建 `ops job`
- Codex 创建 `ops job` / `playbook run`
- agent 消费 `ops job`
### 10. 海外主机必须成为唯一运维大脑
大陆机器只负责:
- 执行
- 回传
- 承载业务
不再承担“人工登录后做决定”的角色。
---
## 十九、下一步最值得继续补的 6 件事
基于当前仓库状态,接下来最值得继续补的,不再是“把主链路补出来”,而是把已经成型的体系收成真正可长期运维的平台。
### 1. 给死信队列补正式操作面
当前已经有:
- 本地待补发
- 本地死信
- 控制面可见的队列快照
下一步最该补的是:
- 死信详情查看
- 死信重放
- 死信丢弃
- 死信按 `detail_code` 聚类
否则“看见死信”之后,操作者还是得回到节点本地处理。
### 2. 把 action template preview / batch policy preview 正式后端化
在真正创建 `ops jobs` 之前,要让控制面先统一回答:
- 哪些节点被阻断
- 哪些节点需要审批
- 哪些节点只是 warning
- 当前批次策略是否合理
这样发布、重启、停 Worker 这类高风险动作,才能在创建对象前先收口风险。
### 3. 把接管验收、标准巡检、现场观察彻底收进 playbook run
现在 playbook run 骨架已经有了,而且其中一部分已经开始从“打开弹窗”进入“直接执行正式动作”。下一步要继续把:
- `onboarding.acceptance`
- `inspection.standard`
- `scene.logs.key`
- `scene.logs.full`
都变成真正稳定的编排对象,而不是“部分走编排、部分还停留在前端直触发”。
当前已经落地的直接驾驶动作包括:
- `bootstrap_run`
- `run_acceptance`
- `run_standard_inspection`
- `open_diagnostics`
- `run_scene_logs_key`
- `run_scene_logs_full`
这意味着纳管收口、标准巡检和现场观察都已经开始直接进入正式执行链,而不是先把操作者送去弹窗二次选择。
发布审阅链也应该遵守同样原则:
- `review_smart_rollout_preview`
- `review_control_rollout`
- `fix_rollout_blockers`
这几类动作不应只返回“打开 launchpad 审阅页”的 UI intent而要同时直接返回结构化 `release_launchpad_review` 结果让自动驾驶、CLI 和后端任务能直接消费 launchpad 预案。
并且这里的自动化等级也要彻底锁死:
- 这三类动作属于 `safe_auto`
- 它们的职责是“审阅”和“解释”
- 不是“创建发布对象”
所以默认返回应是:
- 结构化 `release_launchpad_review`
- 可选 `ui_intent = release_launchpad_review`
- 但不直接创建 rollout / publish 记录
同理,`focus_release_hub` 也不应继续只是一个纯导航意图:
- 页面侧仍可消费 `ui_intent = focus_release_hub`
- 但后端同时还应返回 `release_hub_preview`
至少包含:
- `summary`
- `launchpad`
这样海外 Codex、CLI、driver-feed 调用方即使不打开页面,也能继续拿这份摘要决定后续是:
- 继续审阅 launchpad
- 进入发布创建
- 还是先回补门禁缺口
`release_package` 也应遵守同样的“先给后端摘要,再保留 UI 入口”原则:
- 页面仍可通过 `ui_intent = open_release_dialog` 进入打包区
- 但动作返回里还应包含 `release_package_preview`
至少包括:
- `available`
- `package_name`
- `release_version_suggestion`
- `reason`
这样 Node Agent、Release Hub、海外 Codex 驾驶员三边的判断都能先基于同一份“发布包现场状态”,而不是必须打开页面后才能知道有没有包可发。
同理:
- `open_release_dialog`
也不应只是纯导航
应同时返回 `release_dialog_preview`
- `open_rollout_dialog`
也不应只是纯导航
应同时返回 `rollout_dialog_preview`
这样就算暂时仍保留页面入口,自动驾驶层也已经可以先读:
- Release 当前摘要
- Launchpad 当前摘要
- 发布包是否存在
而不是必须依赖前端抽屉作为唯一信息入口。
继续往下一层,模板类动作和发布部署模板动作也应该统一走“预览优先”:
- `open_playbook_dialog`
应返回 `playbook_preview`
- `open_action_template_dialog`
应返回 `template_preview`
- `open_release_deploy_worker / open_release_deploy_control / open_release_deploy_custom`
应返回 `release_deploy_preview`
其中 `release_deploy_preview` 至少应包含:
- `template`
- `summary`
- `launchpad`
- `target_node_codes`
- `execution_mode`
- `release_id`
这样自动驾驶层拿到的就不是“去某个弹窗”,而是一份已经够继续判断的执行预案。
再往前,焦点类动作也不应继续只是“跳到某个详情页”:
- `focus_playbook_run`
应返回 `playbook_run_focus`
- `open_playbook_run_latest_events`
应返回 `playbook_run_events_preview`
- `focus_latest_job_events`
应返回 `job_events_preview`
- `focus_activity_item`
应返回 `activity_focus_preview`
这样 Codex、CLI、OpsCenter 三边在进入具体页面之前,就已经能先读到:
- 当前编排主状态
- 最近事件流
- 当前 job 回执
- 当前 activity 焦点对象
后续“跳到页面”就只是补充查看,而不是唯一信息入口。
这里还要把动作分级口径正式固定下来:
- 只要动作已经满足:
- 后端可直接执行
- 不修改现场状态
- 主要返回 `preview / focus / summary / events`
- 那么它就不应继续归在 `ui_only`
- 而应统一归到 `safe_auto`
当前已经应按这个口径归类的动作包括:
- `handover_first_gap`
- `view_first_gap`
- `open_playbook_dialog`
- `open_action_template_dialog`
- `focus_playbook_run`
- `focus_activity_item`
- `focus_latest_job_events`
- `open_playbook_run_latest_events`
- `open_release_dialog`
- `open_rollout_dialog`
- `open_release_deploy_control`
- `open_release_deploy_worker`
- `open_release_deploy_custom`
- `focus_release_hub`
- `release_package`
也就是说,页面仍然可以消费这些动作的 `ui_intent`,但 Codex / CLI / 自动驾驶网关已经可以把它们当成:
- “可直接执行的只读观察动作”
而不是:
- “必须先打开某个页面才能继续”
而真正进入发布创建层的动作,例如:
- `publish_latest_worker`
- `create_smart_release_rollout_worker`
- `create_smart_release_rollout_control`
- `create_release_rollout_worker`
- `create_release_rollout_control`
则必须继续保持:
- `guarded_auto`
- `confirm_then_execute`
- 正式写入发布与审计链
### 4. 把 Release Artifact 管道做成正式不可变发布链
现在 Release / Rollout 对象已经成型,下一步最关键的是补齐:
- 构建产物打包
- checksum / manifest
- 上传与留存
- channel 激活
- 回滚版本选择
也就是把“版本对象存在”继续推进到“版本供应链完整”。
### 5. 把 SSH Rescue Executor 收成首发接管与紧急救援闭环
终局不是回到 SSH 主运维,但 SSH 仍应有正式位置:
- 首次 bootstrap
- Node Agent 尚未接入时的应急接管
- Agent 故障时的救援执行
这一层应继续做成正式 executor而不是保留为人工口令。
### 6. 把 Node Agent / ReleaseHub / OpsCenter contract 固化为 schema / OpenAPI
现在最怕的已经不是“没有能力”,而是:
> 页面、后端、Codex、CLI 在继续迭代时发生字段漂移
所以后续上线前,建议正式抽出:
- `schemas/ops_agent_protocol.md`
- `schemas/release_hub_contract.md`
- `schemas/ops_driver_contract.md`
- `schemas/ops_playbook_contract.md`
并且不只要有 schema 文件,还要让控制面直接暴露统一索引:
- `GET /api/v1/ops/contracts`
- `GET /api/v1/ops/contracts/{contract_key}`
前者负责列出整张 contract map后者负责按 key 钻取单份协议和相关 contract。
控制面前端与海外 CLI 也要统一走这两层入口:
- 页面默认只拉 registry避免首屏过重
- 当用户点击某个 contract 时,再按 key 拉 detail
- detail 返回里必须带 `related_contract_keys` / `related_contracts`
- 这样排查一条链路时,可以从 `ops_stack_diagnosis_contract` 直接跳到 `ops_driver_contract``release_hub_contract``ops_agent_protocol`
或者直接写入后端 schema / OpenAPI 注释,让 contract 成为真正受版本控制的对象。
---
## 二十、海外 Codex 驾驶员的正式闭环
如果要把“海外主机单脑控制面”真正做成长期稳定体系,海外 Codex 驾驶员不应该被设计成:
- 远端 shell 执行器
- 看日志后临时拼命令的脚本
- 依赖人工不断复制上下文的助手
它应该是严格跑在统一 contract 上的“驾驶层”。
### 1. 默认入口只看 3 类信号
海外 Codex 驾驶员默认只消费:
1. `driver-feed`
2. `codex-brief`
3. `activity-stream`
分别回答:
- 现在最该处理什么
- 这一步能不能自动执行
- 最近执行后现场发生了什么
也就是说,驾驶员不应该先去翻零散日志,再猜下一步,而是先看控制面已经结构化好的主线。
### 2. 默认执行顺序必须固定
标准顺序应固定成:
1. `driver-feed`
-`top_recommendation`
2. `driver-focus-preview`
- 看 contract / gate / request preview
3. `driver-focus-run`
- 只有后端 gate 允许时才执行
4. `activity-stream`
- 回看这一步是否真正落地
5. 必要时钻取:
- `playbook run`
- `ops job`
- `release / rollout`
这样驾驶员并不是“直接开车”,而是:
- 先看路标
- 再看交通规则
- 然后再执行
### 3. 驾驶员永远不直接决定 shell
正式规则必须收死:
- 驾驶员不直接 SSH 到节点上拼 shell
- 驾驶员不自己判断某台机器该不该 `git pull`
- 驾驶员不自己决定 systemd 命令如何组合
- 如果控制面已经把首个缺口节点收敛成 `bootstrap_run / run_acceptance`
- 驾驶员也不能退回成泛化的 `fix_managed_nodes`
- 而应优先围绕同一个节点继续做预览、执行和回执追踪
驾驶员只做三件事:
1. 选择动作对象
2. 请求后端解析门禁
3. 创建正式执行对象
最终执行必须落到:
- `ops job`
- `playbook run`
- `release / rollout`
- `node agent task`
这样才具备:
- 审计
- 回放
- 回滚
- 风险分级
### 4. 驾驶员必须把“看见问题”和“处理问题”拆开
终局方案里最容易犯的错,就是把:
- 看见节点离线
- 打开日志
- 重启服务
- 改配置
- 再观察
全部写成一段混杂的脚本。
正确方式应该拆成两层:
- 观察层
- `driver-feed`
- `activity-stream`
- `inspection`
- `release launchpad`
- 执行层
- `driver-actions/execute-resolved`
- `ops job`
- `playbook run`
- `rollout`
这样后面无论是:
- 人工值班
- 海外 Codex 自动驾驶
- 后台一键按钮
都能共用同一套观察口径与执行口径。
补充一条正式约束:
- `driver-feed`
- 顶层 payload 需要带 `contract_navigation`
- 每条 `entries`
- 每条 `activity_focus`
都要带 `contract_navigation`
- `activity-stream`
- 顶层 payload 需要带 `contract_navigation`
- 每条 `items`
都要带 `contract_navigation`
这样海外单脑控制面里的卡片、时间线、CLI drilldown、Codex 驾驶员才能共享同一条“协议跳转链”,而不是 UI 有一套、CLI 有一套、排障时再临时翻文档。
### 5. 驾驶员需要一套明确的升级路径
真正上线时,建议把海外 Codex 驾驶员分成 4 档能力,而不是一步到位全自动:
#### L1 观察驾驶
- 只看 `driver-feed / codex-brief / activity-stream`
- 只输出建议,不执行
#### L2 门禁驾驶
- 可以跑 `driver-focus-preview`
- 可以对 `safe_auto` 动作执行 `driver-focus-run`
-`guarded_auto` 只提示确认
这里的典型分界现在应明确到动作名,避免后续接入新驾驶员时再自己猜:
- `safe_auto`
- `review_smart_rollout_preview`
- `review_control_rollout`
- `fix_rollout_blockers`
- `run_standard_inspection`
- `run_scene_logs_key`
- `run_scene_logs_full`
- `guarded_auto`
- `publish_latest_worker`
- `create_smart_release_rollout_worker`
- `create_smart_release_rollout_control`
- `create_release_rollout_worker`
- `create_release_rollout_control`
#### L3 编排驾驶
- 可以创建:
- `playbook run`
- `ops job`
- `release / rollout`
- 但仍不直连 shell
#### L4 救援驾驶
- 仅在 Node Agent 失效或接管初期
- 允许走受控 `SSH Rescue Executor`
- 仍然必须把动作写回控制面审计链
这样自动化能力能逐档放开,而不是一开始就把所有权力交给一段脚本。
### 6. 海外主机最终应该成为唯一“决策写入口”
终局不是“海外主机能看到更多日志”,而是:
> 所有决策类动作,都只能从海外控制面写入。
这意味着:
- 大陆节点不再自己 `git pull` 决定版本
- 大陆节点不再靠人工 SSH 决定是否重启
- 大陆节点只负责执行、回传、承载业务
而海外主机负责:
- 产物选择
- 风险判断
- 动作签发
- 回执汇总
- 运行态总览
这才是真正的“单脑控制面”。
### 7. 对你这个项目,最推荐的默认日常路径
如果按最省心、后续最少返工的标准,建议以后日常操作尽量收成下面这条路径:
1. 海外主机打开 OpsCenter
2.`driver-feed.top_recommendation`
3.`driver-focus-preview`
4.`driver-focus-run`
5. 如果动作升级,进入:
- `inspection`
- `playbook run`
- `release hub`
6. 如果执行异常,看:
- `activity-stream`
- 死信队列
- 回执补发状态
7. 只有在 Agent 不可用时,才进入 SSH Rescue
这样后续再新增 1 台大陆机器,不会再变成“重新写一套部署说明”,而只是:
- 新节点 bootstrap
- Node Agent 注册
- 控制面验收
- 进入正式运维
架构不会再被机器数量拖垮。
---
## 二十一、死信队列必须从“可见”升级到“可操作”
当前这套体系已经能看见:
- 哪台节点存在 `dead_letter`
- 有多少条死信
- 最早死信时间是什么
这说明“现场可见性”已经有了。
但如果没有正式操作面,控制面还是会卡在最后一步:
- 看到死信
- 给出建议
- 最后仍然要人工 SSH 去处理
这就违背了“海外单脑控制面”的目标。
所以这层必须继续升级成正式对象:
- 单节点队列总览
- 队列记录列表
- 死信详情
- 单条重放
- 批量重放
- 丢弃
- 主动冲刷
其中第一阶段已落地:
- `GET /api/v1/ops/nodes/{node_code}/delivery-queue`
- `GET /api/v1/ops/nodes/{node_code}/delivery-queue/records`
- `POST /api/v1/ops/nodes/{node_code}/delivery-queue/flush`
- `POST /api/v1/ops/nodes/{node_code}/delivery-queue/replay`
- `POST /api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/replay`
- `POST /api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/discard`
当前这组入口明确采用:
- `record_visibility=head_only`
也就是控制面先稳定看到:
- 节点级队列状态
- `pending / dead_letter` 头部记录
而不是假装已经具备远端全量死信列表与直接操作能力。
也就是说现在已经进入“可操作的第一阶段”:
- 控制面能看节点级队列状态
- 控制面能看 `pending / dead_letter` 头部记录
- 控制面能通过正式 `ops job` 触发冲刷 / 重放 / 丢弃
- 但还没有把远端全量记录目录完整投影成控制面对象
并且这第一阶段现在已经不是“后端接口预留”,而是已经形成了三层统一操作面:
- OpsCenter 托管节点表可直接打开“回执队列治理”抽屉
- driver recommendation 遇到 `dead_letter / retrying` 时,会优先落到标准动作模板
- 海外单入口 CLI 已支持直接查看 / 冲刷 / 重放 / 丢弃
也就是说当前这层已经具备明确的人机协作分工:
- 页面负责看现场、做单条处理、做危险动作确认
- driver / Codex 负责把“当前最该处理什么”压成建议
- CLI 负责在海外主机上做统一批处理和交接执行
具体到当前仓库,已经形成下面这些稳定入口:
### 1. 页面入口
- OpsCenter 托管节点表:
- `队列`
- `立即冲刷`
- `重放死信`
- `单条重放`
- `单条丢弃`
- 丢弃动作强制要求填写原因
- 当前页面仍明确遵守:
- `record_visibility=head_only`
也就是页面不会假装自己能枚举远端完整死信目录,而是只围绕“当前可见头部记录”和“节点级摘要”操作。
### 2. 驾驶建议入口
当前 `driver_recommendations` 已经升级成:
- `dead_letter`
- 主动作:直接执行 `replay_delivery_queue`
- 后端落点:创建 `delivery.queue.replay` 的标准 `ops job batch`
- 次动作:查看 Worker 日志
- `retrying`
- 主动作:直接执行 `flush_delivery_queue`
- 后端落点:创建 `delivery.queue.flush` 的标准 `ops job batch`
- 次动作:查看 Worker 日志
这一步很关键,因为它意味着控制面不再只是说“这里有死信,你自己想办法”,也不再只是把人送去模板弹窗,而是已经能直接走正式 `ops job` 链路下发队列治理动作。
### 3. 海外单入口 CLI
当前 `drive_ops_center.sh` 也已经补上了这组命令:
- `bash domain-api/deploy/multi-region/drive_ops_center.sh queue-status <node_code>`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh queue-records <node_code> [state]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh queue-flush <node_code> [limit] [requested_by]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh queue-replay <node_code> [limit] [requested_by]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh queue-replay-record <node_code> <record_id> [requested_by]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh queue-discard-record <node_code> <record_id> <reason> [requested_by]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh activity-stream [key=value ...]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh activity-preview [activity_key] [requested_by]`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-feed`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-preview`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh driver-focus-run`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh codex-brief`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-preview`
- `bash domain-api/deploy/multi-region/drive_ops_center.sh codex-focus-run`
建议把这组命令视为“海外集中治理 Node Agent 回执队列”的正式入口,而不是临时调试命令。
并且现在这组命令的输出口径也要冻结下来:
- `activity-stream / driver-feed / codex-brief`
- condensed summary 必须打印顶层 `contract_navigation`
- `activity-preview / driver-focus-preview / codex-focus-preview / stack-next`
- condensed summary 必须打印 `preview.contract_key / contract_version / contract_primary_endpoint / preview_endpoint / schema_doc_path`
- 所有 `selected_entry`
- 必须尽可能保留 `contract_navigation`
这样海外 CLI、页面按钮、未来海外 Codex 驾驶员看到的就不是三套不同“解释文本”,而是一套可追踪到协议、接口和 schema 文档的正式输出。
同理,海外单脑控制面导出的交接包也不能只停留在:
- `stack summary`
- `ops plane`
- `participation`
还应该把 contract-aware 驾驶面一起固化进去:
- `contracts registry`
- `driver-feed`
- `activity-stream`
- `codex-brief`
- `stack-next preview`
- `driver-focus preview`
- `activity preview`
- `codex-focus preview`
并且 preview 类报告允许“当前没有焦点但仍导出文件”,文件尾部只记录 `command_exit_code=1`,这样交接人拿到包以后不会误解成“导出中断”,而能明确判断是“现场当时没有可预览入口”。
进一步讲,这个交接包还应该再固定一层机器摘要:
- `manifest.json`
它不是替代原始报告,而是给海外 Codex / 后台自动驾驶 / 未来 OpsCenter UI 二次消费的摘要面。
正式口径应该固定成:
- `meta.json`
- 负责说明这包是谁、何时、针对哪个 profile / 哪组 API 生成的
- `manifest.json`
- 负责说明这包里每份报告是否存在、exit code 是多少、哪些 preview 当前可用、现场暴露了哪些 contract key
- 如果某个 required 报告已经解析出 `http_status` 且不是 `2xx`,也必须记入 `required_failures`
- `*.txt`
- 保留完整原始现场输出,供人类继续钻取
并且 `doctor-export` 命令自己的 stdout 也应该回传一份缩略版 `summary + decision`,这样海外 Codex 不必先打开文件树,也能先做第一轮分流判断。
进一步往终局落地时CLI 侧还应该固定一个更适合机器先消费的统一入口:
- `doctor-decision`
正式定位应该是:
- `doctor-export`
- 负责生成完整交接包
- `doctor-decision`
- 负责读取 `manifest.json`
- 如果没有现成 `manifest.json`,就临时生成一份 export
- 然后只回传 `summary + decision`
- 并且允许用 `-` 明确关闭 overseas 拓扑探测,避免因为默认海外地址不可达把第一轮分流卡死
这里的 `summary` 不能只做 surface 成败汇总,还要继续固定带出 launchpad 接管口径:
- `launchpad_recommended_target_node_code`
- `launchpad_recommended_recovery_label`
- `launchpad_recommended_recovery_summary`
- `launchpad_onboarding_bootstrap_pending_nodes`
- `launchpad_onboarding_acceptance_ready_nodes`
同时,`decision.evidence` 也要镜像这组字段,确保海外 Codex / 自动驾驶器 / 人工值班
在只消费 `doctor-decision` 的前提下,就能直接判断:
- 当前最该先接哪台节点
- 当前是“还缺 bootstrap”还是“已经 ready等待 acceptance”
- 是否需要继续下钻 `release-launchpad / driver-feed / codex-brief`
这样海外单脑控制面的“第一跳”就稳定了:
- 海外 Codex 先跑 `doctor-decision`
- 后台自动驾驶按钮先跑 `doctor-decision`
- 人工值班也先看 `doctor-decision`
并且从当前实现口径上,`doctor-decision` 不应再只消费旧的 `next_step_action_code`,而要优先消费:
- `diagnosis.operator_decision`
- `diagnosis.next_actions`
- `diagnosis.recommended_commands`
这意味着如果总检已经明确判定“先看现场日志 / 先打通节点接管 / 先看 Release Hub”那么 `doctor-export` 产出的 `manifest.json``doctor-decision` 最终给出的 `recommended_commands` 也必须同步落到同一条主决策,不允许再次退化成泛化建议。
只有当 `decision.status` 显示需要继续钻取时,才去打开整包 `*.txt``manifest.json` 深挖。
而且 `doctor-export / doctor-decision` 这一层本身也应该固定成“步骤级超时继续导出”:
- 某个 surface 卡住,不允许整包诊断悬死
- required report 失败,要进入 `required_failures`
- required report 失败明细,要进入 `required_failure_details`
- optional preview 不可用,要进入 `optional_unavailable`
- optional preview 不可用明细,要进入 `optional_unavailable_details`
- 海外单脑永远优先拿到一份可判断的 `decision`,而不是拿到一个还在转圈的进程
并且这些明细不能只停留在“文件名失败”:
- `reports[].failure.reason_code`
- `reports[].failure.label`
- `reports[].failure.detail`
至少要能稳定区分:
- `command_timeout`
- `command_failed`
- `http_unreachable`
- `http_error`
- `result_unparsed`
再往前一步,控制面 API 自身也应该暴露正式“运行指纹”:
- `/health`
- `/api/v1/runtime/status`
- `/api/v1/runtime/build-info`
这些接口至少应该稳定给出:
- `build.source`
- `build.package_name`
- `build.generated_at`
- `build.commit_sha`
- `build.commit_ref`
- `build.manifest_path`
- `build.route_surface.surface_complete`
- `build.route_surface.missing_paths`
这样以后看到 `ops/contracts` 返回 404 时,就不用再靠猜:
- 如果 `build` 缺失,说明运行实例还没切到新版本
- 如果 `build` 存在但 `route_surface` 缺路由,说明当前实例加载的仍然不是完整协议面
- 只有 `build``route_surface` 都完整,才值得继续往 preview / execute 深挖
### 4. 当前阶段的明确边界
当前仍然要刻意坚持下面这几点,避免以后又滑回“假装可控”:
- 不直接 SSH 进节点手改 `pending / dead-letter` 文件
- 不允许页面、CLI、Codex 各自定义不同的 replay / discard 逻辑
- 所有操作都必须落成正式 `ops job`
- 单条 replay / discard 当前只允许针对控制面可见头部记录
这样未来从 `head_only` 升级到“远端全量记录正式投影”时,扩的是可见范围,不是重写整个治理模型
推荐以后直接以 [ops_agent_protocol.md](/www/wwwroot/getDomain/docs/schemas/ops_agent_protocol.md:133) 为母本,把 `delivery_queue` 从 heartbeat 快照扩到控制面操作面。
正式要求应该固定成:
- Agent 只负责补发与执行
- 控制面负责查看、聚类、重放、丢弃
- 页面、CLI、Codex 不允许各自发明不同的死信处理逻辑
---
## 二十二、Playbook Run 必须成为正式一等对象
当前仓库里 `playbook` 已经不只是“预留想法”,而是已经有:
- catalog
- preview
- execute
- run detail
- run events
- rerun
- cancel
这说明骨架已经具备,只差把 contract 冻结清楚。
后续最关键的不是再补更多“脚本型能力”,而是把这几类正式收口:
- `onboarding.bootstrap`
- `onboarding.acceptance`
- `inspection.standard`
- `scene.logs.key`
- `scene.logs.full`
- `scene.diagnostics`
也就是说:
- 接管验收
- 标准巡检
- 现场观察
都应该优先变成 `playbook run`,而不是继续散落在:
- 页面按钮
- Driver Action 临时逻辑
- 手工命令
推荐以后直接以 [ops_playbook_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_playbook_contract.md:1) 为正式母本。
这样后续:
- 页面点“标准巡检”
- CLI 执行“现场观察”
- 海外 Codex 驾驶员决定“先验收还是先抓日志”
最终看到的都是同一类对象:
-`preview`
-`execute`
- 最后围绕 `playbook run / playbook events` 观察
这样控制面才算真正从“按钮集合”进化成“正式编排平台”。
---
## 二十三、单脑控制面最终要固定成 4 张操作面
Node Agent、Release Hub、Driver、Playbook 这些听起来很多,但终局页面不应该把操作者淹没在概念里。
最终应该稳定成 4 张操作面。
其中“现场面 + 队列面”建议以后直接以 [ops_observability_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_observability_contract.md:1) 为正式母本,避免后面又把这层能力重新拆散到页面局部状态里。
### 1. 接管面
负责回答:
- 哪些节点已经纳管
- 哪些节点只是集群可见但未纳管
- 哪些节点 token 过期
- 哪些节点 agent 心跳异常
- 哪些节点已经通过接管验收
这一面主要消费:
- `ops_agent_protocol`
- `managed nodes`
- `bootstrap-plan`
- `onboarding.acceptance`
### 2. 现场面
负责回答:
- 哪些节点正在执行
- 哪些节点在线但未参与
- 哪些节点近窗有吞吐
- 日志回传当前是否关闭 / 关键 / 全量
- 当前应该优先巡检哪台节点
这一面主要消费:
- `overview`
- `inspection`
- `activity-stream`
- `driver_recommendations`
- `playbook runs`
### 3. 队列面
负责回答:
- 哪台节点的回执队列有积压
- 哪台节点有死信
- 头部记录是什么
- 当前应该冲刷、重放,还是丢弃
这一面主要消费:
- `delivery_queue`
- `delivery_queue.records`
- `flush / replay / discard`
这里要持续坚持:
- 当前阶段允许 `head_only`
- 但不允许退回人工 SSH 改文件
### 4. 发布面
负责回答:
- 当前默认 release 是谁
- 默认 gate 是 `blocked / attention / ready`
- 哪些节点可以灰度
- 哪一批 rollout 正在执行
- 哪一轮 rollout 需要审批 / 暂停 / 回滚
这一面主要消费:
- `release_hub_contract`
- `launchpad`
- `default_rollout_gate`
- `rollouts`
这样页面心智模型才会稳定:
- 接管面:先让节点进体系
- 现场面:看现场和巡检
- 队列面:处理回执不一致
- 发布面:推进版本与回滚
这 4 张面板不是 4 套系统,而是:
> 同一套控制面 contract 的 4 个观察窗口
---
## 二十四、新节点从“拿到机器”到“可以发版”必须走标准路径
后续不应该再写“某台机器特殊处理说明”,而要把新节点接入路径固定为下面 8 步。
### 1. 签发接入凭证
控制面生成:
- token
- bootstrap plan
- env 内容
- systemd 文件
- 启动检查命令
这一步之后页面、CLI、Codex 都只能复用同一份 bootstrap 对象。
### 2. 节点安装 Node Agent
节点完成:
- 安装 agent
- 写入 env
- 启动 systemd
此时节点还只是:
- `agent installed`
还不算已经正式接管成功。
### 3. 节点注册并进入托管目录
节点完成:
- `register`
- `heartbeat`
控制面能看见:
- 节点身份
- capabilities
- service_names
- delivery queue 摘要
这时节点进入:
- `managed candidate`
### 4. 立即跑接管验收
控制面不应等待人工另开命令,而应立刻执行:
- `onboarding.acceptance`
必要时自动追加:
- `inspection.standard`
这样新节点接入不会停留在“能连上就算完成”,而会直接走到:
- 基础服务是否正常
- 日志是否可收口
- 诊断是否可采
- worker / api 拓扑是否正确
### 5. 如果有问题,先走现场面而不是手工 SSH
验收失败后,优先动作应该是:
- 查看 `activity-stream`
- 打开 `playbook run detail`
-`inspection`
-`delivery queue`
只有 Node Agent 已明显不可用,才进入 SSH Rescue。
### 6. 验收通过后进入发布门禁
节点通过验收后,不应马上让它进入发布目标集合,而是先进入:
- release gate readiness
也就是明确判断:
- agent 在线否
- inspection 健康否
- delivery queue 正常否
- 是否允许 remote-agent rollout
### 7. 先进入 Worker / Control 正确角色集合
接入后必须明确:
- 它是 `worker`
- 还是 `control`
- 是否 `effective_worker`
- 是否允许参与 rollout
不能让“纳管成功”和“自动进入发版目标”之间没有门槛。
### 8. 最后才进入 Release / Rollout
到这一步才允许:
- worker canary
- batch rollout
- advance / pause / rollback
这条标准路径的真正意义是:
> 新节点接入不再是一次性脚本,而是正式运维生命周期的开始
---
## 二十五、后续实现时绝不再回退的 10 条边界
为了避免系统越来越大以后又退回人工联调,下面 10 条边界建议直接视为终局铁律。
### 1. 不再把 `git pull` 当正式发布方式
正式发布只认:
- release artifact
- checksum
- rollout
### 2. 不再让节点自己决定版本
节点只能执行:
- 下载
- 校验
- 切换
- 回滚
不能自己挑版本。
### 3. 不再让页面、CLI、Codex 各自拼动作语义
所有动作必须收口到:
- action template
- playbook
- rollout
- delivery queue action
### 4. 不再把 SSH 当日常主链路
SSH 只保留:
- bootstrap
- rescue
### 5. 不再让回执队列治理脱离 `ops job`
无论:
- flush
- replay
- discard
都必须形成正式留痕。
### 5.1 不再让发布兜底路径退回“打开面板”
`release_progression` 现在也应该遵守同一原则:
- 有明确 Launchpad 推荐时,优先走 `publish_latest_worker`
- 已经存在默认 Release、但 Launchpad 还没形成明确推荐时,兜底也要直接走 `create_release_rollout_worker`
也就是说,不能因为“还没形成完整 launchpad 推荐”就退回到“打开 Worker 点状灰度面板”这种 UI 导航式动作。
### 6. 不再让“在线”直接等于“参与检测”
必须明确区分:
- 在线
- 参与执行
- 在线待命
- 近窗有吞吐
- 负载同步中
### 7. 不再让页面自己猜跳转落点
后端必须给:
- `ui_intent`
- `run_code`
- `activity_key`
- `focus_step_key`
### 8. 不再让日志体系只靠全量大文本
必须分层:
- event
- playbook / job log
- runtime log
- diagnostics bundle
### 9. 不再把 root/www 混合工作区当正常运维模型
正式版只允许:
- release build side
- release deploy side
角色分清,而不是在目标节点长期维护一份脏 git 工作区。
### 10. 不再让“设计口径”和“实现口径”分裂
所以现在开始,后续新增能力都建议优先先补到:
- `ops_agent_protocol.md`
- `ops_driver_contract.md`
- `ops_playbook_contract.md`
- `release_hub_contract.md`
再去写页面和执行器。
这样你这套系统才会越来越像:
> 正式控制平台
而不是:
> 一直迭代的一组强力调试脚本
## 二十六、海外单脑控制面的总检入口也要正式化
上面整套设计如果只停留在:
- 有很多 endpoint
- 有很多检查脚本
- 有很多 contract 文档
那后面现场一复杂,还是会退回“先想想这次该跑哪个脚本”。
所以海外单脑控制面还需要一个固定的总检入口:
- `check_ops_center_stack.sh`
它的职责不是替代:
- `check_ops_contracts.sh`
- `check_ops_plane.sh`
- `check_node_agent.sh`
- `check_release_hub.sh`
而是把这四层先按固定顺序收口成一次总览:
1. 先看 API 与 contract registry
2. 再看 `link_snapshot / overview` 当前驾驶主线
3. 再看托管节点、Node Agent、回执队列
4. 最后看 ReleaseHub 与最近 playbook / activity
这样后续海外主机第一次接手现场时,默认起手式就固定为:
```bash
cd /opt/domaincheck/domain-api
bash domain-api/deploy/multi-region/check_ops_center_stack.sh http://127.0.0.1:8100
# 如果只想看最终收口,不展开全量原始 JSON
bash domain-api/deploy/multi-region/check_ops_center_stack.sh http://127.0.0.1:8100 summary
```
它本质上回答的是 5 个问题:
- 当前控制面跑的是哪一版 contract
- 当前最该处理的 driver recommendation 是什么
- 当前有哪些节点已经被正式接管、哪些还没进入稳定执行面
- 当前回执队列有没有死信或待补发积压
- 当前 ReleaseHub 是 ready / attention / blocked 哪一种
而在正式实现上,这份总检结果不应该只停留在“把几段 JSON 拼起来”,而是要直接形成一层可被人和程序共用的诊断输出:
- `diagnosis.surface_status`
说明总检表面层是不是完整联通
- `diagnosis.automation_status`
说明海外单脑自动化执行面是不是已经真正可接管
- `diagnosis.stack_status`
作为总检最终结论
- `diagnosis.issues`
作为问题清单
- `diagnosis.next_step`
作为默认下一步动作
- `diagnosis.quick_commands`
作为现场立刻可复制执行的命令集合
这样一来,总检结果就不只是“读给人看”的说明书,而是后续:
- 海外 Codex 驾驶
- 后台按钮自动诊断
- 交接包导出
- 标准作业入口编排
都可以直接消费的正式 contract。
对应的正式入口也应该固定为:
- `GET /api/v1/ops/stack-diagnosis`
CLI 总检脚本只是它的终端包装层,而不应该继续长期承担“总检规则真正定义者”的角色。
进一步说CLI 默认动作入口也应该固定下来:
- `bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next`
它不再要求海外操作人先从总检结果里人工抄:
- `action_code`
- `focus_ref`
- `recommended command`
而是直接消费:
- `diagnosis.next_step`
然后统一进入:
- `driver-actions/resolve`
- `driver-actions/execute-resolved`
这一步很重要,因为它把“总检”和“下一步执行”正式接成一条链:
- 页面顶部总检入口
- 海外 CLI 单入口
- 后续海外 Codex 驾驶员
都不再自己猜“下一步该做什么”,而是统一信任一份正式 contract。
如果现场执行 `stack-next` 时拿到的是:
- `404 Not Found`
那也不要把它理解成“设计不成立”,而应理解为:
- 运行中的控制面 API 版本还没升级到包含 `/api/v1/ops/stack-diagnosis`
- 或代码已同步,但 `domaincheck-api` 尚未重启
也就是说,后续现场排障里,`404` 属于:
- 部署版本未对齐问题
而不是:
- 总检 contract 本身设计错误
这一步很关键,因为它把“海外主机的第一反应”也变成正式 contract 的一部分,而不是继续依赖人工记忆。
也就是说,未来不管是:
- 人工运维
- 海外 Codex 驾驶员
- 后台按钮触发自动诊断
都应该优先先看这份总检结果,再决定下一步钻取哪一层。
只有这样,这套体系才真正具备:
> 单入口观察,分层下钻,协议不漂移
## 二十七、终局实现顺序不能再乱
为了避免后面继续一边联调、一边改口径,这套体系后续实现顺序必须固定。
建议严格按下面 6 段推进:
### 1. 先冻结 contract再扩页面和执行器
优先冻结的母本应固定为:
- [ops_stack_diagnosis_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_stack_diagnosis_contract.md:1)
- [ops_driver_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_driver_contract.md:1)
- [ops_job_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_job_contract.md:1)
- [ops_agent_protocol.md](/www/wwwroot/getDomain/docs/schemas/ops_agent_protocol.md:1)
- [release_hub_contract.md](/www/wwwroot/getDomain/docs/schemas/release_hub_contract.md:1)
- [ops_observability_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_observability_contract.md:1)
只有这些 contract 先稳住页面按钮、CLI、Codex、Node Agent 才能围绕同一套结构化对象工作。
### 2. 再固定控制面的统一执行链
控制面日常动作主链必须固定为:
1. `stack-diagnosis`
2. `driver-actions/resolve`
3. `driver-actions/execute-resolved`
4. `ops job`
5. `Node Agent / rollout executor`
6. `event / complete / activity`
也就是说:
- 页面不直接执行壳命令
- CLI 不直接拼最终执行接口
- Codex 不直接猜动作 target api
所有高层动作都先下沉为统一 resolve / execute-resolved contract。
### 3. 再把 Node Agent 执行面补齐
Node Agent 侧必须先补齐:
- `register`
- `heartbeat`
- `pull`
- `jobs/{job_id}/start`
- `jobs/{job_id}/complete`
- `jobs/{job_id}/events`
- 本地待补发 / 死信队列
直到这条链稳定之前,不应把“节点能 SSH 上去”误判成“执行面已经正式接管”。
### 4. 再把 Release Hub 收成正式发布面
Release Hub 后续必须坚持:
- 包只认 artifact
- 发布只认 rollout gate
- 批次推进只认正式 rollout / ops job
- 节点不允许直接 `git pull`
这样后续:
- worker canary
- controller rollout
- 失败回滚
- 灰度暂停
才会继续沿用同一个正式模型,而不是变回“发布时再单独写一套命令”。
### 5. 最后才是 SSH Rescue
SSH 不应再作为日常主链路,只保留给下面几类场景:
- Node Agent 尚未接管
- Node Agent 失联
- 运行环境损坏到 agent 无法自救
- release 切换半途损坏,需要紧急修复目录或 systemd
而且即使走 SSH也要继续满足
- 创建正式 `ops job`
- 留下执行人、节点、动作来源
- 回写控制面审计链
### 6. 页面和 Codex 只能做“消费者”,不能做第二大脑
终局架构里:
- 页面负责展示和触发
- CLI 负责终端入口
- Codex 负责智能判断和编排
但三者都不能再自己定义:
- 新状态机
- 新动作对象
- 新门禁口径
- 新下一步推断逻辑
否则“海外单脑控制面”就又会退化成三套脑子。
## 二十八、上线前必须通过的收口验收
如果以后要说“这套系统已经进入正式长期可维护状态”,至少应通过下面 10 条验收。
### 1. 总检入口必须真实可用
下面命令必须能在海外控制面直接打通:
```bash
cd /opt/domaincheck/domain-api
bash domain-api/deploy/multi-region/check_ops_center_stack.sh http://127.0.0.1:8100 summary
```
而且结果里必须能给出:
- `diagnosis.stack_status`
- `diagnosis.issues`
- `diagnosis.next_step`
### 2. 默认下一步必须可以直接消费
下面命令必须成立:
```bash
bash domain-api/deploy/multi-region/drive_ops_center.sh stack-next http://127.0.0.1:8100
```
也就是说,现场不应再需要人工从总检结果里抄:
- `action_code`
- `focus_ref`
- `command`
### 3. 所有标准动作都必须落为正式对象
后续任何标准动作都必须能落成:
- `driver action`
- `ops job`
-`playbook run`
不允许再出现“后台按钮一按,后端直接去某台机器执行一个临时命令,但控制面里没有正式记录”。
### 4. Node Agent 接管和 runtime 心跳必须明确区分
控制面必须能够直接区分:
- `已接管`
- `仅运行态在线`
- `待接入`
- `心跳过期`
- `失联`
绝不能再用“API 能看到节点在线”代替“Node Agent 已正式接管”。
### 5. 回执队列必须能治理,不只是能看见
必须能在页面或海外单入口完成:
- `queue-status`
- `queue-records`
- `queue-flush`
- `queue-replay`
- `queue-discard-record`
否则后续只要出现一次跨地域回执积压,就又会退回到手工删文件。
### 6. 远端日志回传必须可开关
多机联调时,海外控制面必须能够:
- 默认关闭,避免长期噪声
- 调试时打开,拿到远端关键或全量日志
- 调试结束后关闭
否则后续每新增一台大陆机器,都会重新回到“复制日志给 Codex 看”的低效模式。
### 7. Release Hub 必须可执行 canary 到 rollout
至少要能稳定完成:
- 选默认版本
- 看 rollout gate
- 发 worker canary
- 看结果
- 继续批次
- 异常暂停 / 回滚
### 8. 节点发布必须只认 artifact不认 git pull
一旦进入正式执行链,节点侧必须坚持:
-`git pull`
- 不直接以运行目录为发布源
- 不允许 controller / worker 各自拉不同本地临时代码
### 9. 救援必须是正式例外,不是常态
如果某类动作每次都需要:
- SSH 上机
- 手工重启
- 手工改 env
- 手工粘日志
那说明它还没有进入正式执行面,不能算收口完成。
### 10. 页面、CLI、Codex 三端口径必须一致
同一条动作在三端看到的:
- 状态
- 下一步
- 风险说明
- focus_ref
- target_api
必须一致。
只有这样,后面你才不会再遇到:
- 页面说能做
- CLI 说不能做
- Codex 又判断成第三种情况
## 二十九、后续绝不能回退的 8 种反模式
后续无论现场多急,都不建议再回到下面这些模式。
### 1. 新增页面按钮,直接在后端执行 shell
这是最容易把平台重新打回“脚本集合”的退路。
正确做法应始终是:
- 先 resolve
- 再 execute-resolved
- 最终落成 `ops job`
### 2. 页面、CLI、Codex 各自拼 request payload
一旦三端自己拼:
- `action_code`
- `target_api`
- `focus_ref`
- `execution chain`
后面就一定会再次口径漂移。
### 3. 用 runtime 心跳冒充正式接管
“节点在线”不等于:
- Node Agent 已接管
- 可以标准发布
- 可以标准巡检
- 可以标准回执
这两者必须永远分开。
### 4. 把发布流程重新做成节点本地更新
正式发布链绝不应回退到:
- `git pull`
- 覆盖运行目录
- 临时复制文件
正确做法只能是:
- 构建 artifact
- 生成 rollout
- 由执行面标准接收和切换
### 5. 把 SSH 当成默认执行器
SSH 只能是:
- bootstrap
- rescue
- 临时过渡
而不能成为:
- 日常发布
- 日常巡检
- 日常日志拉取
- 日常服务控制
的默认方式。
### 6. 故障时重新回到“复制日志给 Codex”
终局目标是:
- 海外控制面统一拿日志
- Codex 直接消费控制面 contract
而不是每次故障又退回:
- 现场机器跑命令
- 人工复制粘贴
- 再让 Codex 二次解释
### 7. 新增机器时再写一份特殊部署说明
以后新增大陆 controller / worker不应再写
- “这台机器例外”
- “这台机器单独执行某个手工命令”
- “这台机器按另一套说明”
而应继续只走:
- bootstrap plan
- Node Agent 接管
- onboarding acceptance
- 标准巡检 / rollout
### 8. 因为赶时间,就跳过正式审计链
只要动作会改变远端节点现场,就必须有:
- 发起人
- 动作来源
- 目标节点
- 执行结果
- 回执事件
否则后续节点一多,你就再也无法回答:
- 是谁改的
- 为什么改
- 哪次改坏了
- 哪台节点没跟上
这 8 条如果长期守住,后面这套系统才不会越做越散,而会越来越像真正的:
> 海外单脑控制平台
## 三十、这一轮继续冻结的 4 组关键字段
这轮设计继续往前收口时,有 4 组字段必须正式冻结否则后面最容易再次出现“页面一套、后端一套、Codex 一套”的漂移。
### 1. Execution Scene 的参与态字段必须固定
现在“节点在线”和“节点真正参与检测”已经被证明不能混为一谈。
所以后续 `execution_scene` 里每个节点都应该优先稳定返回:
- `participation_state`
- `participation_label`
- `participation_reason`
推荐值直接固定成:
- `dispatch_active`
- `recent_only`
- `standby`
- `load_syncing`
这样页面“参与检测节点”、CLI 摘要、海外 Codex 驾驶员才能直接区分:
- 在线但未参与
- 正在领任务 / 正在执行
- 近窗刚参与但当前已收口
- 控制面和现场仍在同步
这层后续建议直接以 [ops_observability_contract.md](/www/wwwroot/getDomain/docs/schemas/ops_observability_contract.md:1) 为观察面母本。
### 2. 远端日志回传必须从“有无样本”升级成“覆盖到哪些节点”
远端检测日志回传后续不能再只回答:
- 开了没
- 有样本没
这还不够支撑多机现场。
正式 contract 还要继续稳定返回:
- `source_node_summaries`
- `covered_participating_node_summaries`
- `missing_participating_node_summaries`
也就是说,控制面要能直接回答:
- 哪些参与节点已经有样本
- 哪些参与节点还没有样本
- 每台已覆盖节点当前拿到了多少关键行 / 全量行
- 最近一条样本来自谁
这样后面你不需要再自己翻日志去判断“到底是哪台机器没回传”,页面和 Codex 都能直接聚焦缺口节点。
### 3. Node Agent 拉到的任务包裹必须固定
Node Agent 后续不能只拿到:
- `action`
- `payload`
然后自己去猜这是不是 rollout、是不是巡检、是不是日志采样。
正式 Agent Job Envelope 应继续带上:
- `job_type`
- `step_key`
- `step_title`
- `policy`
- `release_context`
- `focus_ref`
这样同一个 Agent 才能稳定承接:
- 标准巡检
- Worker 日志
- 诊断收集
- Release 部署
- Rollout 回滚
这层后续建议直接以 [ops_agent_protocol.md](/www/wwwroot/getDomain/docs/schemas/ops_agent_protocol.md:1) 为执行面母本。
### 4. Release Hub 不能只显示版本,还必须冻结 manifest / readiness / preview
Release Hub 如果只停在:
- latest release
- rollout list
那本质上还是“看版本页面”,不是正式发布控制面。
接下来必须固定的 3 个发布对象是:
- `artifact manifest`
- `operational_readiness.rows`
- `rollout preview`
它们分别回答:
- 这个包到底会改哪些服务、怎么验、怎么回滚
- 哪台节点为什么能发 / 不能发
- 真正发 rollout 之前预计会分几批、风险在哪
这层后续建议直接以 [release_hub_contract.md](/www/wwwroot/getDomain/docs/schemas/release_hub_contract.md:1) 为发布面母本。
这 4 组字段一旦收死后面再往下做页面、后端、CLI、Codex 自动驾驶时,就不会再反复经历:
- 先做一个能跑的版本
- 现场一复杂就发现字段不够
- 再回头改 contract
- 然后前后端一起返工
后续真正高效的节奏应该变成:
1. 先定 contract
2. 再做后端聚合
3. 再做页面展示
4. 最后接 Codex 自动驾驶
只有这样Node Agent、Release Hub、OpsCenter 才会越来越像一个统一系统,而不是三块并排长大的模块。