From de47c06d973c5ee5a3ad7f4a7bebb9526d19e02f Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Tue, 15 Sep 2026 15:07:08 +0000 Subject: [PATCH] docs(tasks): stop making the product owner hand-copy a 40-hex SHA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit migrate-staging-database 的 deploy_sha 挡的是「迁移必须钉在真正过了门禁的 修订上」:格式校验、查该 SHA 有没有成功的 backend-quality-gate 运行、与 staging head 比对后把纯文档前进的判定交给背书过的 controller。这三条不能丢。 但产品手填的那个值,恰恰就是「最新一个过了门禁的 staging 提交」——机器能 自己算,而且需要的 API 查询在同一步里已经写好了。改成:留空即自动解析, 填了就完全走原路径(回滚与迁移到更早修订的唯一手段)。解析出来后仍然照常 跑一遍全部校验,两条路径共用同一套门。 范围比看上去小:Deploy Staging 在门禁通过后由 backend-quality-gate 自动 dispatch,正常根本不用点;真正每次都要手填的只有 Migrate Staging Database 一个按钮。 生产两个按钮保持手填。它们额外要 allow_rollback / recovery_reference / restore_verified,设计意图就是逼人说清楚要发什么;在生产省掉这步不是便利, 是拆护栏。 AGENTS.md §2.7 禁止代理改 workflow,产品本次明确授权,已写进决策记录, 否则执行方会拒改;授权范围仅限本单点名的文件与改动。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu --- docs/tasks/README.md | 1 + ...-staging-dispatch-autofill-sha-20260915.md | 152 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 docs/tasks/TASK-staging-dispatch-autofill-sha-20260915.md diff --git a/docs/tasks/README.md b/docs/tasks/README.md index f8a1a5d0..a024b252 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -229,6 +229,7 @@ | `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-staging-dispatch-autofill-sha-20260915.md` | — | `Migrate Staging Database` 每次都要手抄 40 位 SHA,而那个值恰恰是「最新一个过门禁的 staging 提交」——机器能自己算,查询代码那一步里就有。改成留空自动解析、填了仍走原路径(回滚用),三条安全属性一条不丢。**产品 2026-09-15 明确授权修改该 workflow,执行方不得以 AGENTS.md §2.7 拒改**;生产两个按钮保持手填,那是护栏不是麻烦 | 待领取 | `codex/staging-dispatch-autofill-sha-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-staging-dispatch-autofill-sha-20260915.md b/docs/tasks/TASK-staging-dispatch-autofill-sha-20260915.md new file mode 100644 index 00000000..0425ac1a --- /dev/null +++ b/docs/tasks/TASK-staging-dispatch-autofill-sha-20260915.md @@ -0,0 +1,152 @@ +# TASK · `Migrate Staging Database` 不再要求手填 SHA + +- 日期:2026-09-15 +- 基线 commit:`origin/staging` @ `ebd6175b` +- 执行分支:`codex/staging-dispatch-autofill-sha-20260915` +- 影响面:`.gitea/workflows/migrate-staging-database.yml`(以及可选的 `deploy-staging.yml` 手动表单) + +--- + +## 1. 产品诉求(原话) + +> 我不想手填 sha 了,反人类,能不能改一下。 + +--- + +## 2. 先说清楚这个 SHA 在挡什么(不能直接删) + +`.gitea/workflows/migrate-staging-database.yml` 的第一步 `Validate current staging revision and successful gate` 用 `inputs.deploy_sha` 做三件事: + +1. 格式校验:必须是 40 位小写十六进制。 +2. **查这个 SHA 上有没有跑成功过 `backend-quality-gate`**(`event=push`、`branch=staging`、`conclusion=success`)。查不到就 `no successful exact-SHA staging quality gate run found` 并退出。 +3. 读当前 `staging` head。若输入 ≠ head,标记 `head_check=deferred`——因为纯文档推送不触发门禁,staging 合法地会领先于最后一个被门禁验过的 SHA;是不是「纯文档前进」留给后面那份**门禁背书过的** controller bundle 里的 `deploy/is-docs-only-range.sh` 判定。 + +**它的作用是:把迁移钉在一个真正过了门禁的修订上。** 这个属性一条都不能丢。 + +但——**产品手填的那个值,恰恰就是「最新一个过了门禁的 staging SHA」**。这是一个机器可以自己算出来的量,而且算它需要的 API 查询**这一步里已经写好了**。让人肉去查、去复制、去粘贴 40 位十六进制,纯粹是把机器的活交给了人。 + +## 3. 范围比看上去小 + +| workflow | 现在要不要手填 | 本单是否处理 | +| --- | --- | --- | +| **`Migrate Staging Database`** | **要**,且没有任何自动触发 —— **这就是痛点** | **是** | +| `Deploy Staging` | 表单里要,但**正常情况下不用点**:`backend-quality-gate.yml` 在门禁通过后会自己 dispatch 它(并同时传 `deploy_sha` 与 `gate_run_id`) | 可选,见任务 2 | +| `Deploy Production` / `Migrate Production Database` | 要 | **否**,见 §4.2 | + +所以真正每次都要手填的,只有**一个**按钮。 + +--- + +## 4. 决策记录 + +### 4.1 产品明确授权修改 workflow + +`AGENTS.md` §2.7 写着「不改 `.gitea/workflows/**`……这三件事只由产品负责人触发」。**产品负责人在 2026-09-15 主动要求改这一条**,本单据此授权执行方修改 `.gitea/workflows/migrate-staging-database.yml`。 + +**执行方不得以 §2.7 为由拒改**,但授权范围**仅限本单点名的文件与改动**:把 `deploy_sha` 变成可选并在留空时自动解析。其余 workflow、其余步骤一律不动。 + +### 4.2 生产两个按钮**保持手填**,这是故意的 + +`Deploy Production` 与 `Migrate Production Database` 除了 SHA 还要求 `allow_rollback`、`recovery_reference`、`recovery_created_at`、`restore_verified` —— 这些输入的设计意图就是**逼你把「我要发什么」说出来**。`AGENTS.md` §1.4 也把生产部署定为手动流程。 + +**在生产上省掉这步打字不是便利,是拆掉护栏。** 本单不碰它们。若之后产品仍觉得生产也烦,另开一单单独评估。 + +### 4.3 自动解析的定义 + +留空时,workflow 自己解析出的 SHA = **`staging` 分支上、最新一个有成功 `backend-quality-gate` 运行的提交**。 + +这与产品手工查找的对象逐字相同,因此三条安全属性全部原样保留:仍然要求存在成功的精确 SHA 门禁运行(现在是"由这个条件选出来的");仍然做 head 比对与 `head_check=deferred`;仍然把纯文档前进的判定交给背书过的 controller。 + +--- + +## 5. 硬红线 + +1. **只改 `workflow_dispatch.inputs` 与第一步 `Validate current staging revision and successful gate` 的解析逻辑。** 该步骤输出的三个值(`sha` / `gate_run_id` / `head_check`)语义与格式不得改变;**后面所有步骤一行都不许动**。 +2. **不得放宽任何校验**:40 位格式校验保留;"必须存在成功的精确 SHA 门禁运行"保留;`head_check=deferred` 的分支与注释保留;`is-docs-only-range.sh` 的下游判定保留。 +3. **不得改 `deploy-production.yml` / `migrate-production-database.yml`**(§4.2)。 +4. **不得改 `backend-quality-gate.yml`**,包括它自动 dispatch `deploy-staging` 的那一段。 +5. **手填仍然必须可用**:填了值就走原路径(这是回滚和"迁移到某个更早修订"的唯一手段)。留空才自动解析。 +6. 自动解析失败(查不到任何成功门禁运行、API 报错)时**必须硬失败并给出人话原因**,不得回退成"用 staging head"——head 可能根本没过门禁。 +7. `concurrency.group: staging-mutation` 不动。 + +--- + +## 6. 任务分解 + +### 任务 1 · `migrate-staging-database.yml`:SHA 变可选 + +**1.1** 输入改为: + +```yaml +deploy_sha: + description: 留空=自动用最新一个通过门禁的 staging 提交;填写=迁移到指定的 40 位 SHA(回滚用) + required: false + type: string +``` + +**1.2** 第一步开头加解析分支: + +- `DEPLOY_SHA` 非空 → **完全走现在的逻辑**,一行不改。 +- `DEPLOY_SHA` 为空 → 查 `actions/runs?branch=staging&event=push&status=success`,按现有那段 `jq` 的同一套过滤条件(`.path | split("@")[0] | endswith("backend-quality-gate.yml")`、`head_branch == "staging"`、`event == "push"`、`conclusion == "success"`)取 **`id` 最大**的一条,其 `head_sha` 即为解析结果;随后**继续走原有的全部校验**(格式、gate 查找、head 比对)。 + +**注意**:解析出来之后不要跳过第 2 步的门禁查找。让它照常再查一遍——多一次 API 调用,换"两条路径走同一套校验",值得。 + +**1.3** 在日志里显式打印一行,例如:`resolved deploy_sha= (latest gated staging commit)`。**产品需要在日志里看到自己到底迁移了哪个修订。** + +**1.4** 解析不到时的失败信息要是人话,例如:`staging 上还没有任何通过门禁的提交,先等门禁跑完再迁移`。 + +**验收标准** + +- 留空运行:日志里有 `resolved deploy_sha=…`,后续步骤与手填同一 SHA 时的行为逐步一致。 +- 手填运行:行为与改动前**完全一致**。 +- 手填一个没过门禁的 SHA:仍然被拒。 +- staging 上不存在成功门禁运行时留空运行:硬失败,信息可读。 + +### 任务 2 · `deploy-staging.yml`(可选,产品可要可不要) + +同样把 `deploy_sha` 改成可选 + 自动解析。**收益较低**——门禁通过后它会被自动 dispatch,手动表单只在重跑或回滚时用;而回滚场景本来就要手填。 + +`allow_rollback` **保持 required**:自动解析出的永远是最新修订,不构成回滚;真要回滚就得手填 SHA 并显式勾选。 + +做不做由执行方看改动成本决定,**结论写进 PROGRESS**。 + +### 任务 3 · 文档 + +- `deploy/README.md`:把 staging 迁移那一节改成「直接点 Run,SHA 留空即可;只有回滚到更早修订才需要填」,并写明生产两个按钮**仍然必须手填**及其理由。 +- `docs/tasks/PROGRESS-staging-dispatch-autofill-sha-20260915.md`:改了哪些行、两条路径各自的验证记录。 +- **不新增 BUG 编号**(这是运维易用性改动,不是缺陷)。当前最大 BUG-717。 +- 不动 `CHANGELOG.md`(用户可见行为无变化)。 + +--- + +## 7. 让步顺序 + +1. 若 Gitea 的 `workflow_dispatch` 在这个版本上不支持 `required: false` 的空字符串(部分版本会传空串而非缺省),**改用"填 `latest` 三个字母表示自动"** 而不是留空。目的一样:不用打 40 位。哪种可行写进 PROGRESS。 +2. 任务 2 做不了就不做,不阻塞任务 1。 +3. **绝不让步**:不得放宽门禁校验;不得改生产两个 workflow;不得改 `backend-quality-gate.yml`;手填路径必须保留可用。 + +--- + +## 8. 开工前置命令 + +```bash +cd /workspace/Jyotisha +git status -sb | head -1 +git fetch origin --prune +git worktree add -b codex/staging-dispatch-autofill-sha-20260915 \ + .worktrees/staging-dispatch-autofill-sha-20260915 origin/staging +cd .worktrees/staging-dispatch-autofill-sha-20260915 +sed -n '36,90p' .gitea/workflows/migrate-staging-database.yml # 要改的那一步 +``` + +⚠️ **这一单改的是部署管线本身。** 改完必须确认 YAML 能被 Gitea 正确解析——一个语法错误会让按钮直接消失。推之前本地跑一次 `python3 -c "import yaml,sys; yaml.safe_load(open('.gitea/workflows/migrate-staging-database.yml'))"` 之类的校验。 + +交付:`git push origin HEAD:staging`,推完核对远端 SHA,**并确认 Gitea 上这个 workflow 仍然出现在列表里、表单能打开**。 + +--- + +## 9. 产品侧验证(改完之后) + +1. Gitea → `Migrate Staging Database` → **SHA 留空** → Run。 +2. 在日志第一步看 `resolved deploy_sha=…`,确认它就是你以为的那个提交。 +3. 后面照常。