docs: add frontend response contract baseline

This commit is contained in:
2026-04-21 00:16:55 +08:00
parent be5a1b51ed
commit d2ad81d38c
3 changed files with 265 additions and 1 deletions

View File

@@ -14,7 +14,8 @@
交付物:[M0_SSE_INTERFACE_BASELINE_2026-04-20.md](./M0_SSE_INTERFACE_BASELINE_2026-04-20.md) 交付物:[M0_SSE_INTERFACE_BASELINE_2026-04-20.md](./M0_SSE_INTERFACE_BASELINE_2026-04-20.md)
- [x] 整理当前所有 `/generated-*` 静态资源前缀 - [x] 整理当前所有 `/generated-*` 静态资源前缀
交付物:[M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md](./M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md) 交付物:[M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md](./M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md)
- [ ] 整理当前前端直接依赖的响应头、envelope、错误格式 - [x] 整理当前前端直接依赖的响应头、envelope、错误格式
交付物:[M0_FRONTEND_RESPONSE_CONTRACT_BASELINE_2026-04-20.md](./M0_FRONTEND_RESPONSE_CONTRACT_BASELINE_2026-04-20.md)
### 仓库边界 ### 仓库边界

View File

@@ -0,0 +1,262 @@
# M0前端直接依赖的响应头、Envelope 与错误格式冻结基线
日期:`2026-04-20`
依据来源:
- `server-node/src/http.ts`
- `server-node/src/middleware/responseEnvelope.ts`
- `server-node/src/middleware/errorHandler.ts`
- `server-node/src/middleware/requestId.ts`
- `packages/shared/src/http.ts`
- `src/services/apiClient.ts`
- `src/services/authService.ts`
- `src/services/aiService.ts`
- `src/editor/shared/jsonClient.ts`
- `src/services/apiClient.test.ts`
## 1. 文档目的
这份文档用于完成 `M0` 的第六条任务:
- 整理当前前端直接依赖的响应头、envelope、错误格式
这里的“直接依赖”指的是:如果 Axum 重写时把这些头或 body 结构改掉,当前前端 `src/services/*`、编辑器请求层和鉴权异常处理就会立刻出问题。
## 2. 冻结结论
当前前端直接依赖的响应契约,冻结为以下 4 层:
1. 请求侧默认会发送 `x-genarrative-response-envelope: v1`
2. 响应侧默认要回 `x-request-id``x-api-version``x-route-version`
3. 成功响应在请求方要求 envelope 时,必须返回标准 `ok/data/error/meta` 结构。
4. 错误响应既要兼容标准 envelope也要兼容旧式 `{ error, meta }` / `{ message, code }` 解析回退。
补充结论:
1. 当前正式前端代码里,没有生产用例主动关闭 envelope 请求头。
2. `x-response-time-ms` 当前不是前端代码的直接读取项,但属于现有兼容头集合,重写时仍应保留。
3. 鉴权链路额外直接依赖错误码 `CAPTCHA_REQUIRED``error.details.captchaChallenge`
## 3. 当前前端直接依赖矩阵
| 依赖项 | 当前值/结构 | 当前消费者 | 当前作用 |
| --- | --- | --- | --- |
| 请求头 | `x-genarrative-response-envelope: v1` | `src/services/apiClient.ts``src/editor/shared/jsonClient.ts` | 请求标准 envelope 响应。 |
| 响应头 | `x-request-id` | `src/services/apiClient.ts` | 构造 `ApiClientError.meta.requestId` 的回退来源。 |
| 响应头 | `x-api-version` | `src/services/apiClient.ts``packages/shared/src/http.ts` | 识别标准 envelope / error body。 |
| 响应头 | `x-route-version` | `src/services/apiClient.ts` | 构造 `ApiClientError.meta.routeVersion` 的回退来源。 |
| 成功 body | `{ ok: true, data, error: null, meta }` | `unwrapApiResponse(...)` | 前端默认解包标准成功 envelope。 |
| 错误 body | `{ ok: false, data: null, error, meta }` | `ApiClientError``parseApiErrorMessage(...)` | 标准错误解析。 |
| 旧错误 body | `{ error, meta }` / `{ message, code }` | `parseApiErrorMessage(...)` | 老接口或非标准错误回退解析。 |
| 错误细节 | `error.code === 'CAPTCHA_REQUIRED'``error.details.captchaChallenge` | `src/services/authService.ts` | 手机验证码发送前的验证码挑战弹出。 |
## 4. 请求侧冻结要求
### 4.1 Envelope 请求头
当前前端默认行为:
1. `src/services/apiClient.ts` 会自动补:
- `x-genarrative-response-envelope: v1`
2. `src/editor/shared/jsonClient.ts` 也会自动补:
- `x-genarrative-response-envelope: v1`
当前后端接受的 envelope 触发值:
1. `1`
2. `true`
3. `v1`
4. `envelope`
但当前前端真实发送值冻结为:
1. `v1`
补充冻结点:
1. 虽然 `apiClient` 提供了 `omitEnvelopeHeader` 选项,但当前生产代码没有实际依赖它。
2. 因此第一阶段 Axum 应默认兼容“前端请求即要 envelope”的模式。
### 4.2 鉴权与凭证约定
当前前端请求层默认还会做:
1. `Authorization: Bearer <token>` 自动注入。
2. `credentials: same-origin`
3. 遇到 `401` 时尝试走 `/api/auth/refresh` 自动刷新。
这不是本文重点,但它解释了为什么 envelope 和错误格式必须在 `/api/auth/refresh` 上也保持兼容。
## 5. 响应头冻结要求
### 5.1 必须保留的前端直接依赖头
| 响应头 | 当前来源 | 当前前端用法 |
| --- | --- | --- |
| `x-request-id` | `requestIdMiddleware` + `applyApiResponseHeaders` | `ApiClientError.meta.requestId` 的 header 回退来源。 |
| `x-api-version` | `applyApiResponseHeaders` | 当前标准 API 契约版本识别。 |
| `x-route-version` | `applyApiResponseHeaders` | `ApiClientError.meta.routeVersion` 的 header 回退来源。 |
### 5.2 兼容头但非直接读取项
| 响应头 | 当前状态 | 说明 |
| --- | --- | --- |
| `x-response-time-ms` | 当前统一输出 | 目前前端代码未直接读取,但设计文档与联调约定已锁定,不能随意删除。 |
补充冻结点:
1. `requestIdMiddleware` 会优先回显请求方传入的 `x-request-id`,否则服务端自生成。
2. `ApiClientError` 读取元信息时优先取 body `meta`,没有再回退到 headers。
3. 这意味着即便 envelope body 缺少部分 `meta` 字段headers 仍必须完整。
## 6. 成功响应 Envelope 冻结格式
当前标准成功 envelope
```json
{
"ok": true,
"data": {},
"error": null,
"meta": {
"apiVersion": "2026-04-08",
"requestId": "req-xxx",
"routeVersion": "2026-04-08",
"operation": "runtime.story.initial",
"latencyMs": 12,
"timestamp": "2026-04-20T00:00:00.000Z"
}
}
```
冻结规则:
1. `ok` 必须为 `true`
2. `data` 为真实业务负载。
3. `error` 必须为 `null`
4. `meta.apiVersion` 必须存在,因为 `unwrapApiResponse(...)``isApiResponse(...)` 依赖它判断标准 envelope。
补充说明:
1. 如果请求未带 envelope 头,当前后端可以直接返回裸 `data`
2. 但由于当前前端默认都会请求 envelope第一阶段 Axum 基本等价于“所有 JSON 成功响应都要兼容这个结构”。
## 7. 错误响应 Envelope 与旧格式回退
### 7.1 当前标准错误 envelope
```json
{
"ok": false,
"data": null,
"error": {
"code": "UNAUTHORIZED",
"message": "缺少 Authorization Bearer Token",
"details": null
},
"meta": {
"apiVersion": "2026-04-08",
"requestId": "req-xxx",
"routeVersion": "2026-04-08",
"operation": "auth.me",
"latencyMs": 3,
"timestamp": "2026-04-20T00:00:00.000Z"
}
}
```
冻结规则:
1. `ok` 必须为 `false`
2. `data` 必须为 `null`
3. `error.code``error.message` 必须存在。
4. `error.details` 可为对象或 `null`
5. `meta.apiVersion` 必须存在。
### 7.2 当前旧式错误格式回退
当请求未要求 envelope或某些链路仍走旧写法时当前后端与前端仍兼容以下错误结构
1. `{ "error": { "code": "...", "message": "...", "details": ... }, "meta": {...} }`
2. `{ "message": "...", "code": "..." }`
3. `{ "error": { "message": "..." } }`
4. 纯文本错误响应
`parseApiErrorMessage(rawText, fallbackMessage)` 的当前回退顺序固定为:
1. `parsed.error.message`
2. 顶层 `message`
3. `error.code` 或顶层 `code`,拼成 `fallbackCODE`
4. 原始文本
5. 调用方的 `fallbackMessage`
这意味着:
1. Axum 第一阶段不能只兼容标准 envelope而忽略旧错误解析的回退行为。
2. 至少在迁移过渡期,`parseApiErrorMessage(...)` 可识别的信息要继续保留。
## 8. 前端对错误细节的业务级直接依赖
### 8.1 验证码挑战
`src/services/authService.ts` 当前明确依赖:
1. `error instanceof ApiClientError`
2. `error.code === 'CAPTCHA_REQUIRED'`
3. `error.details.captchaChallenge`
冻结要求:
1. 如果后端要继续触发验证码挑战,必须继续返回:
- `code: 'CAPTCHA_REQUIRED'`
- `details.captchaChallenge`
2. 不能只返回中文文案而不带结构化 `details`
### 8.2 元信息透传
`ApiClientError` 当前会保留:
1. `status`
2. `code`
3. `details`
4. `meta.apiVersion`
5. `meta.requestId`
6. `meta.routeVersion`
7. `meta.operation`
8. `meta.latencyMs`
9. `meta.timestamp`
冻结要求:
1. Axum 不能把这些字段全都删成单纯 `message` 字符串。
2. 即使部分业务 UI 现在没显示这些字段,它们已经进入前端错误对象结构。
## 9. 当前消费者清单
以下文件已构成当前前端的直接依赖面:
1. `src/services/apiClient.ts`
2. `src/services/authService.ts`
3. `src/services/aiService.ts`
4. `src/editor/shared/jsonClient.ts`
5. `packages/shared/src/http.ts`
## 10. 本轮冻结后的硬约束
后续迁移中,不允许出现以下情况:
1. 删除 `x-genarrative-response-envelope: v1` 的请求协商能力。
2. 删除 `x-request-id``x-api-version``x-route-version` 这些当前前端直接依赖的响应头。
3. 把成功 envelope 从 `{ ok, data, error, meta }` 改成其他字段名。
4. 把错误 envelope 从 `{ ok: false, data: null, error, meta }` 改成只有 `message`
5. 删除 `CAPTCHA_REQUIRED + details.captchaChallenge` 这一结构化错误契约。
6. 让前端默认请求 envelope但后端返回裸数据且不再可被 `unwrapApiResponse(...)` 识别。
## 11. 本任务完成定义
当以下条件成立时,这条任务视为完成:
1. 当前前端直接依赖的响应头、envelope、错误格式已有书面冻结清单。
2. 已明确哪些是前端直接读取项,哪些是兼容保留项。
3. 后续 Axum handler、错误中间件、response envelope 中间件可以直接按本文对齐,而不再靠人工试错。

View File

@@ -19,6 +19,7 @@
- [M0_MODULE_MIGRATION_BASELINE_2026-04-20.md](./M0_MODULE_MIGRATION_BASELINE_2026-04-20.md):当前 `12` 个内部模块的迁移归属基线,用于锁定 Rust crate、SpacetimeDB bounded context 与 Axum/application 分工。 - [M0_MODULE_MIGRATION_BASELINE_2026-04-20.md](./M0_MODULE_MIGRATION_BASELINE_2026-04-20.md):当前 `12` 个内部模块的迁移归属基线,用于锁定 Rust crate、SpacetimeDB bounded context 与 Axum/application 分工。
- [M0_SSE_INTERFACE_BASELINE_2026-04-20.md](./M0_SSE_INTERFACE_BASELINE_2026-04-20.md):当前 `6` 条 SSE 接口及其事件格式冻结基线,用于 Axum SSE 兼容和前端 contract 回归。 - [M0_SSE_INTERFACE_BASELINE_2026-04-20.md](./M0_SSE_INTERFACE_BASELINE_2026-04-20.md):当前 `6` 条 SSE 接口及其事件格式冻结基线,用于 Axum SSE 兼容和前端 contract 回归。
- [M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md](./M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md):当前正式 `/generated-*` 静态资源前缀冻结基线,用于 Axum 静态资源兼容层与 OSS 对象键规划。 - [M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md](./M0_GENERATED_STATIC_PREFIX_BASELINE_2026-04-20.md):当前正式 `/generated-*` 静态资源前缀冻结基线,用于 Axum 静态资源兼容层与 OSS 对象键规划。
- [M0_FRONTEND_RESPONSE_CONTRACT_BASELINE_2026-04-20.md](./M0_FRONTEND_RESPONSE_CONTRACT_BASELINE_2026-04-20.md)当前前端直接依赖的响应头、envelope 与错误格式冻结基线,用于 Axum 中间件与错误响应兼容。
## 维护规则 ## 维护规则