name: Release # A `v*` tag publishes a changelog. It does NOT build anything. # # Milestone 318 step 2 removed the tag trigger from build.yml: by the time # anyone tags a commit, `main` has already built and published it, and a # rebuild would re-push `:c-` — which rule 145 forbids even when the # source matches, since image configs carry timestamps and "same source" does # not mean "same manifest". That left the tag with no consequence at all. # # This is the consequence it has instead. Step 6 put the derived version in the # Settings footer, so an operator can say WHICH build they are running; this # says what is IN it that was not in the one they ran last month. Both halves # of one question (note #3127 §5). # # Nothing here runs on a schedule and nothing auto-tags on merge. Release tags # are bookmarks — cut one when you will want to point at that day by name, # otherwise don't (note #3127 §0). FC went twelve weeks between v26.06.04.0 and # the next one and nothing was wrong. A schedule would turn an optional # bookmark back into ceremony, which is the thing this milestone is removing. # # Cutting the tag is an explicit operator action under rule 2 ("`main` — never # without explicit request", which since 2026-08-28 covers PR, merge and tag # alike). This lane only decides what happens once they do. # # Requires repo secret RELEASE_TOKEN with the `write:release` scope — the same # PAT build.yml uses for the ext- XPI asset cache. on: push: tags: ['v*'] # So a release body can be regenerated after the fact — the publisher PATCHes # an existing release rather than falling through on a conflict, so re-running # this on a tag rewrites the body instead of silently keeping the first one # (note #3127 §6.7). workflow_dispatch: inputs: tag: description: 'Tag to (re)publish notes for' required: true jobs: changelog: runs-on: python-ci container: image: git.fabledsword.com/bvandeusen/ci-python:3.14 steps: - uses: actions/checkout@v4 with: # Load-bearing twice over: the previous release is found by walking # ancestry back through the tag graph, and the cross-check against # the derived web version calls artifacts.sh, which reads commit # times. A shallow clone would find no previous tag and emit the # entire history as the changelog — plausible-looking and wrong. fetch-depth: 0 ref: ${{ github.event.inputs.tag || github.ref }} # The `:c-` rollback refs are only real if `main` built this commit. # The script checks that against origin/main and downgrades the claim to # "unverified" when it cannot resolve one; fetching it here means that # downgrade stays an actual signal instead of firing on every release. - name: Make main's history resolvable run: git fetch --no-tags --quiet origin +main:refs/remotes/origin/main || true # TAG goes through the environment, not through `${{ }}` inside the # run block. The value is operator-supplied, and an expression expanded # into a shell line is expanded BEFORE the shell sees it — there is no # quoting that makes that safe. On a tag push it is empty and the script # falls back to GITHUB_REF. - name: Publish the derived changelog env: RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} TAG: ${{ github.event.inputs.tag }} run: | set -eu if [ -n "${TAG:-}" ]; then python3 scripts/release_notes.py "$TAG" else python3 scripts/release_notes.py fi