docs(tasks): give the title repair a button — move it into a migration

BUG-699 / 704 的数据修补写成了 repair-rectification-session-titles.mjs,要
SCHEMA_DATABASE_URL。但 deploy/run-staging-migration.sh 只做三件事:跑
002-ensure-business-compatibility-roles.sql、compose --profile migration run
--rm migrator 应用 frontend/supabase/migrations/ 下的 SQL、打印已应用清单,
不执行任意 Node 脚本。其余 workflow 也都不跑它。于是一个已经验收的修复卡在
"没有按钮"上,用户的历史会话名字还全是错日期。

脚本里 TITLE_MATCH_SQL / TITLE_APPLY_SQL 和活跃时间回填本来就是纯 SQL,
Node 只负责连库和打印。搬进一次性迁移就能复用产品已经会用的
Migrate Staging Database 按钮,不用 SSH、不用口令。

生产安全性已核实:7b620c7a 没有 use-rectification-surface.ts,标题是固定的
「生时校正」也不写库,where 在生产上自然匹配 0 行,天然 no-op。

红线:SQL 逐字照搬不许优化;不得动 workflow 或 compose;用户手写标题不碰;
必须幂等;两段 update 各要 RAISE NOTICE 打行数——那是产品唯一看得到数字的
地方。脚本降级为只读核对工具,不删。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
This commit is contained in:
Jesse_Chen
2026-09-15 15:01:30 +00:00
co-authored by Claude Fable 5
parent e7016551db
commit ebd6175b40
2 changed files with 131 additions and 0 deletions
+1
View File
@@ -228,6 +228,7 @@
| `TASK-ephemeris-page-20260915.md` | `PROGRESS-ephemeris-page-20260915.md` | **前端单**:P1 星历页,今日五要素 + 当日行运(相对本命宫位)+ 未来九十天换座与停滞,底部「带这天去提问」出口。页面不得出现任何运势判断。含实证缺陷:panchanga 写死 Lahiri 与账户 Raman 分裂(关联 BUG-703;本单标注为 BUG-707)。侧边栏入口由 chart-page 单交付。BUG 段 707709 | 已验收(带修复单) | `d3a2c48b` |
| `TASK-readonly-pages-fix-20260916.md` | `PROGRESS-readonly-pages-fix-20260916.md` | 三份只读页单的验收修复:**BUG-710** 七政 `ketu_mode`/`sidereal_mode` 收了请求却从不传给引擎,`calculation.ketu_mode` 回写请求值而非实际值(实测请求 descending-node 仍返回 apogee 盘,无警告);**BUG-711** 星历单断言 sidebar 不得含 `/ephemeris`,与星盘单按任务书添加的入口直接冲突,staging 现在是红的;**BUG-712** `ephemeris_events` golden 存全精度浮点跨机不稳,且 golden 缺失时自动重建。另附部署缺口:`deployment.gitCommit` 仍是 `2d7698ea`。BUG 段 710+ | 待验收 | `codex/readonly-pages-fix-20260916` |
| `TASK-chart-page-blocking-open-20260915.md` | `PROGRESS-chart-page-blocking-open-20260915.md` | **P1**:星盘页开一次要等很久且常常只给一句「过一会儿再打开」。实测引擎五个调用合计 0.75 秒、mapper 13 种形态零抛出——瓶颈在 `/chart` 是动态路由 + 侧栏改成硬文档跳转,整页 SSR 等完 1 串 4 并才开始画,白屏最长 45 秒(BUG-716);`postEngine` 把 429/500/超时/坏 JSON 全碾成 `null` 且零日志,两种性质相反的故障共用一句文案(BUG-715);开页并行打两个重计算限流端点(配额 2)、无缓存,且「打开即有」印在失败页上(BUG-717)。**串行在 readonly-pages-fix 之后** | 待验收 | `codex/chart-page-blocking-open-20260915` |
| `TASK-rectification-title-repair-migration-20260915.md` | — | BUG-699 / 704 的数据修补写成了 Node 脚本(要 `SCHEMA_DATABASE_URL`),但 `Migrate Staging Database` 只跑 `migrator` 应用 SQL 迁移、不执行任意脚本——产品没有任何按钮能修自己那批错名字的会话。脚本里本来就是纯 SQL,搬进一次性迁移即可复用现成按钮。生产停在 `7b620c7a`(无 `use-rectification-surface.ts`),where 自然匹配 0 行,是 no-op | 待领取 | `codex/rectification-title-repair-migration-20260915` |
| `TASK-api-server-decomposition-20260916.md` | `PROGRESS-api-server-decomposition-20260916.md` | **重构单(串行在 qizheng 单之后)**:把业务逻辑搬出 `JyotishAPIHandler`。核心不是行数,是全仓 3 处靠 `JyotishAPIHandler.__new__` 伪造空壳 handler 借方法(`consultation_workflow_service` ×2、`capture_report_blocked_repairs_golden`、`local_accuracy_report`,MCP 也走这条),依赖方向反了、handler 没有 `headers`/`wfile` 随时可炸。四阶段:拆 `__new__` 后门 → 抽 ≥150 行业务方法 → `do_POST`/`do_GET` 改路由表 → 重新冻结行数 baseline(余量 300→50)。纯搬运不改行为,`test_api_server_security.py` 3841 行断言一条不许改。预计 11,314 → 约 9,230 行。BUG 段 710+ | 待领取 | — |
## 命名与归档
@@ -0,0 +1,130 @@
# 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.7workflow 只由产品负责人触发和修改)。这条迁移必须能被**现有**的 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,不需要命令行,不需要数据库口令。**