cmux NEXT: Update Channel setting picks a release ring (Stable/Nightly/Canary/Dev)

Merged
TE
16d ago
+1209-25

Summary

Adds the updates.channel setting (Settings > General > Updates > "Update Channel"): Stable, Nightly (default), Canary or Dev. This is the client half of release rings. The design is cmuxterm-hq docs/release-rings.md (manaflow-ai/cmuxterm-hq#2694, merged), with the sources it draws on in knowledge/inspiration/.

  • A ring is a set of sparkle:channel tags in the one cmux NEXT appcast (files-next.cmux.com/nightly-next/next/appcast-arm64.xml). The rings form a ladder, and each accepts its own tag plus every slower ring's: cmux-next-stable < cmux-next < cmux-next-canary < cmux-next-dev. There are no new feeds, bundle ids or keys.
  • Nightly is today's behaviour exactly. cmux-next is the tag every published item carries now, and allowedSparkleChannels for the Nightly ring is the same set as before. Canary and Dev get exactly what Nightly gets until CI publishes ring-tagged items. Stable gets nothing until soaked items are tagged, and its help text says so.
  • Sparkle's allowedChannels(for:) reads the ring from UpdateDriver.ring (UpdateController.ring). The read-only probe (updates.check, tagged DEV builds) now parses sparkle:channel and skips items the ring doesn't accept, so it never reports an update Sparkle won't install. Untagged items are still offered on every ring.
  • updates.status reports ring. Agents can't change the setting (.userOnly): which builds a Mac installs is the person's call.
  • Feeds other than cmux-next (stable, NIGHTLY, RC) still accept no tagged item on any ring.

Not in this PR:

  • CI publishing canary/dev/stable-tagged items. That belongs to the nightly-train owners. The design doc notes that sparkle_generate_appcast.sh and the rollback workflow must keep each item's tag first.
  • The App Store row.
  • The palette and CLI choice. The existing "Switch Update Channel" still swaps between the cmux and NIGHTLY apps.
  • web-ota's updates.web (#22777), whose default will follow this setting.

Generated exports (settings-schema.json, schemas/defaults/cmux-next.json, docs/mdm/*) are left to the autofix, per CLAUDE.md.

Risk

risk:high, because this is the updater. The default (Nightly) leaves the accepted Sparkle tags exactly as they are, so nothing changes for anyone unless they pick another ring. A non-default ring only changes which items of the existing signed feed are offered. Sparkle's EdDSA check and its never-downgrade rule are unchanged.

Changelog

  • Added: Settings > Updates > Update Channel picks Stable, Nightly, Canary or Dev builds of cmux NEXT (Nightly is unchanged; the other rings fill in as CI publishes them).

Observed / Not run

  • Ran: python3 scripts/cmux-next/gen-settings-sources.py --check passed. python3 scripts/cmux-next/prepush-gate.py reported no new violations. scripts/localization_catalog.py check reported no errors for the 6 new keys (all 9 required locales; its other errors are already on base).
  • Not run: Swift build and tests (UpdateRingTests, UpdateRingProbeTests). Agents don't compile on this host, so these run on CI. I haven't regenerated the settings exports.
    Agent session: huge-blue

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Adds the updates.channel setting (Settings > Updates > Update Channel) so users choose which release ring of cmux NEXT they get: Stable, Nightly (default), Canary, or Dev.

  • Rings are a ladder of sparkle:channel tags in the one cmux NEXT appcast; each ring accepts its own tag plus every slower ring's (stable < nightly < canary < dev), so a faster ring always gets at least what a slower ring gets and switching to a slower ring never downgrades.
  • Nightly is today's behavior exactly: it accepts the one tag every published item carries now, so nothing changes until CI publishes ring-tagged items. Stable gets nothing until items soak for a week; Canary and Dev get Nightly's builds for now.
  • Sparkle's allowedChannels, the read-only updates.check probe, and updates.status all follow the ring; the probe filters by the baked feed's allowed tags, like Sparkle does, so it never reports an update Sparkle won't install even on a test feed.
  • configure takes the ring as an optional String so the App needs no CmuxUpdater type in its public API, and a call without it keeps the current ring. Agents can't change the setting (.userOnly).
  • Feeds other than cmux NEXT still accept no tagged items on any ring. All 21 catalog locales are translated, and the settings exports (settings-schema.json, defaults, docs/mdm/*) are regenerated.

Not included: CI publishing ring-tagged items, the App Store row, and the existing palette/CLI channel switch.