Files
Jyotisha/docs/tasks/TASK-consultation-subject-binding-20260922.md
T
2026-09-22 11:19:35 +08:00

116 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务书 · 普通聊天人物资料服务端真值与会话绑定(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` 核对一致。