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

122 KiB
Raw Permalink Blame History

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_nodesnon_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_keyenable_log_sync_full
  • 再自动复核 execution_scene.log_sync
  • 再自动给出下一跳 drill-down

当前海外单入口已经有这一条组合命令:

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 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_approvalhaltedcompleted_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 返回必须统一包裹

当前所有接口都已经走:

{
  "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

请求体

{
  "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

请求体

{
  "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

请求体

{
  "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

请求体

{
  "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

当前返回字段

{
  "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

请求体

{
  "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

请求体

{
  "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

请求体

{
  "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 为任务对象母本,再由 ops_agent_protocol.md 继续冻结 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_contractrelease_hub_contractops_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.jsondoctor-decision 最终给出的 recommended_commands 也必须同步落到同一条主决策,不允许再次退化成泛化建议。

只有当 decision.status 显示需要继续钻取时,才去打开整包 *.txtmanifest.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 缺路由,说明当前实例加载的仍然不是完整协议面
  • 只有 buildroute_surface 都完整,才值得继续往 preview / execute 深挖

4. 当前阶段的明确边界

当前仍然要刻意坚持下面这几点,避免以后又滑回“假装可控”:

  • 不直接 SSH 进节点手改 pending / dead-letter 文件
  • 不允许页面、CLI、Codex 各自定义不同的 replay / discard 逻辑
  • 所有操作都必须落成正式 ops job
  • 单条 replay / discard 当前只允许针对控制面可见头部记录

这样未来从 head_only 升级到“远端全量记录正式投影”时,扩的是可见范围,不是重写整个治理模型

推荐以后直接以 ops_agent_protocol.md 为母本,把 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 为正式母本。

这样后续:

  • 页面点“标准巡检”
  • CLI 执行“现场观察”
  • 海外 Codex 驾驶员决定“先验收还是先抓日志”

最终看到的都是同一类对象:

  • preview
  • execute
  • 最后围绕 playbook run / playbook events 观察

这样控制面才算真正从“按钮集合”进化成“正式编排平台”。


二十三、单脑控制面最终要固定成 4 张操作面

Node Agent、Release Hub、Driver、Playbook 这些听起来很多,但终局页面不应该把操作者淹没在概念里。

最终应该稳定成 4 张操作面。

其中“现场面 + 队列面”建议以后直接以 ops_observability_contract.md 为正式母本,避免后面又把这层能力重新拆散到页面局部状态里。

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

这样后续海外主机第一次接手现场时,默认起手式就固定为:

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再扩页面和执行器

优先冻结的母本应固定为:

只有这些 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. 总检入口必须真实可用

下面命令必须能在海外控制面直接打通:

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 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 为观察面母本。

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 为执行面母本。

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 为发布面母本。

这 4 组字段一旦收死后面再往下做页面、后端、CLI、Codex 自动驾驶时,就不会再反复经历:

  • 先做一个能跑的版本
  • 现场一复杂就发现字段不够
  • 再回头改 contract
  • 然后前后端一起返工

后续真正高效的节奏应该变成:

  1. 先定 contract
  2. 再做后端聚合
  3. 再做页面展示
  4. 最后接 Codex 自动驾驶

只有这样Node Agent、Release Hub、OpsCenter 才会越来越像一个统一系统,而不是三块并排长大的模块。