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
12 KiB
TASK 归档下线 · 验收修复单:两条 Python 源码合同仍要求归档入口存在,门禁红
- 日期:2026-09-21
- 基线 commit:
5a4b8738(origin/staginghead;未部署,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. 根因
两层:
- 直接原因:前序单删了归档,但没同轮更新断言它存在的两条 Python 合同。
- 结构原因:
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. 硬红线
- 不得删除
tests/test_session_management_entrypoints.py,也不得把它整体改成宽松断言。 - D3 里那条
writeChatSession的 POST/PATCH 合同必须活着。 - 不得改任何前端业务代码——本单只动 Python 测试与记录。
- 不得触碰会话删除相关的断言(
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()做,否则会误伤。
- 从必须出现的 token 列表里删掉
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/,而不是只 grepfrontend/。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 的核心(不得 bumpupdated_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/ArchiveRestoreimport、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. 让步顺序
- 无本地 Python venv → 用系统
python3跑那一个测试文件即可,run_quality_gate.py跑不了就记进BLOCKED.md,最终以门禁为准。 - F2 的
frontend/AGENTS.md那条不得让步——它是这次唯一真正的防复发措施。 - F1 不得让步。
9. 开工前置命令
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. 部署后真人走查(产品负责人,本单与前序单合并验)
- 会话行「…」菜单里不再有「归档」。
- 以前归档过的对话重新出现在侧栏,且时间没有被改成今天。
- 删除会话仍然正常(含确认框)。
- 侧栏能看到 09-08 以来的生时校正会话,时间从上到下单调递减(BUG-987/988 的真机确认,仍欠)。
- 连点五次「新建对话」不堆出空行;发一句话后出现一行,时间是今天(BUG-989,仍欠)。