nightly-next: sign once as cmux NIGHTLY and publish to the one feed

Merged
TE
1d ago
+443-2113

Leo, 2026-11-05: "JUST POINT ME TO THE ONE APP/THE ONE FEED", "THERE SHOULDN'T BE 2"; and "if we're only doing 1, just do the best app version to stick with". The app the team runs is cmux NIGHTLY (com.cmuxterm.app.nightly), with evidence in cmuxterm-hq#7156. So nightly-next now signs each build once, as cmux NIGHTLY, and publishes it to the feed that app polls. The cmux NEXT-identity build, its feed writes, and the second signing are gone. Carries out #33050.

One signing

  • decide's nightly-next identity becomes cmux NIGHTLY: com.cmuxterm.app.nightly, cmux-nightly and cmux.nightly.entitlements. That means the NIGHTLY Developer ID profile, keychain group 7WLXT3NR37.com.cmuxterm.app.nightly and passkeys. sign_channel is nightly and profile_channel is NIGHTLY. This is the same path the NIGHTLY-identity notary probe already takes. The embed step refuses a profile for any other App ID.
  • The cmux NEXT sign-in scheme override (LSEnvironment:CMUX_AUTH_CALLBACK_SCHEME) is dropped. cmux NIGHTLY takes its own default.
  • Removed: the five copy jobs (legacy-identity-target, sign-, verify-, publish-nightly-identity-legacy, nightly-identity-legacy-published) and the 17 in-job copy steps. The sign job's budget goes from 320 back to 160 minutes.

One feed

  • NIGHTLY_NEXT_FEED_BASE is https://files-next.cmux.com/nightly-next (R2 prefix nightly-next/). publish-nightly's compare-and-swap write, guard, ring tags, read-back and notes now all go to that feed.
  • The release push uses feed=nightly-next-legacy, the id the app's ReleasePushListener gives that feed.
  • The rolling DMG name is now moved by publish-nightly-next-github-names, only while the build is the feed's newest.
  • The build-number checks read that feed plus classic NIGHTLY.
  • Canary verdicts and pulled.json stay in nightly-next/next/, where the app reads them (UpdaterService.channelFolder).
  • verdict, rollback and canary follow the one feed. verdict no longer retags the retired feed.
  • The feed keeps the Nightly build before the newest (nightly_next_ring.py legacy, in both ring modes), so a launch crash has a build to roll back to. Today's live feed holds a single item, which leaves the launch smoke's decide() with nothing to serve. With the ring off, an unreadable live feed now warns and publishes the new build instead of stopping.

Handover for a stray cmux NEXT install

  • Sparkle never installs a different bundle id. The app has no informational-update support, and a higher item with no enclosure would go down its normal install path.
  • So the retired feed gets no appcast item. It gets a signed announcement card, release-notes/next/retired-feed-announcements.json, signed with the content key and written as nightly-next/next/notes/announcements.json. That is the one feed-side message the app shows. It is text only and points to cmux.com/next.

Not touched

  • files.cmux.com/nightly and every classic-frozen path.
  • The nightly runner routing.

The scheduled watchers that run from main move in #36228, which lands first.

Follow-ups

  • Delete the copy's now-unused scripts and their unit tests.
  • stable-next: its feed has never published; it reads the retired feed and expects a cmux NEXT DMG.
  • The dev measurement tools that install "cmux NEXT.app".

Tests

  • Rebased onto feat-cmux-next 4a2ed94e (#35918). Its signer-only routes are unchanged on every remaining job, and the signing-route test test_no_signing_job_ever_resolves_to_a_shared_label passes.
  • Every nightly, signing and routing test passes except test_nightly_next_arm64_only.py, test_ci_upload_artifact_retention.py, test_short_timeout_guard.py and test_ci_signing_keychain_is_deleted_before_bundle_code.py. These four fail the same way on base 4a2ed94e.
  • Rewritten for the new contract: test_nightly_next_identity.py, test_nightly_next_post_publish.py and test_nightly_next_rolling_dmg_identity.py.
  • New: test_a_crash_rolls_back_on_the_one_feeds_shape (launch smoke decide() on a two-item cmux-next feed) and the ring tests for keeping the build before the newest.
  • Checks on the removed jobs were dropped from the routing, coverage, runner and universal-build tests.

Agent session: agent session

🤖 Generated with Claude Code


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


Summary by cubic

Signs every nightly-next build once, as cmux NIGHTLY (com.cmuxterm.app.nightly), and publishes it to the one feed the app polls, instead of building a cmux NEXT identity and re-signing a second copy for a separate feed. Implements #33050.

One signing, one feed

  • Removes the NEXT-identity build, the second signing, and the copy jobs; the sign job's budget drops back to 160 minutes.
  • The verdict workflow's retag now guards the one feed's own base (nightly-next/, where its DMGs sit) instead of the verdicts' folder, so it no longer refuses every promotion.
  • The feed keeps the Nightly build before the newest one so a launch crash still has a build to roll back to, and an unreadable live feed now warns and publishes instead of stopping.
  • The canary reads the one feed but pulls verdicts from the verdicts' folder (nightly-next/next/), where the app reads them; deriving that path from the feed matched nothing, so every scheduled tick re-ran the canary and dispatched another verdict.
  • The retired feed holds a signed announcement card pointing stray cmux NEXT installs at cmux NIGHTLY; classic NIGHTLY (files.cmux.com/nightly) is untouched.
  • Tests follow the one signing: the publish-path budget and rolling-name pins no longer reference the removed copy jobs.