feat(ci): advance v1 automatically once the gate is green on main

Nothing moved v1 when main advanced, so a merged change was inert
until someone remembered to retag by hand — it happened on 2026-09-22
(PR #25 merged, v1 stayed on the previous release) and was only caught
because a person asked whether the tag had moved.

Option 1 from gitdan-actions#27 (automate it) over option 2 (fail loud
while it lags): a `release-tag` job, gated with `needs: selftest` so a
broken build never reaches it, force-moves v1 to the pushed commit
using the run's built-in GITHUB_TOKEN. If that token turns out not to
have write access, the push fails and the job goes red in the Actions
UI — a loud failure either way, not the silent one this replaces.
Whether the token actually has write access here is unverified short
of a real merge; that merge is the next step for this branch.

README's Versioning section documented the old manual step as a
deliberate decision "never something a merge does by itself" — that
claim is now false, so it's rewritten to describe the automated job
and keeps the manual command as the recovery path for when the job
can't push.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UkSjXXtU6JYcN2vPntWhfb
This commit is contained in:
2026-09-22 11:51:51 -05:00
co-authored by Claude Sonnet 5
parent 21dffdb725
commit 7f18cb2436
2 changed files with 46 additions and 10 deletions
+30
View File
@@ -101,3 +101,33 @@ jobs:
# file.
- name: Selftests
run: bash scripts/selftest.sh
release-tag:
name: move v1 to main
# `needs:` is what makes this "after the gate is green" rather than
# merely "after a push": a failed selftest skips this job outright, so
# v1 can never advance onto a broken build. The `if:` restricts it to an
# actual push to main -- a pull_request run targeting main shares this
# workflow but has no ref worth tagging.
needs: selftest
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
timeout-minutes: 2
# Requests write access from the run's built-in token (see README's
# Versioning section for what's actually verified about it). Without
# this the checkout below still succeeds -- it's the push that would be
# rejected, which is a red job, not a silent no-op.
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
# Lightweight tag, matching what v1 already is (`git cat-file -t v1`
# reports `commit`, not `tag`) -- no identity needed to move it, only
# to push it.
- name: Force v1 to this commit
run: |
git tag -f v1 "${{ github.sha }}"
git push --force origin v1
+16 -10
View File
@@ -634,13 +634,20 @@ entry, another permission — is a `v2`, not a `v1` move. Everything else moves
`v1`: correctness fixes, new optional inputs, and anything internal to
`scripts/`.
**Moving the tag is a release step, and it is the operator's.** Merging to
`main` ships nothing to anybody. `v1` is a lightweight tag and does not follow
a branch, so until it is re-pointed every consumer keeps fetching the commit it
already named, whatever `main` now says. The gap is deliberate: re-pointing
`v1` changes what another repository's CI executes on its next run, so it is a
decision taken once, knowingly, after the merge — never something a merge does
by itself.
**Moving the tag is automatic, gated on the same build that gates a PR.** A
`release-tag` job in `.gitea/workflows/ci.yaml` runs on every push to `main`,
`needs: selftest`, and force-moves `v1` to that commit once selftest
succeeds — a broken build never reaches it, so `v1` can't advance onto one.
It pushes with the run's built-in `GITHUB_TOKEN`; if that token turns out not
to have write access, the push step fails and the job goes red in the Actions
UI. That's a loud failure, not the silent one this replaced: `v1` stays put,
and nobody has to notice on their own that it lagged.
This used to be a manual step, treated as a deliberate release decision taken
once, knowingly, after the merge — in practice it was still forgotten
(gitdan-actions#27): PR #25 merged to `main` and `v1` stayed on the previous
release until someone asked whether it had moved. The manual form below is
now the recovery path, for when the automated job can't push:
```bash
git fetch origin
@@ -651,9 +658,8 @@ git ls-remote --tags origin v1 # must equal git rev-parse origin/main
**Downstream** are emowheel, which pins `cargo-cache@v1` and
`cargo-cache-publish@v1` across its CI workflow, and zemyna, migrating to the
same pin. Both pick a move up on their next run with no change on their side,
which is the whole point of the moving pointer and also the reason the move is
not automatic.
same pin. Both pick a move up automatically on their next run with no change
on their side, which is the whole point of the moving pointer.
---