# PJ2026-010403 API契约

## 修改历史

| 版本 | 对应 commit id | 更新日期 | 变更说明 |
| --- | --- | --- | --- |

当前正文仍在规格治理草稿中；未定稿前不新增版本号，不为单次编辑追加 `待提交` 版本。

## 正文

## PJ2026-010403 API契约需求规格

## 1. 文档控制

| 字段 | 内容 |
| --- | --- |
| 编号 | PJ2026-010403 |
| 短名 | API契约 |
| 层级 | L2 课题 |
| 状态 | 已生效 |
| 实现引用版本 | draft-2026-06-20-p0-workbench-pure-read-api; draft-2026-06-20-p0-error-diagnostics; draft-2026-06-20-p1-view-local-timing-ticker; draft-2026-06-22-p1-workbench-redis-derived-cache; PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-zero-split-durable-realtime; draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-01050105 Web鉴权 draft-2026-06-18-p0-auth |
| 需求规格模板 | [ISO/IEC/IEEE 29148 需求规格模板](../../templates/iso-iec-ieee-29148-requirements-spec-template.md) |
| 上级规格 | [PJ2026-0104 客户端](PJ2026-0104-client.md) |
| 关联规格 | [PJ2026-0104010803 Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md)、[PJ2026-01060505 Workbench性能](PJ2026-01060505-workbench-performance.md) |
| 规格治理索引 | [规格治理](spec-governance.md) |

本文采用 ISO/IEC/IEEE 29148 需求规格模板的项目裁剪版：正文只保留 API 契约的稳定使命、范围、术语、系统边界、内部分工和原子需求。

## 2. 目的和范围

### 2.1 目的

API契约负责定义 Cloud Web、HWLAB CLI 和自动化脚本共同使用的 RESTful HTTP API 入口语义，使同一用户请求在 Web、CLI 和 HTTP 调用下使用一致的 method、path、query/body、schema、鉴权主体、错误语义和结果标识。

本课题的目标状态是：客户端入口可以通过同源 API 证明业务路径是否可用；后端业务事实改变时，API 契约负责稳定暴露字段和错误，而不在客户端侧发明第二套事实模型。

### 2.2 范围内

- Cloud Web 同源相对 REST/JSON path、route policy 和绝对 URL 禁止边界。
- Web session、API key、AuthPrincipal、actor 摘要和权限错误在客户端入口中的表达。
- Agent session、chat、steer、cancel、result、trace 和 inspect 的用户可见 API 形态。
- Workbench workspace、Admin Access、provider profile、HWPOD node-ops、gateway 和性能摘要等入口的字段稳定性。
- JSON 成功/失败响应、HTTP 状态、错误分类、脱敏、默认摘要和完整展开边界。
- Workbench pure-read API 的缓存诊断字段，包括 `cacheStatus`、`cacheAgeMs`、projection revision/seq、dbQueryAvoided 和 Redis unavailable/stale 的低基数表达。

### 2.3 范围外

- 用户、组织、API key、额度、role、OpenFGA relation 和账本真相归 [用户管理](PJ2026-0105-user-management.md)。
- AgentRun run、command、runner job、session continuation 和 provider runtime 归 [Agent编排](PJ2026-0102-agent-orchestration.md)。
- HWPOD 资源身份、硬件操作、node 路由和原始硬件事实归 [硬件池](PJ2026-0101-hardware-pool.md)。
- CaseRun、评价、aggregate、回放和训练反馈归 [HarnessRL](PJ2026-0103-harness-rl.md)。
- 运行端点、FRP/Caddy、Secret、rollout 和 Prometheus 监控归 [平台运维](PJ2026-0106-platform-ops.md)。
- Redis workload、镜像、资源、NetworkPolicy、TTL 数值、metrics scrape 和运行面发布归 [平台运维](PJ2026-0106-platform-ops.md) 与 [Workbench性能](PJ2026-01060505-workbench-performance.md)；API契约只定义客户端可见字段和诊断边界。

## 3. 术语表

| 术语 | 定义 |
| --- | --- |
| 同源 API | Cloud Web origin 下的相对 REST/JSON HTTP API path，供浏览器和 HWLAB CLI 使用。 |
| route policy | 哪些 path 可以通过 Cloud Web 代理、哪些只能作为显式 Cloud API 诊断或内部服务调用的边界规则。 |
| AuthPrincipal | 后端根据 Web session 或 API key 恢复出的用户主体摘要。 |
| CanonicalPrincipal | `/auth/session` 和受保护业务 route 共享的最小身份结果，包含 `authMethod`、`identityAuthority`、`sessionKind`、actor 摘要和后端 capability 摘要；它是业务 route 的唯一身份输入。 |
| 业务标识 | sessionId、turnId、traceId、runId、commandId、caseId 等用户可继续查询的稳定标识；projectId、workspaceId 和 conversationId 只能作为筛选、metadata 或兼容映射字段，不作为前端鉴权 authority。 |
| 错误语义 | HTTP status、错误码、blocker、failureKind、route 和下一步提示组成的可定位失败表达。 |
| W3C trace context | `traceparent` / `tracestate` / `baggage` 等跨进程传播上下文；API 响应只允许把 `traceparent` 和脱敏后的 OTel trace id 暴露给客户端，不向用户展示 `tracestate`、`baggage` 或上游私有上下文。 |
| OTel trace_id | OpenTelemetry/W3C trace context 中的 16-byte 非全零 trace id，以 32 位十六进制字符串表达；它用于从用户截图或复制信息跳转到 trace backend，不替代业务 `traceId`、`sessionId`、`runId` 或权限判断。 |
| HwlabErrorEnvelope | API 用户可见失败响应的统一 JSON envelope，承载错误 code、用户文案、分类、层级、retryable 和脱敏诊断字段。 |
| ErrorDiagnostic | 错误诊断字段集合，至少包含 OTel trace id、request id、route、serviceId、observedAt 和 `valuesRedacted=true`；它只用于排障和复制，不参与 Workbench lifecycle 或 final response 判断。 |
| Session detail | `GET /v1/workbench/sessions/{sessionId}` 这类按稳定 sessionId 或后端认可 opaque session handle 查询当前会话 metadata、消息指针和状态的 RESTful detail resource。 |
| Conversation detail | 迁移期兼容资源；可返回 conversation metadata、sessionId、threadId 和 message seed，但不得继续作为 Web deep link 或前端 active session authority。 |
| TurnSnapshot | 按 turnId 或 traceId 查询一次用户提交/steer/retry/cancel 生命周期、终态、message 指针和执行引用的 RESTful snapshot。 |
| TraceEventPage | 按 traceId 和 cursor/seq 分页查询 trace events 的 RESTful page；只表达 trace 明细，不决定主 timeline 的 final response。 |
| Message/Part | Web 和 CLI 共同渲染会话 timeline 的持久消息事实；message 表达角色、父子关系和终态摘要，part 表达文本、工具、文件、错误、trace 摘要等可组合内容。 |
| Timing metadata | Workbench DTO 中用于显示消息最近事件和轮次耗时的投影时间字段，至少包括 `startedAt`、`lastEventAt`、`finishedAt` 和 sealed `durationMs`；相对 age 只能由浏览器显示层用本地 now 临时计算。 |
| EventStream | Cloud Web origin 下的 SSE 实时入口，用于推送 server.connected、session、turn、message、part、trace 和 workspace lifecycle 事件。 |
| WorkbenchProjectionWriter | [PJ2026-0104010803 Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md) 定义的唯一写入组件，把 AgentRun run、command、event 和 result 转换为 HWLAB durable Workbench facts。 |
| WorkbenchReadModel | [PJ2026-0104010803 Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md) 定义的唯一读模型，从 durable Workbench facts 组装 session detail、message page、turn snapshot、trace event page 和 session rail summary。 |
| Derived read cache | WorkbenchReadModel 内部可选的 Redis 派生读缓存；它只缓存 durable projection 派生快照，不改变 API 资源、权限、lifecycle 或 final response 语义。 |
| CacheDiagnostic | Workbench pure-read 响应或 OTel span 中用于解释缓存行为的诊断字段集合，至少包含 `cacheStatus`、`cacheAgeMs`、cacheKeyClass、projectionSeq 或等价字段；它不参与业务状态判断。 |
| cacheStatus | 缓存行为枚举，至少包含 `hit`、`miss`、`stale`、`unavailable`；该字段只能用于性能诊断和 `/performance` 展示，不得用于推断 session/turn/trace lifecycle。 |
| projection commit | projection writer/finalizer 对 message、part、turn、trace 和 session summary 的一次幂等持久化提交；终态 commit 必须保持这些事实语义一致。 |
| projectedSeq | HWLAB durable trace event 的单调分页游标；与 AgentRun `sourceSeq` / `sourceEventId` 分离，用于 Workbench trace page、SSE replay 和 fake-server fixture。 |
| 读侧推理 | API route、read model、SSE consumer、compat wrapper、CLI renderer、Web reducer/selectors、fake-server 或测试断言根据 trace tail、message text、tool event、result cache、session summary、list row、workspace snapshot、localStorage 或 elapsed timeout 推断 lifecycle、terminal、running 或 final response 的行为；API契约禁止该模式。 |

## 4. 系统边界和接口

本规格把 API契约作为客户端方向下的接口语义层看待；本章只描述输入、输出和责任边界。

| 边界项 | 内容 |
| --- | --- |
| 外部使用者 | Cloud Web、HWLAB CLI、自动化脚本、平台管理员和需要同源 API 的浏览器客户端。 |
| 外部输入 | HTTP method、相对 path、JSON body、query、Web session cookie、Authorization header、`traceparent`、sessionId、turnId、traceId 和业务参数；projectId、workspaceId、conversationId 只允许作为显式筛选或兼容 metadata 输入。 |
| 受控资源 | API path、schema、route policy、AuthPrincipal 摘要、错误码、响应摘要和兼容性契约。 |
| 外部输出 | JSON 响应、HTTP status、`traceparent`、`x-hwlab-otel-trace-id`、`x-request-id`、route、actor、业务标识、错误分类、脱敏状态、trace/result 指针、cache diagnostic 和下一步查询提示。 |
| 用户接口 | Cloud Web 同源 API、HWLAB CLI 同源 REST request/client 命令、HTTP API。 |
| 系统边界 | API契约负责接口形态和错误表达；不拥有用户、Agent、硬件、CaseRun、账本或发布事实。 |

## 5. 内部分工与规格索引

| 编号 | 模块或课题 | 规格文档 | 主责边界 | 上游依赖 | 下游支撑 |
| --- | --- | --- | --- | --- | --- |
| PJ2026-01040301 | 同源路由 | 本规格 6.1 | Cloud Web 相对 REST/JSON path、route policy 和绝对 URL 禁止边界 | 平台入口、Web工作台 | HWLAB CLI、Cloud Web |
| PJ2026-01040302 | 鉴权主体 | 本规格 6.2 | Web session、API key、actor 摘要和权限错误表达 | 用户管理 | Web、CLI、业务 API |
| PJ2026-01040303 | Agent接口 | 本规格 6.3 | conversation、session、chat、steer、cancel、result、trace 和 inspect API 形态 | Agent编排、用户管理 | Web工作台、HWLAB CLI |
| PJ2026-01040304 | 资源接口 | 本规格 6.4 | workspace、access、provider、HWPOD node-ops、gateway 和性能摘要入口 | 全部业务 L1 | Web、CLI、admin |
| PJ2026-01040305 | 错误输出 | 本规格 6.5 | JSON 成功/失败响应、错误码、route/status、脱敏和渐进披露 | 全部业务 L1 | 用户、脚本、排障 |
| PJ2026-01040306 | 缓存诊断 | 本规格 6.6 | Workbench pure-read cache diagnostic 字段、脱敏边界和禁止状态仲裁 | Workbench唯一投影、Workbench性能 | Web、CLI、Performance 页 |

目标后端模块应至少拆分为 route/handler、schema/contract、read model、persistence/projection、AgentRun adapter、trace event pager、SSE/event publisher 和 compat wrapper。route 文件只负责 HTTP 入口、鉴权桥接和错误映射；schema/contract 只负责类型和字段；read model 只组装查询模型；adapter 只转换外部执行事实；projection 只写 durable facts；compat wrapper 只提供旧 path 到新资源的兼容映射，不拥有第二套业务事实。

### 5.1 Workbench唯一投影 API 边界

[PJ2026-0104010803 Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md) 集中定义 AgentRun facts 到 Workbench durable facts 的目标架构、数据流、terminal commit 时序和 cloud-api 重启恢复时序。API契约在本文只保留接口边界：`GET /v1/workbench/*` 只读取 `WorkbenchReadModel`；SSE 只发布 projection commit notification；compat wrapper 只能调用同一 read model 或正式 mutation。

若 AgentRun 事实已经 terminal 但 Workbench projection 尚未追平，GET 响应应暴露 `projectionStatus`、`lastProjectedSeq`、`sourceRunId`、`sourceCommandId`、`blocker` 或等价诊断字段；不得在 GET 中同步调用 AgentRun、Code Agent manager、legacy conversation manager、billing finalizer、workspace repair、trace polling 或 result polling 来推进事实。读模型、SSE publisher、compat wrapper 和 CLI render 都不能各自解释 terminal、running、final response 或 trace pagination；它们只能重放 durable projection。

API 契约严禁读侧推理。`GET /v1/workbench/*`、`/v1/agent/*` compat wrapper、SSE event envelope 和 CLI render 只能返回唯一 Workbench durable projection 中已经存在的 lifecycle/final/status 字段；不得从 `result?.status ?? trace?.status`、最后一条 trace event `completed`、message text 是否为空、part/tool row 状态、session list summary、workspace selected state、轮询耗时或 elapsed timeout 合成 terminal、running、finalResponse 或 `caught-up`。投影缺失或矛盾时返回结构化 unknown/projecting/degraded/blocker，而不是在读侧选择一个优先级最高的事实源。

Derived read cache 只能作为 WorkbenchReadModel 内部性能优化。`GET /v1/workbench/*` 可以返回 `cacheStatus`、`cacheAgeMs`、cacheKeyClass、projectionSeq、dbQueryAvoided 或等价 CacheDiagnostic 字段，用于解释 hit/miss/stale/unavailable 和读路径耗时；这些字段不得影响 HTTP resource identity、AuthPrincipal、owner visibility、lifecycle、terminal、finalResponse、projectionStatus、404/410 判断或 compat wrapper 映射。Redis unavailable 时，API 只能返回 `cacheStatus=unavailable` 加受控 DB read 或明确 degraded，不能让 liveness 失败，也不能切换到 Cloud API/Web 前端 Redis 直连。

### 5.2 Workbench pure-read DTO 字段矩阵

`GET /v1/workbench/*` 的响应 DTO 只表达已经提交的 durable Workbench projection 和独立诊断。API 可以为渐进披露裁剪长字段，但必须保留 authority 边界和可继续查询的稳定标识。

| DTO | 必需身份字段 | lifecycle/final 字段 | diagnostic 字段 | 禁止解释 |
| --- | --- | --- | --- | --- |
| SessionRailItem | `sessionId`, `currentTurnId` 可空 | `status`, `running`, `summary`, `updatedAt` | `projectionStatus`, `blocker` 可空 | 用 list row 推断当前 message final 或 trace terminal。 |
| SessionDetail | `sessionId`, `threadId`/`conversationId` metadata 可空 | `status`, `running`, `currentTurnId`, `messagePageCursor` | `projectionStatus`, `lastProjectedSeq`, `sourceRunId`, `sourceCommandId`, `blocker` | 在 detail GET 内调用 AgentRun、workspace repair 或 result sync 推进事实。 |
| MessagePage | `sessionId`, `messageId`, `partId`, `turnId` | `role`, `status`, `sealedAt`, `parts[].finalResponse` | part/message diagnostic 可空 | 根据空 text、trace tail 或 result cache 合成 assistant final。 |
| TurnSnapshot | `turnId`, `traceId`, `sessionId`, `sourceRunId`, `sourceCommandId` | `status`, `terminal`, `failureKind`, `sealedAt`, `messageIds` | `projectionStatus`, `lastProjectedSeq`, `blocker` | 根据最后一条 trace event、轮询耗时或 transport error 推断 terminal。 |
| TraceEventPage | `traceId`, `range`, `events[].projectedSeq` | trace event `type/status` 和 durable terminal event | `hasMore`, `nextCursor`, `sourceSeq/sourceEventId` 审计字段 | 用 `hasMore=false` 推断 turn terminal；混用 `sourceSeq` 和 `projectedSeq` 作为 cursor。 |
| ProjectionDiagnostic | `sessionId`, `turnId`, `traceId`, `sourceRunId`, `sourceCommandId` | 无主 timeline 写权 | `projectionStatus`, `health`, `lag`, `lastProjectedSeq`, `sourceLatestSeq`, `blocker`, `updatedAt` | 把 diagnostic 文案覆盖 sealed final response。 |
| TransportDiagnostic | `sessionId`, `turnId`, `traceId` 可空 | 无主 timeline 写权 | SSE、poll、hydration、result sync 或 network health | 把 transport failure 改写成 message/turn terminal 状态。 |

Workbench message/turn DTO 可以输出 timing metadata，但字段权威必须保持单一：`startedAt`、`lastEventAt`、`finishedAt` 和 sealed `durationMs` 来自 Workbench durable projection；running 消息的“最近/耗时”由浏览器以这些时间戳和本地 now 计算；terminal 消息使用 sealed `durationMs` 或等价 sealed `finishedAt - startedAt`，不得继续增长。`lastEventAgeMs` 若在迁移期保留，只能作为服务端观测时刻的快照或诊断字段，不能作为 Cloud Web 用户可见相对时间的显示权威，也不能参与 lifecycle、terminal、projectionStatus、session sorting 或 final response 选择。

Workbench pure-read DTO 可以携带 CacheDiagnostic，但字段必须与业务事实分仓表达。`cacheStatus`、`cacheAgeMs`、`cacheKeyClass`、`projectionSeq`、`dbQueryAvoided`、`dbQueryDuration` 和 `payloadBytes` 只能放在 `diagnostic`、`performance`、`meta` 或等价非 authority 字段下；默认响应不得输出完整 cache key、actor id、session id 以外的派生 key、trace id 组合 key、prompt、assistant 正文、tool 参数、stdout/stderr、Secret、token、cookie、Authorization header 或 DB DSN。客户端可以显示或上报这些诊断用于性能分析，但不得把 `hit` 视为新鲜、把 `stale` 视为 terminal、把 `unavailable` 视为业务失败，或根据 cache miss 触发 read-through repair。

API 禁止路径清单如下：GET read-through repair、session JSON lifecycle 推理、result cache lifecycle 推理、trace tail lifecycle 推理、workspace/conversation authority、billing finalizer side effect、Code Agent manager shortcut、frontend status priority 和 compat wrapper second authority。需要推进 projection 时必须由 writer/finalizer、后台 scheduler、显式 mutation 或受控 replay/reprojection 入口执行；GET、SSE、CLI render 和 fake-server 只能观察结果。

### 5.3 Workbench cache diagnostic 数据流

```mermaid
flowchart LR
  Client[Web / CLI] --> Route[GET /v1/workbench/*]
  Route --> Auth[AuthPrincipal + owner visibility]
  Auth --> Read[WorkbenchReadModel]
  Read --> Cache[(Derived Redis cache)]
  Cache -->|hit with matching authority input| Dto[Pure-read DTO]
  Cache -->|miss / stale / unavailable| Facts[(Durable Workbench facts)]
  Facts --> Read
  Read --> Dto
  Dto --> Diag[CacheDiagnostic meta]
  Diag --> Client
```

缓存诊断数据流要求 route 先完成鉴权和 owner visibility，再由 WorkbenchReadModel 使用 durable facts authority input 访问 Redis 派生缓存。cache hit 只能返回等价 pure-read DTO；cache miss、stale 或 unavailable 必须回到 durable facts read 或明确 degraded，不得触发 AgentRun、result polling、workspace repair、projection finalizer 或前端 Redis 直连。

```mermaid
sequenceDiagram
  participant C as Web/CLI
  participant A as Cloud API route
  participant R as WorkbenchReadModel
  participant K as Redis derived cache
  participant F as Durable facts
  C->>A: GET /v1/workbench/sessions
  A->>R: principal + query + route
  R->>K: get cache key with authority input
  alt hit and revision matches
    K-->>R: derived DTO + cacheAgeMs
  else miss/stale/unavailable
    R->>F: read durable projection
    F-->>R: authoritative DTO or degraded/blocker
    R->>K: set derived snapshot when allowed
  end
  R-->>A: DTO + CacheDiagnostic
  A-->>C: JSON response, lifecycle from durable projection only
```

## 6. 原子需求

### 6.1 CLIENT-API-REQ-001 同源路由

| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| CLIENT-API-REQ-001 | 同源路由 | PJ2026-01040301 同源路由 | [PJ2026-010401 Web工作台](PJ2026-010401-web-workbench.md)、[PJ2026-010402 HWLAB CLI](PJ2026-010402-hwlab-cli.md)、[平台运维](PJ2026-0106-platform-ops.md) |

API契约应定义 Cloud Web origin 下的相对 REST/JSON path 和 route policy，使浏览器与 HWLAB CLI 能通过同一同源入口访问业务 API。

同源路由必须拒绝把绝对 URL、内部服务地址或旧运行面端口作为正式用户入口。平台运维提供公开入口和代理支撑；API契约只规定客户端可依赖的 path 和错误形态。

API契约必须区分同源 API path 与浏览器 history route：`/auth*`、`/v1*`、`/health*` 等 API path 返回 JSON 语义，`/workbench`、`/login` 和其他 Cloud Web 页面路由由 Web工作台处理。API/edge 直达入口对页面路由返回 JSON 404 是正确边界，不应被改造成 SPA fallback；反过来，Web正式公开入口必须通过 route policy 把 API path 分流到 API 服务。

RESTful API 必须保持无状态请求语义：在相同 method、path、query、body、auth 主体和相同后端业务事实下，应返回同一语义结果；服务端不得为了补齐某个列表或状态而隐藏读取浏览器 workspace selected、localStorage、Web snapshot、上一次页面状态或其他客户端临时状态。列表窗口需要包含某个当前资源时，必须通过显式 query/path/body 传入稳定业务 id，例如 session list 通过显式 include session id 请求把当前 session 纳入同一列表响应；服务端只能从当前 Web session/API key 恢复 AuthPrincipal，校验该 id 对当前 actor 是否可见并返回后端事实，不得接受客户端拼接出的 project、workspace、status、final response、markdown、running 动效或 stub 作为事实来源。

当前会话消息恢复必须使用 session 主路径：`GET /v1/workbench/sessions/{sessionId}` 返回 session metadata 和消息指针，`GET /v1/workbench/sessions/{sessionId}/messages` 返回 message/part page，turn 和 trace 分别由 `TurnSnapshot` 与 `TraceEventPage` 补洞。`GET /v1/workbench/workspace` 可以返回 selected ids、workspace revision 和 composer 配置，但不得成为消息本体 authority；`GET /v1/workbench/sessions` 可以返回 rail 摘要和显式 include 的列表项，但不得替代当前 session detail/message page。前端恢复、深链、刷新和 session 切换必须以稳定 sessionId 或后端认可 opaque session handle 调用同一 detail/message resource，不得在 detail resource 失败时切到 workspace snapshot、conversation detail、列表摘要、JSON-RPC、内部 manager、浏览器缓存或空 stub 继续构造当前消息面板。

### 6.2 CLIENT-API-REQ-002 鉴权主体

| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| CLIENT-API-REQ-002 | 鉴权主体 | PJ2026-01040302 鉴权主体 | [用户管理](PJ2026-0105-user-management.md)、[PJ2026-010402 HWLAB CLI](PJ2026-010402-hwlab-cli.md) |

API契约应以 AuthPrincipal 摘要表达 Web session 和 API key 恢复出的当前用户，使 Web、CLI 和 HTTP API 可以一致处理未登录、无权限、账号禁用、额度不足和工具能力缺失。

鉴权主体的真相由用户管理负责。API 响应只输出 actor、authMethod、requiredAuthMethod、role/status、scope 摘要和错误分类，不输出 session token、完整 API key、password hash、Secret value 或可复用凭据。

`/auth/session` 是 Cloud Web 的最小身份验证端点。浏览器请求只依赖同源 `hwlab_session` cookie transport；前端不得向该端点传入 API key、user-billing token、cookie prefix、localStorage token 或第二套 authority。该端点成功时必须返回 canonical principal，至少包含：

| 字段 | 语义 | 脱敏边界 |
| --- | --- | --- |
| `authenticated` | 当前请求是否已通过认证 | 布尔值 |
| `authMethod` | 认证 transport 来源，例如 `web-session`、`user-billing-session`、`user-billing-api-key`、`local-bootstrap` | 枚举，不输出 token |
| `identityAuthority` | 身份事实归属，例如 `hwlab-local`、`user-billing`、`bootstrap-recovery` | 枚举，不输出底层凭据 |
| `sessionKind` | 调用形态，例如 `browser`、`api-key`、`recovery` | 枚举 |
| `actor` | 当前用户摘要，至少包含稳定 id、role、status；可包含 username/displayName | 不输出 password hash、session secret、完整邮箱验证 payload |
| `capabilities` | 后端授权摘要，例如 `web`、`workbench`、`codeAgent`、`billing`、`admin` | 只输出 `available`、`forbidden`、`unlinked`、`unavailable` 或等价短枚举 |
| `valuesRedacted` | 响应是否遵守脱敏边界 | 必须为 true |

未登录请求必须返回 401 和结构化 unauthenticated 结果；禁用、撤销或 recovery 能力不足必须返回 403 或业务 blocker，并保留 `authMethod`、`identityAuthority`、`sessionKind` 的可诊断摘要。响应不得输出 cookie、session token、API key secret、完整 user-billing token、provider Secret、DB DSN 或 raw dependency payload。

业务 route 只能消费认证层产生的 canonical principal 和权限配额的 capability 结果。Usage、Billing、API keys、Workbench、Code Agent 和 Admin route 不得自行调用 token/cookie 抽取函数来决定用户身份；如果需要访问 user-billing、API key 或账单后台，应由后端以 principal 为输入桥接业务服务，并把 `available/forbidden/unlinked/unavailable` 或结构化 blocker 返回给客户端。

### 6.3 CLIENT-API-REQ-003 Agent 用户接口

| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| CLIENT-API-REQ-003 | Agent接口 | PJ2026-01040303 Agent接口 | [Agent编排](PJ2026-0102-agent-orchestration.md)、[用户管理](PJ2026-0105-user-management.md)、[PJ2026-010401 Web工作台](PJ2026-010401-web-workbench.md) |

API契约应定义显式 Agent session、turn、chat、steer、cancel、result、trace 和 inspect 的用户可见 RESTful HTTP 形态，使 Web 和 HWLAB CLI 能围绕同一 sessionId、turnId、traceId、runId 和 commandId 操作。conversationId、threadId 可以作为后端返回的 metadata 或兼容映射字段存在，但不得替代 sessionId 成为 Web deep link 或 active authority。

Agent 接口不得通过 session latest、浏览器历史、旧 cache 或内部 manager 生成第二套 final response。Agent编排提供执行事实，用户管理校验 owner 和权限，客户端只稳定暴露用户入口。

Trace 查询必须采用无状态 RESTful 资源形态：`GET /v1/agent/traces/{traceId}` 是客户端读取 Code Agent trace events、runnerTrace、terminalEvidence、finalResponse 和 traceSummary 的权威入口。请求只依赖 method、path、query、auth 主体和后端业务事实；Web 不得使用动作式 `/v1/agent/chat/trace/{traceId}` 作为恢复会话、切换 session 或 completed 消息加载 trace 的前端路径，也不得把浏览器 workspace snapshot、localStorage 或消息 stub 拼成 trace 响应。

Trace resource 必须支持按事件位置继续读取，例如 `sinceSeq` / `cursor` 与 `limit` 这类 query 参数。服务端返回超长 trace 时应按完整事件边界截断，并在同一个资源响应中给出 `range`、`hasMore`、`nextSinceSeq` 或等价阶段性位置，使客户端可以继续请求下一段；响应不得返回半条事件，不得要求 JSON-RPC、回放按钮、内部 manager path 或第二套 trace endpoint 才能补齐完整可读事件。分页请求必须保持无状态和幂等：同一 `traceId`、同一位置参数、同一权限主体下重复请求不得污染 trace 事实。

`POST /v1/agent/chat`、`GET /v1/agent/turns/{traceId}` 和 result 入口可以返回 `traceUrl` 指针，但该指针必须指向 RESTful trace resource。trace resource 可以为了短返回默认裁剪事件，但完整事件补齐、终态 final response、AgentRun run/command identity、错误和脱敏边界仍由同一 trace resource 表达；客户端不得要求用户手动“回放 Trace”才能获得完成态 trace。

目标 Workbench RESTful API 资源设计如下。旧 `/v1/agent/*` 路径可以作为兼容 wrapper 暂存，但 Web 主路径应逐步收敛到这些资源语义。

| 资源 | Method / path | 请求字段 | 响应主体 | 状态码 | 权威边界 |
| --- | --- | --- | --- | --- | --- |
| WorkspaceSelection | `GET /v1/workbench/workspace` | `projectId`, `workspaceId` 可选 | selected ids、workspace revision、composer 配置、public origin 摘要 | 200/401/403 | 只读 selection/config，不返回或修补 message 事实 |
| WorkspaceSelection | `PATCH /v1/workbench/workspace` | 显式 selected project/workspace/conversation ids | 更新后的 selection/config | 200/400/401/403 | 只有显式 mutation 可改变 selection |
| Session list | `GET /v1/workbench/sessions` | `cursor`, `limit`, `includeSessionId`，可选后端认可筛选字段 | session/conversation rail 摘要 page | 200/401/403 | 当前用户可见 session 列表和排序，不替代 detail |
| Session detail | `GET /v1/workbench/sessions/{sessionId}` | path sessionId | session metadata、message page 指针和当前 turn/trace 摘要 | 200/401/403/404 | session metadata，不拥有 trace events |
| Message page | `GET /v1/workbench/sessions/{sessionId}/messages` | `cursor`, `limit`, `before/after` | message + parts page、next cursor | 200/401/403/404 | 会话 timeline 的 message/part 事实 |
| Conversation detail | `GET /v1/agent/conversations/{conversationId}` | path conversationId | conversation metadata、message page seed、sessionId、threadId、lastTraceId、turn summary | 200/401/403/404 | 迁移期兼容 detail，不替代 session 主路径 |
| Turn admission | `POST /v1/workbench/sessions/{sessionId}/turns` | prompt/parts、delivery、client message ids、可选 metadata | `202 Accepted` + turnId、traceId、userMessageId、assistantMessageId、status URLs | 202/400/401/403/409/422 | 只表示输入已接收，不表示执行完成 |
| Turn snapshot | `GET /v1/workbench/turns/{turnId}` | path turnId | status、terminal、message ids、traceId、AgentRun refs、failureKind | 200/401/403/404 | turn 状态补洞，不承载 trace events |
| Trace event page | `GET /v1/workbench/traces/{traceId}/events` | `afterSeq/cursor`, `limit` | ordered events、range、hasMore、next cursor | 200/401/403/404/410 | trace 明细分页，不决定主 timeline final response |
| EventStream | `GET /v1/workbench/events` | optional sessionId/turnId/traceId filters | SSE events + heartbeat | 200/401/403 | 实时加速和通知，不替代 REST snapshot |
| Cancel turn | `POST /v1/workbench/turns/{turnId}/cancel` | reason 可选 | updated TurnSnapshot | 200/202/401/403/404/409 | 精确取消目标 turn |
| Steer turn | `POST /v1/workbench/turns/{turnId}/steer` | text/parts、delivery | accepted steer TurnSnapshot 或 continuation id | 202/400/401/403/404/409 | 绑定明确 running turn |
| ProviderProfile | `GET /v1/workbench/provider-profiles` | project/workspace 可选 | profile summary list | 200/401/403 | 配置摘要，不输出 Secret |
| Diagnostic resource | `GET /v1/workbench/diagnostics/{resource}` | resource id/query | redacted health/status summary | 200/401/403/404 | 诊断可见性，不改变业务事实 |

所有 GET path 必须无业务副作用。需要 repair、finalize、sync 或 migration 时，应由后台 finalizer、显式 mutation 或持久化投影入口执行，不能隐藏在 workspace、conversation、turn 或 trace GET 中。所有分页响应必须包含稳定 cursor 或 seq 边界；trace event page 的分页 cursor 使用 HWLAB `projectedSeq`，并保留 AgentRun `sourceSeq/sourceEventId` 作为来源审计字段，二者不得重叠或互相替代。所有错误响应必须包含 route、status、code/blocker、redacted actor、target resource 和下一步建议。

`GET /v1/workbench/traces/{traceId}/events` 的可见性必须以 trace 所属 turn/message/session durable facts 和当前 actor visibility 判断。历史 trace 不再是 session 当前 `lastTraceId` 时仍应可读；缺页或投影滞后应返回 projection blocker/degraded，而不是 `workbench_trace_not_found` 的 actor 不可见误导。

Cancel mutation 在转发 AgentRun cancel 前必须核验本地 sealed terminal 或上游 run/command state。若 command 已 terminal，API 应返回 already-terminal/no-op 或触发 terminal projection，不得把 completed/failed/blocked 等真实 terminal 改写为 canceled。Workbench read paths 仍保持纯读，不在 GET 中执行该核验或修复。

Workbench read-model dependency timeout 必须返回结构化 degraded/readiness/blocker 响应；PostgreSQL pool connect/query timeout 不得作为未捕获异常杀死 cloud-api 进程，也不得让 liveness 因单次 Workbench read query 变成 EOF。

新增或重构的核心后端文件头部必须标注遵循的 SPEC 编号、短名和实现引用版本，例如 `SPEC: PJ2026-0104010803 唯一投影 draft-2026-06-20-p0-durable-facts-model; draft-2026-06-20-p1-zero-split-durable-realtime; PJ2026-010403 API契约 draft-2026-06-20-p0-workbench-pure-read-api; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0`，并简述文件职责。实现文件不得只写 issue 编号、`latest` 或 `current` 作为规格引用。

### 6.4 CLIENT-API-REQ-004 资源与管理接口

| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| CLIENT-API-REQ-004 | 资源接口 | PJ2026-01040304 资源接口 | [硬件池](PJ2026-0101-hardware-pool.md)、[用户管理](PJ2026-0105-user-management.md)、[Agent编排](PJ2026-0102-agent-orchestration.md)、[平台运维](PJ2026-0106-platform-ops.md) |

API契约应定义 workbench workspace、Admin Access、provider profile、HWPOD node-ops、gateway 诊断和性能摘要等资源与管理入口，使 Web 和 HWLAB CLI 可以一致读取状态、提交操作和展示错误。

资源与管理接口只定义调用形态和字段稳定性。HWPOD node-ops 的硬件语义归硬件池，provider profile runtime 归 Agent编排，权限和账号归用户管理，运行健康和公开入口归平台运维。

Gateway 相关 API 只允许作为云端同源入口暴露：用户、Agent 或 smoke 调用 Cloud API / API契约中的 gateway/HWPOD path，由 HWPOD 服务再路由到主动出站的 AI 网关。API契约不得要求客户端直接访问用户 PC gateway，也不得把 gateway 本地端口、内网地址或临时 poll URL 写入正式客户端输出。

### 6.5 CLIENT-API-REQ-005 错误与输出合同

| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| CLIENT-API-REQ-005 | 错误输出 | PJ2026-01040305 错误输出 | [PJ2026-010402 HWLAB CLI](PJ2026-010402-hwlab-cli.md)、[PJ2026-010401 Web工作台](PJ2026-010401-web-workbench.md)、[HarnessRL](PJ2026-0103-harness-rl.md) |

API契约应保证成功和失败都以可定位 JSON 形式表达，至少包含调用 route、HTTP status、错误码或 blocker、必要业务标识、脱敏 actor 或权限摘要，以及可继续查询的 result/trace/status 入口。

默认响应不得输出爆炸或泄漏敏感值；需要完整 trace、原始响应或长日志时，必须通过显式展开参数、下载入口或已有业务产物获取。错误输出只负责说明失败，不替代缺失能力实现。

Cloud API 请求入口必须统一 extract 或 create W3C `traceparent`，并把 OTel trace id、span id、request id、route 和 serviceId 绑定到同一次 request context。所有 200、4xx 和 5xx 响应都必须带 `traceparent`、`x-hwlab-otel-trace-id` 和 `x-request-id`；业务 handler、dependency client、dev-entrypoint proxy、AgentRun adapter、user-billing、HWPOD 或其他下游调用只能传播该 context，不能在同一请求内随机生成第二个用户可见 trace id。

用户可见 API 失败响应应统一使用 `HwlabErrorEnvelope` 或等价结构：

```ts
interface HwlabErrorEnvelope {
  ok: false;
  status: number | "failed";
  error: {
    code: string;
    message: string;
    userMessage?: string;
    category?: string;
    layer?: string;
    retryable?: boolean;
    diagnostic: {
      traceId: string | null;
      spanId?: string | null;
      requestId?: string | null;
      route?: string | null;
      serviceId?: string | null;
      observedAt: string;
      valuesRedacted: true;
    };
  };
  valuesRedacted: true;
}
```

`error.message` 和 `error.userMessage` 只承载用户可理解文案；`error.diagnostic.traceId` 承载可复制的 OTel trace id。响应不得向用户外显 `tracestate`、`baggage`、cookie、API key、完整 Authorization header、DB DSN、provider token、prompt、assistant 正文、tool 参数、stdout/stderr 长段、raw upstream payload 或可复用凭据。`traceparent` 可作为标准 header 返回，但默认页面只展示 `trace_id` 字段。

错误 envelope 的结构化日志必须记录同一 `trace_id`、`request_id`、`error.code`、route、status 和 layer，使日志、span 和用户错误诊断能关联。`trace_id` 不得作为 Prometheus label；如需 metrics 关联，只能通过低基数 route/status/code label、exemplar 或受控 debug payload 承载。

Workbench 投影 blocker、proxy timeout、鉴权失败、provider profile、web-performance、全局 route exception 和 dev-entrypoint proxy 错误都应映射到同一 envelope。历史 `{ error: string }` 字段可在迁移期作为 `userMessage` 展示兼容，但不得成为唯一错误事实；新增用户可见错误路径禁止只返回裸字符串、`reason` 或未脱敏 raw exception。

### 6.6 CLIENT-API-REQ-006 Workbench cache diagnostic 字段

| 编号 | 短名 | 主责模块 | 关联模块 |
| --- | --- | --- | --- |
| CLIENT-API-REQ-006 | 缓存诊断 | PJ2026-01040306 缓存诊断 | [Workbench唯一投影](PJ2026-0104010803-workbench-unique-projection.md)、[Workbench性能](PJ2026-01060505-workbench-performance.md) |

Workbench pure-read API 可以在成功或 degraded 响应中输出 CacheDiagnostic，用于说明 Redis 派生读缓存是否命中、是否 stale、是否 unavailable、快照年龄和是否避免 durable DB 查询。该诊断至少应能表达 `cacheStatus=hit|miss|stale|unavailable`、`cacheAgeMs`、cacheKeyClass、projectionSeq 或等价 revision、dbQueryAvoided、dbQueryDuration 和 payloadBytes；字段位置必须与 lifecycle/final/status 分仓。

CacheDiagnostic 不得成为业务状态仲裁字段。Web、CLI、compat wrapper、fake-server 和 web-probe 不得根据 `cacheStatus`、`cacheAgeMs`、Redis key presence 或 dbQueryAvoided 推断 session running/completed、turn terminal、trace caught-up、final response、deleted/archived/not-found 或权限可见性。缓存 unavailable 不应触发 `/health/live` 失败；route 可以返回受控 DB read 结果或明确 degraded/blocker，但不能静默切换到 Cloud API/Web 前端 Redis 直连或旧 fallback path。

CacheDiagnostic 必须脱敏并保持低基数。响应、日志、OTel span 和 `/performance` 默认展示不得包含完整 cache key、actor id 聚合维度、trace id 组合 key、prompt、assistant 正文、tool 参数、stdout/stderr、Secret、token、cookie、Authorization header、DB DSN 或 raw upstream payload。需要排查单次请求时，只允许通过 OTel trace id、request id、route、cacheKeyClass、projectionSeq 和 valuesRedacted 摘要关联。

## 7. 过程控制

本规格不单独索引一般过程 issue；跨 L1 的内测、灰度和阶段活动索引统一保留在 [PJ2026-01 HWLAB 总规格](PJ2026-01-HWLAB.md) 的 `7. 过程控制`。Workbench Redis 派生读缓存架构执行 issue 为 [#1870](https://github.com/pikasTech/HWLAB/issues/1870)，P0 SPEC-first 子 issue 为 [#1871](https://github.com/pikasTech/HWLAB/issues/1871)，后续实现必须引用 `draft-2026-06-22-p1-workbench-redis-derived-cache`。
