Files
Jyotisha/docs/tasks/TASK-archive-retire-fix-20260921.md
T
Jesse_ChenandClaude Opus 5 d3e0edfd1b docs(tasks): 归档下线验收结论与修复单(BUG-992)
5a4b8738 业务三层均正确;门禁 run 2836 红在两条 Python 源码合同
仍要求归档入口存在。同类第四次(933/934/939),防复发落到
frontend/AGENTS.md 的 grep 口径。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0199rbQDTsUbCVw84wc8BTFe
2026-09-21 19:09:44 +08:00

182 lines
12 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.
# TASK 归档下线 · 验收修复单:两条 Python 源码合同仍要求归档入口存在,门禁红
- 日期:2026-09-21
- 基线 commit:`5a4b8738`(`origin/staging` head;**未部署**,staging 仍停在 `df329fa9`)
- 分支:`codex/archive-retire-fix-20260921`
- BUG 编号起点:**BUG-992**(`docs/BUG_HISTORY.md` 当前最大号 `BUG-991`)
- 前序:`TASK-archive-retire-20260921.md` / `PROGRESS-archive-retire-20260921.md`
- 关联记录:**BUG-933、BUG-934、BUG-939**(同一类复发,第四次)、BUG-991
---
## 1. 事故实证
Gitea `backend-quality-gate` run **2836**(`5a4b8738`):`validate` 第 6 步失败,`publish` 跳过,**staging 未部署**。
红的**不是前端**,是 Python 那一段:
```
FAILED tests/test_session_management_entrypoints.py::test_chat_history_management_actions_are_exposed
- assert 'toggleArchivedSession' in '"use client";\n\nimport Link from "next/link"; …'
FAILED tests/test_session_management_entrypoints.py::test_archiving_never_calls_the_delete_endpoint
- ValueError: substring not found
== Quality gate failed ==
```
我在本机 `python3 -m pytest tests/test_session_management_entrypoints.py -q` 复现,**恰好这两条,没有第三条**。
两条都是拿 Python 读前端源码做字符串断言的合同测试:
- `test_chat_history_management_actions_are_exposed`:`_home_surface()`(`page.tsx` + 若干 `lib/` + 四个 hook)拼上 `globals.css` 与 `sidebar-session-row.tsx`,然后逐个断言一串 token 必须出现。其中五个是归档的:`toggleArchivedSession`、`showArchivedSessions`、`已归档,可在左侧归档中恢复。`、`归档`、`恢复`。
- `test_archiving_never_calls_the_delete_endpoint`:`source.index("function toggleArchivedSession")` 切片,断言归档走 PATCH 而不是 DELETE。函数没了,`index()` 直接抛 `ValueError`。
前序单的验收标准里我写的是「`grep -rn "归档" frontend/src` 无命中」,执行方照做了并且做到了——**`tests/` 目录下的 Python 合同不在 `frontend/` 里,这条 grep 扫不到。**
### 1.1 这是同一类问题的第四次
| 记录 | 现象 |
| --- | --- |
| BUG-933(09-17) | 四条针对 `page.tsx` 的源码合同没跟着外壳搬到 `(app)/layout.tsx`,门禁必红 |
| BUG-934(09-17) | `tests/test_daily_and_rectification_entrypoints.py` 仍找 `62903b7a` 已删除的卡片类名 |
| BUG-939(09-18) | `test_birth_time_journey_contract` 仍读已搬走的 `app/page.tsx`,staging 门禁全红 |
| **BUG-992(本条)** | `tests/test_session_management_entrypoints.py` 仍要求归档入口存在 |
共同形状:**Python 测试对前端源码做字符串断言,而前端改动的自查只 grep `frontend/`**。改前端的人不知道 `tests/` 里有东西盯着自己。
---
## 2. 根因
两层:
1. **直接原因**:前序单删了归档,但没同轮更新断言它存在的两条 Python 合同。
2. **结构原因**:`tests/test_session_management_entrypoints.py` 把「这些入口必须存在」写成对前端源码文本的 `in` 断言。这种测试在**新增**时有价值(防止入口被误删),但在**有意删除**入口时必然反过来挡路,而且它待在 Python 目录里,前端改动者的自查半径覆盖不到。
---
## 3. 决策记录
- **D1:更新那两条测试,不是删掉整个文件。** 它挡住了这次交付,但它盯的其余入口(重命名、删除、确认框、收藏、转发、`share_payload_version`、删除路由的 `user_id` 与 `count !== 1`)都还有效。
- **D2:归档的断言从「必须存在」翻成「必须不存在」。** 删了就删干净,留一条反向断言,避免以后谁又把归档加回来。
- **D3:`test_archiving_never_calls_the_delete_endpoint` 整条重写而不是删除。** 它里面 `assert 'method: mode === "create" ? "POST" : "PATCH"' in write_contract` 与归档无关,是写入助手的独立合同,**必须搬进保留下来的测试里,不得随函数一起丢掉**。
- **D4:不得为了让门禁绿而回退 `5a4b8738` 的任何业务改动。** 迁移、接口、界面三层都已验收正确(见 §6)。
---
## 4. 硬红线
1. 不得删除 `tests/test_session_management_entrypoints.py`,也不得把它整体改成宽松断言。
2. D3 里那条 `writeChatSession` 的 POST/PATCH 合同必须活着。
3. 不得改任何前端业务代码——本单只动 Python 测试与记录。
4. 不得触碰会话删除相关的断言(`test_chat_session_delete_is_server_controlled_and_granted` 原样)。
---
## 5. 任务分解
### F1|更新两条 Python 合同(BUG-992)
- `test_chat_history_management_actions_are_exposed`:
- 从必须出现的 token 列表里删掉 `toggleArchivedSession`、`showArchivedSessions`、`已归档,可在左侧归档中恢复。`、`归档`、`恢复`。
进度记录里按 AGENTS.md §7.3 写三栏:**原值** = 这五个 token 必须出现;**新值** = 必须不出现;**原因** = 归档能力于 2026-09-21 整体下线(BUG-991,产品拍板)。
- 追加反向断言(D2):这五个 token 一个都不得出现在 `source` 里。注意 `恢复` 这类通用词可能在别处合法出现——**只对 `SESSION_ROW` 与 `use-session-management.ts` 两份源码做反向断言**,不要对整个 `_home_surface()` 做,否则会误伤。
- `test_archiving_never_calls_the_delete_endpoint` → 重写并改名(例如 `test_session_archive_is_retired_and_write_helper_still_patches`):
- 断言 `_home_surface()` 里不存在 `function toggleArchivedSession` / `toggleArchivedView` / `archived_at:` 写入;
- 保留 `assert 'method: mode === "create" ? "POST" : "PATCH"' in write_contract`(D3);
- 保留「删除仍走 DELETE」的语义:断言 `deleteSession` 的切片里确有 `method: "DELETE"`(原测试是反向断言归档不含 DELETE,现在改成正向断言删除含 DELETE,覆盖面不降)。
- **验收标准:**
- `python3 -m pytest tests/test_session_management_entrypoints.py -q` 全绿,测试函数数量不减。
- 把任意一个归档 token 手工加回 `sidebar-session-row.tsx` 后该测试转红(执行方本地自测一次即可,不必提交这个临时改动)。
- 门禁 run 转绿、`publish` 不再跳过。
### F2|防复发:改前端时把 `tests/` 也扫进去(BUG-992)
这是第四次了,光写"记得改"没用。要落成可执行的东西:
- 在 `frontend/AGENTS.md` 加一条:**删除或重命名任何前端符号、类名、可见文案之前,必须 `git grep -n "<符号>" -- tests/ frontend/`,而不是只 grep `frontend/`。** `tests/` 下有多条 Python 合同对前端源码做字符串断言(现存:`test_session_management_entrypoints.py`、`test_daily_and_rectification_entrypoints.py`、`test_birth_time_journey_contract.py`)。
- `docs/BUG_HISTORY.md` 的 BUG-992 防复发写同一条,并关联 BUG-933 / 934 / 939。
- **验收标准:** `frontend/AGENTS.md` 有这条;BUG-992 记录的「复发自」明确指向 BUG-939。
(**不做**:把这些 Python 合同搬进 `frontend/tests/`。那是另一轮的事,本单不扩范围。若执行方认为值得做,写进进度记录当建议,不要顺手动。)
### F3|记录(BUG-992)
- `docs/BUG_HISTORY.md` 新增 BUG-992;BUG-991 在门禁绿且部署后由验收方改 `resolved`,**本单不要替它改**。
- `docs/tasks/README.md` 状态板加本单。
- 不需要动 `CHANGELOG.md`(用户侧无变化,前序单已写过)。
---
## 6. 前序单(`5a4b8738`)的验收结论——业务三层全部正确,不要重做
我在 `.worktrees/verify-20260921` 用 `df329fa9` 作基线独立复跑:
| 项 | 基线 `df329fa9` | `5a4b8738` | 结论 |
| --- | --- | --- | --- |
| `tsc --noEmit` | 0 错 | 0 错 | 通过 |
| `npm run lint` | 0 error / 119 warning | 0 error / 119 warning | 通过 |
| `npm test` | 3641 / 34 红 / 21 skip | 3644 / 34 红 / 22 skip | 失败清单 `diff` 逐条一致;用例名 `diff` **+3 无删除** |
| `next build --webpack` | 通过 | 通过 | `/` 仍 `○ Static` |
| 首屏 gzip | 643,116 | 642,382 | **−734 B / −0.11%** |
| Python 定向 | — | **2 红** | 见 §1,本单要修的就是这个 |
新增的三条用例:`PATCH compatibility accepts and ignores a legacy archived_at write`、`session row menu is rename, pin, share and delete — not archive`、`retire archive migration clears archived_at without bumping updated_at`(本机无 Docker 时 skip)。
逐条对前序任务书:
- **迁移(T3)正确**:只 `set archived_at = null where archived_at is not null`,**没有任何 `updated_at` 赋值**,`schema_owner` 守卫与既有迁移同形,幂等。红线 3 的核心(不得 bump `updated_at`,否则历史会话集体跳顶、毁掉 BUG-988)守住了。
- **接口(T2)正确**:列表恒查 `is("archived_at", null)`;`?archived=1`、`isArchivedSessionQuery`、`applyArchiveFilter` 全删;`chatSessionMetadataPatchSchema` 去掉 `archived_at`,`extractChatSessionMetadataPatch` 读到但不放进 patch,`PATCH` 只带 `archived_at` 时走新的 `payloadHasIgnoredArchive` 分支回 `{ok:true}` 不落库——红线 6 的向后兼容做到了。
- **界面(T1)正确**:行菜单只剩 收藏/取消收藏、重命名、转发、删除;`Archive` / `ArchiveRestore` import、`SidebarSession.archived`、`archivedCount`、`showingArchived`、`onToggleArchivedView`、`onToggleArchived`、`toggleArchivedSession`、`toggleArchivedView`、`showArchivedSessions` 全删;`visibleSessions` 收敛成单个 `isListedSidebarSession`(`useState` 少一个)。
- **易漏点做到了**:`applyLegacySessionControls` 里 archived 的那一半删干净、pinned 那一半保留,`clearLegacySessionControlKeys` 仍清那个 localStorage key。
- **红线 2 守住**:`local-postgres-not.test.ts` 一字未动。
- **红线 1 守住**:`DELETE /api/sessions/[id]`、确认框、行菜单「删除」与其测试原样。
- **防复发断言到位**:`sidebar-contract.test.ts` 新增的那条把菜单项锁成 `["重命名","转发","删除"]` + 动态收藏项,并反向断言无归档。
### 6.1 一条 P3(不挡本单,执行方可顺手,也可不做)
新的迁移测试 `retire archive migration clears archived_at without bumping updated_at` **没有执行迁移文件本身**:它对文件正文做了两条正则(`/set archived_at = null/`、`doesNotMatch /updated_at\s*=/`),行为部分则手抄了一条同样的 `update` 语句去跑。参照 `rectification-session-title-repair-migration.test.ts` 的做法(SQL 从文件里切出来、与文件逐字比对),这里也可以把 `update` 语句从 `retireSql` 里切出来执行,避免文件与手抄语句漂移。现在的写法能挡住最要命的那条(bump `updated_at`),所以不算缺陷,只是可以更紧。
---
## 7. 交付前必须全跑
- `python3 -m pytest tests/test_session_management_entrypoints.py -q` → 全绿
- `.venv/bin/python scripts/run_quality_gate.py --profile quick`(有 venv 时)
- 前端不改,但仍跑一遍 `tsc --noEmit` / `npm run lint` 确认没被带歪
- `npm test` 全量 → 与 `5a4b8738` 的失败清单逐条一致(本机 3644 / 34 红 / 22 skip)
---
## 8. 让步顺序
1. **无本地 Python venv** → 用系统 `python3` 跑那一个测试文件即可,`run_quality_gate.py` 跑不了就记进 `BLOCKED.md`,最终以门禁为准。
2. F2 的 `frontend/AGENTS.md` 那条不得让步——它是这次唯一真正的防复发措施。
3. F1 不得让步。
---
## 9. 开工前置命令
```bash
cd /workspace/Jyotisha
git status -sb | head -1
git fetch origin --prune
git worktree add -b codex/archive-retire-fix-20260921 \
.worktrees/archive-retire-fix-20260921 origin/staging
cd .worktrees/archive-retire-fix-20260921
python3 -m pytest tests/test_session_management_entrypoints.py -q # 复现两条红
git grep -n "toggleArchivedSession\|showArchivedSessions\|归档" -- tests/ frontend/ # 记录开工命中
```
开工必读:`docs/BUG_HISTORY.md` 的 BUG-933、BUG-934、BUG-939(同类三次)、BUG-991;AGENTS.md §5、§7.3。
---
## 10. 部署后真人走查(产品负责人,本单与前序单合并验)
1. 会话行「…」菜单里不再有「归档」。
2. 以前归档过的对话重新出现在侧栏,且时间**没有**被改成今天。
3. 删除会话仍然正常(含确认框)。
4. 侧栏能看到 09-08 以来的生时校正会话,时间从上到下单调递减(BUG-987/988 的真机确认,仍欠)。
5. 连点五次「新建对话」不堆出空行;发一句话后出现一行,时间是今天(BUG-989,仍欠)。