feat: add ops center and node onboarding flow

This commit is contained in:
Your Name
2026-04-18 23:52:51 +08:00
parent 246838ae4c
commit b9c29481b5
142 changed files with 89727 additions and 186 deletions

View File

@@ -0,0 +1,702 @@
# 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
这样这套系统才能真正从“调试台”升级成“驾驶舱”。