# 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. 后面照常。