Files
Jyotisha/docs/tasks/PROGRESS-rectification-title-repair-migration-20260916.md
T
Jesse_ChenandClaude Opus 5 10afdcddeb
Independent Staging Quality Gate / validate (push) Canceled after 38s
Independent Staging Quality Gate / publish (push) Canceled after 0s
docs(tasks): 校正标题修补迁移的进度记录与 Bug 历史回填
- PROGRESS-rectification-title-repair-migration-20260916.md:前提复核、
  逐字一致的复核命令、幂等两层的说明、基线比对与环境缺口。
- BUG_HISTORY:BUG-699 / BUG-704 的「修复」补记改由迁移承担,「验证」
  的欠账改成待产品在 staging 应用后从 NOTICE 回填。不新增编号。
- BLOCKED.md:BLK-002 无 Docker / 无本地 PostgreSQL,test:db 未跑、
  db:migrate:check 缺 SCHEMA_DATABASE_URL,附已实跑的替代证据。
- deploy/README.md:一次性数据修补走同一个 Migrate Staging Database
  按钮,行数看日志里的 notice 行。
- docs/tasks/README.md:该单改记待验收。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
2026-09-16 01:39:03 +00:00

157 lines
11 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.
# PROGRESS · 把会话标题/活跃时间修补做成迁移
- 日期:2026-09-16
- 任务书:`docs/tasks/TASK-rectification-title-repair-migration-20260915.md`
- 分支:`codex/rectification-title-repair-migration-20260916`
- 实际基线:`origin/staging` @ `37e6c519`(任务书写的 `e7016551` 早已被超车,前提已逐条复核,见 §1)
- 未推 staging,按指派只提交到本分支,验收与合入由指派方做。
---
## 1. 开工前的前提复核(结论:前提全部成立)
| 任务书的前提 | 复核方式 | 结论 |
| --- | --- | --- |
| 修补脚本仍在 | `frontend/scripts/repair-rectification-session-titles.mjs` | 在,3601 字节 |
| 脚本仍是纯 SQL,Node 只连库/打印 | 通读全文 | 成立:`TITLE_MATCH_SQL` / `TITLE_APPLY_SQL` / `ACTIVITY_APPLY_SQL` 全是 SQL 字符串 |
| 数据还没被别的轮次修过 | `frontend/supabase/migrations/` 下无对应迁移 | 成立,无人做过 |
| where 条件对得上当前 schema | 逐列核对 | 成立,见下 |
| 标题格式没变 | `frontend/src/lib/agent-reply.ts``datedSessionTitle` | 成立,仍是 `M月D日 · 生时校正``uniquifySessionTitle` 仍可追加 ` HH:MM`,与脚本正则完全对应 |
schema 逐列核对:`chat_sessions``title` / `created_at` / `updated_at` / `user_id``20260715030000``session_type``20260721110000`(取值仍含 `birth_time_rectification`);`agentic_rectification_cases``session_id` / `user_id` / `last_activity_at``agentic_rectification_turns``case_id` / `created_at``20260812010000`。全部仍在。
另外两件影响正确性、任务书没提、我主动核过的事:
1. **UPDATE 不会误触发触发器。** `chat_sessions` 上只有两个触发器,`chat_sessions_default_rectification_model``chat_sessions_pin_model_config_version`,都是 `before insert or update of <指定列>``session_type` / `model_id` / `model_config_version`)。本迁移只写 `title``updated_at`,两个都不触发。
2. **没有自动 `updated_at` 触发器。** 所以标题那段 update 不会顺带把 `updated_at` 推成今天、把会话顶到侧栏最前——那正是 BUG-699 的现象本身,踩上就等于一边修一边复发。
---
## 2. 做了什么
### 2.1 新增迁移(任务 1
`frontend/supabase/migrations/20260916020000_rectification_session_title_repair.sql`
- 时间戳排在当前最后一条 `20260916010000_consultation_session_capacity.sql` 之后。
- 权限守卫沿用 `20260915010000` 的写法:`current_user <> 'schema_owner'``raise exception`errcode `42501`
- 两段 update 放在一个 `do $repair$` 块里,各自 `get diagnostics ... = row_count``raise notice` 打出行数。
- 顺序与脚本一致:先标题,后活跃时间。
**SQL 逐字一致是机器保证的,不是我说的。** 迁移的 SQL 不是手敲的,是用 `sed` 从脚本里按行切出来拼进去的;随后
`frontend/tests/rectification-session-title-repair-migration.test.ts` 把脚本导出的三个常量与迁移正文比对(只做行尾空白归一化,因为脚本拼的是 `where ${TITLE_MATCH_SQL}`,会留一个 `.sql` 文件不该有的行尾空格)。以后改了任何一边而没同步另一边,这条测试就红。
复核者可以自己再验一遍:
```bash
cd frontend
diff <(sed -n '38,45p' supabase/migrations/20260916020000_rectification_session_title_repair.sql) \
<(sed -n '35,42p' scripts/repair-rectification-session-titles.mjs) # TITLE_APPLY
diff <(sed -n '47,55p' supabase/migrations/20260916020000_rectification_session_title_repair.sql) \
<(sed -n '21,29p' scripts/repair-rectification-session-titles.mjs) # TITLE_MATCH
diff <(sed -n '59,70p' supabase/migrations/20260916020000_rectification_session_title_repair.sql) \
<(sed -n '62,73p' scripts/repair-rectification-session-titles.mjs) # ACTIVITY_APPLY
```
三个 diff 都是空的。Asia/Shanghai 时区、正则、`case``coalesce(turns.last_turn_at, case_row.last_activity_at)``updated_at <` 单调守卫,一个字都没动。
### 2.2 幂等性怎么保证的
两层,互相独立:
1. **迁移器层**`migration.schema_migrations` 以文件名为主键记账,`db-migrate.mjs` 见到已记账的文件就 `already applied ... continue`。正常路径下这条迁移一辈子只执行一次。
2. **SQL 自身**(真正兜底的一层,防的是有人手工重放):
- 标题:改完之后,`title` 里的月日就等于 `created_at` 的 Asia/Shanghai 月日,`is distinct from` 两个条件同时为假;`created_at is null` 的行被改成不带日期的 `生时校正`,连 `title ~ '^[0-9]{1,2}月...'` 正则都不再匹配。where 整体不再命中这些行。
- 活跃时间:改完之后 `session.updated_at` 恰好等于 `coalesce(turns.last_turn_at, case_row.last_activity_at)``<` 为假。`<` 同时也是单调守卫,任何情况下只前进不回拨。
所以重复应用影响 0 行,`RAISE NOTICE` 会打出 `repaired_titles=0` / `refreshed_activity=0`
### 2.3 一处任务书没写、但不补就等于白做的改动
任务书 §5 任务 1.4 说「`RAISE NOTICE` 打出的行数会出现在 `Migrate Staging Database` 的日志里——这是产品唯一能看到数字的地方」。**照原样实现的话,那个数字不会出现。**
staging 的 migrator 服务跑的是 `npm run db:migrate``frontend/scripts/db-migrate.mjs`,用的是 node-postgres。node-postgres 把 NOTICE 作为 Client 的 `notice` 事件抛出,**没有监听器就直接丢掉**,既不报错也不打印。原来的 `db-migrate.mjs` 全文没有 `notice` 二字。
所以在 `runMigrations` 里加了 4 行 notice 转发,把 NOTICE 打成 `notice <message>` 走既有 `logger`。这不是顺手改别的东西,是任务 1.4 的落地前提;没有它,产品点完按钮看到的是一片空白。
边界都守住了:没动 `deploy/run-staging-migration.sh`、没动 `deploy/docker-compose.postgres.yml`、没动任何 `.gitea/workflows/**`(任务书硬红线 2)。迁移仍由**现有** migrator 原样应用。没有测试断言过 migrator 的日志行,只有 `tests/model-configuration-security.test.ts` 传了个 `logger: console` 进去,不校验输出。
### 2.4 脚本降级为只读核对(任务 2)
`frontend/scripts/repair-rectification-session-titles.mjs`
- 文件头注明修补已由迁移承担,本文件只用于核对,并指名那条迁移。
- `--apply` 不再写库,直接报错并指向迁移与 `Migrate Staging Database` 按钮(实测退出码 1)。
- 保留两段 count 查询;**文件没删**(任务书 2.2)。
- 三个 SQL 常量改为 `export`,让它成为迁移的唯一出处、并被合同测试读取——顺带也让它们不再是 lint 眼里的死变量。
- 加了 `invokedPath === import.meta.url` 守卫。原来第 95 行的 `main().catch(...)` 是无条件执行的,`import` 这个文件就会去连库;合同测试要导入它,不加这个守卫测试会去连数据库。
---
## 3. 验证
| 项 | 命令 | 结果 |
| --- | --- | --- |
| 类型 | `./node_modules/.bin/tsc --noEmit` | **0 错** |
| lint | `npm run lint` | **0 error**120 warning,全部在本轮未改的文件里;`scripts/db-migrate.mjs``scripts/repair-rectification-session-titles.mjs`、新测试三者 0 条) |
| 新合同测试 | `npx tsx --test tests/rectification-session-title-repair-migration.test.ts` | **5/5 通过** |
| 全量测试(本分支) | `npm test` | tests **3334** / pass 3288 / **fail 31** / skipped 15 |
| 全量测试(基线) | 同一工作树 detach 到 `origin/staging` @ `37e6c519``npm test` | tests **3329** / pass 3283 / **fail 31** / skipped 15 |
| 迁移文件级校验 | 假连接串调 `runMigrations({check:true})` | 报错停在 `connect ECONNREFUSED`,说明文件名正则/重复检查/目录扫描都过了 |
| `db:migrate:check` | `npm run db:migrate:check --prefix frontend` | **未通过——环境缺口**,见 §4 |
| `test:db` | `npm run test:db --prefix frontend` | **没跑——无 Docker**,见 §4 |
基线比对结论:本轮**净增 5 个测试且全绿,31 条失败一条不多一条不少**,测试总数只增不减,没有改写或弱化任何既有断言。
CLI 行为实测:
```
$ node scripts/repair-rectification-session-titles.mjs --apply
--apply has been removed; the repair now ships as migration 20260916020000_rectification_session_title_repair.sql. Apply it with Gitea -> Migrate Staging Database.
exit=1
$ node scripts/repair-rectification-session-titles.mjs
SCHEMA_DATABASE_URL is required
exit=1
```
---
## 4. 环境缺口(不得记成通过)
本机**没有 Docker**`docker info` 失败),**也没有本地 PostgreSQL**`which psql postgres pg_ctl initdb` 全空)。因此:
- `npm run test:db --prefix frontend` 没跑。
- `npm run db:migrate:check --prefix frontend` 在连库之前就以 `SCHEMA_DATABASE_URL is required` 退出 1,拿不到 pending 清单。这是缺库缺口,不是迁移本身有问题——同一入口的文件级校验已单独走通(见 §3)。
- **这条迁移的 SQL 从未在真实 PostgreSQL 上执行过**(它原本所在的脚本也一次都没跑过)。静态旁证是三种写法在已应用的迁移里都有先例:`update public.chat_sessions as session``20260808030000``20260901010000`)、`join lateral``20260814020000` 等)、`get diagnostics ... = row_count``20260811020000`)。旁证不等于实跑。
已登记为 `BLOCKED.md`**BLK-002**。真实证据只能由产品在 staging 点一次按钮后回填。
---
## 5. 产品侧下一步
1. 等门禁转绿、`Deploy Staging` 发上去。
2. Gitea →`Migrate Staging Database`→ 填那个 SHA → 运行。
3. 在日志里找这两行,就是修了多少行:
```
notice rectification_session_title_repair repaired_titles=<n>
notice rectification_session_title_repair refreshed_activity=<n>
```
4. 刷新页面,看历史校正会话的名字是否恢复成真实日期。
5. 把两个数字回填进 `docs/BUG_HISTORY.md` 的 BUG-699 / BUG-704「验证」段,并划掉 `BLOCKED.md` 的 BLK-002。
生产停在 `7b620c7a`,没有 `use-rectification-surface.ts`,标题固定为「生时校正」且不写库,两段 where 自然匹配 0 行。**将来这条随 `main` 提升进生产时打出 `repaired_titles=0` 是预期结果,不是失败。**
---
## 6. 没做的事
- 没推 `staging`,没碰 `main`
- 没动 `deploy/run-staging-migration.sh``deploy/docker-compose.postgres.yml`、任何 `.gitea/workflows/**`
- 没改任何前端行为:`src/` 一行未动。
- 没动 `CHANGELOG.md`(任务书 3.2:用户可见行为不变)。
- 没新增 BUG 编号(任务书 3.2:这是交付通道补齐,不是新缺陷)。
- 没把迁移复制进 `frontend/db/migrations/`BUG-127 / BUG-144,新测试里有断言)。