diff --git a/.github/workflows/gate.yml b/.github/workflows/gate.yml index fded20e..2b2b368 100644 --- a/.github/workflows/gate.yml +++ b/.github/workflows/gate.yml @@ -9,20 +9,16 @@ on: types: [opened, synchronize, reopened, edited] push: branches: [main] - workflow_call: - inputs: - ref: - description: Commit to gate. upstream-sync passes its sync commit and merges only after this run passes. - type: string - required: true + # upstream-sync dispatches this on its sync branch. A dispatched run reports `gate`, `smoke`, and + # `statechart` under the same names as a pull request run, so the main ruleset's required checks + # cover bot syncs too. A reusable call would report `gate / gate` and never satisfy them. + workflow_dispatch: jobs: gate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - with: - ref: ${{ inputs.ref }} - uses: oven-sh/setup-bun@v2 with: bun-version: 1.4.4 @@ -59,8 +55,6 @@ jobs: timeout-minutes: 10 steps: - uses: actions/checkout@v7 - with: - ref: ${{ inputs.ref }} - uses: oven-sh/setup-bun@v2 with: bun-version: 1.4.4 @@ -93,8 +87,6 @@ jobs: working-directory: plugins/pstack/skills/statechart/scripts steps: - uses: actions/checkout@v7 - with: - ref: ${{ inputs.ref }} - uses: actions/setup-node@v7 with: node-version: 22 diff --git a/.github/workflows/upstream-sync.yml b/.github/workflows/upstream-sync.yml index 910d858..b6dff17 100644 --- a/.github/workflows/upstream-sync.yml +++ b/.github/workflows/upstream-sync.yml @@ -23,6 +23,7 @@ jobs: outputs: pr: ${{ steps.build.outputs.pr }} sha: ${{ steps.build.outputs.sha }} + branch: ${{ steps.build.outputs.branch }} env: GH_TOKEN: ${{ github.token }} steps: @@ -110,22 +111,39 @@ jobs: pr=$(gh pr create --base main --head "sync/upstream-$SHORT" \ --title "sync: upstream pstack $SHORT ($version)" --body-file "$RUNNER_TEMP/body.md" | tail -n1) if [ "$code" -eq 0 ] && [ "$gate" -eq 0 ]; then - printf 'pr=%s\nsha=%s\n' "$pr" "$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + printf 'pr=%s\nsha=%s\nbranch=%s\n' "$pr" "$(git rev-parse HEAD)" "sync/upstream-$SHORT" >> "$GITHUB_OUTPUT" exit 0 fi echo "left $pr open for a human: sync exit $code, gate exit $gate" exit 1 # A merge made with github.token starts no push run on main, so this job is the only full gate a - # sync gets. A red gate skips the merge and leaves the PR open for a human. + # sync gets. It dispatches gate.yml on the sync branch rather than calling it: the dispatched run + # puts `gate`, `smoke`, and `statechart` on the sync commit, which the main ruleset requires before + # any merge. A red gate fails this job, skips the merge, and leaves the PR open for a human. gate: needs: sync if: needs.sync.outputs.sha != '' + runs-on: ubuntu-latest permissions: - contents: read - uses: ./.github/workflows/gate.yml - with: - ref: ${{ needs.sync.outputs.sha }} + actions: write + env: + GH_TOKEN: ${{ github.token }} + SHA: ${{ needs.sync.outputs.sha }} + BRANCH: ${{ needs.sync.outputs.branch }} + steps: + - run: | + gh workflow run gate.yml --repo "$GITHUB_REPOSITORY" --ref "$BRANCH" + id="" + for _ in $(seq 1 30); do + id=$(gh run list --repo "$GITHUB_REPOSITORY" --workflow gate.yml --event workflow_dispatch \ + --branch "$BRANCH" --json databaseId,headSha \ + --jq "[.[] | select(.headSha == \"$SHA\")][0].databaseId // empty") + [ -n "$id" ] && break + sleep 10 + done + [ -n "$id" ] || { echo "no gate run appeared for $SHA on $BRANCH" >&2; exit 1; } + gh run watch "$id" --repo "$GITHUB_REPOSITORY" --exit-status --interval 30 merge: needs: [sync, gate] diff --git a/PORTING.md b/PORTING.md index 1fc53d5..74ef846 100644 --- a/PORTING.md +++ b/PORTING.md @@ -144,8 +144,12 @@ bash omp-port/check-port.sh "" "$(gh api 'repos/cursor/plugins/commits?path=psta The gate runs as its own workflow, `gate.yml`, on every pull request and on every push to `main`. Neither trigger gated the two syncs the bot merged, #19 and #32. Each PR got a `pull_request` run that ended with no jobs a second after the merge, and the merge, made with `github.token`, started -no `push` run. So `upstream-sync` calls `gate.yml` itself, as a reusable workflow on the sync -commit, and its `merge` job runs only after that call passes. A sync that fails the build, +no `push` run. So `upstream-sync` dispatches `gate.yml` on the sync branch and its `merge` job runs +only after that run passes. A dispatch, unlike a reusable call, reports the checks as `gate`, +`smoke`, and `statechart` on the sync commit. The `main` ruleset requires exactly those three, and a +personal repository cannot exempt the GitHub Actions bot from a ruleset, so a reusable call's +`gate / gate` names would leave every bot merge blocked. The ruleset also requires a pull request +for every change to `main` and forbids force-pushes and deletion. A sync that fails the build, `check-port.sh`, or `gate.yml` stays open for a human, and later runs stop at the open-PR guard until someone resolves it.