Skip to content

ci+release: split tray and platform into independent release tracks#91

Merged
ntatschner merged 1 commit into
nextfrom
chore/release-tracks-split
May 24, 2026
Merged

ci+release: split tray and platform into independent release tracks#91
ntatschner merged 1 commit into
nextfrom
chore/release-tracks-split

Conversation

@ntatschner
Copy link
Copy Markdown
Collaborator

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.

Track Tag schema Version surface Fires
tray tray-vX.Y.Z[-channel.N] crates/starstats-client/Cargo.toml + tauri.conf.json release.yml
platform vX.Y.Z[-channel.N] (unchanged) [workspace.package].version (drives core + server) release-images.yml

Web-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 — 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". The starstats-client → starstats-core dep is a path dep, so version skew is irrelevant. starstats-core + starstats-server still inherit from workspace.
  • .github/workflows/release.yml — trigger tray-v*. validate-tag strips the prefix before applying existing channel rules + emits a bare_version output. 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, replacing 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).
  • .github/workflows/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 (necessary shared-dep over-eagerness). Self-bump commits are skipped to break the loop.
  • .github/workflows/ci.ymlshould-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).
  • crates/starstats-client/src/config.rs + tauri.conf.jsonmanifest_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 via next.config.mjs's env: block, sourced from workspace Cargo.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.4 starts with t, not v — so release-images.yml's existing tags: ['v*'] trigger never fires on tray tags. No tags-ignore workaround needed.

Migration

One-time, per the user's "internal/dogfood, re-pair acceptable" call:

  • Old in-the-wild tray installers polling release-manifests/<channel>.json get a 404 on their next update check. Users re-download + re-pair from the latest GH release.
  • First post-merge auto-alpha will tag both tray-v1.8.5-alpha.1 AND v1.8.5-alpha.1 (the PR itself touches both track paths), establishing the baseline.

Out of scope (future PRs)

  • Platform GH releases with their own changelog (currently release-images.yml only publishes container images, no GH Release).
  • release-notes/<track>/<version>.md narrative summaries appended into release bodies.
  • Roadmap-emit track-aware slugs.

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 the should-run job). Whichever lands first is fine — the other rebases trivially on top.

Test plan

  • node --test scripts/release-promote.test.mjs — 67 tests pass locally
  • All 4 workflow YAML files parse cleanly
  • CI run on this PR is green
  • After merge to next: verify auto-alpha fires both tracks (this PR touches both tray and platform paths)
  • Follow-up tray-only commit on next produces only tray-vX.Y.Z-alpha.N (no platform tag)
  • Follow-up web-only commit produces only vX.Y.Z-alpha.N (no tray tag)
  • First live cutover produces tray-v1.8.5 + v1.8.5 simultaneously
  • Web sub-footer chip renders "platform v1.8.5" on a deployed build
  • GH Release body for first tray-v* after merge shows the per-component changelog (commits touching only tray paths)

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.
@ntatschner ntatschner force-pushed the chore/release-tracks-split branch from 8f5eea7 to 77fc43a Compare May 23, 2026 23:41
@ntatschner ntatschner merged commit 87972fd into next May 24, 2026
10 checks passed
@ntatschner ntatschner deleted the chore/release-tracks-split branch May 24, 2026 01:38
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>
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant