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

703 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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`
最小字段:
```json
{
"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`
最小字段:
```json
{
"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`
最小字段:
```json
{
"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`
最小字段:
```json
{
"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 类最近结果建议统一结构:
```json
{
"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`
最小字段:
```json
{
"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`
当前阶段建议最小字段:
```json
{
"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
- 建议跳转到哪里观察
最小返回体建议:
```json
{
"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`
最小字段建议:
```json
{
"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
这样这套系统才能真正从“调试台”升级成“驾驶舱”。