fix(release): keep owners and grants in production recovery points

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-14 21:01:52 +08:00
co-authored by Grok (agent loop)
parent d13250f40a
commit 09906cdccc
13 changed files with 381 additions and 145 deletions
@@ -1151,10 +1151,14 @@ test("production deploy is manual-only and consumes the accepted staging artifac
assert.match(production, /\[\[ "\$current_main" == "\$current_staging" \]\]/);
assert.match(production, /bash "\$checker" --api "\$DEPLOY_SHA" "\$current_staging"/);
assert.match(production, /bash deploy\/resolve-release-revision\.sh/);
// 原值: 没有断言锁住 CHECK_RELEASE_GATE=true,删掉开关测试仍绿
// 新值: 正向部署必须带着这个开关调用解析脚本
// 原因: 开关关掉后,正向路径不再要求跑过发布门禁
assert.match(production, /CHECK_RELEASE_GATE=true bash deploy\/resolve-release-revision\.sh/);
// 原值: /CHECK_RELEASE_GATE=true bash deploy\/resolve-release-revision\.sh/ 同时匹配回滚那一行
// 新值: 正向断言锚在 >>"$GITHUB_OUTPUT" 的那一次调用
// 原因: 删掉正向那一处时,回滚行仍能让旧正则通过
assert.match(production, /CHECK_RELEASE_GATE=true bash deploy\/resolve-release-revision\.sh >>"\$GITHUB_OUTPUT"/);
// 原值: 回滚部署不读恢复点格式
// 新值: allow_rollback 为 true 时,主机上的恢复点格式必须 >= 2
// 原因: 8 月以来不带权限的旧恢复点不能再用来回退
assert.match(production, /ALLOW_ROLLBACK" == true[\s\S]*recovery format is below 2; dispatch Create production recovery first/);
// 原值: 回滚用 main 头自己的 push 门禁,jq 取不到 first 就失败
// 新值: 回滚的控制器改用 RESOLVE_MODE=discover 解析出的 R;目标仍须在已审查的 main 历史里
// 原因: 测试环境头只多了文档时没有门禁记录,说明书里的回退会被拒绝
@@ -1229,6 +1233,10 @@ test("production runner validates state and migrations before switching exact im
assert.match(runner, /^#!\/usr\/bin\/env bash\nset -euo pipefail\nset \+x\n/);
assert.match(runner, /another production mutation holds the host lock/);
// 原值: 回滚部署不读恢复点格式
// 新值: ALLOW_ROLLBACK=true 时主机脚本也拒绝低于 2 的恢复点
// 原因: 工作流检查被绕过时,回退仍不能使用 8 月的旧恢复点
assert.match(runner, /\[ "\$ALLOW_ROLLBACK" = "true" \][\s\S]*recovery format is below 2; dispatch Create production recovery first/);
assert.match(runner, /sync-production-tree\.sh/);
assertOrder(runner, [
"validate-production-env.sh",
@@ -1307,10 +1315,18 @@ test("production recovery workflow creates a verified encrypted off-site artifac
assert.doesNotMatch(runner, /rm -f[^\n]*mutation\.lock|chown -R/);
assert.match(runner, /another production mutation holds the host lock/);
assert.match(runner, /usage_percent < 70/);
assert.match(runner, /pg_dump[\s\S]*--format=custom --no-owner --no-acl/);
// 原值: pg_dump 与 pg_restore 都带 --no-owner --no-acl
// 新值: 只保留 --format=custom,还原核对也不再丢掉属主和权限
// 原因: 旧恢复点还原后网站账号对所有表都没有权限,四个计数却仍然对得上
assert.match(runner, /pg_dump[\s\S]*--format=custom/);
assert.doesNotMatch(runner, /--no-owner|--no-acl/);
assert.match(runner, /table_grants_match/);
assert.match(runner, /column_privileges_match/);
assert.match(runner, /table_owners_match/);
assert.match(runner, /RECOVERY_FORMAT=2/);
assert.match(runner, /openssl enc -aes-256-cbc -salt -pbkdf2/);
assert.match(runner, /openssl enc -d -aes-256-cbc -pbkdf2/);
assert.match(runner, /pg_restore[\s\S]*--no-owner --no-acl --exit-on-error/);
assert.match(runner, /pg_restore[\s\S]*--exit-on-error/);
assert.match(runner, /DROP DATABASE IF EXISTS[\s\S]*WITH \(FORCE\)/);
assert.match(runner, /restore_database_removed/);
assert.match(runner, /gitea-actions-run-\$\{RECOVERY_RUN_ID\}/);
@@ -1354,6 +1370,11 @@ test("Gitea production schema migration is exact-SHA gated and isolated from ETL
assert.match(workflow, /recovery_created_at:[\s\S]*required: true[\s\S]*type: string/);
assert.match(workflow, /restore_verified:[\s\S]*required: true[\s\S]*default: false[\s\S]*type: boolean/);
assert.match(workflow, /\[\[ "\$RESTORE_VERIFIED" == "true" \]\]/);
// 原值: 生产迁移不读恢复点格式
// 新值: 读取 RECOVERY_FORMAT,低于 2 就拒绝并提示先创建恢复点
// 原因: 不带权限的旧恢复点不能再当作这次迁移的退路
assert.match(workflow, /RECOVERY_FORMAT/);
assert.match(workflow, /recovery format is below 2; dispatch Create production recovery first/);
assert.match(workflow, /%Y-%m-%dT%H:%M:%SZ/);
assert.match(workflow, /age > timedelta\(hours=24\)/);
assert.match(workflow, /migration files run sequentially and are not atomic as a whole/);
@@ -1377,6 +1398,8 @@ test("Gitea production schema migration is exact-SHA gated and isolated from ETL
assert.match(runner, /date -u -d "\$RECOVERY_CREATED_AT" \+%s/);
assert.match(runner, /recovery_age_seconds <= 24 \* 60 \* 60/);
assert.match(runner, /production recovery point must have restore_verified=true/);
assert.match(runner, /RECOVERY_FORMAT/);
assert.match(runner, /recovery format is below 2; dispatch Create production recovery first/);
assert.match(runner, /migration files run sequentially and are not atomic as a whole/);
assert.match(runner, /migration_runner_database_url="postgresql:\/\/migration_runner:/);
assert.match(runner, /role%3Dschema_owner/);