Files
getDomain/docs/schemas/ops_observability_contract.md
2026-04-18 23:52:51 +08:00

16 KiB
Raw Blame History

domainCheck Ops Observability Contract

1. 目标

这份文档用于冻结海外单脑控制面里“看现场”这一层的正式 contract。

它不负责:

  • 发布决策
  • Driver 推荐
  • Playbook 编排

它负责回答 3 类问题:

  1. 哪些节点现在真正处于执行现场
  2. 这些节点最近巡检收口结果是什么
  3. Node Agent 回执队列现在是否健康

也就是说,这份 contract 的职责是:

让页面、CLI、Codex 看到同一份现场事实,而不是各自从日志和状态字串里猜


2. 入口接口

建议把下面这些接口视为同一观察面的一组正式入口:

  • GET /api/v1/ops/overview
  • GET /api/v1/ops/inspection-overview
  • GET /api/v1/ops/activity-stream
  • GET /api/v1/ops/nodes/{node_code}/handover
  • 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

配套关系:

  • overview
    • 首页收口
  • inspection-overview
    • 节点巡检收口
  • activity-stream
    • 最近异常活动
  • handover
    • 单节点接管阻断与下一步建议
  • delivery-queue
    • Node Agent 回执健康

正式要求:

  • 页面不能自己再定义第 5 套“观察口径”
  • CLI 不应重新从数据库拼现场
  • Codex 不应通过自然语言日志推断核心状态

3. Execution Scene Contract

来源:

  • GET /api/v1/ops/overview
  • overview.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_active
      • recent_only
      • standby
      • load_syncing
  • participation_label
    • 直接给页面、CLI、Codex 展示的人类可读标签
  • participation_reason
    • 用一句稳定摘要解释为什么节点落在当前分组

正式要求:

  • 页面必须直接区分:
    • 在线但未参与
    • 正在执行或领任务
    • 近窗刚参与但当前已收口
  • 新消费方不允许再从:
    • status
    • current_load
    • items_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 台参与节点,建议继续观察未回传样本节点。"
}

推荐状态:

  • disabled
  • waiting_sample
  • partial_coverage
  • healthy
  • full_capture

这层是“现场可见性”最关键的辅助状态。

正式要求:

  • 如果存在参与节点但日志回传还没开,优先推荐开关键回传
  • 如果日志回传已开但没有样本,优先推荐抓 Worker 日志
  • 页面和 Codex 都不能再自己定义另一套日志覆盖结论

节点级日志覆盖字段也应固定:

  • source_nodes
    • 为兼容旧页面保留的节点编码列表
  • source_node_summaries
    • 当前已有日志样本的节点级统计对象
  • covered_participating_node_summaries
    • 已参与且已拿到样本的节点对象
  • missing_participating_node_summaries
    • 已参与但尚未拿到样本的节点对象

source_node_summaries[] 至少应返回:

  • node_code
  • line_count
  • key_line_count
  • full_line_count
  • last_at
  • last_line

missing_participating_node_summaries[] 至少应返回:

  • node_code
  • region
  • role
  • participation_state
  • participation_label
  • participation_reason
  • missing_reason

推荐 missing_reason

  • no_sample
  • delayed_flush
  • filtered_out

兼容要求:

  • 旧页面仍可继续消费:
    • source_nodes
    • missing_sample_node_codes
  • 新页面、CLI、Codex 应优先消费:
    • source_node_summaries
    • covered_participating_node_summaries
    • missing_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 至少覆盖:
    • healthy
    • full_capture
    • historical_sample
    • waiting_sample
    • missing_sample
    • disabled
    • standby
    • unknown
  • 当节点当前在线但未参与时,必须明确返回 standby
  • 当节点正在参与但还没有样本时,必须明确返回 waiting_sample
  • 当节点已有历史样本但当前不在参与面中时,必须明确返回 historical_sample

5. Inspection Overview Contract

来源:

  • GET /api/v1/ops/inspection-overview
  • overview.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_kind
    • problem_label
    • problem_level
    • recommended_action_code
    • ui_intent

这样页面、CLI、Codex 才能真正消费“节点巡检结论”,而不是再从几十条 job 记录里回推问题。


6. Inspection Row Contract

rows[] 中每个节点建议最少包含:

  • node_code
  • region
  • role
  • effective_worker
  • problem_kind
  • problem_label
  • problem_level
  • recommended_action_code
  • ui_intent
  • latest_health_snapshot
  • latest_worker_logs
  • latest_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
  }
}

推荐状态:

  • healthy
  • retrying
  • dead_letter
  • stalled

正式约束:

  • 控制面要明确告诉操作者当前是 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

来源:

  • flush
  • replay
  • record replay
  • record 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 应支持限流 / limit
  • flush 应支持显式 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_text
    • status_label
    • focus_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

这样这套系统才能真正从“调试台”升级成“驾驶舱”。