# Wave 1 W1-T6 — chat 多入口后端契约(状态:已就位)

| 字段 | 值 |
|---|---|
| 日期 | 2026-05-04 |
| Wave | 1 / Day 3 |
| 范围 | P1-11 chat 多入口后端契约 |
| 文件改动 | **零代码改动**(后端契约 + 前端 6 分支均已就位) |

## 一、走读结论

E-2 调研(`docs/_overview/04b-feedback/P1-3-4-cross-page-params.md`)报告:
> 前端已 6 分支修了,只剩后端契约待核

**实地走读 2026-05-04 确认**:**后端契约也已实现**,前后端形成完整链路。

### 后端 `apps/backend/app/routers/xcx_chat.py`

```python
@router.get("/messages", response_model=ChatMessagesResponse)
@trace_service("通过上下文查询消息", "Get messages by context")
async def get_chat_messages_by_context(
    context_type: str = Query(..., alias="contextType"),
    context_id: str = Query(..., alias="contextId"),
    page: int = Query(1, ge=1),
    page_size: int = Query(50, ge=1, le=100),
    user: CurrentUser = Depends(require_approved()),
) -> ChatMessagesResponse:
    """CHAT-2b: 通过上下文类型和 ID 查询消息(自动查找/创建对话)。"""
    svc = ChatService()
    chat_id = svc.get_or_create_session(
        user_id=user.user_id,
        site_id=user.site_id,
        context_type=context_type,
        context_id=context_id if context_id else None,
    )
    messages, total, resolved_chat_id = svc.get_messages(...)
    return ChatMessagesResponse(chat_id=resolved_chat_id, ...)
```

### 前端 `apps/miniprogram/miniprogram/pages/chat/chat.ts`

L213 注释:`// 优先级:historyId → taskId → customerId → coachId → general`

实际 6 分支(`onLoad(options)`):

| 分支 | 入参 | 调用 | 后端契约 |
|---|---|---|---|
| 1 | `options.historyId` | `loadMessages(historyId)` | `GET /api/xcx/chat/{chat_id}/messages` (CHAT-2a) |
| 2 | `options.taskId` | `loadMessagesByContext('task', options.taskId)` | `GET /api/xcx/chat/messages?contextType=task&contextId=...` (CHAT-2b) |
| 3 | `options.customerId` | `loadMessagesByContext('customer', options.customerId)` | 同上 (customer) |
| 4 | `options.coachId` | `loadMessagesByContext('coach', options.coachId)` | 同上 (coach) |
| 5 | `options.sourcePage` | `loadMessagesByContext(sourcePage, '')` | 同上 (任意 sourcePage,空 contextId) |
| 6 | 默认 | `loadMessagesByContext('general', '')` | 同上 (general) |

`fetchChatMessagesByContext` 调的就是 CHAT-2b 端点。

## 二、前后端契约一致性核对

| 字段 | 前端传 | 后端期望 | 一致? |
|---|---|---|---|
| context type | `'task' / 'customer' / 'coach' / 'general' / sourcePage` | Query alias=`contextType` | ✅ |
| context id | 字符串(可空) | Query alias=`contextId` | ✅ |
| 返回值 chatId | 前端缓存 `setData({ chatId })` 用于后续发送 | response.chat_id | ✅ |
| chatId 归属验证 | 后续发送时由后端校验(L164 stream / L367 send_message) | 已实现 | ✅ |

## 三、E-2 调研为何标"待核"

E-2 调研产物时点(2026-05-04 早于本走读)只读到了前端,标"后端契约待核"。本次走读确认后端契约**早已实现**(CHAT-2b 端点存在 + 业务逻辑完整 + 类型定义完备)。

可能的早期 timeline(推测):
- 前端先实现 6 分支,占位调 `/api/xcx/chat/messages?contextType=...`
- 后端某次会话补全 CHAT-2b
- E-2 调研只读前端文件,未读后端 → 标"待核"

## 四、本次行动

**无代码改动**,只产出本审计文件作为"已就位"标记,避免后续误以为 W1-T6 仍是待办。

W1-T6 状态:**关闭**。

## 五、未覆盖项(Wave 5 / 单测补漏)

- 单测覆盖:`tests/test_chat_get_or_create_session.py` 缺失,留 Wave 2/3 补
- 4 种 contextType 端到端 e2e 测试:留 Wave 1 §14 走查时验证(W1-T8)
- contextType / contextId 校验:可加 Pydantic Enum 限制(目前是 free string,留 Wave 5 文档收尾时可强化)

## 六、关联

- 调研:[`docs/_overview/04b-feedback/P1-3-4-cross-page-params.md`](../../_overview/04b-feedback/P1-3-4-cross-page-params.md) E-2
- 总报告:[`docs/_overview/04b-feedback/00-P1-feedback-response-summary.md`](../../_overview/04b-feedback/00-P1-feedback-response-summary.md) §三 `**前端已 6 分支修了,只剩后端契约待核**`(描述更新为已就位)
- Wave 1 kickoff:[`docs/_overview/WAVE-1-KICKOFF.md`](../../_overview/WAVE-1-KICKOFF.md) §二 W1-T6
