16 KiB
domainCheck Ops Observability Contract
1. 目标
这份文档用于冻结海外单脑控制面里“看现场”这一层的正式 contract。
它不负责:
- 发布决策
- Driver 推荐
- Playbook 编排
它负责回答 3 类问题:
- 哪些节点现在真正处于执行现场
- 这些节点最近巡检收口结果是什么
- Node Agent 回执队列现在是否健康
也就是说,这份 contract 的职责是:
让页面、CLI、Codex 看到同一份现场事实,而不是各自从日志和状态字串里猜
2. 入口接口
建议把下面这些接口视为同一观察面的一组正式入口:
GET /api/v1/ops/overviewGET /api/v1/ops/inspection-overviewGET /api/v1/ops/activity-streamGET /api/v1/ops/nodes/{node_code}/handoverGET /api/v1/ops/nodes/{node_code}/delivery-queueGET /api/v1/ops/nodes/{node_code}/delivery-queue/recordsPOST /api/v1/ops/nodes/{node_code}/delivery-queue/flushPOST /api/v1/ops/nodes/{node_code}/delivery-queue/replayPOST /api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/replayPOST /api/v1/ops/nodes/{node_code}/delivery-queue/records/{record_id}/discard
配套关系:
overview- 首页收口
inspection-overview- 节点巡检收口
activity-stream- 最近异常活动
handover- 单节点接管阻断与下一步建议
delivery-queue- Node Agent 回执健康
正式要求:
- 页面不能自己再定义第 5 套“观察口径”
- CLI 不应重新从数据库拼现场
- Codex 不应通过自然语言日志推断核心状态
3. Execution Scene Contract
来源:
GET /api/v1/ops/overviewoverview.execution_scene
最小字段:
{
"summary": "当前存在 2 台有效执行节点,其中 1 台正在领任务,1 台在线待命。",
"dispatch_active_nodes": [
{
"node_code": "mainland-worker-01",
"region": "mainland",
"role": "worker",
"status": "busy",
"participation_state": "dispatch_active",
"participation_label": "正在执行",
"participation_reason": "当前存在已领任务和执行中工作负载",
"current_load": 9,
"items_claimed": 9,
"items_running": 5,
"items_completed_recent": 14
}
],
"recent_only_nodes": [],
"standby_nodes": [
{
"node_code": "mainland-controller-01",
"region": "mainland",
"role": "control",
"status": "online",
"participation_state": "standby",
"participation_label": "在线待命",
"participation_reason": "节点在线、有效,但当前没有领任务、执行中任务或近窗吞吐",
"current_load": 0
}
],
"load_syncing_nodes": [],
"counts": {
"dispatch_active": 1,
"recent_only": 0,
"standby": 1,
"load_syncing": 0
}
}
分组语义必须固定:
dispatch_active_nodes- 当前正在领任务、执行任务、或仍有实时工作负载
recent_only_nodes- 当前不再运行,但近窗刚有吞吐
standby_nodes- 在线、有效,但当前不参与
load_syncing_nodes- 节点与控制面状态尚未完全同步
正式约束:
- “在线”不等于“参与检测”
- “有效执行节点”不等于“正在跑”
- 页面展示、CLI 摘要、Codex 判断必须沿用这 4 类分组
节点参与态附加字段也应固定:
participation_state- 机器判断主键,推荐值:
dispatch_activerecent_onlystandbyload_syncing
- 机器判断主键,推荐值:
participation_label- 直接给页面、CLI、Codex 展示的人类可读标签
participation_reason- 用一句稳定摘要解释为什么节点落在当前分组
正式要求:
- 页面必须直接区分:
- 在线但未参与
- 正在执行或领任务
- 近窗刚参与但当前已收口
- 新消费方不允许再从:
statuscurrent_loaditems_running手工推断“这个节点到底算不算正在参与”
4. Log Sync Runtime Contract
来源:
overview.execution_scene.log_sync- 或
overview.log_sync
最小字段:
{
"status": "partial_coverage",
"status_label": "部分覆盖",
"mode": "key",
"mode_label": "关键回传",
"enabled": true,
"source_nodes": [
"mainland-worker-01"
],
"source_node_summaries": [
{
"node_code": "mainland-worker-01",
"line_count": 42,
"key_line_count": 42,
"full_line_count": 0,
"last_at": "2026-04-18 06:40:00",
"last_line": "域名检测完成: demo.com"
}
],
"covered_participating_node_summaries": [
{
"node_code": "mainland-worker-01",
"region": "mainland",
"role": "worker",
"participation_state": "dispatch_active",
"participation_label": "正在执行",
"participation_reason": "当前存在已领任务和执行中工作负载",
"line_count": 42,
"key_line_count": 42,
"full_line_count": 0,
"last_at": "2026-04-18 06:40:00",
"last_line": "域名检测完成: demo.com"
}
],
"missing_participating_node_summaries": [
{
"node_code": "mainland-worker-02",
"region": "mainland",
"role": "worker",
"participation_state": "dispatch_active",
"participation_label": "正在执行",
"participation_reason": "当前存在已领任务和执行中工作负载",
"missing_reason": "no_sample"
}
],
"covered_participating_nodes": 1,
"participating_nodes_total": 2,
"missing_sample_node_codes": [
"mainland-worker-02"
],
"last_line_at": "2026-04-18 06:40:00",
"summary": "当前已覆盖 1/2 台参与节点,建议继续观察未回传样本节点。"
}
推荐状态:
disabledwaiting_samplepartial_coveragehealthyfull_capture
这层是“现场可见性”最关键的辅助状态。
正式要求:
- 如果存在参与节点但日志回传还没开,优先推荐开关键回传
- 如果日志回传已开但没有样本,优先推荐抓 Worker 日志
- 页面和 Codex 都不能再自己定义另一套日志覆盖结论
节点级日志覆盖字段也应固定:
source_nodes- 为兼容旧页面保留的节点编码列表
source_node_summaries- 当前已有日志样本的节点级统计对象
covered_participating_node_summaries- 已参与且已拿到样本的节点对象
missing_participating_node_summaries- 已参与但尚未拿到样本的节点对象
source_node_summaries[] 至少应返回:
node_codeline_countkey_line_countfull_line_countlast_atlast_line
missing_participating_node_summaries[] 至少应返回:
node_coderegionroleparticipation_stateparticipation_labelparticipation_reasonmissing_reason
推荐 missing_reason:
no_sampledelayed_flushfiltered_out
兼容要求:
- 旧页面仍可继续消费:
source_nodesmissing_sample_node_codes
- 新页面、CLI、Codex 应优先消费:
source_node_summariescovered_participating_node_summariesmissing_participating_node_summaries
补充节点级现场入口:
GET /api/v1/ops/nodes/{node_code}/scene-log
最小字段:
{
"node_code": "mainland-worker-01",
"status": "healthy",
"status_label": "关键覆盖",
"mode": "key",
"mode_label": "关键回传",
"log_sync_enabled": true,
"summary": "节点当前正在参与检测,已保留 42 条现场日志样本。",
"node": {
"region": "mainland",
"role": "worker",
"status": "busy",
"current_load": 6,
"last_heartbeat_at": "2026-04-18 06:40:00"
},
"participation": {
"detect_participating": true,
"participation_state": "claimed",
"participation_label": "已领待跑",
"participation_bucket": "dispatch_active",
"participation_reason": "已领取任务,等待线程继续执行。"
},
"source_summary": {
"line_count": 42,
"key_line_count": 42,
"full_line_count": 0,
"last_at": "2026-04-18 06:40:00",
"last_line": "[2026-04-18 06:40:00] [mainland-worker-01] 域名检测完成: demo.com"
},
"records_total": 42,
"records_visible": 80,
"records": [
{
"created_at": "2026-04-18 06:38:00",
"node_code": "mainland-worker-01",
"message": "开始检测域名: demo.com",
"mode": "key",
"line": "[2026-04-18 06:38:00] [mainland-worker-01] 开始检测域名: demo.com"
}
]
}
节点级入口的正式语义:
- 这是“看某一台节点当前现场”的正式入口,不允许页面再自己从聚合样本中二次猜测
status至少覆盖:healthyfull_capturehistorical_samplewaiting_samplemissing_sampledisabledstandbyunknown
- 当节点当前在线但未参与时,必须明确返回
standby - 当节点正在参与但还没有样本时,必须明确返回
waiting_sample - 当节点已有历史样本但当前不在参与面中时,必须明确返回
historical_sample
5. Inspection Overview Contract
来源:
GET /api/v1/ops/inspection-overviewoverview.inspection
最小字段:
{
"status": "attention",
"status_label": "待处理",
"summary": "存在 1 台节点最近缺少 Worker 日志收口。",
"counts": {
"healthy_nodes": 1,
"attention_nodes": 1,
"problem_nodes": 0
},
"rows": [
{
"node_code": "mainland-worker-01",
"problem_kind": "missing_worker_logs",
"problem_label": "缺少 Worker 日志收口",
"problem_level": "warning",
"recommended_action_code": "open_worker_logs",
"ui_intent": {
"kind": "node_inspection_detail",
"node_code": "mainland-worker-01"
},
"latest_health_snapshot": {},
"latest_worker_logs": {},
"latest_diagnostics": {}
}
]
}
设计要求:
- 巡检不是几条离散 job 记录
- 必须按节点重新聚合
- 必须直接给出:
problem_kindproblem_labelproblem_levelrecommended_action_codeui_intent
这样页面、CLI、Codex 才能真正消费“节点巡检结论”,而不是再从几十条 job 记录里回推问题。
6. Inspection Row Contract
rows[] 中每个节点建议最少包含:
node_coderegionroleeffective_workerproblem_kindproblem_labelproblem_levelrecommended_action_codeui_intentlatest_health_snapshotlatest_worker_logslatest_diagnostics
其中 3 类最近结果建议统一结构:
{
"status": "success",
"status_label": "成功",
"occurred_at": "2026-04-18 06:35:00",
"job_code": "ops-20260418-001",
"summary": "最近一次 Worker 日志收集成功,共采样 120 行。"
}
正式边界:
logs.collect默认指 Worker 日志收口- Node Agent 辅助排障日志不应混成同一种巡检结果
7. Delivery Queue Summary Contract
来源:
GET /api/v1/ops/nodes/{node_code}/delivery-queue
最小字段:
{
"node": {
"node_code": "mainland-worker-01",
"title": "mainland-worker-01"
},
"summary": {
"state": "dead_letter",
"label": "死信",
"reason": "当前有 2 条记录进入死信。",
"pending_count": 3,
"dead_letter_count": 2,
"oldest_pending_at": "2026-04-18 06:20:00",
"oldest_dead_letter_at": "2026-04-18 06:18:00"
},
"record_visibility": "head_only",
"capabilities": {
"can_flush": true,
"can_replay": true,
"can_discard": true
}
}
推荐状态:
healthyretryingdead_letterstalled
正式约束:
- 控制面要明确告诉操作者当前是
head_only还是full_projection - 不允许页面假装能列出远端完整目录
- 不允许因为能看见死信就绕过
ops job直接改文件
8. Delivery Queue Record Contract
来源:
GET /api/v1/ops/nodes/{node_code}/delivery-queue/records
当前阶段建议最小字段:
{
"records": [
{
"record_id": 101,
"state": "dead_letter",
"state_label": "死信",
"request_id": "req-20260418-001",
"kind": "job_complete",
"created_at": "2026-04-18 06:18:00",
"updated_at": "2026-04-18 06:19:00",
"summary": "某次任务完成回执多次补发失败。",
"visibility": "head_only"
}
],
"summary": {
"returned_records": 1,
"record_visibility": "head_only"
}
}
当前阶段要求:
- 单条
replay / discard只针对控制面可见记录 - 当前
head_only不等于未来不能扩展 - 以后若进入
full_projection,扩的是可见范围,不是重写操作模型
9. Delivery Queue Action Contract
来源:
flushreplayrecord replayrecord discard
正式要求:
- 所有动作都必须落成正式
ops job - 返回体要能告诉调用方:
- 是否已受理
- 生成了什么 job
- 建议跳转到哪里观察
最小返回体建议:
{
"accepted": true,
"job_code": "ops-20260418-queue-001",
"ui_intent": {
"kind": "job_events",
"job_code": "ops-20260418-queue-001"
}
}
这里的边界必须比“临时脚本”更严格:
discard必须带原因replay应支持限流 / limitflush应支持显式requested_by
10. Activity Stream Contract
来源:
GET /api/v1/ops/activity-stream
最小字段建议:
{
"items": [
{
"kind": "ops_job",
"activity_key": "ops-job:101",
"title": "logs.collect",
"subtitle": "ops-20260418064500-a1b2c3",
"summary": "目标节点 mainland-worker-01 / 发起 web-ui / 方式 Node Agent",
"summary_text": "目标节点 mainland-worker-01 / 发起 web-ui / 方式 Node Agent",
"status": "running",
"status_label": "执行中",
"execution_mode": "remote-agent",
"execution_mode_label": "Node Agent",
"target_node_codes": [
"mainland-worker-01"
],
"occurred_at": "2026-04-18 06:45:08",
"ui_intent_kind": "job_events",
"focus_ref": {
"kind": "ops_job",
"job_id": 101,
"job_code": "ops-20260418064500-a1b2c3",
"action": "logs.collect",
"target_node_code": "mainland-worker-01"
}
}
],
"summary": {
"status": "running",
"status_label": "执行中",
"summary_text": "最近活动里存在正在推进的现场、任务、编排或回传。",
"filtered_total": 6,
"unfiltered_total": 18,
"latest_at": "2026-04-18 06:45:08"
}
}
正式要求:
activity-stream不是页面内部 table 数据- 它必须是:
- 页面
- CLI
- Codex 驾驶员
- 自动驾驶编排器 共同消费的“最近活动摘要”
字段语义需要固定:
summary_text- 是直接消费的活动短摘要
status_label- 是稳定的人类可读状态标签
target_node_codes- 是活动关联的节点口径,不允许调用方再从自然语言里猜
ui_intent_kind- 是当前活动默认落点类型
focus_ref- 是跨页面 / CLI / Codex 的统一定位对象
正式边界:
- 活动流应保留老字段以兼容现有页面
- 新消费方优先用:
summary_textstatus_labelfocus_ref
- 不允许新客户端再从
title + subtitle + summary手工解析定位参数
11. 与 Driver / Playbook / Release 的边界关系
这份 contract 只负责观察面和现场治理,不负责替代其他 contract。
边界应明确为:
ops_driver_contract- 决定“现在优先做什么”
ops_playbook_contract- 决定“标准多步动作怎么编排”
release_hub_contract- 决定“版本怎么发、怎么停、怎么回滚”
ops_observability_contract- 决定“现场事实是什么、当前缺口在哪里”
这样后续系统再扩展时,不会又回到“所有东西都写在 overview 一坨大 json 里”的状态。
12. 终局验收标准
如果这份 contract 真正落地,海外单脑控制面应该做到:
1. 不登录大陆机器也能回答现场问题
例如:
- 谁在跑
- 谁在线但没跑
- 谁近窗刚跑过
- 谁日志没回来
- 谁队列死信了
2. 不复制大日志也能先做判断
先看:
- execution scene
- inspection overview
- activity stream
- delivery queue
再决定是否需要全量日志或 diagnostics bundle。
3. 页面、CLI、Codex 看到的是同一现场
不能出现:
- 页面说节点正在执行
- CLI 说节点待命
- Codex 说节点离线
4. 所有高风险动作都有正式观察落点
也就是:
- replay 后能看 job
- discard 后能看审计
- inspection 后能看节点收口
- rollout 后能看 launchpad / gate / rollout jobs
这样这套系统才能真正从“调试台”升级成“驾驶舱”。