Files
yuanshuai c91a64fddb
CI / Python 3.12 (push) Waiting to run
CI / Python 3.9 (push) Waiting to run
chore(repo): initialize team collaboration repository
2026-07-27 20:40:12 +08:00

91 lines
4.6 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.
# 控制平面 ↔ Host Agent 协议
> **当前状态(2026-07-27**:独立 Host Agent 已实现注册、注册口令、bearer token、
> 本地 `0600` 凭据文件、主机指纹和心跳;控制平面只保存 token SHA-256 摘要。
> 心跳迟到 15 秒会把 Host 降级为 `DEGRADED`,丢失 30 秒会标记为 `OFFLINE`
> 安全预检会先刷新该状态,避免失联 Host 被误判为可用。
> Step 租约、事件、完成与产物端点仍是下一阶段契约。`make demo` 暂时继续使用
> 控制平面内置模拟执行器验证状态、恢复与证据链。
Agent 是**拉模式**。控制平面永远不主动连接测试主机。
理由:客户测试主机在内网/NAT 后面,很多实验室根本不给外部反连;
拉模式还顺带解决了 Agent 重启后的重新接入问题。
## 端点
| 状态 | 方法 | 路径 | 用途 |
|---|---|---|---|
| 已实现 | POST | `/api/v1/agent/register` | 首次注册 / token 轮换;上报主机指纹与工具能力 |
| 已实现 | POST | `/api/v1/agent/{agent_id}/heartbeat` | bearer 心跳,上报健康和设备探针;返回待办指令 |
| 已实现 | GET | `/api/v1/agent/hosts` | 查看 Host 与 Agent 在线投影,不返回 token |
| 待实现 | POST | `/api/v1/agent/{agent_id}/lease` | 领取下一个步骤(长轮询,最多挂 20s) |
| 待实现 | POST | `/api/v1/agent/{agent_id}/steps/{step_id}/events` | 批量上报执行事件与日志片段 |
| 待实现 | POST | `/api/v1/agent/{agent_id}/steps/{step_id}/complete` | 上报步骤终态 + UnifiedResult + 产物清单 |
| 待实现 | POST | `/api/v1/agent/{agent_id}/artifacts` | 上传产物(日志 / 结果文件 / 画面) |
带外控制器走独立端点(它必须能在主机死后独立上报):
| 方法 | 路径 | 用途 |
|---|---|---|
| POST | `/api/v1/oob/{controller_id}/heartbeat` | 带外心跳 + 电源状态 + 温度 + 画面指纹 |
| POST | `/api/v1/oob/{controller_id}/observation` | 主动上报观测(蓝屏画面、掉电、按钮触发) |
## 心跳返回的指令
心跳响应已经带 `commands` 字段;当前固定为空数组。命令队列接入后,Agent 收到后立即执行:
```json
{
"ok": true,
"server_time": "2026-07-27T03:14:15Z",
"commands": [
{"kind": "CANCEL_STEP", "step_id": "..."},
{"kind": "SOFT_RESTART", "reason": "recovery_L1"},
{"kind": "EMERGENCY_STOP", "run_id": "..."},
{"kind": "REFRESH_ASSETS"}
]
}
```
紧急停止走心跳而不是新连接——心跳是唯一能保证到达的通道。
最坏情况延迟 = 心跳间隔(5s)。真正需要毫秒级的停止靠带外物理急停,不靠软件。
## 租约(lease)语义
```
Agent ──lease──▶ 控制平面
├─ 有活:分配步骤,step → DISPATCHED,写租约(TTL 60s
└─ 没活:长轮询挂起最多 20s,返回 204
Agent ──ack───▶ step → RUNNING
(租约到期未 ack 或未 complete)→ step 回 PENDING,重新派发
```
租约 TTL 必须**大于**步骤超时?不——恰恰相反:租约由 Agent 在执行期间通过心跳续期。
Agent 死了 → 心跳停 → 租约到期 → 步骤回收。这是 Agent 崩溃能被发现的机制之一
(另一条是心跳超时判定,见 state-machine.md §5)。
## 认证
首次注册带 `X-FlashOps-Enrollment-Token`。开发态未配置注册口令时允许本机演示;
生产态未配置时注册接口返回 503。注册成功下发 bearer token,之后每个请求带
`Authorization: Bearer <token>``agent_id``test_host_id` 绑定,重新注册会轮换 token。
原始 token 仅存于 Agent 本机 `0600` state 文件;控制平面数据库只保存 SHA-256 摘要。
Agent 客户端保留可选 gateway Basic Auth 能力,供未来重新启用边界认证或部署在其他网关后使用;
当前 `flashops.imagebrewing.com` 预览环境未启用这层认证。
**下一波必须升级**:方案 §6.2 要求"每个 Agent 使用唯一证书或密钥"。
bearer token 在客户内网够用,但要进客户安全评审得上 mTLS。
升级点在 `api/deps.py::require_agent()` 一处。
## 幂等(待实现)
Step 租约接入时,所有 Agent → 控制平面的 Step 写请求必须带
`Idempotency-Key`(Agent 生成的 UUID)。控制平面需要记录并拒绝重复副作用;
`run_events` 已有唯一约束 `(run_id, idempotency_key)`,但当前注册 / 心跳链路尚未实现
通用的请求幂等存储。
这一条在真实实验室里不是可选项:主机重启、网线松动、Wi-Fi 掉线是常态,
没有幂等就会出现"一次故障记了三条"的时间线,取证时说不清。