diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index cd895f2..aa49ad5 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -44,6 +44,10 @@ on: description: 'Rebuild every image even if the published revision matches' type: boolean default: false + refresh: + description: 'Behave as the weekly base refresh: build main against fresh bases, publish through the candidate tag' + type: boolean + default: false # The base-image refresh (milestone 326 step 4, #3154). # @@ -72,8 +76,25 @@ on: # Deriving it per job invites the two halves to disagree: sign-extension would # derive dev's extension version while build-web bundled main's, and the # release download would 404 on a version that exists perfectly well. +# IS THIS A BASE REFRESH? Asked in five places and previously spelled five +# ways — `github.event_name == 'schedule'` in an `if:`, `$GITHUB_EVENT_NAME` in +# one shell, an `EVENT:` env passed into another, and a bare expression on +# `pull:`. Five spellings of one fact is how half of them come to disagree +# after somebody adds a sixth trigger. +# +# The `refresh` dispatch input is here so this path can be EXERCISED. A weekly +# cron is otherwise testable once a week, which is not a cadence anything can +# be developed against — the same reason `force_build` exists (#3252, added to +# confirm #3190 was gone rather than wait for it to recur). It is also what +# makes the milestone-362 gate verifiable at all: a gate has to be watched +# rejecting something before anyone can believe it is wired up. +# +# Note this is a STRING comparison, not a boolean. Forgejo delivers +# workflow_dispatch inputs as strings, so `inputs.refresh` is 'true'/'false' +# and `&&` on it would treat the string 'false' as truthy. env: - BUILD_REF: ${{ github.event_name == 'schedule' && 'main' || github.ref }} + IS_REFRESH: ${{ (github.event_name == 'schedule' || github.event.inputs.refresh == 'true') && 'true' || 'false' }} + BUILD_REF: ${{ (github.event_name == 'schedule' || github.event.inputs.refresh == 'true') && 'main' || github.ref }} # Requires repo secret RELEASE_TOKEN — a Forgejo PAT with scopes: # - write:package, read:package (for docker push to git.fabledsword.com) @@ -143,7 +164,7 @@ jobs: # evaluate — this file already gates steps on it — so the guard cannot # be disabled by the same uncertainty it exists to cover. - name: Guard — a scheduled run must have checked out main - if: github.event_name == 'schedule' + if: env.IS_REFRESH == 'true' run: | set -eu BRANCH=$(git rev-parse --abbrev-ref HEAD) @@ -437,7 +458,7 @@ jobs: # See sign-extension's copy for why this guard exists. - name: Guard — a scheduled run must have checked out main - if: github.event_name == 'schedule' + if: env.IS_REFRESH == 'true' run: | set -eu BRANCH=$(git rev-parse --abbrev-ref HEAD) @@ -528,7 +549,7 @@ jobs: # Checked BEFORE the ref test, not after: a scheduled run's # GITHUB_REF is the default branch (dev), so the main test would # never fire on it. - if [ "${GITHUB_EVENT_NAME:-}" = "schedule" ]; then + if [ "${IS_REFRESH:-}" = "true" ]; then echo "tags=git.fabledsword.com/bvandeusen/fabledcurator:latest" >> "$GITHUB_OUTPUT" echo "channel=main" >> "$GITHUB_OUTPUT" elif [ "${GITHUB_REF##*/}" = "main" ]; then @@ -628,7 +649,6 @@ jobs: # A scheduled refresh has to bypass reuse by construction: it # rebuilds the SAME source, so fc.revision always matches and the # check would skip every refresh there has ever been. - EVENT: ${{ github.event_name }} run: | set -eu DERIVED=$(sh scripts/artifacts.sh revision web) @@ -678,7 +698,7 @@ jobs: # force/schedule branch below gives: one step decides what this job # does. A promote condition derived independently could disagree with # the tag the build actually wrote. - if [ "${EVENT:-}" = "schedule" ]; then + if [ "${IS_REFRESH:-}" = "true" ]; then echo "build_ref=$IMAGE:refresh-candidate" >> "$GITHUB_OUTPUT" echo "promote=true" >> "$GITHUB_OUTPUT" else @@ -718,7 +738,7 @@ jobs: if [ "${FORCE:-false}" = "true" ]; then echo "hit=false" >> "$GITHUB_OUTPUT" echo "reuse: force_build set — building regardless" - elif [ "${EVENT:-}" = "schedule" ]; then + elif [ "${IS_REFRESH:-}" = "true" ]; then echo "hit=false" >> "$GITHUB_OUTPUT" echo "reuse: scheduled base refresh — building regardless" elif [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then @@ -845,7 +865,7 @@ jobs: # churn #3265 is about. # # Only on the schedule. An ordinary push wants the cached base. - pull: ${{ github.event_name == 'schedule' }} + pull: ${{ env.IS_REFRESH == 'true' }} # ONE tag, the channel's. Every other tag is written by the step # below, registry-side. buildx here pushes the first tag to the # registry and then re-pushes the rest through the DOCKER driver, @@ -1074,7 +1094,7 @@ jobs: # See sign-extension's copy for why this guard exists. - name: Guard — a scheduled run must have checked out main - if: github.event_name == 'schedule' + if: env.IS_REFRESH == 'true' run: | set -eu BRANCH=$(git rev-parse --abbrev-ref HEAD) @@ -1125,7 +1145,7 @@ jobs: SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-7) # Mirrors build-web's tag list and its schedule handling; see # the comments there. - if [ "${GITHUB_EVENT_NAME:-}" = "schedule" ]; then + if [ "${IS_REFRESH:-}" = "true" ]; then echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-ml:latest" >> "$GITHUB_OUTPUT" echo "channel=main" >> "$GITHUB_OUTPUT" elif [ "${GITHUB_REF##*/}" = "main" ]; then @@ -1208,7 +1228,6 @@ jobs: # A scheduled refresh has to bypass reuse by construction: it # rebuilds the SAME source, so fc.revision always matches and the # check would skip every refresh there has ever been. - EVENT: ${{ github.event_name }} run: | set -eu DERIVED=$(sh scripts/artifacts.sh revision ml) @@ -1254,7 +1273,7 @@ jobs: # force/schedule branch below gives: one step decides what this job # does. A promote condition derived independently could disagree with # the tag the build actually wrote. - if [ "${EVENT:-}" = "schedule" ]; then + if [ "${IS_REFRESH:-}" = "true" ]; then echo "build_ref=$IMAGE:refresh-candidate" >> "$GITHUB_OUTPUT" echo "promote=true" >> "$GITHUB_OUTPUT" else @@ -1294,7 +1313,7 @@ jobs: if [ "${FORCE:-false}" = "true" ]; then echo "hit=false" >> "$GITHUB_OUTPUT" echo "reuse: force_build set — building regardless" - elif [ "${EVENT:-}" = "schedule" ]; then + elif [ "${IS_REFRESH:-}" = "true" ]; then echo "hit=false" >> "$GITHUB_OUTPUT" echo "reuse: scheduled base refresh — building regardless" elif [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then @@ -1345,7 +1364,7 @@ jobs: # churn #3265 is about. # # Only on the schedule. An ordinary push wants the cached base. - pull: ${{ github.event_name == 'schedule' }} + pull: ${{ env.IS_REFRESH == 'true' }} # ONE tag, the channel's. Every other tag is written by the step # below, registry-side. buildx here pushes the first tag to the # registry and then re-pushes the rest through the DOCKER driver, @@ -1565,7 +1584,7 @@ jobs: # See sign-extension's copy for why this guard exists. - name: Guard — a scheduled run must have checked out main - if: github.event_name == 'schedule' + if: env.IS_REFRESH == 'true' run: | set -eu BRANCH=$(git rev-parse --abbrev-ref HEAD) @@ -1611,7 +1630,7 @@ jobs: SHORT_SHA=$(printf '%s' "$GITHUB_SHA" | cut -c1-7) # Mirrors build-web's tag list and its schedule handling; see # the comments there. - if [ "${GITHUB_EVENT_NAME:-}" = "schedule" ]; then + if [ "${IS_REFRESH:-}" = "true" ]; then echo "tags=git.fabledsword.com/bvandeusen/fabledcurator-agent:latest" >> "$GITHUB_OUTPUT" echo "channel=main" >> "$GITHUB_OUTPUT" elif [ "${GITHUB_REF##*/}" = "main" ]; then @@ -1694,7 +1713,6 @@ jobs: # A scheduled refresh has to bypass reuse by construction: it # rebuilds the SAME source, so fc.revision always matches and the # check would skip every refresh there has ever been. - EVENT: ${{ github.event_name }} run: | set -eu DERIVED=$(sh scripts/artifacts.sh revision agent) @@ -1740,7 +1758,7 @@ jobs: # force/schedule branch below gives: one step decides what this job # does. A promote condition derived independently could disagree with # the tag the build actually wrote. - if [ "${EVENT:-}" = "schedule" ]; then + if [ "${IS_REFRESH:-}" = "true" ]; then echo "build_ref=$IMAGE:refresh-candidate" >> "$GITHUB_OUTPUT" echo "promote=true" >> "$GITHUB_OUTPUT" else @@ -1780,7 +1798,7 @@ jobs: if [ "${FORCE:-false}" = "true" ]; then echo "hit=false" >> "$GITHUB_OUTPUT" echo "reuse: force_build set — building regardless" - elif [ "${EVENT:-}" = "schedule" ]; then + elif [ "${IS_REFRESH:-}" = "true" ]; then echo "hit=false" >> "$GITHUB_OUTPUT" echo "reuse: scheduled base refresh — building regardless" elif [ -n "$PUBLISHED" ] && [ "$PUBLISHED" = "$DERIVED" ]; then @@ -1831,7 +1849,7 @@ jobs: # churn #3265 is about. # # Only on the schedule. An ordinary push wants the cached base. - pull: ${{ github.event_name == 'schedule' }} + pull: ${{ env.IS_REFRESH == 'true' }} # ONE tag, the channel's. Every other tag is written by the step # below, registry-side. buildx here pushes the first tag to the # registry and then re-pushes the rest through the DOCKER driver,