feat(rectification): start from an offer card in the consultation, come back to it (BUG-1247~1251)

TASK-rectification-in-chat-step1-20261006, direct execution.

- The homepage 生时校正 pill and the text trigger (isRectificationHandoffQuestion)
  are gone. The consult agent decides (RECTIFICATION_OFFER_RULE, one
  definition) and calls offer-birth-time-rectification; the server decides
  whether a card may show (self, product on, no case in progress,
  feature_pricing price). The offer is stored with the answer and travels on
  run.completed; the card hangs under that answer.
- Opening from the card binds the offer to the case in the consultation's own
  messages (no table change); GET/POST /api/rectification/cases/[caseId]/source
  find the source and go back: one divider (time changed or not), earlier
  answers captioned 按校正前的时间, and a 按新时间重新看 button that sends only
  when clicked. Auto-return after the post-adopt check or a terminal case, only
  for work done in this view.
- Model history skips the divider and notes answers written with the old time.

Full suite 4987 / fail 24, identical to 6ec8efd5 (4973 / 24), no test name
lost; tsc 0; lint 0 errors; build keeps / Static, first-load gzip +0.20%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
This commit is contained in:
Jesse_Chen
2026-10-07 12:58:58 +08:00
co-authored by Claude Opus 5.5
parent 328f919d5d
commit 6ad0bea320
51 changed files with 1487 additions and 243 deletions
+49
View File
@@ -16852,3 +16852,52 @@
- 防复发:上述新测试;VOICE.md 新增本节规则与对照表。
- 相关记录:BUG-1257、BUG-1153 / BUG-1241(回执与交付句防重复,确认未受影响)、TASK-rectification-in-chat-step1-20261006 C8(同样不再叫人新建对话)。
- 修复版本:staging `6ec8efd5`(2026-10-07 部署核对:health gitCommit=6ec8efd5、各项 ok,`/api/account` 401,`/login` 200;门禁 run 1759 一次通过)。真机未走,状态保持 fixed-pending-verify
## BUG-1247 | 首页生时校正按钮与文字口令:入口在对话之外、对话里只靠固定口令跳转
- 状态:fixed-pending-verify(分支 `codex/rectification-in-chat-step1-20261006`;部署与真机清单完成前不标 resolved)
- 首次发现 / 最近更新:2026-10-07 / 2026-10-07
- 来源:产品决定生时校正并入对话(任务书 `docs/tasks/TASK-rectification-in-chat-step1-20261006.md`,P1–P3、C1–C8),产品要求直接执行。
- 现象:首页有独立「生时校正」pill;普通对话里只有打出「生时校正 / 先完成生时校正」这类原话才会新开一条校正会话,原问题只剩一行展示。
- 根因:入口按产品面划分,不按对话需要;对话侧用前端正则 `isRectificationHandoffQuestion` 判断意图(违反禁正则判意图口径)。
- 修复:删首页 pill、`rectificationEntryHint`、`rectificationEntryLabels` / `resolveRectificationEntryAction`;删 `isRectificationHandoffQuestion` 及发送路径里的交接分支与 `pendingConsultationQuestion` 展示。历史里的校正会话照常打开,只读历史里的「再次校正」保留。
- 验证 / 防复发:见 BUG-1248 汇总;断言更新 14 个测试文件(三栏注释)与 Python `test_daily_and_rectification_entrypoints.py`。
- 相关记录:BUG-976/977(禁正则判意图)、BUG-1115(交接前保存来源会话,已无此路径)。
- 修复版本:待发布
## BUG-1248 | 对话里没有「推得不准」时的校正提议
- 状态:fixed-pending-verify(分支 `codex/rectification-in-chat-step1-20261006`;部署与真机清单完成前不标 resolved)
- 首次发现 / 最近更新:2026-10-07 / 2026-10-07
- 来源:产品决定生时校正并入对话(任务书 `docs/tasks/TASK-rectification-in-chat-step1-20261006.md`,P1–P3、C1–C8),产品要求直接执行。
- 现象:用户在普通对话里说过去的事对不上、出生时间是估的,agent 没有任何校正相关的指令或入口。
- 修复:`RECTIFICATION_OFFER_RULE`(`consultation-thinking-plan.ts`,只定义一处)进首轮、追问与申报时段指令:先分读法 / 时间问题,时间问题才在写回答前调用 `offer-birth-time-rectification`;同会话提议过且用户没接不再主动提(`userAsked=true` 例外)。工具只记录本轮提议,能否出卡由服务端 `decideRectificationOffer` 决定(本人、`isProductEnabled("rectification")`、无进行中 case、`feature_pricing` 价格);提议随回答一起经 `complete_consultation_response` 存进消息,并在 `run.completed` 事件里带给浏览器。卡片 `RectificationOfferCard` 挂在该回答下。
- 验证:新增 `rectification-in-chat-20261007.test.ts` 14 条(服务端决策 5 种结果、工具一轮一张卡与会话内不重复提议、事件带提议、规则进三种指令、卡片 / 分隔线 / 小字渲染、浏览器回读保留字段等)。DeepSeek(deepseek-flash)模拟 4 场景各 2 次:只是不认同解释 2/2 不调用;过去的事对不上 + 时间是估的 2/2 调用;家人档案 2/2 调用后被拒、一句话说明;已提议又被拒 2/2 不再推;成功后正文提到卡片 3/3(工具说明加强后复测)。全量见进度记录。
- 修复版本:待发布
## BUG-1249 | 校正不知道是从哪条对话开始的
- 状态:fixed-pending-verify(分支 `codex/rectification-in-chat-step1-20261006`;部署与真机清单完成前不标 resolved)
- 首次发现 / 最近更新:2026-10-07 / 2026-10-07
- 来源:产品决定生时校正并入对话(任务书 `docs/tasks/TASK-rectification-in-chat-step1-20261006.md`,P1–P3、C1–C8),产品要求直接执行。
- 修复:打开接口在 `intent=homepage` 且带 `sourceSessionId` 时,把该会话里最新一张 `offered` 提议卡改为 `opened` 并写入 `caseId` / `rectificationSessionId`(调用者自己的客户端,`messages` 为本人可更新列;best effort)。新 `GET /api/rectification/cases/[caseId]/source` 在本人最近 20 条咨询会话里找绑定该 case 的提议卡,刷新后仍能找回来源。不改表。
- 验证:同上测试文件(绑定一次、幂等、无提议不绑定、来源查找)。真实库读写未在本机跑(无 Docker):只用了已有查询写法(eq / order / limit / maybeSingle / update),见进度记录。
- 修复版本:待发布
## BUG-1250 | 校正做完让用户「新建对话」,原问题和上下文丢失
- 状态:fixed-pending-verify(分支 `codex/rectification-in-chat-step1-20261006`;部署与真机清单完成前不标 resolved)
- 首次发现 / 最近更新:2026-10-07 / 2026-10-07
- 来源:产品决定生时校正并入对话(任务书 `docs/tasks/TASK-rectification-in-chat-step1-20261006.md`,P1–P3、C1–C8),产品要求直接执行。
- 修复:`POST /api/rectification/cases/[caseId]/source` 由服务端读 case 的 `accepted_time` 判断是否改了时间,在来源对话末尾追加一条分隔消息(`rectificationReturn`,每个 case 一次)并把提议卡标为 `returned`;浏览器重读该会话并切过去。校正面「回到刚才的对话」按钮(仅有来源时);采用后核对结束(`verified_idle`)或 case 终态时,在本视图做过操作的校正 2.5 s 后自动回去(`useRectificationAutoReturn`),历史打开的已结束 case 不自动跳。分隔线下「按新时间重新看」走 `onFollowUp`,点击才发送。分隔消息不显示操作行、不参与「重新生成」。
- 验证:同上测试文件(追加一次、改 / 未改两种文案、标记 returned、不点不发、自动返回只跟本视图操作)。浏览器级与真实库未验,见真机清单。
- 修复版本:待发布
## BUG-1251 | 改了出生时间后,模型仍读得到旧时间下的回答
- 状态:fixed-pending-verify(分支 `codex/rectification-in-chat-step1-20261006`;部署与真机清单完成前不标 resolved)
- 首次发现 / 最近更新:2026-10-07 / 2026-10-07
- 来源:产品决定生时校正并入对话(任务书 `docs/tasks/TASK-rectification-in-chat-step1-20261006.md`,P1–P3、C1–C8),产品要求直接执行。
- 修复:`storedConsultationTurns` 跳过分隔消息;最近一次改时间的分隔线之前的助手回答,在模型历史里前缀「(以下回答按校正前的出生时间,不要沿用其中的盘面判断)」;会话摘要早于改时间时同样加这句。
- 验证:同上测试文件(改了 / 没改两种情况、摘要)。
- 修复版本:待发布
@@ -0,0 +1,42 @@
# PROGRESS:生时校正并入对话 · 第一步 — 2026-10-07
产品 2026-10-07 要求直接执行。实现:Claude fork 子代理;分支 `codex/rectification-in-chat-step1-20261006`(基线 `origin/staging` `328f919d`,代码同 `6ec8efd5`)。未推送。BUG-1247~1251 均为 fixed-pending-verify。
## 实现(按任务书)
| 项 | 做法 |
| --- | --- |
| T1 / P3 / C1 | 删首页「生时校正」pill、`rectificationEntryHint`、`rectificationEntryLabels` / `resolveRectificationEntryAction`;删 `isRectificationHandoffQuestion` 与发送路径里的交接分支、`pendingConsultationQuestion` 展示与传参。`page.tsx` 1357 → 1337 行,`useState` / `useRef` 数不变;新增一个 `onStartRectification` 动作 |
| T2 / C2 / C3 | `RECTIFICATION_OFFER_RULE` 只定义在 `consultation-thinking-plan.ts`,进首轮、追问与申报时段指令。工具 `offer-birth-time-rectification`(本命与申报时段两种 agent 都有):只记录本轮提议;能否出卡由服务端 `decideRectificationOffer` 决定(本人、产品开关、无进行中 case、`feature_pricing` 价格);会话里已提议过且非用户主动要求时拒绝。提议随回答经 `complete_consultation_response` 存进 `messages`,`run.completed` 事件带给浏览器 |
| T3 / C4 | 打开接口带 `sourceSessionId` 时把该会话最新一张提议卡绑定到 case;`GET /api/rectification/cases/[caseId]/source` 在本人最近 20 条咨询里找来源;与校正水合并行读取 |
| T4 / C5 / C6 / C8 | `POST …/source` 由服务端读 `accepted_time` 判断是否改了时间,追加一次分隔消息、提议卡标 `returned`;浏览器重读来源会话并切过去。校正面「回到刚才的对话」按钮;`verified_idle` 或 case 终态时,本视图内做过操作的 case 2.5 s 后自动返回(历史打开的不跳)。分隔线、「按校正前的时间」小字、「按新时间重新看」(走普通 `onFollowUp`,点了才发)。C8 文案在 BUG-1258 已改为「之后的对话都按这个时间来看」,本单保持 |
| T5 / C7 | 模型历史跳过分隔消息;改时间前的助手回答与早于改时间的会话摘要前缀「(以下回答按校正前的出生时间,不要沿用其中的盘面判断)」 |
| T6 | DESIGN.md、VOICE.md、CONTEXT.md(来源对话、提议卡)、CHANGELOG、BUG-1247~1251、本记录、真机清单 `docs/testing/rectification-in-chat-step1-20261006.md` |
数据库:**不改表、不加迁移**。关联存在咨询会话自己的 `messages` JSON 里(`rectificationOffer` / `rectificationReturn`),写入用调用者自己的客户端(`messages` 是本人可更新列,`20260715030000` 起即有授权)。
## 偏离与原因
- 任务书 C4 说「刷新后仍在(服务端存)」:来源不单独存,读时在本人最近 20 条咨询里找绑定了该 case 的提议卡(绑定会写该会话,所以它在最近更新里)。代价:若用户之后在 20 条以上别的对话里聊过,再打开这次校正时找不到来源(只是没有返回按钮,不报错)。
- 来源对话的消息改写是读后整写,没有乐观锁:本地 Postgres 兼容层取回的 `updated_at` 精度不一定能做等值比较。并发风险只在用户同时在来源对话里提问时出现(此时人在校正面)。
- 提议工具成功后的说明,经模型实测改为「正文最后一段必须有一句提到卡片」(原写法 2/2 没提,改后 3/3 提到)。
- 首页卡片专用的 `RECTIFICATION_OPENING_LABEL` 一并删除(侧栏行的打开中文案是另一个常量,不变)。
## 验证(Linux + Node 22.14)
| 项 | 基线 `6ec8efd5` | 本分支 |
| --- | --- | --- |
| `tsc --noEmit` | — | 0 错 |
| `npm run lint` | 0 error / 126 warning | 0 error / 125 warning |
| `npm test` | 4973 项 / fail 24 | 4987 项 / fail 24;失败名单逐条一致(均为无 Docker 的数据库 / 部署类);测试名逐条比对,无消失,新增 14 条 |
| `next build` | `/` ○ Static,首屏 gzip 567,201 B | `/` ○ Static,568,343 B(+0.20%) |
| Python 快速门 | — | py_compile / JSON / 审计 / pytest(329.7 s)通过;其中 `npm test` 一步因上述 24 条环境失败而红,与基线相同 |
断言改动 14 个前端测试文件 + `tests/test_daily_and_rectification_entrypoints.py`,每处写原值 / 新值 / 原因。
模型实测(DeepSeek deepseek-flash,公开名人 golden 盘,脚本在会话临时目录、不入库):只是不认同解释 2/2 不调用提议工具;「过去的事对不上 + 时间是估的」2/2 调用;家人档案 2/2 调用后被服务端拒、一句话说明;已提议后再说不准 2/2 被拒且不再推;成功后正文提到卡片 3/3。
## 环境缺口
- 无 Docker:来源绑定 / 返回的真实库读写(`chat_sessions.messages` 读改写、`maybeSingle`、`order + limit`)未真查;只用了本地 Postgres 兼容层已实现的写法。部署后按真机清单第 4–8 步验。
- 无登录态浏览器:卡片、返回、自动返回、按钮不点不发均为渲染与源码合同测试,真机清单 11 步待产品。
+1 -1
View File
@@ -425,7 +425,7 @@
| `TASK-rectification-nadi-seconds-research-20261005.md` | `PROGRESS-rectification-nadi-seconds-research-20261005.md` | **「纳迪秒级校准」可证伪检验(离线)**:竞品宣传「问前事到天 → 秒级」。本仓主链只用三层小运,Sookshma / Prana 与 D150 从未进评价集;v5 真值 52/77 是整 5 分钟(秒级无真值可对)。N0 五层小运 + D150 底座(前三层与主链对账 0 差)、N1 拟合率 vs 安慰剂日期(核心)、N2 留一件预测、N3 六题后区间内再细分能否提头名、N4 岁差 / 坐标 / 时间扰动的噪声地板、N5 D150 结构层(原文比对 blocked)、N6 结论 + 对外口径草稿。规则先登记再跑;不改生产代码;不得重调 BUG-1091 已关的权重 | 已验收(Claude 10-05:N1/N2/N3/N5 不过门、N4 触发文案禁令;Linux 复跑内容 0 差,结果改 LF;补 Lahiri/KP/True Chitra 对照,月亮差 0.83′ 第 5 层仍换 94%)→ 不立实现单 | 分支 `codex/rectification-nadi-seconds-research-20261005`(BUG-1240 `closed_by_design`) |
| `TASK-rectification-delivery-dup-adopt-20261006.md` | `PROGRESS-rectification-delivery-dup-adopt-20261006.md` | **校正交付一次两段回答 + 点「改用…盘解读」被拒 `adoption_not_allowed`**(10-06 真机):收尾补位按文字子串防重,遇采用旁白 Agent 改写版失效(BUG-1153 复发);accept/GET 调 `decideFromDossier` 不带出生日期,未成年探针复活致判定与出卡时相反(BUG-598/680 同族);卡片按钮不看公开 can_adopt、拒绝码直出英文。不放宽采用门,判定入参统一 + 结构防重 + 按钮判据 + 中文提示(BUG-1241~1243) | **已验收,待推 staging**(Claude 10-06:首版 `b977f482` 验收 T1/T3 过、T2 未过;修复单 F1 由 Claude 直接执行——防重改为「上一条包含交付句」,真实答题路径测试修前红修后绿;F2 疑似 BUG-1246 复核不成立已撤回、留守护测试。全量 npm 失败名与基线逐名相同、tsc 0、lint 0 error;PG17 重放未做,见 PROGRESS) | 分支 `codex/rectification-delivery-dup-adopt-20261006`;修复单 `TASK-rectification-delivery-dup-adopt-fix-20261006.md` |
| `TASK-consult-conversational-answer-20261006.md` | `PROGRESS-consult-conversational-answer-20261006.md` | **普通对话首轮去汇报骨架,改成聊天**(10-06 产品反馈「像机器在汇报」):首轮取消全部 `##`(三个固定节 + 按对象标题,多人改分段落点名)、行动不强制(盘上有具体指向才顺口一句,问「怎么办」再展开)、首轮三到六段;示范删可抄的行动句(实测被逐字照抄);思考栏去「这周可以做什么」。推翻 10-01 D2 与 D8 首轮部分(BUG-1244~1245) | 已验收 | 28113fa0 验收未过 → 修复单 `TASK-consult-conversational-answer-fix-20261006.md` 由 Claude 直接执行(含去掉助手头像),复验通过后推 staging;真机清单 10 步待产品 |
| `TASK-rectification-in-chat-step1-20261006.md` | `PROGRESS-rectification-in-chat-step1-20261006.md` | **生时校正并入聊天 · 第一步**:删首页「生时校正」按钮与文字口令;普通对话里用户说不准时由 agent 分清读法 / 时间问题,时间问题出提议卡(本人、开关、价格服务端校验,同会话最多主动提一次);点卡带来源打开校正;采用 / 未采用 / 手动返回都回到原对话,分隔线 + 旧回答标「按校正前的时间」+ 点了才发的「按新时间重新看」;模型历史标注旧时间。须等 dup-adopt 单合入后开工;第二步(卡片嵌进同一对话)另立单(BUG-1247~1251) | 待领取 | — |
| `TASK-rectification-in-chat-step1-20261006.md` | `PROGRESS-rectification-in-chat-step1-20261006.md` | **生时校正并入聊天 · 第一步**:删首页「生时校正」按钮与文字口令;普通对话里用户说不准时由 agent 分清读法 / 时间问题,时间问题出提议卡(本人、开关、价格服务端校验,同会话最多主动提一次);点卡带来源打开校正;采用 / 未采用 / 手动返回都回到原对话,分隔线 + 旧回答标「按校正前的时间」+ 点了才发的「按新时间重新看」;模型历史标注旧时间。须等 dup-adopt 单合入后开工;第二步(卡片嵌进同一对话)另立单(BUG-1247~1251) | 待验收 | 2026-10-07 产品要求直接执行;Claude fork 子代理实现,分支 `codex/rectification-in-chat-step1-20261006`(未推);不改表;真机清单 11 步 |
| —(直接执行,无任务书) | `PROGRESS-consult-readable-20261006.md` | **回答铺满一行、输出不抖、不念分析清单**(10-06 真机截图):答案正文去 `text-wrap: pretty`(WebKit 整段等长 + 逐字重排,BUG-1252);清单开头写明只用于分析、婚恋三层改生活说法、字段白话对照、括号最多两条(BUG-1253) | 已验收 | Claude 直接执行;真机清单 `docs/testing/consult-readable-20261006.md` 待产品 |
| —(直接执行,无任务书) | —(记录在 BUG-1254) | **门禁偶发红:数据库测试排队等槽位超时**:46 个 fixture 文件抢 2 个槽位、固定等 5 分钟,排在后面的随机失败;改为槽位易手即续期、连续 10 分钟无进展才失败(BUG-1254) | 已验收 | Claude 直接执行;门禁实际效果待看 |
| —(直接执行,无任务书) | —(记录在 BUG-1255) | **回答口吻改知心姐姐、放开读心**(10-07 产品决定):人设统一;读心守三条(先答再读、可能/多半一句、当事人要有依据且未受冲);删冲突的旧规矩。推翻 BUG-1070 读心部分 | 已验收 | Claude 直接执行;真机清单 `docs/testing/consult-voice-sister-20261007.md` 待产品 |
@@ -0,0 +1,17 @@
# 真机清单 · 生时校正从对话进入、做完回到原对话(2026-10-07)
对应 BUG-1247~1251,任务书 `docs/tasks/TASK-rectification-in-chat-step1-20261006.md`。在 staging 上用自己的账号(本人档案)走,deepseek-v4-flash 一遍、另一个模型一遍。回答原文与出生资料不记进仓库。
| 步 | 怎么做 | 通过标准 |
| --- | --- | --- |
| 1 | 打开首页 | 只有「今日星语」一枚按钮,没有「生时校正」 |
| 2 | 新对话问「我和伴侣还能走下去吗」,答完后说「你说我们磨合多,我觉得不对,我们很少吵架」 | agent 认下、改读;回答下面**没有**提议卡 |
| 3 | 接着说「你说我 2019 年换工作,其实那年什么都没发生;我出生时间是我妈估的」 | 回答最后一句提到可以核对出生时间;下面出现提议卡,写着大约几题、几点、答第一题才扣;按钮「开始核对出生时间」 |
| 4 | 刷新页面,回到这条对话 | 提议卡还在 |
| 5 | 点「开始核对出生时间」 | 进入校正;输入框上方有「回到刚才的对话」;刷新后仍在;回原对话看提议卡变成「出生时间核对已经开始」+「继续核对出生时间」 |
| 6 | 在校正里答完、采用一个时间、做完采用后核对 | 几秒后自动回到原对话;末尾一行「出生时间改为 HH:MM(生时校正)」;之前的回答上方都有「按校正前的时间」;有「按新时间重新看:「…」」按钮;**不点就不出新回答、点数不变** |
| 7 | 点「按新时间重新看」 | 正常回答一轮、扣一次点;回答按新时间,不沿用旧回答里的盘面判断 |
| 8 | 另开一条对话照第 3 步说,进入校正后中途点「回到刚才的对话」 | 回到原对话,分隔线写「出生时间没有改」,没有「按校正前的时间」;侧栏里那次校正还在、能再打开 |
| 9 | 同一条对话里第 3 步那类话再说一次(之前没点卡) | agent 不再主动出卡;明确说「我想核对出生时间」时才再出 |
| 10 | 切到一个家人档案问同样的话 | 一句「给家人校正暂时还不支持」,不出卡 |
| 11 | 侧栏打开一个 10 月 7 日之前的校正会话 | 能正常打开、看结果;没有「回到刚才的对话」按钮,也不会自动跳走 |