diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 62b8f395..16bf425e 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -11460,3 +11460,20 @@ - 相关记录:BUG-721(同一形状的第三个实例)、BUG-734(同一轮的另一处)、BUG-733(同进程差分怎么写) - 复发自:无 - 修复版本:待发布 + +## BUG-736 | 纯前端改动打红 Python 源码合同,快速门连续两轮带红合入 + +- 状态:resolved +- 首次发现:2026-09-16 +- 最近更新:2026-09-16 +- 影响面:`tests/test_supabase_user_data_contract.py`、`scripts/run_quality_gate.py --profile quick`、`frontend/src/app/api/sessions/route.ts`、`frontend/src/hooks/use-synastry.ts` +- 用户现象:无(门禁问题,用户不可感知)。 +- 触发条件:纯前端轮次移动了 `page.tsx` / 路由里的字面量或把逻辑搬进新 hook。 +- 根因:`test_supabase_user_data_contract.py` 用正则扫前端源码,`_home_surface()` 只拼 `OPTIONAL_HOME_HOOKS` 白名单里的文件。两轮改动各打红一批断言:`149e1ec4` 把创建路由改走 `chatSessionCreateInsertRow(...)`,`user_id: user.id` 字面量消失;状态下沉第二批把合盘搬进新建的 `use-synastry.ts`,它不在白名单里,于是 `fetch("/api/synastry"` 与 `未能存入历史` 在并集里找不到。**三条都是断言过期,归属校验与云端持久化保证本身没丢。** +- 为什么没被拦住:这几条是 **Python 测试**,但断言对象是**前端源码**。前端轮次的验收口径(tsc / lint / `npm test` / `next build`)不含 Python 快速门,而 AGENTS §10 只要求「任何 Python 改动」跑快速门——纯前端改动落在两张网中间。验收方(Claude)两轮都只跑了前端套件,未跑快速门,因此带红合入。 +- 修复:`OPTIONAL_HOME_HOOKS` 补 `use-synastry.ts` 并加注释说明新增 Home 级 hook 必须同步登记;`user_id: user.id` 断言按「原值 / 新值 / 原因」三栏改成 `userId: user.id`(路由)+ `user_id: input.userId`(`chat-session-write-contract.ts`),两处都断,不放宽。 +- 验证:`.venv/bin/python -m pytest tests/test_supabase_user_data_contract.py` 8 条全绿;`run_quality_gate.py --profile quick` 的 pytest 段 **792 passed / 1 skipped / 0 failed**(此前 3 failed)。快速门整体仍退出 1,剩余唯一原因是本机无 `rsync` 与无 Docker 的既有环境缺口。 +- 防复发:**改动 `page.tsx`、Home 级 hook 或 `frontend/src/app/api/**` 的轮次,验收必须另跑 `run_quality_gate.py --profile quick`**,不能只跑前端套件——有 Python 测试把前端源码当断言对象。新增 Home 级 hook 必须同步登记进 `OPTIONAL_HOME_HOOKS`。取门禁退出码不得隔着管道(`| tail` 会把退出码换成 `tail` 的)。 +- 相关记录:BUG-729~731(创建路由改造)、BUG-464(会话持久化归属) +- 复发自:无 +- 修复版本:待发布 diff --git a/tests/test_supabase_user_data_contract.py b/tests/test_supabase_user_data_contract.py index f0b45890..ac7b5078 100644 --- a/tests/test_supabase_user_data_contract.py +++ b/tests/test_supabase_user_data_contract.py @@ -57,6 +57,10 @@ OPTIONAL_HOME_HOOKS = ( _FRONTEND_SRC / "hooks" / "use-consultation-run.ts", _FRONTEND_SRC / "hooks" / "use-profile-onboarding.ts", _FRONTEND_SRC / "hooks" / "use-rectification-surface.ts", + # 状态下沉把合盘从 page.tsx 搬进了自己的 hook(2026-09-16)。首页表面是这 + # 些文件的并集,新 hook 不进这张白名单,下面的合盘断言就会在代码完全正确 + # 的情况下变红。新增 Home 级 hook 时必须同步加进来。 + _FRONTEND_SRC / "hooks" / "use-synastry.ts", ) @@ -133,14 +137,20 @@ def test_chat_page_uses_authenticated_cloud_persistence() -> None: assert '.from("profiles")' not in source assert '.from("chat_sessions")' not in source assert 'await writeChatSession(session.id, values, mode)' in source - assert 'mode === "create" ? "/api/sessions"' in ( + write_contract = ( Path(__file__).resolve().parents[1] / "frontend" / "src" / "lib" / "chat-session-write-contract.ts" ).read_text(encoding="utf-8") - assert 'user_id: user.id' in create_route + assert 'mode === "create" ? "/api/sessions"' in write_contract + # 原值: assert 'user_id: user.id' in create_route + # 新值: 路由传 userId,由 chat-session-write-contract 落成 user_id + # 原因: 创建路由改走 chatSessionCreateInsertRow(...)(BUG-729~731 那一轮), + # 字面量搬了家;归属校验本身没变,断言要跟着搬,不是放宽。 + assert 'userId: user.id' in create_route + assert 'user_id: input.userId' in write_contract assert '.eq("user_id", user.id)' in item_route assert '.upsert(' not in source assert 'await persistSession(userSession)' not in source