From 4720a214a2ed0d57650d2515f55d27300858aa6b Mon Sep 17 00:00:00 2001 From: wtclaude Date: Tue, 4 Aug 2026 11:13:47 -0500 Subject: [PATCH] ci(release): recompose the installer bundle after publishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 0 item 3 of docs/installer/PLAN.md wired up from this side. The installer does not resolve "latest" at run time — it installs the exact combination named by a published bundle manifest (PLAN.md §7.1), so until now a new overlay release was invisible to operators until the installer repo's nightly cron noticed it. Adds a final step that POSTs to RunicGateway/installer's bundle workflow-dispatch endpoint. That job re-reads this tarball's manifest.json and checks its declared `protocol` against the sidecar's PROTOCOL_VERSION before publishing anything (gate 1) — the check this repo cannot perform for itself, since the C# plugin announces no version on the wire. It replaces the TODO the header has carried since #7, which was deliberately left unimplemented while there was nothing to dispatch. Dispatch, don't wait (PLAN.md §7.3): Gitea's dispatch endpoint returns no run handle, so there is nothing to poll — a waiting step would have to guess which run is its own while holding a runner idle. The bundle job runs its own gates regardless of who started it. A dispatch failure is a warning, never a failure of this job. By the time this step runs the release is published and correct, so failing the run would misreport that; the installer's nightly cron recomposes from whatever the latest releases actually are, making a dropped dispatch cost latency rather than correctness. That also means REGISTRY_TOKEN having write on the installer repo is a nicety, not a new hard requirement — noted in the header. Verified the workflow still parses and that the new step is last, gated on release=='true', and contains no path that can exit non-zero. Co-Authored-By: Claude --- .gitea/workflows/release.yml | 61 ++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 34ac2b6..2b90211 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -51,18 +51,22 @@ # # Prerequisites (Settings → Actions → Secrets on RunicGateway/servuo-plugins): # REGISTRY_TOKEN — Gitea access token with `write:repository`, to push the -# tag and create the release. +# tag and create the release. The final step also dispatches +# RunicGateway/installer's bundle workflow, so the token +# ideally has write there too — a nicety, not a requirement: +# without it the step warns and that repo's nightly cron +# picks the release up instead. # REGISTRY_USER — the Gitea username that token belongs to. # # These are checked by an explicit preflight step rather than left to fail # wherever they happen to be used first — see the comment on that step for why # an absent token does NOT simply fail the tag push. # -# TODO (Phase 0 item 3): once the installer repo's bundle workflow exists, append -# a final step here that POSTs to its workflow-dispatch endpoint, so a new -# overlay release recomposes the bundle immediately instead of waiting for the -# nightly cron (PLAN.md §7.2). Deliberately absent until there is something to -# dispatch — a step that 404s every release is worse than no step. +# The final step POSTs to the installer repo's bundle workflow, so a new overlay +# release recomposes the compat matrix immediately instead of waiting for that +# repo's nightly cron (PLAN.md §7.2). It was deliberately absent until Phase 0 +# item 3 landed something to dispatch — a step that 404s on every release is +# worse than no step. name: Release overlay @@ -84,6 +88,9 @@ env: # house style set by link (pre-1.0; the release version is independent of the # protocol version, which lives in overlay.toml). SEED_VERSION: "0.1.0" + # Notified after a release so the installer's compat matrix picks up this + # overlay immediately rather than at its next nightly run (PLAN.md §7.2). + INSTALLER_REPO: RunicGateway/installer jobs: release: @@ -425,3 +432,45 @@ jobs: -F "attachment=@dist/${f}" >/dev/null echo " uploaded ${f}" done + + # ── Recompose the installer's bundle manifest ──────────────────────── + # The installer does not resolve "latest" at run time — it deploys the + # exact overlay named by a published bundle (docs/installer/PLAN.md §7.1). + # An overlay release that nobody recomposes around is therefore a release + # no operator will ever be offered. This tells the installer repo to + # rebuild that manifest now rather than leaving the new version invisible + # until its nightly cron. + # + # That job re-reads this tarball's manifest.json and checks its declared + # `protocol` against the sidecar's PROTOCOL_VERSION before publishing + # anything (PLAN.md §7.1, gate 1) — which is the check this repo cannot + # perform for itself, since the C# plugin announces no version on the wire. + # + # DISPATCH, DON'T WAIT (PLAN.md §7.3). Gitea's workflow-dispatch endpoint + # returns no run handle, so there is nothing to poll: a waiting step would + # have to guess which run is its own and hold a runner idle to do it. + # + # A failure here is a WARNING, never a failure of this job. The release is + # already published and correct by this point, and failing the run would + # misreport that. The installer's nightly cron recomposes from whatever the + # latest releases actually are, so a dropped dispatch costs latency, not + # correctness. + - name: Ask the installer repo to recompose its bundle + if: ${{ steps.plan.outputs.release == 'true' }} + env: + REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -euo pipefail + CI_TOKEN="$(printf '%s' "${REGISTRY_TOKEN}" | tr -d '\r\n')" + HTTP="$(curl -s -o /dev/null -w '%{http_code}' -X POST \ + -H "Authorization: token ${CI_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"ref":"main"}' \ + "https://${GITEA_HOST}/api/v1/repos/${INSTALLER_REPO}/actions/workflows/bundle.yml/dispatches" || echo 000)" + case "$HTTP" in + 20*) echo "Dispatched ${INSTALLER_REPO} bundle.yml (HTTP ${HTTP}) — not waiting for it." ;; + 403|404) + echo "::warning::Could not dispatch ${INSTALLER_REPO} bundle.yml (HTTP ${HTTP}). REGISTRY_TOKEN likely lacks write:repository on that repo. Release ${{ steps.plan.outputs.tag }} is published and fine; its bundle will be composed by the installer's nightly cron instead." ;; + *) + echo "::warning::Dispatching ${INSTALLER_REPO} bundle.yml returned HTTP ${HTTP}. Release ${{ steps.plan.outputs.tag }} is published and fine; the nightly cron will recompose the bundle." ;; + esac