fix(ci): isolate staging gate from legacy main listener
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: Staging Backend Quality Gate
|
||||
name: Independent Staging Quality Gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
+4
-4
@@ -154,7 +154,7 @@ Staging is isolated from production:
|
||||
|
||||
Gitea is the primary source repository and Actions control plane. Gitea automatically injects the per-job `${{ secrets.GITEA_TOKEN }}` token; its access is limited by each workflow's `permissions` block and it must not be configured as a repository secret. Configure repository Actions secrets `REGISTRY_USERNAME`, `REGISTRY_PASSWORD`, and `STAGING_SSH_PRIVATE_KEY`, plus variables `STAGING_HOST`, `STAGING_PORT`, `STAGING_USER`, `STAGING_PATH`, `STAGING_URL`, and `STAGING_KNOWN_HOSTS`. `STAGING_SSH_PRIVATE_KEY` must be the private-key file encoded as one unwrapped base64 line (for example, `base64 < key | tr -d '\n'`), not a multiline PEM/OpenSSH value; staging workflows decode it only into a mode-`0600` temporary file and validate it with `ssh-keygen`. Staging is an independent test line and is not required to equal or remain inside `main` history. A push to `staging` runs the exact-SHA quality gate; its publish job creates immutable API/web image digests plus an allowlisted controller bundle from that same staging SHA, then explicitly dispatches `Deploy staging` from `refs/heads/staging`. The deploy workflow validates the source gate run, consumes only that gate-attested artifact, rejects stale normal releases, and never checks out or executes an untested branch controller. The staging key, database, Resend key, and model-provider keys must not be shared with production. Staging image publishing has no Supabase build variables. GitHub workflows are upstream/mirror fallback only, not the normal staging release path.
|
||||
|
||||
`Staging Backend Quality Gate` runs for relevant `pull_request` paths, pushes to `staging`, and `workflow_dispatch`. It validates the Python/database/frontend contract; only a successful push to `staging` publishes the API/web images and a run-bound artifact containing their `sha256` digests plus the allowlisted controller bundle. The publish job rechecks the current staging head and dispatches `.gitea/workflows/deploy-staging.yml` from `refs/heads/staging` with the exact SHA and source gate run ID. The deploy workflow waits for that gate's success, validates the artifact against the full 40-character commit, and deploys digest references rather than trusting discoverability tags.
|
||||
`Independent Staging Quality Gate` runs for relevant `pull_request` paths, pushes to `staging`, and `workflow_dispatch`. It validates the Python/database/frontend contract; only a successful push to `staging` publishes the API/web images and a run-bound artifact containing their `sha256` digests plus the allowlisted controller bundle. The publish job rechecks the current staging head and dispatches `.gitea/workflows/deploy-staging.yml` from `refs/heads/staging` with the exact SHA and source gate run ID. The deploy workflow waits for that gate's success, validates the artifact against the full 40-character commit, and deploys digest references rather than trusting discoverability tags.
|
||||
|
||||
The staging env file must include these non-secret selectors so Compose cannot fall back to production paths:
|
||||
|
||||
@@ -174,13 +174,13 @@ After source sync and before `up`, the workflow validates `.env.staging` mode/se
|
||||
|
||||
1. Complete the server and Gitea bootstrap: create both mode-`0600` env files, preload the reviewed `postgres:17-alpine` image, and configure the listed Actions variables/secrets. No repository-level Supabase variables are required. Deployment and migration workflows use `--pull never` for PostgreSQL, so database image upgrades remain an explicit operator-controlled maintenance action rather than an application-deploy side effect.
|
||||
2. Push the reviewed test revision directly to `staging`. It may advance independently of `main`; do not merge or reset `main` merely to satisfy staging release mechanics.
|
||||
3. The `Staging Backend Quality Gate` runs for that push and, when successful, publishes API/web images plus an artifact binding the exact SHA to both immutable image digests and the allowlisted staging controller bundle.
|
||||
3. The `Independent Staging Quality Gate` runs for that push and, when successful, publishes API/web images plus an artifact binding the exact SHA to both immutable image digests and the allowlisted staging controller bundle.
|
||||
4. The successful publish job rechecks that `staging` still points at the exact SHA, then dispatches `Deploy staging` from `refs/heads/staging` with the source gate run ID. The deploy workflow waits for that gate's final success, validates the artifact, and performs the normal forward-only release under the shared staging host lock.
|
||||
5. If environment validation fails, fix the server-side env files without committing or copying secrets, then manually rerun `Deploy staging` using **Use workflow from: staging**, the same successful SHA in `deploy_sha`, an empty `gate_run_id`, and `allow_rollback=false`; the workflow resolves a successful exact-SHA staging push gate before mutation.
|
||||
6. If the read-only checker reports a pending migration, stop app deployment and run `Migrate Staging Database` manually using **Use workflow from: staging** with the same full SHA. Migration success does not dispatch deployment.
|
||||
7. After migration succeeds, manually start `Deploy staging` from `staging` with that same exact SHA and `allow_rollback=false`, then confirm `https://staging.jyotisha.chat/api/health` reports it and private API health.
|
||||
|
||||
Application rollback uses the same workflow: manually dispatch `Deploy staging` using **Use workflow from: staging** with a previous known-good full SHA that has a successful `Staging Backend Quality Gate` push run, leave `gate_run_id` empty, and explicitly set `allow_rollback=true`. The requested SHA must be an ancestor of the current `staging` head. Normal deployments reject stale or divergent revisions. Rollback still consumes the selected gate run's digest and controller artifact and is supported only during that artifact's 30-day retention window; after expiry, stop and prepare a separately reviewed republish/recovery change rather than substituting a mutable tag or assuming the old run can still be rerun. Database migrations are separate and are not rolled back by an application deployment. Restore a staging database backup before running any destructive migration rehearsal.
|
||||
Application rollback uses the same workflow: manually dispatch `Deploy staging` using **Use workflow from: staging** with a previous known-good full SHA that has a successful `Independent Staging Quality Gate` push run, leave `gate_run_id` empty, and explicitly set `allow_rollback=true`. The requested SHA must be an ancestor of the current `staging` head. Normal deployments reject stale or divergent revisions. Rollback still consumes the selected gate run's digest and controller artifact and is supported only during that artifact's 30-day retention window; after expiry, stop and prepare a separately reviewed republish/recovery change rather than substituting a mutable tag or assuming the old run can still be rerun. Database migrations are separate and are not rolled back by an application deployment. Restore a staging database backup before running any destructive migration rehearsal.
|
||||
|
||||
Inspect staging without printing secrets:
|
||||
|
||||
@@ -241,7 +241,7 @@ PostgreSQL is private: `deploy/docker-compose.postgres.yml` has no `ports` mappi
|
||||
Use this order for every staging revision:
|
||||
|
||||
1. Review the test change, then push its exact commit directly to `staging`; `main` may remain at a different SHA.
|
||||
2. Wait for `Staging Backend Quality Gate` to pass and publish that exact full SHA's API/web digest and controller artifact. Its publish job dispatches the staging-ref deployment and refuses dispatch if `staging` already advanced.
|
||||
2. Wait for `Independent Staging Quality Gate` to pass and publish that exact full SHA's API/web digest and controller artifact. Its publish job dispatches the staging-ref deployment and refuses dispatch if `staging` already advanced.
|
||||
3. The dispatched `Deploy staging` workflow validates the source gate run and checks the exact SHA in read-only migration-check mode before changing API, web, or Caddy. If it reports pending or drifted migrations, stop; do not retry the application deployment as if it were a migration.
|
||||
4. Open **Migrate Staging Database -> Run workflow**, select **Use workflow from: staging**, and enter the reported full lowercase 40-character SHA in `deploy_sha`. The workflow requires the current `staging` head and a successful exact-SHA staging gate, starts only PostgreSQL, and runs the digest-pinned migrator from the gate-attested controller bundle.
|
||||
5. A successful migration rechecks that `staging` still points at the same exact SHA and prints the ordered migration ledger, but does not dispatch deployment. The operator must then open **Deploy staging -> Run workflow**, select **Use workflow from: staging**, enter the same exact SHA in `deploy_sha`, leave `gate_run_id` empty, and set `allow_rollback=false`. If `staging` advanced, stop rather than substituting a branch name, short SHA, or newer commit.
|
||||
|
||||
+2
-2
@@ -3046,7 +3046,7 @@
|
||||
- 用户现象:staging 作为测试分支需要领先或偏离 main 时,旧部署工作流仍要求 main 与 staging 同一 SHA,且 `workflow_run` 从默认 main 加载控制器,导致 staging-only 变更无法按自身已测试工作流发布。
|
||||
- 触发条件:`staging` 推送了尚未进入 `main` 的测试提交并完成 quality gate。
|
||||
- 根因:staging 发布把 production 的 reviewed-main 收敛约束复用到了测试环境,同时依赖默认分支的 `workflow_run` controller;即使删除 SHA 相等检查,旧 main controller 仍可能继续执行旧门禁。
|
||||
- 修复:staging push gate 在发布同一 exact-SHA 的不可变镜像与 allowlisted controller bundle 后,显式从 `refs/heads/staging` dispatch `Deploy staging`,并传入源 gate run ID;deploy 等待并验证该 gate 最终成功,正常发布仍要求当前 staging HEAD,回滚仍要求当前 staging history 中的旧成功 gate SHA。staging migration 只要求当前 staging HEAD 和 exact-SHA gate artifact。main 与 production workflow 均不改动。
|
||||
- 修复:将 staging gate 重命名为 `Independent Staging Quality Gate`,使默认 main 上遗留的 `workflow_run` 监听器不再匹配并抢占 `staging-mutation` 并发组;staging push gate 在发布同一 exact-SHA 的不可变镜像与 allowlisted controller bundle 后,显式从 `refs/heads/staging` dispatch `Deploy staging`,并传入源 gate run ID。deploy 等待并验证该 gate 最终成功,正常发布仍要求当前 staging HEAD,回滚仍要求当前 staging history 中的旧成功 gate SHA。staging migration 只要求当前 staging HEAD 和 exact-SHA gate artifact。main 与 production workflow 均不改动。
|
||||
- 验证:工作流契约测试锁定 staging-ref dispatch、源 gate run 证明、无 main 引用、当前 HEAD 防陈旧发布、同一 gate artifact 的 controller/digest 校验和回滚祖先限制;远端 gate/deploy 与运行时 SHA 待本次 staging 发布记录。
|
||||
- 防复发:测试环境的部署控制器必须来自被同一 quality gate 证明的 staging SHA;不得重新引入 `workflow_run` 默认分支控制器或 staging/main 相等门禁。production 继续保持独立的 main/staging 收敛要求。
|
||||
- 防复发:测试环境的部署控制器必须来自被同一 quality gate 证明的 staging SHA;staging gate 名称不得恢复为默认 main 遗留监听器匹配的 `Staging Backend Quality Gate`,也不得重新引入 `workflow_run` 默认分支控制器或 staging/main 相等门禁。production 继续保持独立的 main/staging 收敛要求。
|
||||
- 修复版本:本次 staging workflow 提交
|
||||
|
||||
@@ -588,6 +588,8 @@ test("Gitea staging deployment is dispatched from staging after the exact push g
|
||||
const quality = read(giteaQualityWorkflow);
|
||||
const workflow = read(giteaDeployWorkflow);
|
||||
|
||||
assert.match(quality, /^name: Independent Staging Quality Gate$/m);
|
||||
assert.doesNotMatch(quality, /^name: Staging Backend Quality Gate$/m);
|
||||
assert.match(workflow, /^on:\n\s+workflow_dispatch:/m);
|
||||
assert.doesNotMatch(workflow, /workflow_run:|read_ref_sha main|refs\/heads\/main|reviewed main/);
|
||||
assert.match(workflow, /gate_run_id:/);
|
||||
|
||||
Reference in New Issue
Block a user