# TASK · 把会话标题/活跃时间修补做成迁移,让产品能点按钮修 - 日期:2026-09-15 - 基线 commit:`origin/staging` @ `e7016551` - 执行分支:`codex/rectification-title-repair-migration-20260915` - 规模:把一段**已经写好、已经是纯 SQL** 的修补逻辑搬进迁移文件。**不改任何前端行为。** --- ## 1. 问题:修补脚本没有可执行的通道 BUG-699 / BUG-704 的数据修补写在 `frontend/scripts/repair-rectification-session-titles.mjs`。它到今天**一次都没跑过**,因为产品负责人没有任何地方可以运行它: | 现有通道 | 能不能跑这个脚本 | | --- | --- | | Gitea `Migrate Staging Database`(手动) | **不能**。`deploy/run-staging-migration.sh` 只做三件事:跑 `002-ensure-business-compatibility-roles.sql`、`compose --profile migration run --rm migrator`(应用 `frontend/supabase/migrations/` 下的 SQL)、然后打印已应用清单。**不执行任意 Node 脚本。** | | Gitea `Deploy Staging` | 不能,只发布镜像。 | | 其余 workflow | `deploy-production` / `migrate-production-database` / `release-quality-gate` / `reset-staging-account` / `create-production-recovery`,都不跑这个。 | | 本地 | 脚本要 `SCHEMA_DATABASE_URL`(`repair-rectification-session-titles.mjs:62`),那是服务器上的库口令。产品负责人不该拿,验收机也没有。 | | SSH 上服务器手跑 | 技术上可行,但产品负责人是非程序员;`AGENTS.md` 也没有"手工在生产机上跑一次性脚本"的既定流程,且口令不得进聊天。 | **结果:一个已经写好、已经验收的修复,卡在"没有按钮"上。** 用户的历史会话现在名字全是错的日期,等着这一步。 --- ## 2. 修法:改成一次性迁移,复用现成的按钮 脚本里的逻辑**本来就是纯 SQL**——`TITLE_MATCH_SQL` / `TITLE_APPLY_SQL` / `ACTIVITY_COUNT_SQL` 及其 update 全是 SQL 字符串,Node 只负责连库、传 `--apply`、打印计数。搬进迁移文件是机械操作,不需要重写逻辑。 搬完之后产品只需要:**Gitea → `Migrate Staging Database` → 填 SHA → 跑**。和平时迁移数据库一模一样,不用 SSH、不用口令、不用命令行。 ### 2.1 生产安全性(已核实,写进决策) `PROGRESS-rectification-open-retitles-session-20260915.md` 已经核对过:生产停在 `7b620c7a`,那个版本**没有** `use-rectification-surface.ts`,标题用的是固定的「生时校正」,也不写库。**生产上不存在被改坏的标题**,`TITLE_MATCH_SQL` 的 `where` 在生产上自然匹配 0 行。 所以这条迁移在生产上是**天然的 no-op**,不需要为它单独设防,也不必阻止它随 `main` 提升进入生产。 --- ## 3. 决策记录(产品已授权) 1. **改成迁移,不做新 workflow,不走 SSH。** 复用 `Migrate Staging Database` 这个已经存在、产品已经会用的按钮。 2. **保留 `repair-rectification-session-titles.mjs` 作为只读核对工具**,不删——将来排查时还能用它数行数。但它不再是交付路径。 3. 迁移必须**幂等**、只动匹配行、**不猜日期**(取不到 `created_at` 就退回不带日期的「生时校正」,与脚本现有规则一致)。 --- ## 4. 硬红线 1. **SQL 逻辑必须与脚本逐字一致**:`TITLE_MATCH_SQL` 的正则与时区(`Asia/Shanghai`)、`TITLE_APPLY_SQL` 的 `case`、活跃时间回填的 `coalesce(turns.last_turn_at, case_row.last_activity_at)` 与 `updated_at <` 单调守卫,一个都不许改写或"顺手优化"。改了就不是同一套修补了。 2. **不得动 `deploy/run-staging-migration.sh`、`deploy/docker-compose.postgres.yml` 或任何 `.gitea/workflows/**`**(`AGENTS.md` §2.7:workflow 只由产品负责人触发和修改)。这条迁移必须能被**现有**的 migrator 原样应用。 3. **用户手动改过的标题不得触碰**——不匹配 `TITLE_MATCH_SQL` 正则的行一律不动。 4. 迁移必须能重复应用而不产生第二次改动(幂等)。 5. `tsc --noEmit` 0 错;`npm run lint` 0 error;测试总数不降。 --- ## 5. 任务分解 ### 任务 1 · 新增迁移文件 **1.1** 在 `frontend/supabase/migrations/` 下新增一个一次性数据修补迁移(文件名按既有时间戳规范,排在 `20260915010000_rectification_touch_chat_session.sql` 之后)。 **1.2** 内容 = 脚本里那两段 update,原样搬入: - 标题:`TITLE_APPLY_SQL`(含 `TITLE_MATCH_SQL` 的 where) - 活跃时间:脚本里的活跃时间回填 update(含 `updated_at <` 单调守卫) **1.3** 权限守卫按 `20260915010000_rectification_touch_chat_session.sql` 的既有写法(`current_user <> 'schema_owner'` 就 `raise exception`),保持一致。 **1.4** 两段 update 各用 `RAISE NOTICE` 打出影响行数,这样 `Migrate Staging Database` 的日志里能直接看到修了多少行——**这是产品唯一能看到数字的地方**,不能省。 **验收标准** - 迁移在 `npm run test:db` 里能应用(见任务 3 的环境缺口说明)。 - 重复应用第二次,`RAISE NOTICE` 的行数为 0。 - 构造一条「标题日期与 `created_at` 不符」的假数据 → 被修正;构造一条用户手写标题(如「妈妈的盘」)→ **不动**;构造一条 `created_at is null` 的 → 变成不带日期的「生时校正」。 ### 任务 2 · 脚本降级为只读核对 **2.1** `repair-rectification-session-titles.mjs` 去掉 `--apply` 分支(或让它直接报错并提示改用迁移),只保留计数输出。文件头加注释:**修补已由迁移承担,本脚本只用于核对**。 **2.2** 不删文件。 ### 任务 3 · 测试与文档 **3.1** `npm run test:db --prefix frontend`(需 Docker)。**验收机没有 Docker**——执行方若也没有,如实写进 PROGRESS 与 `BLOCKED.md`,不得写成通过。这条迁移的真实证据就是产品在 staging 点一次按钮后的 `RAISE NOTICE` 行数。 **3.2** 文档: - `docs/BUG_HISTORY.md`:回到 **BUG-699** 与 **BUG-704** 两条记录,在「修复」末尾补一句:数据修补已改为迁移 `<文件名>`,随 `Migrate Staging Database` 应用;并把「验证」里的欠账更新为"待产品在 staging 应用后回填行数"。**不新增编号**(当前最大 BUG-717,这是交付通道补齐,不是新缺陷)。 - `deploy/README.md`:在 staging 迁移那一节补一句——一次性数据修补也走同一个按钮,行数看 `RAISE NOTICE`。 - `docs/tasks/PROGRESS-rectification-title-repair-migration-20260915.md`。 - 不动 `CHANGELOG.md`(用户可见行为不变;标题恢复正常已经记在 BUG-699 那一轮)。 --- ## 6. 让步顺序 1. 若活跃时间回填(`updated_at`)在迁移里因为跨表 join 写不干净,**先只交标题修补**,活跃时间那段留到下一轮并写进 PROGRESS。标题是用户直接看得见的,优先。 2. **绝不让步**:SQL 逻辑不得改写;不得动 workflow 或 compose;用户手写标题不得触碰;必须幂等。 --- ## 7. 开工前置命令 ```bash cd /workspace/Jyotisha git status -sb | head -1 git fetch origin --prune git worktree add -b codex/rectification-title-repair-migration-20260915 \ .worktrees/rectification-title-repair-migration-20260915 origin/staging cd .worktrees/rectification-title-repair-migration-20260915/frontend npm ci cat scripts/repair-rectification-session-titles.mjs # 逐字照搬的来源 ls supabase/migrations | tail -3 # 时间戳接在最后一条之后 ``` 交付:`git push origin HEAD:staging`,推完核对远端 SHA。 --- ## 8. 产品侧操作(修完之后) 1. 等门禁转绿、`Deploy Staging` 把新 SHA 发上去。 2. Gitea → **`Migrate Staging Database`** → 填那个 SHA → 运行。 3. 在运行日志里找 `NOTICE`,看修了多少行。 4. 刷新页面,检查历史会话的名字是否恢复成真实日期。 **不需要 SSH,不需要命令行,不需要数据库口令。**