# 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 解析、cloud-api REST 请求和 JSON 输出;默认正式模式不读取 `.device-pod/*.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.bootsharp` - `workspace.cat` - `workspace.rg` - `workspace.apply-patch` - `workspace.put` - `workspace.rm` - `workspace.rmdir` - `workspace.keil` - `workspace.build` - `debug.status` - `debug.chip-id` - `debug.download` - `debug.reset` - `io.ports` - `io.uart.read` - `io.uart.read-after-launch-flash` - `io.uart.write` - `io.uart.jsonrpc` `GET /debug-probe/chip-id`、`GET /io-probe/uart/1` 和 `GET /io-probe/uart/1/tail` 是用户态便捷 REST surface,但不能停留在静态面板或 fake probe;cloud-api 必须在完成 authenticate/grant 后创建对应只读 job,再经 `hwlab-device-pod` executor/gateway/device-host-cli 执行或返回同一套 blocker。 所有 job/status/output 和 probe GET 响应必须包含 `devicePodId`、`targetId`、`profileHash`、`traceId`、`operationId`、`status`、`freshness`、`blocker` 和 bounded output metadata。job output 文本默认最大 12000 bytes;超出时必须设置 `truncation.truncated=true`、`truncation.originalBytes`,并避免把完整 executor/gateway 原始输出嵌回 JSON。真实硬件响应不得把 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、不读取本地 profile 作为默认 authority、不直连 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。 - 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。 ## CLI 实现口径 `tools/device-pod-cli.ts` 是 v0.2 正式 CLI 实现;HWLAB code-agent runner 内的稳定短入口是 `hwpod`。`/app/skills/device-pod-cli/scripts/device-pod-cli.mjs` 只作为兼容启动器,在 runner PATH 缺少 `hwpod` 时临时 fallback,并应记录为 runner image/package 摩擦点。正式 CLI 的默认行为是: - `profile list/show` 调用 cloud-api `/v1/device-pods` 和 `/status`,只显示服务端脱敏 profile 摘要和 `profileHash`。 - `setup first-admin` 和 `admin device-pod upsert/grant` 只作为 cloud-api REST wrapper,用于首次空库 seed 或 admin profile/grant 管理;它们接受显式 `--profile-json` 或 `--device-pod-json`,不得读取本地 `.device-pod/*.json` 作为权威 profile。 - `devicePodId:workspace|debug-probe|io-probe...` selector 转换为 `POST /v1/device-pods/{devicePodId}/jobs` 或 job status/output/cancel REST,不直接调用 `/v1/rpc/hardware.invoke.shell`。 - `bootsharp --pod-id ` 和 `:workspace:/ bootsharp` 都转换为 `workspace.bootsharp` job,用于返回 workspace tree、AGENTS.md 摘要和当前路径提示;该入口是上下文恢复和 DS 派单的首个探测动作,不读取本地 profile。 - workspace 写操作覆盖 `apply-patch`、`put`、`rm`、`rmdir`、`build` 和 `keil add-source/remove-source` 等 Keil 工程维护动作。 - 源码局部编辑优先使用 `apply-patch`;`put` 只用于明确的整文件写入或新文件创建。 - UART 业务覆盖 `read`、`write`、`read-after-launch-flash` 和 `jsonrpc`;JSON-RPC 请求必须由 device-host-cli 校验 response id,除非显式传入允许 id mismatch 的业务参数。 - apply-patch 类失败必须返回可定位的 patch hint,例如缺少 `*** End Patch`、hunk 上下文不匹配或 header 错误;调用方应先重新读取目标文件再重试小 hunk,不应默认绕到整文件覆盖。 - mutating job 继续由 cloud-api 侧强制 lease/reason;CLI 只转发 `reason` 和 `leaseToken`,不在本地绕过。 - `profile create` 这类本地 profile bootstrap 在正式默认路径中返回 `legacy_profile_create_removed`;管理员应使用 cloud-api admin API 管理服务端 profile/grant。 - DS/device-pod prompt 和 skill 示例必须优先使用 `hwpod`,避免把长路径 wrapper 复制成常态命令;host job 轮询时 job id 必须作为 `status/output/cancel` 的紧随位置参数传入,不能放到 flags 后面或靠 shell 管道解析 JSON。 ## D601 F103 v0.2 Gateway SOP D601 Windows F103 gateway 的稳定命名使用 `gws_D601_F103`。不要因为当前 `devicePodId` 或样例设备名是 `device-pod-71-freq` / `71-FREQ`,把 gateway session 改成旧的 `gws_d601_win_71_freq`;profile route 和 Windows gateway 运行脚本必须使用同一组 F103 命名。 当前正式接入的 F103 v2 逻辑设备名为 `D601-F103-V2`。它的 Windows workspace 固定为 `F:\Work\D601-HWLAB`,Keil 工程为 `projects/01_baseline/Projects/MDK-ARM/atk_f103.uvprojx`,Keil target 为 `USART`,Keil 可执行文件为 `C:\Keil_v5\UV4\UV4.exe`,UART 为 `COM9`/`115200`。v0.2 服务端 profile 必须使用 `devicePodId=D601-F103-V2`,不得继续把该 workspace 暴露成 `device-pod-71-freq`。 Windows 侧固定入口: ```text Task Scheduler: HWLAB-DevicePodGateway-D601-F103 run script: C:\Users\liang\device-pod-gateway-rust\run-D601-F103.cmd local status: http://127.0.0.1:7001/status cloud API: http://74.48.78.17:19667 ``` `run-D601-F103.cmd` 中的关键环境变量应保持为: ```cmd set "HWLAB_GATEWAY_CLOUD_URL=http://74.48.78.17:19667" set "HWLAB_ENVIRONMENT=v02" set "HWLAB_GATEWAY_ID=gtw_D601_F103" set "HWLAB_GATEWAY_SESSION_ID=gws_D601_F103" set "HWLAB_GATEWAY_RESOURCE_ID=res_windows_host" set "HWLAB_GATEWAY_BOX_ID=box_windows_host" set "HWLAB_GATEWAY_CMD_CAPABILITY_ID=cap_windows_cmd_exec" set "HWLAB_GATEWAY_CMD_EXEC_ENABLED=1" set "HWLAB_GATEWAY_MAX_INFLIGHT=4" set "HWLAB_GATEWAY_CMD_TIMEOUT_MS=120000" set "HWLAB_GATEWAY_PORT=7001" ``` v0.2 `D601-F103-V2` 的服务端 profile route 必须和 gateway 注册保持一致: ```json { "gatewaySessionId": "gws_D601_F103", "resourceId": "res_windows_host", "capabilityId": "cap_windows_cmd_exec", "hostWorkspaceRoot": "F:\\Work\\D601-HWLAB", "hostCli": "node tools\\device-host-cli.mjs" } ``` 重启 gateway 时使用计划任务,不从临时 shell 直接启动长期进程: ```cmd schtasks /End /TN HWLAB-DevicePodGateway-D601-F103 schtasks /Run /TN HWLAB-DevicePodGateway-D601-F103 powershell -NoProfile -Command "Invoke-RestMethod http://127.0.0.1:7001/status | ConvertTo-Json -Depth 12" ``` 通过 UniDesk Windows route 操作时,工作目录应直接定位到 gateway 目录,例如 `D601:win/c/Users/liang/device-pod-gateway-rust`,再读取 `run-D601-F103.cmd` 或查询 `/status`。`/status` 中至少应看到 `gatewaySessionId=gws_D601_F103`、`cloudUrl=http://74.48.78.17:19667`、`session.status=connected`、`outbound.lastPollError=null`。 G14 v0.2 验收在 `G14:/root/hwlab-v02` 执行,先确认 Cloud Web 同源 CLI 能看到 device pod 状态,再用正式 `device-pod-cli` 创建并轮询 job: ```bash bun tools/hwlab-cli/bin/hwlab-cli.ts client device-pods status device-pod-71-freq \ --base-url http://74.48.78.17:19666 \ --full COOKIE=$(node -pe 'require("./.state/hwlab-cli/session.json").cookie') bun tools/device-pod-cli.ts device-pod-71-freq:workspace:/ ls \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" \ --reason "v02 gws_D601_F103 smoke" \ --timeout-ms 120000 | tee /tmp/hwlab-device-pod-job.json JOB=$(node -e 'const fs=require("fs"); const payload=JSON.parse(fs.readFileSync("/tmp/hwlab-device-pod-job.json", "utf8")); console.log(payload.body.job.id)') bun tools/device-pod-cli.ts job status --pod-id device-pod-71-freq "$JOB" \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" bun tools/device-pod-cli.ts job output --pod-id device-pod-71-freq "$JOB" \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" ``` `D601-F103-V2` 的验收命令使用同一条 v0.2 CLI 链路,只替换 `devicePodId` 并从 `bootsharp` 开始: ```bash bun tools/hwlab-cli/bin/hwlab-cli.ts client device-pods status D601-F103-V2 \ --base-url http://74.48.78.17:19666 \ --full COOKIE=$(node -pe 'require("./.state/hwlab-cli/session.json").cookie') bun tools/device-pod-cli.ts bootsharp --pod-id D601-F103-V2 \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" LEASE=$(bun tools/device-pod-cli.ts lease acquire --pod-id D601-F103-V2 \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" \ --reason "D601-F103-V2 build/download validation" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).body.leaseToken))') bun tools/device-pod-cli.ts D601-F103-V2:workspace:/ build start \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" \ --lease-token "$LEASE" \ --reason "D601-F103-V2 Keil build validation" \ --timeout-ms 120000 bun tools/device-pod-cli.ts D601-F103-V2:debug-probe download start \ --api-base-url http://74.48.78.17:19667 \ --cookie "$COOKIE" \ --lease-token "$LEASE" \ --reason "D601-F103-V2 Keil download validation" \ --capture-uart uart/1 \ --capture-duration-ms 8000 \ --timeout-ms 120000 ``` Code-agent/DS runner 内执行同类验收时,把 `bun tools/device-pod-cli.ts` 换成 `hwpod`;只有 `hwpod` 不在 PATH 时才临时 fallback 到 `/app/skills/device-pod-cli/scripts/device-pod-cli.mjs`。UART 串口若被 Windows 侧工具占用,应把 COM busy/access denied 记录为可选串口证据缺口,Keil build/download 是否通过以 job output 中的 build summary、`Programming Done`、`Verify OK` 和 `Application running` 为准。 DS 或其他 code-agent runner 由 `hwlab-cli client harness submit` 触发时,控制面应保持短连接:提交后用 `client harness result`、`client harness trace --limit ` 和 `client harness audit --require-bootsharp` 轮询,不把 UniDesk `ssh/tran` 连接长期挂在一次 `wait` 上。`client harness wait` 只用于短窗口观察,CLI 会把超长等待压到低于 UniDesk 透传硬超时的安全窗口,并在 JSON 中返回后续短轮询命令。 job 观察优先使用 cloud-api job 入口,避免从 shell 管道里解析整段 JSON: ```bash hwpod job status --pod-id D601-F103-V2 --api-base-url --cookie "$COOKIE" hwpod job output --pod-id D601-F103-V2 --api-base-url --cookie "$COOKIE" ``` `build status/output/cancel ` 和 `download status/output/cancel ` 只作为 selector 便捷别名;当 `` 是 `job_devicepod_*` 这类 cloud-api job 时,别名必须映射到 `/jobs/` 或 `/jobs//output`,不得误启动新的 build/download job。Keil host 侧子 job 仍保留 `build status ` 或 `download status ` 语义,用于查看 Windows `device-host-cli` 创建的嵌套 job。 搜索下载、烧录或串口证据时,带空格、管道符或较长正则的 `workspace rg` 应使用显式参数,避免本地 shell、selector 和 Windows host 三层参数解析产生歧义: ```bash hwpod D601-F103-V2:workspace:/ rg \ --pattern "Programming Done|Verify OK|Application running|Error" \ --path projects/01_baseline/captures \ --api-base-url --cookie "$COOKIE" ``` 验收通过条件:`client device-pods status` 返回 `status=ok` 且 `blocker=null`;gateway sessions 中 `gws_D601_F103` 为 online;`device-pod-cli` job 进入 `completed`,且 D601 Windows gateway `/status.lastDispatch.operationId` 与该 job 的 `operationId` 一致并有 `lastResultAt`。旧 `gws_d601_win_71_freq` session 只能作为历史 stale 线索,不能作为 v0.2 F103 gateway 的目标命名。 ## 测试规格 ## 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。 ## T4 阅读 docs/reference/spec-device-pod.md,然后用 cli 手动测试以下内容:对授权 Device Pod 运行 `workspace put`、`workspace rm`、`workspace rmdir`、`workspace keil add-source/remove-source` 和 `io-probe jsonrpc` 的 `--dry-run` 与一次真实小闭环。确认请求只经过 cloud-api job REST,输出 intent、reason、lease、traceId 和 bounded output,不读取本地 profile 或直连 gateway。 ## 规格的实现情况 | 规格项 | 状态 | 说明 | | --- | --- | --- | | 逻辑 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` executor;executor 已实现内部 job create/get/output/cancel lifecycle 和 gateway/device-host-cli dispatch adapter,无在线 gateway/device-host-cli 时返回 blocker。 | | G14 device-host 功能吸收 | 部分实现 | v0.2 job intent 已覆盖 workspace put/rm/rmdir、Keil 工程维护和 UART JSON-RPC,保持 cloud-api grant/lease/profile authority。 | | 禁止 fake 作为 DEV-LIVE | 已实现/持续约束 | 规格和服务 payload 要求显式标记 fake/source。 |