Files
pikasTech-HWLAB/docs/reference/spec-device-pod.md
T
2026-05-29 10:03:57 +08:00

275 lines
13 KiB
Markdown
Raw 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.
# Device Pod 正式接入规格
本文是 HWLAB `v0.2` 正式接入 `device-pod` 的规格说明。`device-pod` 是一个逻辑设备能力单元,不是 Kubernetes Pod 名称,也不是 code agent 本地 profile 文件。正式接入后,profile 定义 `device-pod`,因此 profile 必须由管理员和服务端权威存储管理,不能由 code agent 本地文件决定路由或资源边界。
实施跟踪见 [pikasTech/HWLAB#533](https://github.com/pikasTech/HWLAB/issues/533),原 `docs/plan/v02-device-pod-spec-migration.md` 和旧 device-pod MVP 计划全文已迁入该 issue 评论。
旧的 `device-pod-cli` 本地 profile 闭环只用于 CLI MVP 和真实硬件最小验证。进入正式多用户系统后,所有用户态设备访问必须收敛到:
```text
device-pod-cli or cloud-web
-> cloud-api auth + device_pod_grants + lease
-> hwlab-device-pod internal REST
-> gateway transport
-> device-host-cli
-> Keil / pyOCD / UART / target
```
## 在系统中的职责划分
`device-pod` 是云端可授权、可租约保护、可审计的逻辑设备能力单元。`hwlab-cloud-api` 是用户身份、grant、lease、profile authority 和用户态 REST API 的入口;`hwlab-device-pod` 是内部执行服务;`hwlab-gateway` 只承载 transport`device-host-cli` 只在硬件 host 侧执行 Keil、pyOCD、UART 和 workspace 操作。
普通用户、浏览器和 Code Agent session 不直接持有 gateway route、host workspace route、Kubernetes Service 直连能力或 profile 修改权。
## 设计目标
- 用最少组件把 `device-pod-cli` 从“本地 profile + RPC/gateway 调用”迁到“1:1 REST 请求”。
- `cloud-api` 是用户身份、device grant、profile authority 和 lease 判断入口。
- `hwlab-device-pod` 承接设备业务:profile 校验后的运行、job 生命周期、freshness、blocker、bounded output 和 gateway 调用。
- `device-pod-cli` 只做 selector 解析、REST 请求和 JSON 输出,不再保存或上传权威 profile。
- 第一阶段只部署一个 `hwlab-device-pod` Deployment/Service,管理多个逻辑 `devicePodId`,避免为每台设备创建独立 k8s Service/Deployment。
- 普通用户和 code agent session 不获得 Kubernetes 用户、Service 直连权限、gateway route 或 host workspace route。
## 逻辑模型
一个 `device-pod` 由四个设备能力要素组成:
```text
device-pod
= deviceTarget
+ debugInterface
+ projectWorkspace
+ ioInterface
```
- `deviceTarget`:被测设备目标,例如开发板、用户 PCB 或仪器模块。
- `debugInterface`:下载、复位、chip-id、probe 状态和调试连接能力。
- `projectWorkspace`:源码、工程、构建工具链和 artifact 边界。
- `ioInterface`:UART、DI/DO、采样、日志和其他设备 I/O 观测/控制能力。
`devicePodId` 是云端和用户界面的稳定身份。实际 k8s Pod 可以重建、滚动或扩容;用户和 code agent 不依赖实际 Pod name。
## Profile Authority
正式接入后,profile 是管理员侧资源:
```text
admin UI/API
-> cloud-api
-> device_pods.profile_json + profile_hash
-> hwlab-device-pod internal execution
```
code agent 本地文件只能作为非权威 hint/cache,最多包含:
```json
{
"devicePodId": "device-pod-71-freq",
"profileHash": "sha256:...",
"cloudApiUrl": "..."
}
```
本地 hint/cache 不得包含以下字段,也不得参与授权或执行路由:
- `gatewaySessionId`
- `resourceId`
- `capabilityId`
- `hostWorkspaceRoot`
- `hostCli`
- Windows workspace 路径
- probe UID、串口端口、Keil 路径等硬件路由字段
正式 profile 必须由 `cloud-api` 从 DB 读取;`hwlab-device-pod` 不接受浏览器、code agent 或 CLI 上传的 profile 作为执行依据。若 `hwlab-device-pod` 需要 profile snapshot,应只接受 `cloud-api` 内部服务凭据转发的 snapshot,或通过内部服务凭据向 `cloud-api` 拉取。该凭据不得挂载进 code agent session Pod。
## 内部架构
正式 device-pod 由 profile registry、job lifecycle、freshness/blocker、bounded output、gateway/device-host adapter 和 lease integration 组成。第一阶段只有一个 `hwlab-device-pod` Deployment 管理多个 `devicePodId`profile authority 和 user grant 在 cloud-api/Postgres 中,device-pod 服务只接受 cloud-api 内部调用。
当前 v02 部署中的 `hwlab-device-pod` 微服务实现情况见 [spec-v02-hwlab-device-pod-service.md](spec-v02-hwlab-device-pod-service.md)。
## Profile Shape
`device_pods.profile_json` 至少表达以下 server-side 字段:
```json
{
"schemaVersion": 1,
"devicePodId": "device-pod-71-freq",
"target": {
"id": "target-id"
},
"projectWorkspace": {
"workspaceRoot": "F:\\Work\\Project",
"projectPath": "FirmWare/MDK-ARM/app.uvprojx",
"targetName": "app",
"hexPath": "FirmWare/MDK-ARM/app/app.hex"
},
"debugInterface": {
"type": "cmsis-dap",
"probeUid": "...",
"uv4Path": "C:\\Keil_v5\\UV4\\UV4.exe"
},
"ioInterface": {
"uart": [
{ "id": "uart/1", "port": "COM4", "baudRate": 921600 }
]
},
"route": {
"gatewaySessionId": "gws_...",
"resourceId": "res_...",
"capabilityId": "cap_...",
"hostWorkspaceRoot": "F:\\Work\\Project",
"hostCli": "node tools\\device-host-cli.mjs"
}
}
```
`profile_json` 不得保存 Git key、云 token、kubeconfig、数据库 URL 或长期 secret。`profile_hash``cloud-api` 对规范化 profile JSON 计算并在所有响应中返回;用户可见响应只能返回脱敏 profile 摘要和 hash。
## 数据表口径
正式规格推荐 `device_pods` 直接保存权威 profile 和 hash,避免额外 profile 微服务:
```sql
CREATE TABLE IF NOT EXISTS device_pods (
id TEXT PRIMARY KEY,
name TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'disabled')),
profile_json TEXT NOT NULL DEFAULT '{}',
profile_hash TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
```
历史版本中的 `profile_ref``gateway_ref``device_pod_json` 可以在迁移时折叠进 `profile_json`。第一阶段不新增 `device_pod_profile_revisions`;需要审计版本、回滚或多环境批准时再引入 profile revision 表。
`device_pod_grants` 仍只表达用户是否拥有完整使用权;它不保存 profile,也不拆 capability。
## REST API
## API 接口说明
用户态 API 只经过 `cloud-api` 暴露:
```text
GET /v1/device-pods
GET /v1/device-pods/{devicePodId}/status
GET /v1/device-pods/{devicePodId}/debug-probe/chip-id
GET /v1/device-pods/{devicePodId}/io-probe/uart/1
GET /v1/device-pods/{devicePodId}/io-probe/uart/1/tail?maxBytes=12000
POST /v1/device-pods/{devicePodId}/jobs
GET /v1/device-pods/{devicePodId}/jobs/{jobId}
GET /v1/device-pods/{devicePodId}/jobs/{jobId}/output
POST /v1/device-pods/{devicePodId}/jobs/{jobId}/cancel
POST /v1/device-pods/{devicePodId}/leases
GET /v1/device-pods/{devicePodId}/leases/current
DELETE /v1/device-pods/{devicePodId}/leases/current
```
管理员 API 由 `cloud-api` 提供:
```text
POST /v1/admin/device-pods
PUT /v1/admin/device-pods/{devicePodId}
POST /v1/admin/device-pod-grants
DELETE /v1/admin/device-pod-grants/{devicePodId}/{userId}
```
`POST /v1/device-pods/{devicePodId}/jobs``intent` 表达具体业务,避免把 REST surface 扩张成大量一次性 route
```json
{
"intent": "workspace.build",
"args": { "profile": "debug" },
"reason": "DEV smoke"
}
```
第一阶段 intent 集合:
- `workspace.ls`
- `workspace.cat`
- `workspace.rg`
- `workspace.apply-patch`
- `workspace.build`
- `debug.status`
- `debug.chip-id`
- `debug.download`
- `debug.reset`
- `io.ports`
- `io.uart.read`
- `io.uart.write`
所有响应必须包含 `devicePodId``targetId``profileHash``traceId``operationId``status``freshness``blocker` 和 bounded output metadata。真实硬件响应不得把 fake、dry-run、SOURCE、LOCAL 或过期缓存标为 `DEV-LIVE`
## 微服务职责
| 服务 | 职责 |
| --- | --- |
| `hwlab-cloud-api` | 用户身份、admin/user、device grant、lease、profile authority、用户态 REST API、转发到内部 device-pod。 |
| `hwlab-device-pod` | 多 `devicePodId` 运行 registry、profile runtime validation、job store、freshness、bounded output、gateway/device-host-cli adapter。 |
| `device-pod-cli` | 把 `devicePodId:surface:path operation args` 1:1 转成 cloud-api REST;不保存权威 profile、不直连 gateway。 |
| `device-host-cli` | Windows host 侧自包含业务工具,负责 Keil、pyOCD、UART、workspace 文件操作。 |
| `hwlab-gateway` | 只做受控 transport,不理解用户权限和 device-pod 授权。 |
| `hwlab-cloud-web` | 展示用户可见 device pod、admin 管理 profile/grant、显示 job/status/freshness。 |
## Kubernetes 口径
v0.2 第一阶段使用一个 `hwlab-device-pod` Deployment 和一个 ClusterIP Service
```text
hwlab-v02/hwlab-device-pod
replicas: 1
manages: many devicePodId
```
不为每个 `devicePodId` 创建 Deployment、Service、Ingress、Secret 或 namespace。这样更符合当前规模:运维对象少、GitOps diff 少、问题定位简单,也不会把设备数量直接放大成 k8s 资源数量。
只有在满足以下条件时,才考虑拆分为多个 `hwlab-device-pod` shard 或 per-device workload
- 单个服务内 job 队列和 freshness 监控互相影响;
- 不同设备需要不同 host network、USB、Secret 或资源 request
- 设备数量增长到单实例状态管理明显吃力;
- 强隔离需求超过应用层 grant 和内部服务凭据能覆盖的范围。
普通用户和 code agent session Pod 不应直接调用 `hwlab-device-pod` Service。正式路径是 `code agent -> cloud-api -> hwlab-device-pod`
## 验收标准
- `device-pod-cli` 在正式模式下不读取 `.device-pod/<devicePodId>.json` 作为权威 profile,只向 cloud-api 提交 `devicePodId`、intent 和 args。
- 普通用户无授权时不能看到或使用任何 device pod;授权后拥有对应 device pod 的完整使用权。
- code agent 不能通过修改本地文件改变 gateway session、resource、host workspace、probe UID 或 UART port。
- `hwlab-device-pod` 不接受无内部服务凭据的 profile snapshot 或 job 请求。
- `hwlab-device-pod` 一个实例可以列出并执行多个 `devicePodId` 的状态/job。
- cloud-api compatibility fallback 只能返回 blocked authority payload,不得合成 fake device pod 数据或作为正式 device-pod DEV-LIVE 证据。
- 强副作用 job 必须有 reason,并在物理互斥需要时获取 `device_leases`
- `POST /v1/device-pods/{devicePodId}/leases` 只对已授权 actor 创建或刷新互斥租约,响应只返回一次性 `leaseToken`;后续强副作用 job 必须通过 `leaseToken``x-hwlab-device-lease-token` 证明持有租约。
- 撤销 device pod grant 必须释放该用户对同一 `devicePodId` 的活动 lease。
## 测试规格
## T1
阅读 docs/reference/spec-device-pod.md,然后用 cli 手动测试以下内容:普通用户无 grant 时访问 `/v1/device-pods` 不得看到任何 device pod;获得 grant 后能看到对应 devicePodId 和脱敏 profileHash,不能看到 gatewaySessionId、hostWorkspaceRoot 或 probe UID 等敏感路由字段。
## T2
阅读 docs/reference/spec-device-pod.md,然后用 cli 手动测试以下内容:尝试通过本地 `.device-pod/*.json` 修改 gateway route 或 workspace route,正式 cloud-api/device-pod 路径必须忽略该本地文件并继续使用服务端 profile authority。
## T3
阅读 docs/reference/spec-device-pod.md,然后用 cli 手动测试以下内容:提交一个强副作用 job,例如 download/reset,缺少 reason 或 lease 时必须被拒绝;获得 lease 后响应必须包含 devicePodId、profileHash、traceId、operationId、freshness、blocker 和 bounded output metadata。
## 规格的实现情况
| 规格项 | 状态 | 说明 |
| --- | --- | --- |
| 逻辑 device-pod 模型 | 已实现为规格 | 四要素、profile shape 和 Kubernetes 口径已定义。 |
| profile server authority | 部分实现 | cloud-api 保存正式 DB profile 并向用户返回脱敏摘要;device-pod executor 不接受用户上传 profile。 |
| 用户 grant + lease | 部分实现 | cloud-api 已实现 admin grant、可见性过滤、lease acquire/current/release、强副作用 job lease 校验和撤销授权释放 lease。 |
| REST/job API | 部分实现 | cloud-api 已实现 list/status/events/probe/job/output/cancel 和 lease API,并可把已授权 job 转发给内部 `hwlab-device-pod` executorexecutor 已实现内部 job create/get/output/cancel lifecycle,无在线 gateway/device-host-cli 时返回 blocker。 |
| 禁止 fake 作为 DEV-LIVE | 已实现/持续约束 | 规格和服务 payload 要求显式标记 fake/source。 |