docs(release): tags become vYYYY.MM.DD.HHMM, and stop telling people to move them
The tag is now the artifact's own version name with a `v` in front, so `v2026.09.10.1432` and `2026.09.10.1432` are one string. Nothing has to reconcile what the tag claims against what the APK reports, and minting one is arithmetic on the tagged commit's timestamp rather than a lookup. The substantive change is the prose. release.yml's header instructed the reader to `git push -f origin vYYYY.MM.DD` on a same-day re-cut. That is the operation the family rulebook forbids outright, and it has incidents behind it — moving a same-day tag forward once took a published release down with it. Anyone who had installed from that tag was holding something it no longer pointed at. With HHMM there is nothing left for mutability to buy: every tag is unique by construction, so a second release the same day is not a collision to resolve, just another tag. The old instruction is recorded as retired rather than deleted. Someone who remembers it should learn it was withdrawn and why, not find it silently absent and assume they misremembered. README contradicted itself inside one sentence — "immutable per-day release tags ... a same-day re-cut moves the tag forward" — and now says which it is, plus a note that pre-2026-09-10 tags keep the old shape and still work. Transition wrinkle, deliberately left for step 3: the non-tag :latest path reconstructs the bundled APK's name from the latest release's commit timestamp, which for the one existing old-shape release yields 2026.09.09.1828 while that APK actually declares 2026.09.09.1895. It fails SAFE — 1828 compares lower, so no false update is offered — and it self-corrects at the first new-scheme release. Step 3 removes the reconstruction entirely by having the sidecar carry recorded values instead of derived ones. Step 2 of 5 — Scribe task #3809, milestone #390. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
This commit is contained in:
@@ -2,15 +2,30 @@ name: release
|
|||||||
|
|
||||||
# Builds and pushes the minstrel container image to the Gitea registry.
|
# Builds and pushes the minstrel container image to the Gitea registry.
|
||||||
#
|
#
|
||||||
# push to main → :main and :latest (latest-release APK bundled)
|
# push to main → :main and :latest (latest-release APK bundled)
|
||||||
# push tag vYYYY.MM.DD → :vYYYY.MM.DD and :latest (freshly-built APK bundled)
|
# push tag vYYYY.MM.DD.HHMM → :vYYYY.MM.DD.HHMM and :latest (fresh APK bundled)
|
||||||
# workflow_dispatch → manual trigger (same rules based on the ref)
|
# workflow_dispatch → manual trigger (same rules based on the ref)
|
||||||
#
|
#
|
||||||
# Release model: per-day CalVer tags (no trailing patch digit). The day's
|
# Release model: the tag IS the artifact's version name with a `v` in front.
|
||||||
# tag is intentionally mutable — if a second release happens the same day,
|
# `v2026.09.10.1432` and `2026.09.10.1432` are the same string, derived from
|
||||||
# move the tag with `git push -f origin vYYYY.MM.DD` and the image tag of
|
# the tagged commit's UTC timestamp — so there is no mismatch to reconcile
|
||||||
# the same name gets overwritten. :latest is updated by every main push
|
# between what the tag says and what the APK reports, and nothing to look up
|
||||||
# AND every tag push, so it always reflects the newest blessed image.
|
# when minting one.
|
||||||
|
#
|
||||||
|
# TAGS ARE IMMUTABLE. Never move, retarget or delete a published tag. A
|
||||||
|
# same-day second release is not a collision — HHMM makes every tag unique
|
||||||
|
# by construction, so the answer is simply another tag.
|
||||||
|
#
|
||||||
|
# This block used to say the opposite: that the per-day tag was
|
||||||
|
# "intentionally mutable" and that a same-day re-cut should
|
||||||
|
# `git push -f origin vYYYY.MM.DD`. That instruction is what the family
|
||||||
|
# rulebook now forbids outright, and it has incidents behind it — moving a
|
||||||
|
# same-day tag forward once took a published release down with it. Anyone
|
||||||
|
# installing from a tag is holding something the tag no longer points at,
|
||||||
|
# which is a worse failure than an extra row in the tag list.
|
||||||
|
#
|
||||||
|
# :latest is updated by every main push AND every tag push, so it always
|
||||||
|
# reflects the newest blessed image.
|
||||||
#
|
#
|
||||||
# APK pipeline: on tag pushes the android-release job builds + signs the
|
# APK pipeline: on tag pushes the android-release job builds + signs the
|
||||||
# Android APK and uploads it as a workflow artifact. The image-release
|
# Android APK and uploads it as a workflow artifact. The image-release
|
||||||
@@ -41,9 +56,10 @@ on:
|
|||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Force-moving the per-day tag (or rapidly re-pushing to main) should
|
# A rapid re-push to main should supersede the in-flight build — the
|
||||||
# supersede the in-flight build — the operator explicitly wants the
|
# operator explicitly wants the later commit to win. Tags no longer enter
|
||||||
# later commit to win.
|
# into this: they are immutable and unique, so no tag build can ever be
|
||||||
|
# superseded by another run on the same ref.
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
@@ -290,8 +306,8 @@ jobs:
|
|||||||
else
|
else
|
||||||
# Main is the protected, post-PR-merge branch. Treat it as the
|
# Main is the protected, post-PR-merge branch. Treat it as the
|
||||||
# rolling stable channel — every main push moves :latest.
|
# rolling stable channel — every main push moves :latest.
|
||||||
# Pinned consumers can target :vYYYY.MM.DD; everyone else
|
# Pinned consumers can target :vYYYY.MM.DD.HHMM, which never
|
||||||
# gets the newest main.
|
# moves; everyone else gets the newest main.
|
||||||
echo "args=-t ${IMAGE}:main -t ${IMAGE}:latest" >> "$GITHUB_OUTPUT"
|
echo "args=-t ${IMAGE}:main -t ${IMAGE}:latest" >> "$GITHUB_OUTPUT"
|
||||||
echo "version=main" >> "$GITHUB_OUTPUT"
|
echo "version=main" >> "$GITHUB_OUTPUT"
|
||||||
echo "::notice::Main-branch build: :main + :latest"
|
echo "::notice::Main-branch build: :main + :latest"
|
||||||
|
|||||||
@@ -113,10 +113,12 @@ Most operational keys have a `MINSTREL_<SECTION>_<FIELD>` env override. Recommen
|
|||||||
Image tags (`git.fabledsword.com/bvandeusen/minstrel:<tag>`):
|
Image tags (`git.fabledsword.com/bvandeusen/minstrel:<tag>`):
|
||||||
|
|
||||||
- `:latest` — the newest blessed image. Moves on every `main` push **and** every release. Recommended for most operators.
|
- `:latest` — the newest blessed image. Moves on every `main` push **and** every release. Recommended for most operators.
|
||||||
- `:vYYYY.MM.DD` — immutable per-day release tags. Pin one of these for a deployment you don't want moving under you. (Per-day CalVer — no trailing patch digit; a same-day re-cut moves the tag forward.)
|
- `:vYYYY.MM.DD.HHMM` — immutable release tags, never moved or deleted. Pin one for a deployment you don't want changing under you. The tag is the build's own version name with a `v` in front, derived from the tagged commit's UTC timestamp, so two releases can never collide and a re-cut is simply a new tag.
|
||||||
- `:main` — the rolling post-merge tip. Same image as `:latest` at push time; choose it if you want to track `main` explicitly rather than the release line.
|
- `:main` — the rolling post-merge tip. Same image as `:latest` at push time; choose it if you want to track `main` explicitly rather than the release line.
|
||||||
|
|
||||||
Every `:latest` and every `:vYYYY.MM.DD` bundles the current signed Android APK, so the in-app update channel is always live. Database migrations run automatically at startup; rollbacks require restoring a Postgres dump.
|
Every `:latest` and every `:vYYYY.MM.DD.HHMM` bundles the current signed Android APK, so the in-app update channel is always live. Database migrations run automatically at startup; rollbacks require restoring a Postgres dump.
|
||||||
|
|
||||||
|
Releases before 2026-09-10 use the older per-day `:vYYYY.MM.DD` shape. Those tags still exist and still work — they are simply not extended.
|
||||||
|
|
||||||
## Specs
|
## Specs
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user