From ad99e0d9fa8df1661aa639aab8e4eba3ee73d815 Mon Sep 17 00:00:00 2001 From: Test Date: Fri, 11 Sep 2026 16:36:47 -0400 Subject: [PATCH] feat(ci): add overlay-promotion-bot workflow (#1010 Phase 1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Automates the diff JJ has been generating by hand (#947, #1005, #1006, #1009, #1011): on a schedule (or manual dispatch), run the existing overlay-promotion.mjs --check against the fork's local/amicode tip, and on drift, run --apply on a deterministic review branch and open/update one PR. Deliberately not built: a signed webhook receiver, a GitHub App, compare-and-swap bot-merge authority, or exception-approval automation. JJ's own promotion PRs (#1009, #1011) were merged 6-27 seconds after opening once CI was green — the toil is producing the diff, not reviewing it, so a human (or required status checks) still merges here. Verified locally against a scratch git remote + the real harmoniqs/opencode clone (not just described): - --check against the current in-sync state exits 0, no PR opened - a simulated fork commit drifts --check to exit 1 - --apply on the review branch (created BEFORE --apply, since #1005's main-branch guard would otherwise refuse it) reproduces the correct manifest + overlay diff - re-running --apply after resetting the branch from main is idempotent (same tree each time, safe to force-push) If the app-bundle overlay is retired later (tracked separately, off #1010), this workflow is deleted, not migrated. --- .github/workflows/overlay-promotion-bot.yml | 115 ++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 .github/workflows/overlay-promotion-bot.yml diff --git a/.github/workflows/overlay-promotion-bot.yml b/.github/workflows/overlay-promotion-bot.yml new file mode 100644 index 00000000..0f1eed89 --- /dev/null +++ b/.github/workflows/overlay-promotion-bot.yml @@ -0,0 +1,115 @@ +# Phase 1 of harmoniqs/amicode#1010 — automate the promotion DIFF, keep the +# human MERGE. +# +# The manual toil this replaces isn't the merge (JJ's own promotion PRs +# — #1009, #1011 — were merged 6-27 seconds after opening, once CI was +# green): it's remembering to run `sync:check`/`sync:apply` at all, and +# typing the exact command. This job runs the SAME script +# (overlay-promotion.mjs) a human would, on a schedule, and opens/updates one +# PR when the fork has drifted. Nothing here has merge authority: a human (or +# required status checks, once branch protection is enabled) still merges. +# +# Deliberately NOT built here, because the actual problem doesn't need it: +# a signed webhook receiver, a GitHub App, compare-and-swap bot-merge +# authority, or exception-approval automation. If the app-bundle overlay +# itself is retired later (see the phases-2-4 note referenced from #1010), +# this workflow is deleted, not migrated. +name: overlay-promotion-bot + +on: + schedule: + - cron: "0 */2 * * *" # every 2 hours; cheap no-op when already in sync + workflow_dispatch: {} # manual/test trigger + +permissions: + contents: write + pull-requests: write + +concurrency: overlay-promotion-bot + +env: + PROMOTION_BRANCH: overlay-promotion + +jobs: + check-and-promote: + runs-on: ubuntu-latest + steps: + - name: Checkout amicode + uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: Checkout harmoniqs/opencode (local/amicode) + uses: actions/checkout@v7 + with: + repository: harmoniqs/opencode + path: .opencode-src + ref: local/amicode + fetch-depth: 0 + token: ${{ secrets.OPENCODE_FETCH_TOKEN }} + + - uses: actions/setup-node@v7 + with: { node-version: 20 } + + - name: Resolve fork tip + declared upstream base + id: rev + run: | + set -euo pipefail + FORK_REV="$(git -C .opencode-src rev-parse HEAD)" + BASE_SHA="$(node -p "require('./packages/app-bundle/manifest.json').upstream_base_sha")" + # overlay-promotion.mjs resolves --base against the source checkout; + # the base commit lives in anomalyco/opencode, not necessarily + # reachable in the fork clone yet. + git -C .opencode-src fetch --no-tags https://github.com/anomalyco/opencode.git "$BASE_SHA" + { echo "fork_rev=$FORK_REV"; echo "base_sha=$BASE_SHA"; } >> "$GITHUB_OUTPUT" + + - name: sync:check — is the overlay already current? + id: check + run: | + set +e + node packages/app-bundle/scripts/overlay-promotion.mjs --check \ + --source "$GITHUB_WORKSPACE/.opencode-src" \ + --revision "${{ steps.rev.outputs.fork_rev }}" \ + --base "${{ steps.rev.outputs.base_sha }}" + echo "status=$?" >> "$GITHUB_OUTPUT" + + - name: sync:apply — promote the drifted revision + if: steps.check.outputs.status != '0' + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # overlay-promotion.mjs refuses --apply directly on main (#1005) — + # switch to the review branch FIRST, same as a human promoting by hand. + git checkout -B "$PROMOTION_BRANCH" + node packages/app-bundle/scripts/overlay-promotion.mjs --apply \ + --source "$GITHUB_WORKSPACE/.opencode-src" \ + --revision "${{ steps.rev.outputs.fork_rev }}" \ + --base "${{ steps.rev.outputs.base_sha }}" + + - name: Open or update the promotion PR + if: steps.check.outputs.status != '0' + env: + GH_TOKEN: ${{ github.token }} + FORK_REV: ${{ steps.rev.outputs.fork_rev }} + run: | + set -euo pipefail + if git diff --quiet -- packages/app-bundle; then + echo "overlay-promotion.mjs reported drift but produced no diff — refusing to open an empty PR." + exit 1 + fi + git add packages/app-bundle + git commit -m "chore: sync overlay to opencode ${FORK_REV:0:10}" + git push --force origin "$PROMOTION_BRANCH" + + if gh pr list --head "$PROMOTION_BRANCH" --state open --json number --jq '.[0].number' | grep -q .; then + echo "PR already open for $PROMOTION_BRANCH — force-push above updated it." + else + gh pr create \ + --head "$PROMOTION_BRANCH" \ + --base main \ + --title "chore: sync overlay to opencode ${FORK_REV:0:10}" \ + --body "Automated promotion (Phase 1 of #1010) — the fork's \`local/amicode\` moved to \`${FORK_REV}\` and the overlay was stale. Generated by \`overlay-promotion.mjs --apply\`, the same script a human would run. Review the diff and merge; CI gates this PR the same as any other." + fi