diff --git a/AGENTS.md b/AGENTS.md index aae5d1de..59ec7d90 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -66,7 +66,7 @@ HWLAB 是硬件实验室运行面和控制面项目。本文是 agent、指挥 - Code Agent 对话就绪与真实回复判定:[docs/reference/code-agent-chat-readiness.md](docs/reference/code-agent-chat-readiness.md) - DEV runtime hotfix runbook 与只读审计:[docs/reference/dev-runtime-hotfix-runbook.md](docs/reference/dev-runtime-hotfix-runbook.md) - Gateway 主动出站 demo、poll/result 和本地 smoke:[docs/reference/gateway-outbound-demo.md](docs/reference/gateway-outbound-demo.md) -- Device Pod 四要素、profile、HWLAB coder 预装合同与 CLI/server 分阶段计划:[docs/reference/device-pod.md](docs/reference/device-pod.md) +- Device Pod 正式规格、profile 服务端权威、REST/job 和多 devicePodId 服务口径:[docs/reference/spec-device-pod.md](docs/reference/spec-device-pod.md);旧链接兼容入口见 [docs/reference/device-pod.md](docs/reference/device-pod.md) - MVP E2E 验收测试与带编号测试报告 issue 规则:[docs/reference/MVP-e2e-acceptance.md](docs/reference/MVP-e2e-acceptance.md) - 指挥官协作、PR 和 runner 交接:[docs/reference/commander-collaboration.md](docs/reference/commander-collaboration.md) - M3 闭环发布运行手册:[docs/reference/m3-loop-rollout-runbook.md](docs/reference/m3-loop-rollout-runbook.md) diff --git a/docs/plan/device-pod-cli-mvp.md b/docs/plan/device-pod-cli-mvp.md index c8cb27fb..29a943ec 100644 --- a/docs/plan/device-pod-cli-mvp.md +++ b/docs/plan/device-pod-cli-mvp.md @@ -1,6 +1,6 @@ # Device Pod CLI MVP 计划 -本文描述 `device-pod-cli` 第一阶段实现计划。长期设备模型以 [../reference/device-pod.md](../reference/device-pod.md) 为准;本文只约束如何开发、调试和验收 CLI MVP。 +本文描述 `device-pod-cli` 第一阶段实现计划。它保留为历史 CLI MVP 闭环记录;正式 v0.2 多用户接入规格以 [../reference/spec-device-pod.md](../reference/spec-device-pod.md) 为准。本文中的本地 `.device-pod/` profile 只代表早期 MVP 输入,不适用于正式 profile authority、授权或路由决策。 ## 目标 @@ -16,7 +16,7 @@ gateway 保持单纯 cmd 转发;硬件协议、串口、下载器和厂商工 CLI 每次执行都从 HWLAB code agent workspace 的 `.device-pod/` 目录读取 profile。推荐文件名为 `.device-pod/.json`;后续可以兼容 YAML,但 MVP 优先使用 JSON,方便 schema 校验和 trace 记录。 -profile 是灵活 source-of-truth,不做中心注册。CLI 输出必须包含 `profilePath`、`profileHash`、`devicePodId` 和 `targetId`,便于确认每次操作实际使用的是哪个 profile。profile 中只允许描述 route、受控 workspace root、debug-probe 能力、io-probe 能力和 host CLI 能力;不得写入 Git key、云端 token、kubeconfig、数据库 URL 或长期 secret。 +在 CLI MVP 阶段,profile 是本地执行输入,不做中心注册。CLI 输出必须包含 `profilePath`、`profileHash`、`devicePodId` 和 `targetId`,便于确认每次操作实际使用的是哪个 profile。profile 中只允许描述 route、受控 workspace root、debug-probe 能力、io-probe 能力和 host CLI 能力;不得写入 Git key、云端 token、kubeconfig、数据库 URL 或长期 secret。正式 v0.2 接入后,这些路由字段必须迁移到 `cloud-api` 服务端权威 profile,本地 profile 不再决定 gateway/resource/workspace。 ## 命令口径 diff --git a/docs/plan/device-pod-server-mvp.md b/docs/plan/device-pod-server-mvp.md index 46f644c9..145c4516 100644 --- a/docs/plan/device-pod-server-mvp.md +++ b/docs/plan/device-pod-server-mvp.md @@ -1,102 +1,12 @@ -# Device Pod Server MVP 计划 +# Device Pod Server MVP 历史计划 -本文描述 `device-pod-cli` 跑通后的第二阶段:实现真正的 `device-pod-server`。长期设备模型以 [../reference/device-pod.md](../reference/device-pod.md) 为准;本文只约束 server MVP 的开发、调试、目标和验收。 +本文保留为历史计划入口。正式 v0.2 device-pod 接入规格已收敛到 [../reference/spec-device-pod.md](../reference/spec-device-pod.md),迁移计划见 [v02-device-pod-spec-migration.md](v02-device-pod-spec-migration.md)。 -## 目标 +旧计划中“`device-pod-server` 与 `device-pod` 一一对应”和“code agent workspace `.device-pod/` 是 profile source-of-truth”的口径,只适用于早期 CLI MVP 讨论,不适用于正式多用户系统。 -`device-pod-server` 是一个与 `device-pod` 一一对应的服务实例,用来提供短同步 RESTful API、短异步 RESTful job API 和后台监控缓存。它解决 CLI 直连 profile/gateway 之后仍无法持续监控设备的问题。 +当前权威口径: -MVP 对外目标是: - -- `device-pod-cli` 的稳定语义能力都能映射到 REST API。 -- server 后台维护 debug-probe 和 io-probe 的最新状态、freshness 和错误 blocker。 -- HWLAB cloud-api 代理 server API,前端只通过 cloud-api 查看设备状态,不直连 gateway 或用户 PC。 -- 前端最小版本只显示 `io-probe:/uart/1` 的最新状态/日志尾部,以及 `debug-probe` 的 chip ID 和 probe 状态。 - -## Profile 同步 - -server 阶段仍不引入中心 profile 注册表。HWLAB code agent workspace 的 `.device-pod/` 目录继续作为 profile source-of-truth;`device-pod-cli` 或 code agent 每次连接 server 前读取 `.device-pod/.json`,自动上传或刷新 profile。 - -server 只保存 active profile、`profileHash`、加载时间和校验结果,作为运行时缓存。profile 修改后不需要改 server 配置或重启服务;下一次 CLI/agent 同步即可更新 active profile。server 收到缺失、坏格式或能力不匹配的 profile 时返回 `profile-missing`、`profile-invalid` 或 `capability-mismatch` blocker,不回退到历史 profile 执行 mutating operation。 - -建议最小 profile API: - -```text -PUT /v1/profile -GET /v1/profile -``` - -CLI 可以提供调试入口: - -```text -device-pod-cli device-pod-71-freq:profile sync -``` - -`profile` 是 CLI/server 控制面同步动作,不属于 deviceTarget 四要素之一。 - -## 最小 REST API - -同步 API 只返回短状态和缓存快照: - -```text -GET /health -GET /v1/status -GET /v1/capabilities -GET /v1/debug-probe/status -GET /v1/debug-probe/chip-id -GET /v1/io-probe/status -GET /v1/io-probe/uart/1 -GET /v1/io-probe/uart/1/tail?maxBytes=12000 -``` - -异步 API 用于下载、复位、workspace build、串口写入和采样窗口等动作: - -```text -POST /v1/workspace/jobs -POST /v1/debug-probe/jobs -POST /v1/io-probe/jobs -GET /v1/jobs/{jobId} -GET /v1/jobs/{jobId}/output -POST /v1/jobs/{jobId}/cancel -``` - -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 -``` - -所有响应必须包含 `devicePodId`、`targetId`、`profileHash`、`traceId`、`operationId`、`status`、`freshness`、`blocker` 和 `evidence`。server、cloud-api 和 frontend 不得把 fake、dry-run、SOURCE、LOCAL 或缓存过期状态标成 `DEV-LIVE`。 - -## 开发方式 - -1. 从已跑通的 `device-pod-cli` profile schema、locator parser、operation adapter 和 JSON 输出合同中抽取共享模块,避免 CLI 与 server 两套语义分叉。 -2. 实现 server skeleton:health、profile sync、status、capabilities、job store、bounded output、lock 和 freshness 模型。 -3. 先接 fake `device-host-cli` adapter,稳定 chip ID、UART tail、job 状态和 blocker 行为。 -4. 再接 gateway/cmd/device-host-cli live adapter,保持 gateway 仍是 transport,不把 server 做成任意 shell 代理。 -5. 在 G14 DEV k3s 中部署单个 device-pod server 实例做 smoke,不改 PROD、不重启无关服务。 -6. 增加 cloud-api proxy 和前端最小展示,只显示 chip ID、probe 状态、UART1 最新片段和 freshness。 - -## 调试方式 - -- `GET /health` 只验证进程、profile loader 和基础依赖,不触发硬件动作。 -- `GET /v1/profile` 展示脱敏后的 active profile 摘要、`profileHash` 和校验状态。 -- fake profile + fake host CLI 用于 server 单测和 G14 DEV 无硬件 smoke。 -- live 调试先看 server 结构化日志中的 `profileHash`、`route`、`jobId`、`freshness` 和 blocker,再到 D518 `device-host-cli` 单独复现硬件问题。 -- UART tail、job output 和日志都必须截断或分页;默认调试输出不能 dump 全量串口日志、源码或 secret。 - -## 验收标准 - -MVP 通过至少需要满足: - -- 修改 `.device-pod/.json` 后,无需重启 server,下一次 CLI/agent 同步即可看到新的 `profileHash`。 -- 无 profile、坏 profile、过期 profile 和 capability mismatch 都有明确 blocker,不执行下载、复位或 I/O 写入。 -- `GET /v1/debug-probe/chip-id` 能通过 fake adapter 和至少一次 live smoke 返回 chip ID 或结构化硬件 blocker。 -- `GET /v1/io-probe/uart/1` 和 tail API 能显示 UART1 最新状态、freshness、截断信息和 evidence。 -- cloud-api 代理能返回同一组字段,前端最小页面能显示 chip ID、UART1 和 freshness,不直连 server/gateway。 -- mutating job 都是短 HTTP 创建、异步轮询、可取消、有 lock、有 approval reason。 -- server 与 CLI 的 operation 名称、错误码、profileHash 和 evidence 字段保持一致。 +- 一个 `hwlab-device-pod` Deployment/Service 在 v0.2 第一阶段管理多个逻辑 `devicePodId`。 +- `cloud-api` 和 Postgres 中的 `device_pods.profile_json` 是正式 profile authority。 +- `device-pod-cli` 只做 REST 客户端,不再用本地 profile 决定 gateway/resource/workspace。 +- `hwlab-device-pod` 承接 job、freshness、blocker 和 gateway/device-host-cli adapter,不保存用户 grant。 diff --git a/docs/plan/v02-device-pod-spec-migration.md b/docs/plan/v02-device-pod-spec-migration.md new file mode 100644 index 00000000..bb679a7d --- /dev/null +++ b/docs/plan/v02-device-pod-spec-migration.md @@ -0,0 +1,136 @@ +# v0.2 Device Pod 正式接入迁移计划 + +本文记录当前 v0.2 源码到 [../reference/spec-device-pod.md](../reference/spec-device-pod.md) 的迁移路径。目标是把已经跑通的 `device-pod-cli -> cloud-api/gateway -> device-host-cli` 最小闭环,迁移成正式多用户系统中的 `cloud-api` 授权 REST 和 `hwlab-device-pod` 设备业务服务。 + +## 当前态 + +从 v0.2 源码看,当前实现仍处于 fake service + CLI MVP 混合状态: + +- `cmd/hwlab-device-pod/main.mjs` 只提供 GET fake payload 和 `/health`,不连接真实硬件,不执行 job。 +- `internal/device-pod/fake-data.mjs` 构造 `/v1/device-pods`、`/status`、`/events`、`/debug-probe/chip-id` 和 UART tail 的 fake 数据。 +- `internal/cloud/server.mjs` 对 `/v1/device-pods` 做 upstream proxy;upstream 不可用时回退 fake payload,没有授权过滤、profile authority、job route 或 lease。 +- `tools/device-pod-cli.mjs` 从 code agent workspace 的 `.device-pod/` 读取 profile,并直接调用 `cloud-api /v1/rpc/hardware.invoke.shell`。 +- `skills/device-pod-cli/assets/device-host-cli.mjs` 承载了真实业务:workspace 文件操作、Keil build/download、pyOCD chip-id/reset、UART read/write 和 host 侧 job 状态。 +- `deploy/k8s/base/workloads.yaml` 已有 `hwlab-device-pod` Deployment,当前是 `replicas=1` 的 fake 服务。 +- G14 集群当前 `hwlab-dev` 存在 running `hwlab-device-pod` Service/Pod;`hwlab-v02` namespace 尚未落地 device-pod 运行面。 + +## 目标差距 + +| 领域 | 当前态 | 规格目标 | +| --- | --- | --- | +| Profile authority | code agent 本地 `.device-pod/*.json` 决定 gateway/resource/workspace | `cloud-api` DB 中 `device_pods.profile_json` 是唯一权威 | +| CLI | 读取 profile 并拼 RPC/gateway shell | 只把 selector 转成 cloud-api REST 请求 | +| Device service | fake GET payload | 多 `devicePodId` registry、profile runtime validation、job/freshness/blocker | +| Cloud API | proxy/fake fallback | auth、grant、lease、profile admin API、job proxy | +| K8s | 一个 fake Deployment | 一个正式 Deployment 管多个 `devicePodId` | +| 用户安全 | 本地 profile 可被 code agent 改写 | code agent 无法改变 gateway route 或硬件资源边界 | + +## 迁移原则 + +- 不引入单独 user-management 或 device-profile 微服务;profile authority 先放在 `cloud-api` 和 Postgres。 +- 不为每个 device pod 创建独立 k8s Deployment/Service;第一阶段一个 `hwlab-device-pod` 管多个逻辑 `devicePodId`。 +- 不把本地 `.device-pod/*.json` 做兼容权威路径;正式模式只允许本地 hint/cache。 +- 不让 `hwlab-device-pod` 接受用户、code agent 或浏览器上传的 profile 执行 job。 +- 不把 fake fallback 标为 live;fake 只用于 UI/fixture/降级展示。 + +## 阶段 1:Schema 收口 + +在多用户迁移中同步调整 `device_pods` 表: + +- 使用 `profile_json` 保存权威 profile。 +- 使用 `profile_hash` 保存规范化 profile hash。 +- 删除或不新增 `profile_ref`、`gateway_ref`、`device_pod_json` 这类容易分散权威的字段。 +- `device_pod_grants` 只表达用户是否拥有完整使用权,不保存 profile 和 capability。 + +不兼容处理:早期 `.device-pod/.json` 不导入为普通用户可写配置;如需复用,只能由 admin 一次性导入为 server-side profile。 + +## 阶段 2:cloud-api Profile 和 Grant API + +在 `hwlab-cloud-api` 内实现: + +- `POST /v1/admin/device-pods`:创建 device pod 和 profile。 +- `PUT /v1/admin/device-pods/{devicePodId}`:更新 profile、状态和名称。 +- `GET /v1/device-pods`:按 `admin/user + device_pod_grants` 过滤。 +- `GET /v1/device-pods/{devicePodId}/status`:授权后读取状态。 +- `POST /v1/device-pods/{devicePodId}/jobs`:授权、必要时 lease、转发内部 service。 + +cloud-api 返回给普通用户的 profile 只能是脱敏摘要和 `profileHash`。`route.gatewaySessionId`、`resourceId`、`hostWorkspaceRoot`、`hostCli`、probe UID 和串口端口不进入普通用户响应。 + +不兼容处理:未登录或未授权访问 `/v1/device-pods*` 返回 `401/403`,不再回退到 default fake device pod。 + +## 阶段 3:hwlab-device-pod 多设备服务 + +把 `cmd/hwlab-device-pod/main.mjs` 从 fake GET 改造成正式服务: + +- 维护内存 registry:`devicePodId -> profileHash/status/freshness/jobs`。 +- 提供内部 REST:status、short probe、job create/status/output/cancel。 +- 从 cloud-api 内部请求接收 profile snapshot,或用内部服务凭据向 cloud-api 拉取 profile。 +- 调用现有 gateway transport 和 Windows 侧 `device-host-cli`。 +- 解析 `device-host-cli` JSON 输出,统一 blocker、bounded output、freshness 和 evidence 字段。 + +`hwlab-device-pod` 不保存用户 grant,不判断用户身份;它只信任来自 cloud-api 的内部服务请求。内部服务凭据不得挂载进 code agent worker/session Pod。 + +不兼容处理:普通用户和 code agent 直连 `hwlab-device-pod` Service 不是支持路径;缺少内部服务凭据时返回 `401/403`。 + +## 阶段 4:device-pod-cli REST 化 + +把 `tools/device-pod-cli.mjs` 改为 1:1 REST 客户端: + +- selector parser 和 JSON 输出合同可以复用。 +- profile loader 降级为 hint/cache loader;正式执行只接受 `devicePodId`、operation、args 和 reason。 +- 删除或禁用正式路径中的 `/v1/rpc/hardware.invoke.shell` 直调。 +- `doctor` 改为检查 cloud-api 可见 device pod、profileHash、授权状态和 REST route,而不是要求本地 profile 可写。 + +不兼容处理:旧 profile bootstrap、uploadHostCli、uploadProfile 仍可保留在历史/维修命令中,但不能作为正式用户操作的默认下一步。 + +## 阶段 5:复用 device-host-cli 业务 + +短期不要重写 Windows 侧硬件能力。`hwlab-device-pod` 通过 gateway 调用已存在的 `device-host-cli`: + +- `workspace ls/cat/rg/apply-patch/build` +- `debug-probe status/chip-id/download/reset/launch-flash` +- `io-probe ports/read/write/read-after-launch-flash` + +`device-host-cli` 仍部署在 Windows workspace 的 `tools\device-host-cli.mjs`,但其路径来自 server-side profile,不来自 code agent 本地文件。 + +## 阶段 6:Cloud Web 和 Admin UI + +Cloud Web 调整: + +- 普通用户只显示授权 device pod。 +- admin 增加 device pod 创建/更新/禁用、profile 编辑、grant 授权/撤销的最小 UI。 +- Device Pod 右侧看板显示 `profileHash`、status、freshness、chip-id、UART tail 和 job 状态。 +- fake fallback 必须明确标记 fake,不参与 DEV-LIVE 判定。 + +## 阶段 7:v0.2 部署面 + +在 `hwlab-v02` namespace 中部署: + +- 一个 `hwlab-device-pod` Deployment,`replicas=1`。 +- 一个 `hwlab-device-pod` ClusterIP Service。 +- cloud-api 和 device-pod 共享的内部服务凭据 Secret,但不挂载给 code agent session Pod。 +- cloud-api 指向 v0.2 独立 Postgres/Secret,不复用 `hwlab-dev` pgdata。 + +不做 per-device Deployment/Service。未来只有在设备数量、隔离要求或资源差异明确出现后,才考虑 shard 或 per-device workload。 + +## 微服务调整 + +| 微服务 | 调整 | +| --- | --- | +| `hwlab-cloud-api` | 增加 profile authority、admin device-pod API、grant/lease、job proxy、脱敏 profile 响应。 | +| `hwlab-device-pod` | 从 fake GET 改成多 devicePodId REST/job service,调用 gateway/device-host-cli。 | +| `device-pod-cli` | 从本地 profile 执行器改成 cloud-api REST 客户端;本地 profile 只作 hint/cache。 | +| `device-host-cli` | 保持 Windows 侧业务能力,后续按真实硬件问题局部增强。 | +| `hwlab-cloud-web` | 增加 admin profile/grant UI,普通用户视图按授权过滤。 | +| `hwlab-agent-worker` | 为 code agent 提供 session-scoped cloud-api credential,不提供 gateway route/profile secret。 | +| GitOps/render | v0.2 新增 device-pod internal token Secret、env 注入和 `hwlab-v02` Service/Deployment。 | + +## 最小验证 + +- 修改 code agent 本地 `.device-pod/*.json` 不能改变正式 device operation 的 gateway/resource/workspace。 +- 未授权 `user` 调用 `/v1/device-pods/{devicePodId}/jobs` 返回 `403`。 +- 授权用户通过 `device-pod-cli` 发起 `workspace.build` 时,请求路径是 `/v1/device-pods/{devicePodId}/jobs`。 +- `hwlab-device-pod` 能在同一进程中管理至少两个 fake 或 real-profile `devicePodId`。 +- `debug.chip-id`、`io.uart.read` 和 `workspace.build` 的响应包含同一 `profileHash`、`traceId`、`operationId`、`freshness` 和 bounded output metadata。 +- code agent session Pod 中不存在可用于调用内部 `hwlab-device-pod` profile/job API 的服务凭据。 +- fake fallback 响应不会被前端或 health 判定为正式 DEV-LIVE device-pod 证据。 diff --git a/docs/plan/v02-multi-user-migration.md b/docs/plan/v02-multi-user-migration.md index aa4f66a9..05295157 100644 --- a/docs/plan/v02-multi-user-migration.md +++ b/docs/plan/v02-multi-user-migration.md @@ -12,8 +12,9 @@ - `web/hwlab-cloud-web/auth.mjs` 是轻量登录前端,默认 `admin/hwlab2026`,并有 local session fallback;它不是正式用户体系。 - `internal/dev-entrypoint/cloud-web-routes.mjs` 仍把 `POST /v1/agent/chat`、`POST /v1/agent/chat/cancel`、`GET /v1/agent/chat/result/*`、`GET /v1/agent/chat/trace/*` 视为 public proxy route,多用户下必须收敛为 auth-required。 - `internal/cloud/code-agent-session-registry.mjs` 以内存 Map 维护 session/conversation/facts,没有 owner、用户 session、Postgres 持久归属或跨 Pod 恢复。 -- `internal/cloud/server.mjs` 的 `/v1/device-pods` 只是代理 `hwlab-device-pod` 或回退 fake payload,没有按 actor 过滤,也没有 POST job/lease 权限入口。 +- `internal/cloud/server.mjs` 的 `/v1/device-pods` 只是代理 `hwlab-device-pod` 或回退 fake payload,没有按 actor 过滤,也没有 profile authority、POST job 或 lease 权限入口。 - `cmd/hwlab-device-pod/main.mjs` 和 `internal/device-pod/fake-data.mjs` 当前只提供 fake GET 数据,不连接真实硬件,也不持久化 device pod 管理数据。 +- `tools/device-pod-cli.mjs` 当前从 code agent workspace 的 `.device-pod/` 读取 profile,并直接调用 `/v1/rpc/hardware.invoke.shell`;正式多用户接入后,本地 profile 不能继续作为 gateway/resource/workspace 的权威来源。 - G14 当前集群里 `hwlab-dev` 有 `hwlab-g14-postgres` 和 PVC;`hwlab-v02` namespace 还未成为独立运行面。v0.2 权限数据不能混用 `hwlab-dev` pgdata。 ## 目标差距 @@ -22,7 +23,7 @@ | --- | --- | --- | | 用户 | 前端默认账号和本地 session fallback | Postgres `users` + `user_sessions`,`admin/user` 两角色 | | Code Agent session | 内存 ownerless registry | `agent_sessions.owner_user_id` 持久归属,result/trace/cancel 按 owner 校验 | -| Device Pod | fake GET 数据,所有人可见 | `device_pods` 管理表 + `device_pod_grants` 授权过滤 | +| Device Pod | fake GET 数据,所有人可见;CLI 本地 profile 可决定 route | `device_pods.profile_json/profile_hash` 服务端权威 + `device_pod_grants` 授权过滤 | | Device 操作 | 还不是正式 job/lease 权限入口 | cloud-api 授权后转发到 device-pod,独占操作用 `device_leases` | | Cloud Web 路由 | 部分 agent route public | 除 health/static/login 外,用户态 API 都 auth-required | | 微服务 | 没有用户服务 | 不新增用户服务,cloud-api 内置 user/auth/access 模块 | @@ -46,7 +47,7 @@ - 新增 `users`、`user_sessions`、`device_pods`、`device_pod_grants`、`device_leases`。 - 扩展现有 `agent_sessions`:`owner_user_id`、`conversation_id`、`thread_id`、`last_trace_id`、`session_json`、`updated_at`。 - 写入 bootstrap `admin`,密码只存 hash。 -- 为 fake/default `device-pod-71-freq` 写入一条 `device_pods` 记录,便于前端从 fake 数据迁到 DB 管理数据。 +- 为 fake/default `device-pod-71-freq` 写入一条 `device_pods` 记录,并由 admin 导入 server-side `profile_json/profile_hash`,便于前端从 fake 数据迁到 DB 管理数据。 不兼容处理:已有 ownerless `agent_sessions` 不继续作为普通用户 session。迁移时一次性归属 bootstrap `admin` 或标记 `expired`,迁移后新 session 必须有 owner。 @@ -90,10 +91,11 @@ - list/status 从 `device_pods` 读取管理数据,再按 actor 过滤。 - `admin` 可创建/更新/禁用 device pod。 +- `admin` 是 profile authority,只能通过 cloud-api 创建/更新 `device_pods.profile_json`。 - `admin` 可 upsert/delete `device_pod_grants`。 - 未授权用户访问具体 device pod 返回 `403`。 -不兼容处理:普通用户不再默认看到 `device-pod-71-freq`。如果需要演示用户看到它,必须显式给该用户授权。 +不兼容处理:普通用户不再默认看到 `device-pod-71-freq`。如果需要演示用户看到它,必须显式给该用户授权。code agent 本地 `.device-pod/*.json` 不再能改变正式 device pod route。 ### 阶段 6:Device jobs 和 lease @@ -105,7 +107,7 @@ cloud-api 先校验 actor 和 grant,再按操作类型获取 `device_leases`,最后转发到 `hwlab-device-pod` 内部 Service。 -不兼容处理:device-pod 服务不面向普通用户直接暴露,不接受浏览器或 worker 直连作为授权依据;所有真实操作必须经过 cloud-api。 +不兼容处理:device-pod 服务不面向普通用户直接暴露,不接受浏览器或 worker 直连作为授权依据,也不接受调用方上传 profile 作为执行依据;所有真实操作必须经过 cloud-api。 ### 阶段 7:v0.2 namespace 和数据面 @@ -127,8 +129,8 @@ cloud-api 先校验 actor 和 grant,再按操作类型获取 `device_leases` | `hwlab-edge-proxy` | 保持透明转发,确保 cookie/header 不被丢弃;不注入业务 actor。 | | `hwlab-agent-mgr` | session 创建参数和状态摘要带 `owner_user_id`、`session_id` label;不自行做最终授权。 | | `hwlab-agent-worker` | Pod/Job/PVC label 带 owner/session,工具调用 device pod 时走 cloud-api。 | -| `hwlab-device-pod` | 从 fake GET 逐步扩展到 jobs API;信任 cloud-api 内部调用,不保存用户 grant。 | -| `hwlab-agent-skills` | device-pod-cli 默认目标改为 cloud-api 授权入口,不直连 device-pod Service。 | +| `hwlab-device-pod` | 从 fake GET 逐步扩展到多 `devicePodId` jobs API;信任 cloud-api 内部调用,不保存用户 grant,不接受用户上传 profile。 | +| `hwlab-agent-skills` | device-pod-cli 默认目标改为 cloud-api 授权 REST 入口,不直连 device-pod Service,不再以本地 profile 作为正式 route authority。 | | GitOps/render | v0.2 增加 namespace、Postgres、SecretRef、ServiceAccount、PVC 和 env 注入。 | ## 最小验证 diff --git a/docs/reference/architecture.md b/docs/reference/architecture.md index 7e7f1c22..f2d59f24 100644 --- a/docs/reference/architecture.md +++ b/docs/reference/architecture.md @@ -38,7 +38,8 @@ For M3 hardware proof, the required runtime participants are: 真实设备目标以 `device-pod` 作为运行时能力单元。`device-pod` 统一封装 `deviceTarget`、`debugInterface`、`projectWorkspace` 和 `ioInterface` 四要素,并通过拆分的 debug/io 接口提供受控 REST/job -能力;权威模型见 [device-pod.md](device-pod.md)。 +能力;正式 profile authority、REST/job 和服务部署规格见 +[spec-device-pod.md](spec-device-pod.md)。 `v0.2` 多用户访问模型只保留 `admin` 和 `user` 两类角色:code agent session 归属于创建用户,device pod 由 `admin` 管理并按用户授权,授权即全权限; diff --git a/docs/reference/device-pod.md b/docs/reference/device-pod.md index 95d9d5d8..204fdcae 100644 --- a/docs/reference/device-pod.md +++ b/docs/reference/device-pod.md @@ -1,221 +1,13 @@ -# Device Pod 设备目标能力模型 +# Device Pod 参考入口 -本文是 HWLAB `device-pod` 的长期参考口径。`device-pod` 是一个逻辑实验台抽象,用于把一个可开发、可编译、可下载、可复位、可观测 I/O 的设备目标封装成统一能力单元。进入 server 阶段后,该能力单元可以由单副本 k8s Pod 承载,并对外提供统一 REST/job API;对外稳定身份由 Service/Deployment 承载,不依赖实际 Pod name。 +本文保留为旧链接兼容入口。HWLAB `v0.2` 正式 `device-pod` 接入规格以 [spec-device-pod.md](spec-device-pod.md) 为权威。 -`device-pod` 不等同于裸物理设备,也不是泛化远程 shell。它是以下四个要素的运行时组合: +关键口径: -```text -device-pod -= deviceTarget -+ debugInterface -+ projectWorkspace -+ ioInterface -``` +- `device-pod` 是逻辑设备能力单元,不是 Kubernetes Pod name。 +- profile 定义 `device-pod`,因此正式接入后 profile 必须由 `admin` 和 `cloud-api` 服务端权威管理。 +- code agent 本地 `.device-pod/` profile 只属于早期 CLI MVP 闭环;正式多用户系统中不得作为路由、授权或硬件资源边界的 source of truth。 +- v0.2 第一阶段使用一个 `hwlab-device-pod` Deployment/Service 管理多个逻辑 `devicePodId`,避免过早引入 per-device k8s workload 运维压力。 +- 正式访问路径是 `device-pod-cli/cloud-web -> cloud-api -> hwlab-device-pod -> gateway -> device-host-cli -> target`。 -## 设计抽象 - -`device-pod` 可以理解成云端可拥有和调度的一张嵌入式实验台。云端拥有的是 `device-pod` 这个逻辑能力单元,不是直接拥有某一块裸板、某一根探针或某一台用户 PC。只要 profile 能把 `deviceTarget`、`debugInterface`、`projectWorkspace` 和 `ioInterface` 四要素绑定清楚,code agent 就可以围绕同一个 `devicePodId` 完成源码修改、构建、下载、复位、状态读取和 I/O 观察,从而覆盖嵌入式开发的完整闭环。 - -四要素的物理落点可以不同,也可以复用同一个硬件实体的不同能力: - -- `deviceTarget` 是被调试和被验证的目标设备。 -- `debugInterface` 提供下载、烧录、复位、debug probe 状态和芯片识别等调试能力。 -- `projectWorkspace` 承载源码、工程、Keil/GCC 等工具链和编译产物。 -- `ioInterface` 提供 AI、AO、DI、DO、UART、截图、日志和其他实时或近实时观测/控制能力。 - -`device-pod` 因此是逻辑打包关系,而不是一对一物理设备关系。一个 DAPLink/MKLink 类探针可以同时拆出 `debugInterface` 的 SWD/CMSIS-DAP 能力和 `ioInterface` 的 UART 能力;同一个 `device-pod` 也可以把该探针与调试 box、USB 摄像头、串口工具或厂商上位机组合起来,共同形成对同一个 `deviceTarget` 的完整访问能力。 - -## 平台化服务模式 - -`device-pod` 把设备目标和访问能力封装成统一逻辑单元后,HWLAB 可以在同一技术模型上承载多种交付模式。不同模式的差异主要在于 `deviceTarget`、probe、workspace、云平台和运维责任分别由谁提供,但对 code agent 和用户界面暴露的仍应是稳定的 `devicePodId`、能力边界、锁、job、trace 和 evidence。 - -- **Device Pod 租赁**:HWLAB 运营常用开发板、调试探针、I/O 工具和 workspace 的设备机房,用户无需自备硬件即可在云端基于真实原型开发;软件验证后可以自行找硬件工程师,或委托平台侧硬件工程师继续绘制 PCB。 -- **用户 PCB 托管**:用户已经完成 PCB 或样机,委托 HWLAB 托管该 `deviceTarget`,平台提供或代管 probe、I/O 工具、workspace 和 `device-pod` 运行面,用户通过云端继续开发、测试和迭代。 -- **用户自有设备接入**:用户保留自己的 `deviceTarget`、debug probe 和 I/O probe,只接入 HWLAB 云;平台把这些资源通过 profile/gateway/device-host-cli 抽象为 `device-pod`,供用户自己远程开发。 -- **委托开发与运维**:在用户自有设备接入的基础上,用户可以授权平台内其他开发者、维护人员或自动化 code agent 围绕同一个 `device-pod` 进行软件开发、调试、故障复现、在线维护和验收。 -- **私有 HWLAB 云部署**:用户公司内部部署自有 HWLAB 云和自有设备池,平台提供安装、升级、运维和最佳实践支持;设备、数据和权限留在用户私有环境内,`device-pod` 模型保持一致。 - -这些模式都不能绕过 `device-pod` 的安全边界:profile 仍不得携带长期 secret;mutating operation 仍需 approval、reason、锁和 evidence;平台不得把缓存、dry-run、前端状态或本地模拟误报为真实 DEV-LIVE 设备证据。 - -## 实施计划 - -长期模型以本文为准;分阶段实现计划单独维护,避免把一次性开发步骤写进长期参考: - -- [Device Pod CLI MVP 计划](../plan/device-pod-cli-mvp.md):先做 code agent 可调用的独立 CLI,通过 profile、gateway 和用户 PC 侧 `device-host-cli` 跑通 workspace、debug-probe 和 io-probe 的最小闭环。 -- [Device Pod Server MVP 计划](../plan/device-pod-server-mvp.md):在 CLI 跑通后再实现真正的 `device-pod-server`,提供与 CLI 对应的 RESTful API、后台监控缓存和前端最小可视化。 - -## Profile 注册与同步 - -MVP 不使用刚性的中心 profile 注册表。`device-pod` profile 的灵活源头是 HWLAB code agent workspace 下的 `.device-pod/` 目录,推荐按 `devicePodId` 存放 profile 文件,例如 `.device-pod/device-pod-71-freq.json`。 - -`device-pod-cli` 每次执行都从当前 code agent workspace 的 `.device-pod/` 读取 profile,并在输出中保留 `profilePath` 和 `profileHash`。进入 `device-pod-server` 阶段后,CLI 或 code agent 仍以 `.device-pod/` 为 profile source-of-truth;每次连接 server 前自动上传或刷新 profile,server 只把 active profile 当作运行时缓存,不把它变成长期配置真相。 - -profile 必须只描述 target、debugInterface、projectWorkspace、ioInterface、gateway route、host CLI 能力和受控路径边界;不得放入 Git key、云端 token、kubeconfig、数据库 URL 或其他长期 secret。profile 校验失败时应返回 `profile-invalid` 或 `profile-missing` blocker,而不是回退到默认设备或任意 shell。 - -## Selector 路径纪律 - -`device-pod-cli` selector 是稳定 API 语法,不是自然语言路径猜测器。`workspace`、`debug-probe` 和 `io-probe` selector 后的路径必须由调用方拼对,尤其是 I/O probe 必须把路径写成一个 shell token,例如 `device-pod-71-freq:io-probe:/uart/1 read`。 - -不要迁就透传路径拼错、`/` 两侧空格或 argv 被模型拆开的写法。`io-probe:/uart / 1 read`、`io-probe:/uart/ 1 read` 这类输入应在 runner 侧直接返回 `invalid-request` 并给出正确写法 hint,不应被自动归一化成 `uart/1`,更不应继续透传到 gateway 或 Windows `device-host-cli`。这样可以把“命令写错”和“硬件/串口不可达”明确区分,避免下游工具为模型 spacing drift 背锅。 - -## HWLAB coder 预装合同 - -HWLAB coder / code agent runner 镜像必须通过正式 CI/CD 预装 device-pod 最小闭环所需的三类入口: - -- **cmd 透传入口**:`/app/tools/tran.mjs` 是 runner 内的稳定合同入口,负责通过已注册 gateway session 对 Windows PC 执行 `cmd`、PowerShell、upload 和 download,并承担 UTF-8、cwd、stdout/stderr、quoting 和文件传输边界。`/app/tools/hwlab-gateway-tran.mjs` 是同一能力的兼容实现入口,不是第二套 transport;若兼容入口存在但 `/app/tools/tran.mjs` 缺失,CI/CD 预装判定不通过,必须补齐 `tran.mjs` 短名,而不是让 device-pod 逻辑适配多个临时入口。 -- **device-pod-cli 入口**:runner 内必须预装 `device-pod-cli` skill,稳定入口为 `/app/skills/device-pod-cli/scripts/device-pod-cli.mjs`,其实现指向 `/app/tools/device-pod-cli.mjs`。code agent 应通过该 CLI 读取 workspace 下 `.device-pod/.json`,再走 cloud-api/gateway/device-host-cli 调用设备能力。 -- **device-host-cli 资产**:runner 内必须随 `device-pod-cli` skill 打包 Windows 侧自包含 host CLI 资产,稳定路径为 `/app/skills/device-pod-cli/assets/device-host-cli.mjs`。新 Windows 硬件 PC 接入时,不运行独立安装器;code agent 使用预装 cmd 透传入口把该资产发送到目标 workspace 的 `tools\device-host-cli.mjs`,再通过同一 cmd 透传入口执行 `node tools\device-host-cli.mjs health` 验证。 - -`/app/skills` 是 HWLAB coder 镜像内 code agent skill 的唯一 canonical 位置。`device-pod-cli` 不再同步到 `/root/.agents/skills`、`/home/ubuntu/.agents/skills` 或 workspace 下的 skill 副本;这些副本会造成 discovery 口径、wrapper 相对路径和 CI/CD 预装内容不同步。默认 prompt、`HWLAB_CODE_AGENT_SKILLS_DIRS`、skill discovery 和验收都必须指向 `/app/skills`。如果某个运行时只能读取 home 目录,应先修复 runner 的 skill discovery 或环境变量,而不是复制第二份 skill。 - -`device-host-cli` 不是一次性预装脚本,而是 HWLAB 内部 code agent 可继续热开发的 host 侧工具。DeepSeek、Codex runner 或其他 HWLAB 内部 code agent 在真实硬件闭环中遇到 Keil、debug probe、串口、文件工作区或硬件启动路径不顺手时,优先在目标 Windows workspace 的 `tools\device-host-cli.mjs` 上新增或修复具名设备能力,并通过 `device-pod-cli -> cloud-api -> gateway -> device-host-cli` 的真实链路热验证;验证通过后再把同一实现回填到 `/app/skills/device-pod-cli/assets/device-host-cli.mjs` 对应的 HWLAB repo 资产,进入下一次 CI/CD 预装。不得因为 host CLI 暂时不顺手而把 `device-pod-cli` 退化为泛化 cmd/shell 入口。 - -目标 Windows workspace 的最小布局为: - -```text -\tools\device-host-cli.mjs -\.device-pod\.json -``` - -完成上述布局后,profile 中的 `hostCli` 应指向 `node tools\device-host-cli.mjs`,`device-pod-cli` 才能稳定执行 workspace、debug-probe 和 io-probe 操作。`device-host-cli` 必须自包含,不能在运行时依赖 Windows 侧 skill 目录;Keil、serial-monitor、mklink、文件编辑等 skill 只允许作为实现参考。 - -CI/CD 判定口径是:构建产物中同时存在 `/app/tools/tran.mjs`、`/app/tools/hwlab-gateway-tran.mjs`、`/app/tools/device-pod-cli.mjs`、`/app/skills/device-pod-cli/SKILL.md`、`/app/skills/device-pod-cli/scripts/device-pod-cli.mjs` 和 `/app/skills/device-pod-cli/assets/device-host-cli.mjs`,`node /app/tools/tran.mjs --help` 能展示 cmd/ps/upload/download 透传帮助,`HWLAB_CODE_AGENT_SKILLS_DIRS=/app/skills`,且 code agent skill discovery 从 `/app/skills` 发现 `device-pod-cli`。只有这些入口都在 runner 中可见,才能称为 HWLAB coder 已具备 device-pod 最小预装能力。 - -## 四要素 - -### `deviceTarget` - -`deviceTarget` 是被测设备目标本身,可以是一个 MCU 最小系统板、完整业务板卡、仪器模块或其他可被 HWLAB 操作的目标对象。它必须有稳定的 `targetId`,用于锁、trace、evidence 和用户界面归因。 - -### `debugInterface` - -`debugInterface` 是设备的开发和调试接口,代表下载、烧录、复位、debug probe 状态等能力。典型物理实现包括 DAPLink、J-Link、ST-Link 或厂商下载器。 - -第一版路径固定为: - -```text -device-pod -> cloud-api -> gateway -> cmd -> device-host-cli -> downloader/debug probe -> target -``` - -`debugInterface` 只暴露设备语义能力,例如 `debug.probe`、`debug.download`、`debug.reset`。不得把它退化成任意 shell 执行入口。 - -`device-host-cli` 必须是用户 PC 侧自包含组件;Keil、串口、DAPLink、J-Link 或其他 skill 代码只能作为实现参考,不能成为运行时依赖。 - -`debugInterface` 不承载源码编译、工程发现或工具链定义;这些属于 -`projectWorkspace`。`debugInterface` 可以消费 `projectWorkspace` 产生的 -artifact,例如 `.hex`、`.bin` 或 `.elf`,并负责把该 artifact 下载、校验、 -复位或附着调试到 `deviceTarget`。 - -### `projectWorkspace` - -`projectWorkspace` 是设备对应的源码、工程文件和编译工具链上下文,例如 Keil 工程、target 名称、构建脚本和 workspace root。它描述如何从源码生成可下载产物,也为 debug/download job 提供工程路径和工具链边界。 - -`projectWorkspace` 可以和 `debugInterface` 位于同一台用户 PC,也可以只通过 gateway 暴露受控 CLI。HWLAB 不直接读取 secret、kubeconfig、DB URL 或完整源码内容;探测必须有界。 - -工程源码的编译和工具链归属 `projectWorkspace`,包括: - -- 源码根目录、工程文件和 target 名称; -- build profile、编译参数和工具链入口; -- build、clean、artifact list 等工程动作; -- `.hex`、`.bin`、`.elf` 等下载产物的位置和元数据。 - -因此 build 失败应归类为 workspace/toolchain blocker;download、reset 或 -probe attach 失败才归类为 debug/probe/target blocker。 - -典型流程为: - -```text -projectWorkspace.build --> artifact --> debugInterface.download --> debugInterface.reset --> ioInterface.status/sample/uart -``` - -### `ioInterface` - -`ioInterface` 是设备的实时或近实时 I/O 观测与控制接口,代表 UART、AI、AO、DI、DO、状态采样、日志抓取、截图等能力。典型实现包括 DAPLink/MKLink 自带串口、独立串口工具、调试 box、USB 摄像头、USB/WiFi/厂商协议上位机或 `device-host-cli`。 - -MVP 推荐路径为: - -```text -device-pod -> cloud-api -> gateway -> cmd -> device-host-cli -> serial/WiFi/USB/vendor protocol -> target -``` - -这里 `cmd` 只作为启动受控 PC 侧 CLI 的 transport;串口、WiFi、厂商协议和硬件细节必须隔离在 `device-host-cli` 或等价上位机 CLI 后面。 - -## 接口拆分 - -`debugInterface` 和 `ioInterface` 必须在模型和 API 中拆开,即使它们由同一个物理探针提供。例如 DAPLink 可以同时提供 SWD/CMSIS-DAP 下载调试口和 UART 串口: - -```text -DAPLink physical probe --> debugInterface: SWD/CMSIS-DAP --> ioInterface: UART -``` - -拆分原因: - -- `debug.download`、`debug.reset` 是低频强副作用动作,通常需要 approval 和 target 互斥锁。 -- `io.status.read`、`io.sample` 是短读或短窗口采样,关注 freshness、采样窗口和输出有界性。 -- `io.write` 类能力属于硬件 I/O 控制,未来也需要 approval,但不能和下载/烧录混成同一类风险。 -- trace、audit、evidence 必须能区分 `debug` 路径和 `io` 路径。 - -MVP 不拆成两个 k8s Pod;同一个 `device-pod` 内保留两个一等接口,共享 `deviceTarget`、`projectWorkspace`、锁、job 和 evidence 归因。 - -## 最小 REST 口径 - -同步 API 只用于短状态和短探测: - -```text -GET /health -GET /v1/profile -GET /v1/status -GET /v1/capabilities -GET /v1/workspace/status -GET /v1/debug/status -GET /v1/io/status -``` - -异步 API 用于下载、复位、采样等动作;每个 HTTP 调用必须短,长耗时由 job 轮询表达: - -```text -POST /v1/workspace/jobs -POST /v1/debug/jobs -POST /v1/io/jobs -GET /v1/jobs/{jobId} -GET /v1/jobs/{jobId}/output -POST /v1/jobs/{jobId}/cancel -``` - -第一版可接受的 intent 示例: - -```text -workspace.detect -workspace.build -workspace.clean -workspace.artifact.list -debug.probe -debug.download -debug.reset -io.status.read -io.sample -io.uart.fetch -``` - -所有响应必须保留 `devicePodId`、`targetId`、`traceId`、`operationId`、`gatewaySessionId`、`interface`、`intent`、`route`、`status` 和 blocker/evidence 字段。真实控制动作不得用 `SOURCE`、`LOCAL`、`DRY-RUN` 或前端状态冒充 `DEV-LIVE`。 - -## 最小系统示例 - -一个 STM32F103 最小系统加 DAPLink 可以完整构成一个 `device-pod`: - -```text -deviceTarget: STM32F103 最小系统 -debugInterface: DAPLink SWD/CMSIS-DAP -projectWorkspace: STM32F103 源码、Keil 工程和编译工具链 -ioInterface: DAPLink UART 串口 -``` - -最小真实闭环也可以表述为:STM32F103 最小系统板作为 `deviceTarget`,一只 MKLink/DAPLink 类探针同时提供 `debugInterface` 和第一版 `ioInterface`,其中 debug 侧负责下载、复位和芯片识别,I/O 侧先只开放 UART;`projectWorkspace` 映射到用户 PC 上的源码目录、Keil 工程和工具链,再通过 gateway/device-host-cli 暴露受控构建和 artifact 能力。这样四要素齐备后,云端 code agent 就能围绕同一个 `device-pod` 完成改源码、编译、下载、复位、读串口的最小嵌入式开发闭环。 - -该模型后续可以扩展到更多 target 或更复杂 I/O probe,但每个 `device-pod` 仍只代表一个明确的设备目标能力单元。 +迁移计划见 [../plan/v02-device-pod-spec-migration.md](../plan/v02-device-pod-spec-migration.md)。历史 CLI MVP 闭环见 [../plan/device-pod-cli-mvp.md](../plan/device-pod-cli-mvp.md),但其中本地 profile 权威口径不适用于正式 v0.2 多用户接入。 diff --git a/docs/reference/multi-user-access.md b/docs/reference/multi-user-access.md index 41afe20a..78616137 100644 --- a/docs/reference/multi-user-access.md +++ b/docs/reference/multi-user-access.md @@ -92,21 +92,22 @@ CREATE INDEX IF NOT EXISTS idx_agent_sessions_conversation ON agent_sessions(con ### `device_pods` -设备能力单元的管理表;profile 语义仍以 [device-pod.md](device-pod.md) 为准。 +设备能力单元的管理表;正式 profile authority 和执行语义以 [spec-device-pod.md](spec-device-pod.md) 为准。profile 定义 device pod,因此 profile 必须由 `admin` 通过 cloud-api 管理,不能由 code agent 本地 `.device-pod/` 文件决定。 ```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_ref TEXT NOT NULL DEFAULT '', - gateway_ref TEXT NOT NULL DEFAULT '', - device_pod_json TEXT NOT NULL DEFAULT '{}', + profile_json TEXT NOT NULL DEFAULT '{}', + profile_hash TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); ``` +`profile_json` 中的 gateway route、host workspace、probe UID、串口端口和 host CLI 都是服务端权威字段;普通用户响应只能看到脱敏 profile 摘要和 `profile_hash`。 + ### `device_pod_grants` 普通用户对 device pod 的授权关系;存在即全权限。 diff --git a/docs/reference/spec-device-pod.md b/docs/reference/spec-device-pod.md new file mode 100644 index 00000000..4b4557f0 --- /dev/null +++ b/docs/reference/spec-device-pod.md @@ -0,0 +1,228 @@ +# Device Pod 正式接入规格 + +本文是 HWLAB `v0.2` 正式接入 `device-pod` 的规格说明。`device-pod` 是一个逻辑设备能力单元,不是 Kubernetes Pod 名称,也不是 code agent 本地 profile 文件。正式接入后,profile 定义 `device-pod`,因此 profile 必须由管理员和服务端权威存储管理,不能由 code agent 本地文件决定路由或资源边界。 + +旧的 `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-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。 + +## 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 只经过 `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 +``` + +管理员 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/.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。 +- fake fallback 只能标记为 fake/source,不得作为正式 device-pod DEV-LIVE 证据。 +- 强副作用 job 必须有 reason,并在物理互斥需要时获取 `device_leases`。