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

11 KiB
Raw Blame History

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.tsdatedSessionTitle 成立,仍是 M月D日 · 生时校正uniquifySessionTitle 仍可追加 HH:MM,与脚本正则完全对应

schema 逐列核对:chat_sessionstitle / created_at / updated_at / user_id20260715030000session_type20260721110000(取值仍含 birth_time_rectification);agentic_rectification_casessession_id / user_id / last_activity_atagentic_rectification_turnscase_id / created_at20260812010000。全部仍在。

另外两件影响正确性、任务书没提、我主动核过的事:

  1. UPDATE 不会误触发触发器。 chat_sessions 上只有两个触发器,chat_sessions_default_rectification_modelchat_sessions_pin_model_config_version,都是 before insert or update of <指定列>session_type / model_id / model_config_version)。本迁移只写 titleupdated_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 exceptionerrcode 42501
  • 两段 update 放在一个 do $repair$ 块里,各自 get diagnostics ... = row_countraise notice 打出行数。
  • 顺序与脚本一致:先标题,后活跃时间。

SQL 逐字一致是机器保证的,不是我说的。 迁移的 SQL 不是手敲的,是用 sed 从脚本里按行切出来拼进去的;随后 frontend/tests/rectification-session-title-repair-migration.test.ts 把脚本导出的三个常量与迁移正文比对(只做行尾空白归一化,因为脚本拼的是 where ${TITLE_MATCH_SQL},会留一个 .sql 文件不该有的行尾空格)。以后改了任何一边而没同步另一边,这条测试就红。

复核者可以自己再验一遍:

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 时区、正则、casecoalesce(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:migratefrontend/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 error120 warning,全部在本轮未改的文件里;scripts/db-migrate.mjsscripts/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 @ 37e6c519npm 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. 环境缺口(不得记成通过)

本机没有 Dockerdocker info 失败),也没有本地 PostgreSQLwhich 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 session2026080803000020260901010000)、join lateral20260814020000 等)、get diagnostics ... = row_count20260811020000)。旁证不等于实跑。

已登记为 BLOCKED.mdBLK-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>
  1. 刷新页面,看历史校正会话的名字是否恢复成真实日期。
  2. 把两个数字回填进 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.shdeploy/docker-compose.postgres.yml、任何 .gitea/workflows/**
  • 没改任何前端行为:src/ 一行未动。
  • 没动 CHANGELOG.md(任务书 3.2:用户可见行为不变)。
  • 没新增 BUG 编号(任务书 3.2:这是交付通道补齐,不是新缺陷)。
  • 没把迁移复制进 frontend/db/migrations/BUG-127 / BUG-144,新测试里有断言)。