116 lines
7.9 KiB
Markdown
116 lines
7.9 KiB
Markdown
# 任务书 · 普通聊天人物资料服务端真值与会话绑定(2026-09-22)
|
||
|
||
## 0. 基线与依赖
|
||
|
||
- 基线 commit:`1bc6a954c597e2817fe72bfedad93119ab19a527`(当前 `origin/staging`);执行方开工前必须 `git fetch origin --prune` 并重新确认。
|
||
- 执行分支:`codex/consultation-subject-binding-20260922`;工作树:`.worktrees/consultation-subject-binding-20260922`。
|
||
- 本单先于人物选择 UI。`TASK-chat-subject-picker-20260922.md` 必须等待本单的服务端 subject resolver 与集成测试完成后再开工;两轮不得同时改 `page.tsx`、会话绑定或咨询 route。
|
||
- 本轮 other 资料只覆盖普通聊天;报告、今日星语、生时校正和其他入口另立任务,不得顺手扩范围。
|
||
- 与本日人物选择 UI 任务严格串行:本单先完成并合入 staging,`TASK-chat-subject-picker-20260922.md` 才能开工;两单不得同时改 `frontend/src/app/api/consult/route.ts`、会话 binding 契约或聊天顶栏接线。
|
||
|
||
## 1. 事故实证
|
||
|
||
会议反馈:切换到其他人物后,聊天仍使用用户本人的资料。
|
||
|
||
调查已确认:
|
||
|
||
- `frontend/src/hooks/use-session-management.ts` 的 `startNewChat` 通过账户级 `activeChartId` 调用 `chartSnapshotForSession`,会话已有 `chart_profile_id/name/role` 元数据,但没有服务端 subject 解析。
|
||
- `frontend/src/app/api/consult/route.ts` 读取会话时只取 `chart_profile_role`,不取 `chart_profile_id`;随后通过 `frontend/src/lib/consultation-route-service.ts` 的 `loadProfile(userId)` 无条件读取登录用户的 `profiles`。
|
||
- `frontend/src/hooks/use-consultation-run.ts` 从全局 `profile` 组装客户端出生字段;这些字段不能作为服务端真值,否则会绕过所有权和资料完整性边界。
|
||
- `frontend/src/app/api/chart-profiles/route.ts` 与 `[id]/route.ts` 有基本 `user_id` 所有权约束,但没有接入咨询真值。
|
||
- 删除 other 资料后前端可能显示“资料已删除”,而 `/api/consult` 仍回退到本人,形成 fail-open 和用户误导。
|
||
|
||
相关历史:BUG-001、BUG-010、BUG-018、BUG-073、BUG-076、BUG-011、BUG-188;当前最大号开工前重新核对。
|
||
|
||
## 2. 根因
|
||
|
||
会话绑定字段目前只是客户端可写/可读的展示元数据,不是服务端确认过的 subject binding。咨询服务没有按 `chat_sessions.chart_profile_id` 解析人物,而是固定使用当前登录用户 `profiles`,所以 UI、会话显示与实际排盘对象可以不一致。
|
||
|
||
## 3. 决策记录(产品已拍板)
|
||
|
||
| 决策 | 本轮口径 |
|
||
|---|---|
|
||
| 会话人物 | 第一条消息发送后锁定;如需换人,新建会话,不改绑已有消息的会话 |
|
||
| 资料编辑 | 会话保留人物 ID,后续普通聊天使用该人物最新资料 |
|
||
| 删除人物 | 旧会话保留阅读;资料不存在/被删除时阻断继续发送,不自动退回本人 |
|
||
| other 范围 | 本轮只打通普通聊天;报告、每日星语、生时校正另立任务 |
|
||
| self 真值 | `self` 始终映射当前用户权威 `profiles`,不信任 chart library JSON 或客户端出生字段 |
|
||
| 名称/关系 | 会话快照用于历史展示;排盘对象由服务端按 ID/role 重新解析 |
|
||
|
||
## 4. 硬红线
|
||
|
||
1. 不得只改 `activeChartId`、顶部文案或客户端 request body 就声称修复。
|
||
2. 不得信任客户端 `name/year/month/day/hour/minute/city/lat/lon/tz` 覆盖服务端 resolver。
|
||
3. `other` 必须校验当前用户所有权、存在性、role、资料完整性;越权、伪造 role/name、删除或缺失必须 fail-closed,不得 fallback 到 self。
|
||
4. 有消息的会话不得 PATCH 改绑;若实现层需要字段存在,必须由服务端拒绝并覆盖测试。
|
||
5. 不改数据库结构,除非任务书另列迁移;不改计费语义、模型选择、普通聊天以外入口。
|
||
6. 不把出生资料、姓名、会话原文或凭据写进测试 fixture、日志、BUG 历史或任务记录;fixture 只能使用 synthetic/golden 数据。
|
||
7. 不增加 `page.tsx` 的 Home 状态/引用,不新增第二个输入框、滚动跟随或加载动画。
|
||
|
||
## 5. 任务分解与验收标准
|
||
|
||
### T1 · 服务端 subject resolver
|
||
|
||
新增独立服务端 helper(建议放 `frontend/src/lib/`,不要把实现堆进 `route.ts`):
|
||
|
||
- 输入当前 `userId` 与会话 binding;读取服务端会话和 chart profile。
|
||
- `self` → 当前用户 `profiles`;`other` → 当前用户拥有的 `chart_profiles.profile`。
|
||
- 服务端重新派生 name/role;会话展示快照不作为排盘真值。
|
||
- 统一处理不存在、删除、越权、role 不一致、资料不完整,并返回稳定内部错误类别。
|
||
- 客户端出生字段只能被忽略或用于非权威一致性检查,绝不能覆盖 resolver。
|
||
|
||
验收:self/other 都由服务端真值生成;other 不会取当前用户 profiles;越权 ID、伪造 name/role、缺资料、删除资料均 fail-closed;错误不泄露内部数据库细节。
|
||
|
||
### T2 · 接入咨询主链
|
||
|
||
- `frontend/src/app/api/consult/route.ts` 读取 `chart_profile_id` 及必要 binding 信息。
|
||
- 将 resolver 注入 `frontend/src/lib/consultation-route-service.ts` 的 prepare 流程。
|
||
- 保留会话 owner 校验、会话类型校验、计费前失败语义;resolver 失败不得扣点、不得进入模型调用。
|
||
- 只修改普通聊天咨询入口,不扩到报告、每日星语、校正。
|
||
|
||
验收:同一问题分别绑定 self/other 时,传入计算与最终咨询上下文的出生资料来自各自服务端 profile;resolver 失败时没有模型调用、没有扣点、公开错误可理解。
|
||
|
||
### T3 · 会话绑定写入与不可变边界
|
||
|
||
- `frontend/src/app/api/sessions/route.ts` 与 `[id]/route.ts` 在写入时校验 chart profile ID/role/name 的一致性,或由服务端根据 ID 填充展示字段。
|
||
- 未发送第一条消息的空会话可按产品规则创建绑定;已有消息的会话不得改绑。
|
||
- 刷新、深链、跨设备恢复保持 binding;打开历史会话不能只靠全局 `activeChartId` 改变实际对象。
|
||
|
||
验收:客户端提交任意伪造 name/role 不能伪造会话主体;服务端保存的绑定可被再次解析;有消息后改绑明确失败;空会话策略与下一轮 picker 一致。
|
||
|
||
### T4 · 服务端回归测试
|
||
|
||
新增或扩展测试,至少覆盖:
|
||
|
||
- self 使用权威 profiles;other 使用当前用户拥有的 chart profile;
|
||
- 另一用户 ID、随机 ID、role/name 不匹配、删除/缺失/不完整资料;
|
||
- 客户端出生字段与服务端资料冲突时仍使用服务端;
|
||
- `/api/consult` 集成确实把 other 资料交给 chart/consultation preparation;
|
||
- resolver 失败不扣点、不调模型、不 fallback;
|
||
- 会话有消息后改绑拒绝;刷新/深链保留 binding;
|
||
- 并发删除与发送时结果确定且 fail-closed。
|
||
|
||
保留现有测试名;若改变既有断言,进度记录写原值/新值/原因。
|
||
|
||
## 6. 让步顺序
|
||
|
||
T1 > T2 > T4 > T3 的 UI 接线。若时间不足,优先完成 resolver、咨询接入和服务端集成测试;不得先交付只改前端显示的 picker。
|
||
|
||
## 7. 开工前置命令
|
||
|
||
```bash
|
||
git status -sb
|
||
git fetch origin --prune
|
||
git worktree add -b codex/consultation-subject-binding-20260922 .worktrees/consultation-subject-binding-20260922 origin/staging
|
||
cd .worktrees/consultation-subject-binding-20260922/frontend
|
||
./node_modules/.bin/tsc --noEmit
|
||
npm run lint
|
||
npm test
|
||
```
|
||
|
||
开工时核对:`grep -n '^## BUG-' docs/BUG_HISTORY.md | tail -1`。当前任务书编写时最大号是 BUG-995;若确认新 Bug,按开工时最大号 +1,若为 BUG-073/076 复发则关联原记录。
|
||
|
||
## 8. 交付与记录
|
||
|
||
实现同轮必须更新 `docs/BUG_HISTORY.md`,说明现象、触发条件、根因、修复、验证和防复发;如尚不能闭环,状态只能是 `investigating` 或 `blocked`。写 `docs/tasks/PROGRESS-consultation-subject-binding-20260922.md`,并在 `docs/tasks/README.md` 状态板登记。不得声称已部署,除非 staging SHA 与 `/api/health.deployment.gitCommit` 核对一致。
|