From 428fc556d3a2ee96475a3f47f80e46c5a68d6602 Mon Sep 17 00:00:00 2001 From: jayteemoney Date: Thu, 5 Nov 2026 14:43:03 +0100 Subject: [PATCH] chore(ci): add staging promotion workflow and branch guidance --- .github/pull_request_template.md | 20 +++++++ .github/workflows/promotion-guard.yml | 23 ++++++++ .github/workflows/test.yml | 2 +- CONTRIBUTING.md | 81 +++++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/promotion-guard.yml create mode 100644 CONTRIBUTING.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..0e5b4e5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## What changed + + + +## Target branch + +- [ ] This PR targets `staging` (normal feature, fix, docs, or chore work) +- [ ] This PR targets `main` and comes from `staging` (production promotion) + +PRs into `main` are rejected unless the source branch is `staging`. + +## Verification + +- [ ] CI is green +- [ ] Change was exercised on the staging deployment +- [ ] No secrets or credentials are included + +## Notes for reviewers + + diff --git a/.github/workflows/promotion-guard.yml b/.github/workflows/promotion-guard.yml new file mode 100644 index 0000000..f4bd454 --- /dev/null +++ b/.github/workflows/promotion-guard.yml @@ -0,0 +1,23 @@ +name: Promotion guard + +on: + pull_request: + branches: [main] + +permissions: + contents: read + pull-requests: read + +jobs: + source-branch: + name: PRs to main must come from staging + runs-on: ubuntu-latest + steps: + - name: Verify source branch + if: github.head_ref != 'staging' + run: | + echo "::error::Changes reach main only by promoting the staging branch. Open this PR against staging instead." + exit 1 + + - name: Source branch allowed + run: echo "Source branch is staging." diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 41a4a02..eac6c55 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,7 @@ name: CI on: push: - branches: [main] + branches: [main, staging] pull_request: # A newer push to the same branch makes the older run pointless. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..d485b85 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing + +## Branch model + +We use three layers of branches: + +``` +feature/fix branch -> staging -> main + (local work) (QA/verify) (production) +``` + +| Branch | Purpose | Direct pushes | Merges into it | +| --- | --- | --- | --- | +| Work branches (`feat/...`, `fix/...`, `chore/...`, `docs/...`) | Day-to-day development | Allowed by the author | — | +| `staging` | Integration and testing ground | Blocked | Work branches via PR | +| `main` | Production | Blocked | `staging` only, via PR | + +`main` is production. Nothing is merged into `main` until it has run on `staging`. + +## Day-to-day flow + +1. Create a work branch off the latest `staging` (not `main`): + + ```bash + git fetch origin + git switch -c feat/my-change origin/staging + ``` + +2. Make changes and run the checks locally: + + ```bash + npm test + cd frontend && npx tsc --noEmit && npm run lint && npm run build + cd ../openclaw-service && npx tsc --noEmit + ``` + +3. Push the branch and open a pull request **targeting `staging`**: + + ```bash + git push -u origin feat/my-change + gh pr create --base staging --fill + ``` + +4. Get CI green and one approving review, then merge into `staging`. + +5. Verify the change on the staging deployment (or locally from `staging`). + +## Promoting to production + +Once `staging` is verified and fit for release: + +1. Open a promotion pull request from `staging` into `main`: + + ```bash + gh pr create --base main --head staging --title "Promote staging to production" --fill + ``` + +2. The `Promotion guard` check rejects any PR into `main` whose source branch is + not `staging`. + +3. Get CI green and one approving review, then merge. `main` deploys to production. + +## Hotfixes + +Hotfixes follow the same path: branch off `staging`, open a PR into `staging`, +verify, then promote `staging` to `main`. If production is broken and an +expedited release is unavoidable, an administrator may temporarily relax branch +protection, but the fix must still be merged back into `staging` afterwards. + +## CI + +`.github/workflows/test.yml` runs on every pull request and on pushes to +`staging` and `main`: + +- Contract and unit tests (`npm test`) +- Frontend typecheck, lint, and production build +- OpenClaw service typecheck + +The `staging` and `main` branches require a pull request, green CI, one +approving review, and an up-to-date branch before merging. Force pushes and +branch deletion are blocked.