diff --git a/docs/tasks/README.md b/docs/tasks/README.md index a85a111c..f8a1a5d0 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -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 段 707–709 | 已验收(带修复单) | `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+ | 待领取 | — | ## 命名与归档 diff --git a/docs/tasks/TASK-rectification-title-repair-migration-20260915.md b/docs/tasks/TASK-rectification-title-repair-migration-20260915.md new file mode 100644 index 00000000..2fa5ec9c --- /dev/null +++ b/docs/tasks/TASK-rectification-title-repair-migration-20260915.md @@ -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.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,不需要命令行,不需要数据库口令。**