4422 lines
122 KiB
Markdown
4422 lines
122 KiB
Markdown
# 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 才会越来越像一个统一系统,而不是三块并排长大的模块。
|