diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 0b583840c..09b912332 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -203,3 +203,51 @@ jobs: push: false build-args: | VERSION=ci + + required: + name: Required checks + needs: + - changes + - frontend + - manager-server + - manager-server-windows-sqlite + - native-control + - docker-build + if: always() + runs-on: ubuntu-latest + + steps: + - name: Verify applicable checks + env: + CHANGES_RESULT: ${{ needs.changes.result }} + FRONTEND_RESULT: ${{ needs.frontend.result }} + MANAGER_SERVER_RESULT: ${{ needs['manager-server'].result }} + WINDOWS_SQLITE_RESULT: ${{ needs['manager-server-windows-sqlite'].result }} + NATIVE_CONTROL_RESULT: ${{ needs['native-control'].result }} + DOCKER_BUILD_RESULT: ${{ needs['docker-build'].result }} + shell: bash + run: | + set -euo pipefail + + results=( + "Scope:${CHANGES_RESULT}" + "Frontend:${FRONTEND_RESULT}" + "Manager Server:${MANAGER_SERVER_RESULT}" + "Manager Server SQLite (Windows):${WINDOWS_SQLITE_RESULT}" + "Native Control:${NATIVE_CONTROL_RESULT}" + "Docker Build:${DOCKER_BUILD_RESULT}" + ) + + for check in "${results[@]}"; do + name="${check%%:*}" + result="${check#*:}" + case "${result}" in + success|skipped) + echo "${name}: ${result}" + ;; + *) + echo "${name} did not complete successfully: ${result:-missing}" >&2 + exit 1 + ;; + esac + done diff --git a/docs/release.md b/docs/release.md index 2d9b8c8c1..4b72dbf5d 100644 --- a/docs/release.md +++ b/docs/release.md @@ -14,6 +14,36 @@ Formal release notes are technical release records. Community-facing release copy is authored separately so it can prioritize user value and readability without weakening the technical notes. +## Release Branch Flow + +`main` is the stable default branch. `dev` is the integration branch. Every +release follows this sequence: + +```text +release/ -> dev -> main -> v tag -> GitHub Release +``` + +1. Freeze the intended release scope on `dev`; do not merge unrelated work + until the tag is created. +2. Create `release/` from `dev`, add the two release-note files and + the Telegram post, then merge its release PR into `dev`. +3. Record the resulting `dev` commit from that release PR as the release SHA. + Before promotion, confirm that `dev` still points to that exact SHA. +4. Open a same-repository `dev -> main` promotion PR. It must pass the normal + PR checks and the `Verify dev promotion source` gate before merging. +5. Reconfirm that the resulting `main` commit contains the recorded release + `dev` SHA, then create the tag from that exact `main` commit. + +Do not open a release PR directly to `main`: branch protection permits only +the repository's `dev` branch to promote into `main`. + +`main` can contain a prior `dev -> main` merge commit that is not an ancestor +of the current `dev` ref. This is normal. Before a new release, require that +`main` has no non-merge commits absent from `dev`; investigate and stop if it +does. The release scope is still invalid if `dev` advances after the release PR +is merged: refresh the notes and repeat the release preflight rather than +including unreviewed changes in the tag. + ## Release Note Files ```text diff --git a/tests/nativeControlScripts.test.mjs b/tests/nativeControlScripts.test.mjs index 60666f576..06bf39a14 100644 --- a/tests/nativeControlScripts.test.mjs +++ b/tests/nativeControlScripts.test.mjs @@ -455,7 +455,7 @@ describe('native control scripts', () => { 'if ($errors.Count -gt 0) { $errors | ForEach-Object { Write-Error $_.Message }; exit 1 }', ].join('; '), ]); - }); + }, 10_000); it('starts Windows processes with custom paths, private files, logs, and stop', () => { if (process.platform !== 'win32') {