release: a tag builds nothing and carries a changelog instead
Android / Build, or is the channel already serving this? (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
CI & Build / Python lint (push) Successful in 4s
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 4s
CI & Build / TypeScript typecheck (push) Successful in 7s
CI & Build / Python tests (push) Successful in 12s
CI & Build / integration (push) Successful in 18s
CI & Build / Build & push image (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Successful in 3m0s
Desktop (Tauri) / Tauri desktop (Linux) (push) Successful in 5m17s
Desktop (Tauri) / Update manifest (push) Successful in 4s
Android / Kotlin + Rust (APK) (push) Successful in 8m4s

Step 7 of M314, the last one. Rule 22 — the old path comes out completely.

## A release stops building

`desktop.yml` no longer triggers on `v*`, and its two `Publish release` steps
are gone. `ci.yml` lost its tag trigger in step 6. So a tag now reaches exactly
one lane: the new `release.yml`, which builds nothing.

That is not a simplification for its own sake. The merge to `main` already
published everything a user can receive — `:latest` + `:<sha>`, both channel
feeds, the updater manifest. A tag rebuilding that source produces identical
artifacts under identical names and re-pushes `:<sha>` with different bytes,
which rule 145 forbids even when they match.

## So what a release is FOR

The changelog (note 3127 §5). Two halves to "what am I running", and the
version answers only the first: which build is this (the footer, /api/config,
the APK's versionName) and what is in it that was not in the one I ran last
month (nothing, until now).

`packaging/release-notes.sh` derives it from git rather than a hand-maintained
CHANGELOG, which drifts into recording what someone MEANT to ship. Capped at 60
entries with the omitted count stated — the first dated release spans 181
commits since `v0.1.0`, and a truncated list that does not say it is truncated
is a lie.

It publishes through `publish-release.sh` rather than making its own API calls,
for the create-or-PATCH-on-409 path: a fixed-tag release that only ever POSTs
keeps whatever body its first run wrote, which is #2182, and reimplementing that
correctly in a second place is how it comes back.

## Retired

`MANIFEST_TAG` and the whole branch behind it. It let the manifest live on a
`stable` pointer release while the bundles sat on a versioned one — a split step
3 removed when `stable` started holding its own bundles. Nothing had passed it
since; a parameter that can only ever receive its own default is a branch nobody
exercises and a comment that goes stale, and its stale text was still telling
readers the installable builds live on the versioned releases.

`desktop/src-tauri/Cargo.toml`'s version and `thoughtsync/__init__.py`'s both
now say out loud that they are not shipped values. The Cargo one carries the
history worth keeping: the old scheme took its base from that line, so `0.2.<run>`
on dev outranked a bare `0.2.0` on main, and the remedy was "remember to bump the
minor before tagging" — documented in a comment, enforced nowhere. #2183 is what
that looked like in the field. **That ritual is now formally dead**, and this is
the deliberate act of killing it rather than a side effect.

## Still there on purpose

`install.sh`'s transitional stable fallback. It cannot go until `main` has
published to `stable` at least once, and that is gated on an operator request.
Removing it now would break the DEFAULT install channel.

#3147

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-29 00:43:21 -04:00
co-authored by Claude Opus 5
parent fa43c2f4e9
commit c2fdc05e5c
7 changed files with 211 additions and 87 deletions
+15 -28
View File
@@ -25,14 +25,15 @@ set -euo pipefail
: "${RELEASE_TAG:?RELEASE_TAG is required (the release holding the bundles)}"
: "${APP_VERSION:?APP_VERSION is required (the version the bundles carry)}"
# Where the manifest is PUBLISHED, which need not be where the bundles live.
# The manifest is published to the release that HOLDS the bundles. There is no
# second place any more.
#
# That split is what makes the stable channel work at all. A versioned release
# (`v0.2.0`) holds the real assets, but the app can only read a URL that never
# changes — so the same manifest is also attached to a `stable` release whose tag is
# permanent and whose only content is this file. It points back at the versioned
# assets, so nothing is duplicated.
MANIFEST_TAG="${MANIFEST_TAG:-$RELEASE_TAG}"
# There used to be: `MANIFEST_TAG` let the manifest live on a `stable` pointer
# release while the bundles sat on a versioned `v*` one, because the app can only
# read a URL that never changes and a versioned tag is not that. M314 step 3 made
# `stable` a rolling release that holds its own bundles, exactly like `dev`, so the
# split had nothing left to bridge — and a parameter that can only ever be passed
# its own default is a branch nobody exercises and a comment that goes stale.
API="$GITHUB_SERVER_URL/api/v1/repos/$GITHUB_REPOSITORY"
AUTH=(-H "Authorization: token $GITHUB_TOKEN")
@@ -115,25 +116,11 @@ pub_date="$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
echo "==> Manifest:"
cat "$work/latest.json"
# --- resolve the release the manifest is published TO ------------------------
if [ "$MANIFEST_TAG" = "$RELEASE_TAG" ]; then
target_id="$release_id"
target_assets="$assets"
else
echo "==> Resolving the $MANIFEST_TAG channel release"
target="$(curl -sS "${AUTH[@]}" "$API/releases/tags/$MANIFEST_TAG")"
target_id="$(printf '%s' "$target" | grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+' || true)"
if [ -z "${target_id:-}" ]; then
# First publish to this channel. A pointer release: no bundles of its own, just
# a permanent tag for the manifest to live under.
echo " creating it (pointer release, manifest only)"
body="{\"tag_name\":\"$MANIFEST_TAG\",\"name\":\"ThoughtSync ($MANIFEST_TAG channel)\",\"draft\":false,\"prerelease\":false,\"body\":\"Update channel pointer. The installable builds live on the versioned releases; this holds only the updater manifest.\"}"
target="$(curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" -d "$body" "$API/releases")"
target_id="$(printf '%s' "$target" | grep -oE '"id"[[:space:]]*:[[:space:]]*[0-9]+' | head -1 | grep -oE '[0-9]+')"
fi
[ -n "${target_id:-}" ] || { echo "ERROR: could not resolve the $MANIFEST_TAG release" >&2; exit 1; }
target_assets="$(curl -sS "${AUTH[@]}" "$API/releases/$target_id/assets")"
fi
# The manifest goes on the same release the bundles were just read from — which is
# also the one `publish-release.sh` created or refreshed moments earlier, so it is
# guaranteed to exist by the time this runs.
target_id="$release_id"
target_assets="$assets"
# Replace rather than duplicate: Forgejo rejects a second asset with the same name,
# and this file is rewritten on every publish by design.
@@ -145,11 +132,11 @@ if [ -n "${old_id:-}" ]; then
curl -fsS -X DELETE "${AUTH[@]}" "$API/releases/$target_id/assets/$old_id" >/dev/null
fi
echo "==> Uploading latest.json to $MANIFEST_TAG"
echo "==> Uploading latest.json to $RELEASE_TAG"
curl -fsS -X POST "${AUTH[@]}" "$API/releases/$target_id/assets?name=latest.json" \
-F "attachment=@$work/latest.json" >/dev/null
echo "==> Done. $MANIFEST_TAG now advertises $APP_VERSION for ${#entries[@]} platform(s)."
echo "==> Done. $RELEASE_TAG now advertises $APP_VERSION for ${#entries[@]} platform(s)."
# --- prune superseded builds from a rolling channel ---------------------------
#