From 98c07849fc0a26b040168309b5e88e1b4840eeae Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Sat, 26 Sep 2026 12:16:41 +0000 Subject: [PATCH 1/2] ci: route changes through staging --- .github/dependabot.yml | 3 ++ .github/pull_request_template.md | 2 ++ .github/rulesets/main.json | 42 ++++++++++++++++++++++ .github/rulesets/staging.json | 41 ++++++++++++++++++++++ .github/workflows/branch-policy.yml | 43 +++++++++++++++++++++++ .github/workflows/ci.yml | 19 ++++++++-- CONTRIBUTING.md | 54 +++++++++++++++++++++++++++++ 7 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 .github/rulesets/main.json create mode 100644 .github/rulesets/staging.json create mode 100644 .github/workflows/branch-policy.yml create mode 100644 CONTRIBUTING.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d8b57a7..184e0cc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,6 +2,9 @@ version: 2 updates: - package-ecosystem: github-actions directory: / + # Updates are tested in staging like every other change; main only takes + # pull requests from staging. + target-branch: staging schedule: interval: monthly commit-message: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 67dcd91..b2c4497 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,5 @@ + + ## What this changes diff --git a/.github/rulesets/main.json b/.github/rulesets/main.json new file mode 100644 index 0000000..92864ef --- /dev/null +++ b/.github/rulesets/main.json @@ -0,0 +1,42 @@ +{ + "name": "Protect main", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/main"], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["merge"] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": false, + "required_status_checks": [ + { "context": "Only staging merges into main" }, + { "context": "Linux Debug" }, + { "context": "Linux Release" }, + { "context": "Windows Debug" }, + { "context": "Windows Release" }, + { "context": "Shaders compile and are committed" }, + { "context": "Headless smoke test" } + ] + } + } + ] +} diff --git a/.github/rulesets/staging.json b/.github/rulesets/staging.json new file mode 100644 index 0000000..9a27259 --- /dev/null +++ b/.github/rulesets/staging.json @@ -0,0 +1,41 @@ +{ + "name": "Protect staging", + "target": "branch", + "enforcement": "active", + "conditions": { + "ref_name": { + "include": ["refs/heads/staging"], + "exclude": [] + } + }, + "bypass_actors": [], + "rules": [ + { "type": "deletion" }, + { "type": "non_fast_forward" }, + { + "type": "pull_request", + "parameters": { + "required_approving_review_count": 0, + "dismiss_stale_reviews_on_push": false, + "require_code_owner_review": false, + "require_last_push_approval": false, + "required_review_thread_resolution": false, + "allowed_merge_methods": ["merge", "squash", "rebase"] + } + }, + { + "type": "required_status_checks", + "parameters": { + "strict_required_status_checks_policy": false, + "required_status_checks": [ + { "context": "Linux Debug" }, + { "context": "Linux Release" }, + { "context": "Windows Debug" }, + { "context": "Windows Release" }, + { "context": "Shaders compile and are committed" }, + { "context": "Headless smoke test" } + ] + } + } + ] +} diff --git a/.github/workflows/branch-policy.yml b/.github/workflows/branch-policy.yml new file mode 100644 index 0000000..e2196fc --- /dev/null +++ b/.github/workflows/branch-policy.yml @@ -0,0 +1,43 @@ +name: Branch policy + +# staging is the testing layer: every change lands there first, and main only +# takes pull requests from staging. This check fails any other pull request +# into main. It also runs on pull requests into staging, where it always +# passes, so retargeting a pull request from main to staging clears the +# failure it picked up there. +# +# pull_request_target runs this file as it exists on the base branch, so a +# pull request cannot edit the check into passing. Nothing from the pull +# request is checked out or run, and the job holds no token permissions. +on: + pull_request_target: + branches: [main, staging] + types: [opened, reopened, synchronize, edited] + +permissions: {} + +jobs: + # The job name is a required status check in .github/rulesets/main.json. + # Keep the verdict inside the step: a job skipped by an `if:` reports + # success, which would satisfy the required check. + staging-to-main: + name: Only staging merges into main + runs-on: ubuntu-latest + steps: + - name: Check where the pull request comes from + env: + BASE_REF: ${{ github.event.pull_request.base.ref }} + HEAD_REF: ${{ github.event.pull_request.head.ref }} + HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }} + run: | + if [ "$BASE_REF" != "main" ]; then + echo "Targets $BASE_REF, which accepts pull requests from any branch." + exit 0 + fi + if [ "$HEAD_REPO" = "$GITHUB_REPOSITORY" ] && [ "$HEAD_REF" = "staging" ]; then + echo "Release pull request from staging into main." + exit 0 + fi + echo "This pull request comes from ${HEAD_REPO:-a deleted fork}:${HEAD_REF}." + echo "::error::main only accepts pull requests from staging. Edit this pull request and change its base branch to staging." + exit 1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b3b786..b9fce4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: CI on: push: - branches: [main] + branches: [main, staging] pull_request: - branches: [main] + branches: [main, staging] workflow_dispatch: inputs: engine_ref: @@ -19,6 +19,9 @@ concurrency: permissions: contents: read +# The check names here (the build matrix names and the other jobs' names) are +# required status checks in .github/rulesets/. Rename both together, or pull +# requests will wait on a check that never reports. jobs: build: name: ${{ matrix.name }} @@ -142,6 +145,18 @@ jobs: fi echo "Built $found" + # staging is the testing layer, so every push there leaves a playable + # build to try before staging is merged into main. bin/ holds the binary + # next to its assets, the same folder a release packages. + - name: Upload the staging test build + if: github.event_name == 'push' && github.ref == 'refs/heads/staging' && matrix.config == 'Release' + uses: actions/upload-artifact@v4 + with: + name: SleakCraft-staging-${{ runner.os }} + retention-days: 14 + if-no-files-found: error + path: SleakCraft/bin/ + shaders: name: Shaders compile and are committed runs-on: ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..5570acc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,54 @@ +# Contributing + +## Branches + +`staging` is the testing layer between a change and `main`. Everything lands in `staging` first, gets tested there, and reaches `main` only when `staging` is merged into it. + +| Branch | Holds | Accepts | +|--------|-------|---------| +| `staging` | Changes being tested | Pull requests from any branch, once CI passes | +| `main` | Tested, released code | Pull requests from `staging` only, once CI passes | + +Open every pull request against `staging`. A pull request into `main` from any other branch fails the **Only staging merges into main** check and cannot be merged. To fix it, edit the pull request and change its base branch to `staging`. + +--- + +## Testing in staging + +Every pull request into `staging` runs the full CI: the Linux and Windows build matrix, the shader check, and the headless smoke test. + +Every push to `staging` also leaves a playable Release build. Open the CI run for the latest `staging` commit in the Actions tab and download `SleakCraft-staging-Windows` or `SleakCraft-staging-Linux` from the bottom of the page. The builds are kept for 14 days. + +--- + +## Releasing staging to main + +1. Open a pull request from `staging` into `main`. +2. Wait for CI to pass. +3. Merge it with **Create a merge commit**. + +`main` only allows merge commits. Squash and rebase copy staging's commits into new ones, so the two branches would stop sharing history, and every later release would replay old commits and hit conflicts that aren't real. + +After a release, GitHub shows `staging` as behind `main`. The commits it is missing are the release merge commits, which change nothing on their own and need no action. + +--- + +## Branch protection + +The rules are the JSON files in `.github/rulesets/`, which GitHub imports as they are: **Settings → Rules → Rulesets → New ruleset → Import a ruleset**. + +| Rule | `main` | `staging` | +|------|--------|-----------| +| Changes arrive by pull request only | ✓ | ✓ | +| No force pushes, no deletion | ✓ | ✓ | +| CI must pass | ✓ | ✓ | +| Source must be `staging` | ✓ | | +| Merge method | Merge commit | Any | + +> **Setting it up?** Import `main.json` only after `.github/workflows/branch-policy.yml` is on `main`. The ruleset requires the check that workflow reports, so until the workflow exists nothing can merge into `main`. **Allow merge commits** must stay on under Settings → General → Pull Requests. + +Neither ruleset requires an approving review, because GitHub does not let authors approve their own pull requests. Raise `required_approving_review_count` once there is a second maintainer. + +Nobody can bypass the rules, admins included. To get out of a jam, set the ruleset's enforcement to **Disabled**, fix the problem, then set it back to **Active**. + +Required checks are matched by name, so renaming a job in `ci.yml` or `branch-policy.yml` means updating the rulesets to match. From 6a78ab63c9484b4a2c84c7d95f5d75c88eaadbfe Mon Sep 17 00:00:00 2001 From: Canberk Pitirli Date: Tue, 29 Sep 2026 18:37:38 +0000 Subject: [PATCH 2/2] ci: let engine check open issues --- .github/workflows/engine-update.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/engine-update.yml b/.github/workflows/engine-update.yml index 519f0ca..5ecc55d 100644 --- a/.github/workflows/engine-update.yml +++ b/.github/workflows/engine-update.yml @@ -12,6 +12,8 @@ on: permissions: contents: read + # The failure step below opens an issue. + issues: write jobs: verify: