docs(tasks): 校正会话标题改写结果而非日期(BUG-1001)
BUG-988 后标题日期与副标题重复,且 BUG-929 删 uniquify 后同日多条 标题相同。产品拍板标题承载 accepted/confirmed 分钟或 candidate_range, 存量批量重算。红线:不得 bump updated_at,标题算式只能有一处实现。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
This commit is contained in:
co-authored by
Claude Opus 5
parent
3fed71ab6d
commit
c6d421edba
@@ -33,6 +33,7 @@
|
||||
|
||||
| 任务书 | 进度 | 主题 | 状态 | 落点 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `TASK-rectification-session-title-result-20260922.md` | — | **校正会话标题改写结果(BUG-1001)**:BUG-988 把副标题改成最后活动时间后,标题里的日期成了重复;而 BUG-929 删掉 `uniquifySessionTitle` 之后,`resolveSessionTitle` 对校正入口只返回 `生时校正 · M月D日`,**同一天多条标题完全相同**(真机截图:9/17 三条同名、9/16 两条同名、9/14 一天 5 条);旧标题的钟点后缀是创建时间、副标题是最后活动时间,同一行两个对不上的时间。**产品 09-22 拍板**:D1 标题改为承载结果——有 `accepted_time`/`confirmed_time` 写 `生时校正 · HH:MM`,否则写 `candidate_range` 的 `生时校正 · HH:MM–HH:MM`(推翻 BUG-929 的日期口径,但「不得再加墙钟去重后缀」保留);D2 存量批量重算(推翻 BUG-929「旧标题不批量改」,先例 `20260916020000_rectification_session_title_repair.sql`);D3 今日节奏保留日期(我判定的例外);D4 只在结果变化时改 title,`open` 路径不动(对 BUG-699 边界的有限扩展)。**红线**:任何写标题的路径都不得 bump `updated_at`(否则 13 条历史会话集体跳顶、毁掉 BUG-988);标题算式必须只有一处实现(一个 SQL 函数,触发器与回填共用)——BUG-987/992 都栽在「两层规则各写一份」。T4 跨层对断不得让步。已 grep 出真正受影响的只有 7 个文件,Python 侧只查裸字符串、无需改。BUG 段 1001 起 | 待领取 | — |
|
||||
| `TASK-rectification-convergence-20260830.md` | `PROGRESS-rectification-convergence-20260830.md` | 收敛重构 v2 | 已验收 | `3a4396a4`、`86ba17ee` |
|
||||
| `TASK-rectification-decision-authority-20260831.md` | `PROGRESS-rectification-decision-authority-20260831.md` | 决策权威归一与停止语义 | 已合入 | `9f011194` |
|
||||
| `TASK-rectification-billing-20260831.md` | — | run_failed 与开场问题槽为空 | 已合入 | `47b4b06b` |
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
# TASK 校正会话标题改为写结果,不再写日期(BUG-1001)
|
||||
|
||||
- 日期:2026-09-22
|
||||
- 基线 commit:`3fed71ab`(`origin/staging` head;开工 `git fetch` 后以实际 head 为准。已部署 SHA 当时是 `1bc6a954`,门禁在 `6d06fca0` 红过,与本单无关)
|
||||
- 分支:`codex/rectification-session-title-result-20260922`
|
||||
- BUG 编号起点:**BUG-1001**(`docs/BUG_HISTORY.md` 当前最大号 `BUG-1000`,开工时复核)
|
||||
- 关联记录:**BUG-929**(本单推翻其标题口径)、BUG-699、BUG-704、BUG-988、BUG-987、BUG-992
|
||||
|
||||
---
|
||||
|
||||
## 1. 事故实证
|
||||
|
||||
产品负责人 2026-09-22 的侧栏截图(真机 staging):
|
||||
|
||||
| 标题 | 副标题 |
|
||||
| --- | --- |
|
||||
| 今日节奏 · 9月18日 | 9月18日 11:18 |
|
||||
| **9月17日 · 生时校正** | 9月17日 19:12 |
|
||||
| 9月17日 · 生时校正 **09:35** | 9月17日 **11:15** |
|
||||
| **9月17日 · 生时校正** | 9月17日 07:57 |
|
||||
| **9月17日 · 生时校正** | 9月17日 00:24 |
|
||||
| 9月16日 · 生时校正 | 9月16日 22:25 |
|
||||
| 9月16日 · 今日节奏 | 9月16日 18:34 |
|
||||
| 9月16日 · 生时校正 | 9月16日 16:20 |
|
||||
| …9月15 / 9月14 共 8 条,其中 9月14 一天 5 条 | … |
|
||||
|
||||
三个问题:
|
||||
|
||||
1. **日期印了两遍。** BUG-988 把副标题改成最后活动时间之后,标题里的日期就是冗余。
|
||||
2. **标题不再能区分同一天的多条。** `uniquifySessionTitle` 在 BUG-929 那轮删掉了,`resolveSessionTitle`
|
||||
现在对校正入口只返回 `datedSessionTitle(at, "生时校正")`(`frontend/src/lib/agent-reply.ts`,符号定位
|
||||
`function datedSessionTitle` 与 `export function resolveSessionTitle`)。截图里 9月17日 三条标题**完全相同**,
|
||||
9月16日 两条也相同;9月14日 那天有 5 条。日期这个维度根本不够用。
|
||||
3. **旧标题里的钟点和副标题对不上。** 带后缀那几条(`… 生时校正 09:35` / 副标题 `11:15`,
|
||||
`… 02:14` / `02:19`,`… 15:37` / `15:38`)是 BUG-929 删 `uniquify` 之前留下的历史标题,
|
||||
BUG-929 当时决定「旧标题不批量改」。标题里的钟点是**创建时间**,副标题现在是**最后活动时间**,
|
||||
同一行摆着两个对不上的时间,读起来像 bug。
|
||||
|
||||
产品负责人的原话是「既然下面有实际日期了,标题是不是就不需要日期了」。
|
||||
但**只做减法会让标题这一列信息量归零**——13 行全叫「生时校正」。所以本单是替换,不是删除。
|
||||
|
||||
---
|
||||
|
||||
## 2. 根因
|
||||
|
||||
侧栏行有两列信息:标题和副标题。BUG-988 把副标题定成「最后活动时间」之后,
|
||||
标题继续承载日期,就同时犯了两个错:与副标题重复,且不承载这次会话的内容。
|
||||
|
||||
普通咨询会话的标题是模型对首问的总结(内容摘要),校正会话没有这条路
|
||||
(`shouldGenerateSessionTitle` 对 `birth_time_rectification` 直接 `return false`),
|
||||
于是退化成了日期。而校正会话真正有区分度、用户真正想在列表里找的,是**这次校正算出了什么**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 决策记录
|
||||
|
||||
产品负责人 2026-09-22 就地拍板:
|
||||
|
||||
- **D1(推翻 BUG-929 的「新标题 `生时校正 · M月D日`」)**:校正会话标题改为承载**校正结果**:
|
||||
- 有 `confirmed_time` 或 `accepted_time` → `生时校正 · HH:MM`
|
||||
- 否则 → `生时校正 · <start>–<end>`(用 `candidate_range`,该列 `not null`,永远有值)
|
||||
|
||||
BUG-929 的其余决定保留:**不得再给同名标题追加墙钟 HH:MM 后缀**(那正是问题 3 的来源)。
|
||||
- **D2(推翻 BUG-929 的「旧标题不批量改」)**:存量校正会话标题一次性批量重算,
|
||||
日期在前的旧格式与钟点后缀一并清掉。有现成先例:`20260916020000_rectification_session_title_repair.sql`
|
||||
就是干这个的,路走过。不改就会上半截新格式、下半截旧格式,比现在还乱。
|
||||
- **D3(我的判断,产品未单独过问,按例外处理)**:**「今日节奏」保留日期**。它一天一条,
|
||||
日期就是它的全部内容,没有别的可写;去掉就全叫「今日节奏」。副标题重复一次日期是可接受的代价。
|
||||
若产品验收时不同意,另开一轮,本单不动它。
|
||||
- **D4(对 BUG-699 边界的有限扩展)**:BUG-699 的防复发写的是「打开已有会话仍不得改 title / updatedAt」。
|
||||
本单要在**校正结果变化时**改 title。这**不是**打开会话时改,`open` 路径一个字都不动。
|
||||
执行方不得以 BUG-699 为由拒改,但也不得顺手放宽 open 路径。
|
||||
|
||||
---
|
||||
|
||||
## 4. 硬红线
|
||||
|
||||
1. **不得 bump `updated_at`。** 写标题的任何路径都不许碰 `chat_sessions.updated_at`
|
||||
(BUG-988 / BUG-704)。存量回填迁移一旦动了它,13 条历史会话会集体跳到列表顶端、全显示今天——
|
||||
那会直接毁掉刚修好的 BUG-988。回填迁移必须有「`updated_at` 逐行不变」的断言。
|
||||
2. **运行时与回填必须共用同一个标题函数。** 今天已经在同一件事上吃过两次亏:
|
||||
BUG-987 是服务端与客户端两套相反的过滤规则,BUG-992 是前端与 Python 两套不同步的合同。
|
||||
本单要求把标题算式写成**一个 SQL 函数**,触发器和回填迁移都调它,不得各写一份。
|
||||
3. **`open` 路径不得改 title**(BUG-699)。
|
||||
4. 不得再引入墙钟 HH:MM 去重后缀(BUG-929 防复发)。
|
||||
5. 不得动会话删除、收藏、重命名;**用户手动重命名过的标题不得被覆盖**(见 T2 的判据)。
|
||||
6. 删改任何前端符号或可见文案之前,按 `frontend/AGENTS.md` 跑 `git grep -n "<符号>" -- tests/ frontend/`,
|
||||
不得只扫 `frontend/`(BUG-992 的教训,已列在 §6)。
|
||||
7. 改任何既有断言写「原值 / 新值 / 原因」三栏(AGENTS.md §7.3);比测试规模比**用例名列表 diff**,
|
||||
不比 `# tests` 汇总数(BUG-995)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 任务分解
|
||||
|
||||
### T1|标题算式落成一个 SQL 函数(BUG-1001)
|
||||
|
||||
新增迁移,建 `public.rectification_session_title(p_accepted time, p_confirmed time, p_range jsonb) returns text`,
|
||||
`immutable`,`set search_path=''`:
|
||||
|
||||
- `p_confirmed` 非空 → `'生时校正 · ' || to_char(p_confirmed, 'HH24:MI')`
|
||||
- 否则 `p_accepted` 非空 → 同上,用 `p_accepted`
|
||||
- 否则 `p_range` 的 `start_time` / `end_time` 都合法(复用现成的
|
||||
`public.agentic_rectification_is_clock`)→ `'生时校正 · ' || start || '–' || end`
|
||||
(**破折号用 U+2013 EN DASH,与交付卡口径一致,不要用连字符**)
|
||||
- 否则 → `'生时校正'`
|
||||
|
||||
- **验收标准:** 四个分支各一条真实 Postgres 断言;`immutable` 且不读表。
|
||||
|
||||
### T2|结果变化时回写标题(BUG-1001)
|
||||
|
||||
仿照现成的 `public.touch_chat_session_from_rectification_case()`
|
||||
(`frontend/supabase/migrations/20260915010000_rectification_touch_chat_session.sql`,
|
||||
同一 security 姿态、同一 `schema_owner` 守卫、同一 `drop trigger if exists` + `create trigger` 写法),
|
||||
新增 `after insert or update of accepted_time, confirmed_time, candidate_range on public.agentic_rectification_cases` 的触发器:
|
||||
|
||||
```
|
||||
update public.chat_sessions
|
||||
set title = public.rectification_session_title(new.accepted_time, new.confirmed_time, new.candidate_range)
|
||||
where id = new.session_id
|
||||
and user_id = new.user_id
|
||||
and session_type = 'birth_time_rectification'
|
||||
and title <> public.rectification_session_title(...) -- 无变化不写
|
||||
and (title = '生时校正' or title ~ <自动派生标题的正则>) -- 用户改过名的不覆盖
|
||||
```
|
||||
|
||||
- **不得**出现 `updated_at = ...`(红线 1)。
|
||||
- 「用户改过名的不覆盖」的判据:标题匹配自动派生形态才覆盖。自动派生形态包含
|
||||
**旧格式**(`M月D日 · 生时校正[ HH:MM]`)、**BUG-929 格式**(`生时校正 · M月D日`)、
|
||||
**本单格式**(`生时校正[ · …]`)三种。这个正则与 `frontend/src/lib/session-title.ts` 的
|
||||
`DATED_ENTRY_TITLE` 是同一件事的两处表达——**T4 要求两边对断**。
|
||||
- **验收标准:**
|
||||
- 真实 Postgres:采用一个分钟 → 标题变 `生时校正 · HH:MM`,同一行 `updated_at` **逐字节不变**;
|
||||
区间收窄 → 标题跟着变;用户手动改成「我的校正」后再采用 → 标题**不被覆盖**。
|
||||
- `last_activity_at` 触发的既有 touch 触发器仍正常(两个触发器互不干扰,各测一条)。
|
||||
|
||||
### T3|新建时不带日期,前端口径跟上(BUG-1001)
|
||||
|
||||
- `frontend/src/lib/agent-reply.ts`:`resolveSessionTitle` 对 `entrypoint === "birth_time_rectification"`
|
||||
与 `isRectificationHandoffQuestion` 分支改为返回 `"生时校正"`(不带日期,结果由 T2 的触发器补)。
|
||||
`datedSessionTitle` 若只剩「今日节奏」一个调用者,保留但改名或加注释说明它现在只服务今日节奏(D3)。
|
||||
- `frontend/src/lib/session-title.ts`:`DATED_ENTRY_TITLE` 要继续认得住**三种**自动派生形态
|
||||
(旧格式、BUG-929 格式、本单格式),否则 `shouldGenerateSessionTitle` / `isAutoDerivedSessionTitle`
|
||||
会把新标题当成用户自定义标题,进而让模型去覆盖它。
|
||||
- **验收标准:**
|
||||
- `agent-reply.test.ts` 断言校正入口返回 `生时校正`(原值 `生时校正 · M月D日`,写三栏说明)。
|
||||
- 新增断言:`isAutoDerivedSessionTitle` 对 `生时校正`、`生时校正 · 05:07`、`生时校正 · 05:00–05:15`、
|
||||
`生时校正 · 9月17日`、`9月17日 · 生时校正`、`9月17日 · 生时校正 09:35` **全部为 true**;
|
||||
对 `我的校正` 为 false。
|
||||
- `shouldGenerateSessionTitle` 对校正会话仍恒 false(不变)。
|
||||
|
||||
### T4|跨层对断(BUG-1001,防复发的主断言)
|
||||
|
||||
- 新增一条测试,把 T1 的 SQL 函数与前端的 `DATED_ENTRY_TITLE` 放在同一张表里对断:
|
||||
同一组输入(六种自动派生形态 + 一个用户自定义标题),SQL 侧「会不会覆盖」与 TS 侧
|
||||
「是不是自动派生」必须给出相同答案。
|
||||
- 理由写进测试注释:BUG-987 与 BUG-992 都是「两层规则各写一份、没人对断」。
|
||||
- **验收标准:** 故意把 SQL 正则改一个字符,这条测试必须转红(执行方本地自测一次,撤回,不提交)。
|
||||
|
||||
### T5|存量批量重算(BUG-1001)
|
||||
|
||||
新增向前业务迁移:
|
||||
|
||||
```sql
|
||||
update public.chat_sessions s
|
||||
set title = public.rectification_session_title(c.accepted_time, c.confirmed_time, c.candidate_range)
|
||||
from public.agentic_rectification_cases c
|
||||
where c.session_id = s.id
|
||||
and s.session_type = 'birth_time_rectification'
|
||||
and s.title <> public.rectification_session_title(c.accepted_time, c.confirmed_time, c.candidate_range)
|
||||
and (s.title 匹配三种自动派生形态之一);
|
||||
```
|
||||
|
||||
- 幂等(第二次跑匹配 0 行)。**不得** `set updated_at`。
|
||||
- 没有对应 case 的校正会话(理论上不存在,`session_id` 是 unique FK)跳过,不报错。
|
||||
- **验收标准:**
|
||||
- 真实 Postgres:造六条不同状态的会话(已确认 / 已采用 / 只有区间 / 旧格式带钟点 / 旧格式不带钟点 / 用户改过名),
|
||||
跑迁移后前五条按规则改写、第六条不动;**全部六行的 `updated_at`、`pinned`、`messages` 逐字节不变**;
|
||||
重跑迁移 0 行受影响。
|
||||
- 迁移里的 `update` 语句由测试**从文件切出来执行**,不得手抄(BUG-995 那轮定的做法)。
|
||||
|
||||
### T6|记录(BUG-1001)
|
||||
|
||||
- `docs/BUG_HISTORY.md` 新增 BUG-1001,**复发自 BUG-929**(标题口径两次返工)。
|
||||
防复发写两条:标题算式只能有一处实现,运行时与回填共用;侧栏同一行的两列不得承载同一个事实。
|
||||
- `CHANGELOG.md`:一句用户能懂的——历史校正会话的名字改成显示算出来的出生时间或当前范围。
|
||||
- `frontend/DESIGN.md` 的 **Navigation item** 一节:标题口径同提交更新
|
||||
(现在写的是「New dated titles are `生时校正 · M月D日` / `今日节奏 · M月D日` (category first)」)。
|
||||
- `docs/tasks/README.md` 状态板加行。
|
||||
|
||||
---
|
||||
|
||||
## 6. 会被牵动的文件(我已 grep 过,照这份查)
|
||||
|
||||
真正断言标题格式的只有这 7 处(`git grep -ln "生时校正 · \|月.日 · 生时校正\|datedSessionTitle\|DATED_ENTRY_TITLE\|datedRectificationTitle\|RECTIFICATION_DATED_TITLE\|repairedRectificationTitle" -- tests/ frontend/`):
|
||||
|
||||
| 文件 | 说明 |
|
||||
| --- | --- |
|
||||
| `frontend/src/lib/agent-reply.ts` | `datedSessionTitle` / `resolveSessionTitle`,T3 改这里 |
|
||||
| `frontend/src/lib/session-title.ts` | `DATED_ENTRY_TITLE` 正则,T3 扩到三种形态 |
|
||||
| `frontend/src/lib/rectification-session-title-repair.ts` | BUG-699/704 的旧修补助手,判断是否仍需要;若被 T5 取代就删并说明 |
|
||||
| `frontend/scripts/repair-rectification-session-titles.mjs` | 同上,旧脚本 |
|
||||
| `frontend/supabase/migrations/20260916020000_rectification_session_title_repair.sql` | **不得修改**(历史迁移),只作为写法先例 |
|
||||
| `frontend/tests/agent-reply.test.ts` | 断言要改 |
|
||||
| `frontend/tests/rectification-session-title-repair.test.ts`、`-migration.test.ts`、`frontend/tests/session-list-filter.test.ts` | 断言要跟着改 |
|
||||
|
||||
**Python 侧确认不需要改**:`tests/test_daily_and_rectification_entrypoints.py` 只断言裸字符串
|
||||
`"生时校正" in source`,任何新格式都满足;`tests/test_api_server_security.py` 的命中与标题无关。
|
||||
(这一条是我替你查过的结论,但开工时仍按红线 6 自己再跑一次 grep。)
|
||||
|
||||
---
|
||||
|
||||
## 7. 交付前必须全跑
|
||||
|
||||
- `cd frontend && ./node_modules/.bin/tsc --noEmit` → 0 错
|
||||
- `npm run lint` → 0 error,warning 不得超过 **119**
|
||||
- `npm test` → 全量;用**用例名列表 diff**与基线比对,只允许新增
|
||||
- `npm run test:db` → **本单必跑**,T2 / T5 的证据全在这里;无 Docker 时按 §8 让步
|
||||
- `npm run build` → `/` 仍 `○ Static`,首屏 gzip ±2%
|
||||
- `python3 -m pytest tests/test_daily_and_rectification_entrypoints.py -q` → 确认未被波及
|
||||
|
||||
---
|
||||
|
||||
## 8. 让步顺序
|
||||
|
||||
1. **无 Docker** → `test:db` 跑不了:T1/T2/T5 的实现与真实 Postgres 测试照写照提交,
|
||||
`BLOCKED.md` 记明,最终以门禁 run 转绿为准;**BUG-1001 在门禁那几条 DB 测试转绿之前不得标 `resolved`**。
|
||||
2. **T5 存量回填与 T1–T4 拆轮** → 允许,但顺序必须是**先 T1–T4 后 T5**:
|
||||
先让新结果能写对,再回填历史。反过来回填完又被旧触发器逻辑覆盖。
|
||||
3. **T4 跨层对断做不出来** → 不得让步。这是本单唯一真正的防复发措施。
|
||||
4. D3(今日节奏保留日期)若产品验收时否决 → 另开一轮,本单不扩范围。
|
||||
|
||||
---
|
||||
|
||||
## 9. 开工前置命令
|
||||
|
||||
```bash
|
||||
cd /workspace/Jyotisha
|
||||
git status -sb | head -1
|
||||
git fetch origin --prune
|
||||
git worktree add -b codex/rectification-session-title-result-20260922 \
|
||||
.worktrees/rectification-session-title-result-20260922 origin/staging
|
||||
cd .worktrees/rectification-session-title-result-20260922/frontend
|
||||
ln -s /workspace/Jyotisha/frontend/node_modules node_modules # 或 npm ci
|
||||
|
||||
# 基线用例名单(比名单,不比总数)
|
||||
npm test 2>&1 | grep -E "^(not )?ok [0-9]+ - " | sed -E 's/^(not )?ok [0-9]+ - //' | sort > /tmp/names-base.txt
|
||||
|
||||
# 自己再跑一次范围确认(红线 6)
|
||||
cd .. && git grep -n "datedSessionTitle\|DATED_ENTRY_TITLE\|生时校正 · " -- tests/ frontend/
|
||||
```
|
||||
|
||||
开工必读:`docs/BUG_HISTORY.md` 的 **BUG-929**(本单推翻它)、BUG-699、BUG-704、BUG-988、BUG-992、BUG-995;
|
||||
`frontend/AGENTS.md`;`frontend/DESIGN.md` 的 Navigation item 一节;
|
||||
`frontend/supabase/migrations/20260915010000_rectification_touch_chat_session.sql`(触发器写法先例)。
|
||||
|
||||
注意:在 `frontend/` 里跑 `npx tsx --test` 会生成未跟踪的 `frontend/frontend/node_modules`,交付前清掉。
|
||||
|
||||
---
|
||||
|
||||
## 10. 部署后真人走查
|
||||
|
||||
1. 侧栏里的校正会话,名字变成 `生时校正 · 05:07`(有结论的)或 `生时校正 · 05:00–05:15`(只有范围的),
|
||||
**不再出现两条同名**。
|
||||
2. 那几条带 `09:35` / `02:14` 这类钟点后缀、且与下面时间对不上的旧标题,全部消失。
|
||||
3. **这些会话在列表里的位置和下面的时间都没有变**——如果它们集体跳到顶端、全显示今天,
|
||||
说明回填动了 `updated_at`,立刻回报(红线 1)。
|
||||
4. 「今日节奏」仍然是 `今日节奏 · M月D日`(D3 的有意例外)。
|
||||
5. 自己手动重命名过的会话,名字没有被改回去。
|
||||
Reference in New Issue
Block a user