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
182 lines
12 KiB
Markdown
182 lines
12 KiB
Markdown
# 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,仍欠)。
|