ci+release: split tray and platform into independent release tracks#91
Merged
Conversation
4 tasks
Decouples the Tauri tray installer cadence from the server + web
container image cadence. Each track now has its own version number,
its own tag schema, its own auto-alpha trigger, and ships through its
own workflow:
- tray: tag tray-vX.Y.Z[-channel.N] -> release.yml
version in crates/starstats-client/Cargo.toml + tauri.conf.json
updater manifest at release-manifests/tray-{channel}.json
- platform: tag vX.Y.Z[-channel.N] -> release-images.yml (unchanged)
version in workspace [workspace.package], inherited by
starstats-core + starstats-server
no auto-update manifest (homelab pulls floats via Komodo)
Glob non-overlap: GitHub's 'v*' anchors at start, so 'tray-v*' never
fires release-images.yml. No tags-ignore needed.
Web-only changes no longer churn the tray's signed MSI / channel
manifest. Tray bug fixes no longer force a server redeploy.
Key changes:
- scripts/release-promote.mjs: every prerelease/live subcommand now
takes a <tray|platform> track argument. New pure helpers tagPrefix,
parseTrackTag, bumpClientCargo. latestSemverFromTags accepts an
optional track filter. 49 new unit tests cover the track plumbing
(67 total, all green).
- crates/starstats-client/Cargo.toml: switched from
'version.workspace = true' to literal 'version = "1.8.4"' so the
client floats independently. starstats-client -> starstats-core dep
is a path dep so version skew is irrelevant.
- release.yml: trigger 'tray-v*', validate-tag strips the prefix,
manifest path is release-manifests/tray-<channel>.json. New step
generates a per-component changelog (git log filtered to tray paths
since the previous tray-v* tag) and injects into the GH Release body
via body_path instead of GH's auto-generated notes (which would
otherwise pick the literal previous tag — possibly a platform vX.Y.Z
— and leak platform-only commits into the tray release body).
- promote.yml: new detect-tracks setup job. workflow_dispatch takes a
track input (tray|platform|both). Push-to-next auto-alpha uses
dorny/paths-filter@v3 to bump only the tracks whose files changed;
docs-only pushes tag nothing. starstats-core triggers both tracks.
Self-bump commits are skipped to break the loop (mirrors ci.yml).
- ci.yml: should-run skip list adds 'chore: bump tray to ' and
'chore: bump platform to ' for the new bot-commit message format.
- release-manifests/{alpha,beta,rc,live}.json renamed to
release-manifests/tray-{alpha,beta,rc,live}.json (git mv, history
preserved). Tray client manifest_url() + tauri.conf.json updater
endpoint updated to match.
- apps/web: platform version surfaced in a sub-footer chip on both
signed-in + marketing layouts. Build-time inlined via next.config.mjs
env block, sourced from workspace Cargo.toml.
- CLAUDE.md branch model section + docs/RELEASING.md quick-reference
table updated. Full design spec at
docs/superpowers/specs/2026-05-23-release-tracks-split.md.
Migration: one-time. Old in-the-wild tray installers point at
release-manifests/<channel>.json which now 404s; users re-download
+ re-pair from the latest GH release. Acceptable per internal/dogfood
deployment posture.
Out of scope (future PRs): platform GH releases with their own
changelog; release-notes/<track>/<version>.md narrative summaries;
roadmap-emit track-aware slugs.
Depends on PR #89 (ci.yml path filters) for the ci.yml conflict — if
this lands first, #89 rebases trivially on top.
See docs/superpowers/specs/2026-05-23-release-tracks-split.md.
8f5eea7 to
77fc43a
Compare
3 tasks
ntatschner
added a commit
that referenced
this pull request
May 24, 2026
…abel (#95) Two follow-ups from the 2026-05-24 incident where: (a) release.yml's back-merge silently soft-failed on conflicts in release-manifests/tray-alpha.json after every release, leaving Invariant #1 violated; and (b) invariant-sentry.yml correctly detected (a) but couldn't open the tracking issue because the 'ci-sentry' label didn't exist. release.yml: add '-X theirs' to the back-merge so main's manifest content always wins. The conflict source is structural — every release rewrites release-manifests/tray-<channel>.json on main, and next's snapshot of that file (originally from the tray- prefix rename in PR #91) goes stale immediately. Main's freshly-written manifest is by definition the correct value, so 'theirs' is safe. No legitimate scenario has next holding a manifest change that main doesn't (manifests are only written by this workflow). invariant-sentry.yml: pre-create the ci-sentry label inline before gh issue create runs (--force is no-op if it already exists). Avoids the 'could not add label: ci-sentry not found' hard fail from run 26349188328. Co-authored-by: Nigel Tatschner <n Tatschner@gmail.com>
4 tasks
ntatschner
added a commit
that referenced
this pull request
May 24, 2026
…URL (#97) Three production-observed issues from 2026-05-24, all caught in the homelab logs: 1. SpiceDB wildcard regression (third recurrence, see v1.5.4 + v1.7.1 in CLAUDE.md). rsi_profile_routes.rs:477 + rsi_org_routes.rs:345 + sharing_routes.rs::check_public were all calling check_permission against a 'user:*' subject, which SpiceDB rejects with 'InvalidArgument: cannot perform check on wildcard subject'. Symptom: /u/{handle} pages 503 with 'spicedb_unavailable'. The safe wrapper SpicedbClient::has_public_view (ReadRelationships + optional_limit=1) already exists in spicedb.rs — discover_routes.rs uses it correctly. These three sites were never migrated. Fixed by routing all three through has_public_view(). 2. Audit chain race. ingest.rs's audit.append() relied on 'SELECT ... FOR UPDATE' on the tail row to serialize concurrent appenders. Postgres FOR UPDATE row-locks the returned rows but does NOT prevent INSERTs at higher seq, so two concurrent ingest handlers can both compute prev_hash = row_hash(N), both INSERT (seq=N+1), and the second one's prev_hash points at the wrong prior row → 'audit_log chain break: prev_hash does not match prior row_hash'. Observed 02:53:18 from three back-to-back ingest batches (200 events each, same user, 200ms window). Audit emission is best-effort so the request succeeded, but the chain integrity is now broken from that row forward. Fixed via pg_advisory_xact_lock at the start of the append transaction. Lock is held until COMMIT/ROLLBACK, fully serializing appends. No schema change. The FOR UPDATE is retained as belt-and-braces. 3. tray-live.json notes URL bug. generate-updater-manifest.mjs hardcoded the notes link as releases/tag/v${version}. Post- release-tracks-split (PR #91), tray tags are 'tray-vX.Y.Z' not 'vX.Y.Z', so the synthesised URL 404s. tray-v1.8.5's manifest shipped with notes -> /releases/tag/v1.8.5 (404) instead of /releases/tag/tray-v1.8.5 (200). Fixed by adding a --tag flag to the script (defaults to v${version} for back-compat) and passing github.ref_name from release.yml. Co-authored-by: Nigel Tatschner <n Tatschner@gmail.com>
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Decouples the Tauri tray installer cadence from the server + web container image cadence. Each track now has its own version number, its own tag schema, and its own auto-alpha trigger.
tray-vX.Y.Z[-channel.N]crates/starstats-client/Cargo.toml+tauri.conf.jsonrelease.ymlvX.Y.Z[-channel.N](unchanged)[workspace.package].version(drives core + server)release-images.ymlWeb-only changes no longer churn the tray's signed MSI / channel manifest. Tray bug fixes no longer force a server redeploy.
Design rationale:
docs/superpowers/specs/2026-05-23-release-tracks-split.md(in this PR).What changed
scripts/release-promote.mjs— everyprerelease/livesubcommand now takes a<tray|platform>track argument. New pure helperstagPrefix,parseTrackTag,bumpClientCargo.latestSemverFromTagsaccepts an optional track filter. 49 new unit tests cover the track plumbing (67 total, all green).crates/starstats-client/Cargo.toml— switched fromversion.workspace = trueto literalversion = "1.8.4". Thestarstats-client → starstats-coredep is a path dep, so version skew is irrelevant.starstats-core+starstats-serverstill inherit from workspace..github/workflows/release.yml— triggertray-v*.validate-tagstrips the prefix before applying existing channel rules + emits abare_versionoutput. Manifest path isrelease-manifests/tray-<channel>.json. New step generates a per-component changelog (git logfiltered to tray paths since the previoustray-v*tag) and injects into the GH Release body viabody_path, replacing GH's auto-generated notes (which would otherwise pick the literal previous tag — possibly a platformvX.Y.Z— and leak platform-only commits into the tray release body)..github/workflows/promote.yml— newdetect-trackssetup job.workflow_dispatchtakes atrackinput (tray/platform/both). Push-to-nextauto-alpha usesdorny/paths-filter@v3to bump only the tracks whose files changed; docs-only pushes tag nothing.starstats-coretriggers both tracks (necessary shared-dep over-eagerness). Self-bump commits are skipped to break the loop..github/workflows/ci.yml—should-runskip list addschore: bump tray toandchore: bump platform tofor the new bot-commit message format.release-manifests/{alpha,beta,rc,live}.jsonrenamed torelease-manifests/tray-{alpha,beta,rc,live}.json(git mv, history preserved).crates/starstats-client/src/config.rs+tauri.conf.json—manifest_url()+ the static updater endpoint updated to the new path.apps/web— platform version surfaced as a sub-footer chip on both signed-in and marketing layouts. Build-time inlined vianext.config.mjs'senv:block, sourced from workspaceCargo.toml.CLAUDE.md+docs/RELEASING.md— branch model + quick-reference table updated.docs/superpowers/specs/2026-05-23-release-tracks-split.md— full design spec.Glob non-overlap
GitHub's
v*tag glob anchors at start of string.tray-v1.8.4starts witht, notv— sorelease-images.yml's existingtags: ['v*']trigger never fires on tray tags. Notags-ignoreworkaround needed.Migration
One-time, per the user's "internal/dogfood, re-pair acceptable" call:
release-manifests/<channel>.jsonget a 404 on their next update check. Users re-download + re-pair from the latest GH release.tray-v1.8.5-alpha.1ANDv1.8.5-alpha.1(the PR itself touches both track paths), establishing the baseline.Out of scope (future PRs)
release-images.ymlonly publishes container images, no GH Release).release-notes/<track>/<version>.mdnarrative summaries appended into release bodies.These were flagged but cut from this PR to keep it focused on the version split mechanism.
Dependency note
This PR conflicts with PR #89 on
ci.yml(both edit theshould-runjob). Whichever lands first is fine — the other rebases trivially on top.Test plan
node --test scripts/release-promote.test.mjs— 67 tests pass locallynext: verify auto-alpha fires both tracks (this PR touches both tray and platform paths)nextproduces onlytray-vX.Y.Z-alpha.N(no platform tag)vX.Y.Z-alpha.N(no tray tag)tray-v1.8.5+v1.8.5simultaneouslytray-v*after merge shows the per-component changelog (commits touching only tray paths)