Skip to content

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

Merged
cmux-bot[bot] merged 4 commits into
feat-cmux-nextfrom
nightly-sign-once-as-nightly
Nov 5, 2026
Merged

cmux-bot[bot] merged 4 commits into
feat-cmux-nextfrom
nightly-sign-once-as-nightly

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

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 (ci(nightly): signing jobs queue only on the signer's label (held) #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.

Written for commit ed3c5dd. Summary will update on new commits.

View guided diff Turn on auto-fix

Each nightly-next build is signed once, as cmux NIGHTLY (com.cmuxterm.app.nightly,
the NIGHTLY profile and keychain group), and published to files-next.cmux.com/nightly-next,
the feed that app polls. The NEXT-identity build, the copy jobs and the second signing
are removed. The feed keeps the Nightly build before the newest one so a launch crash
has a build to roll back to. The retired nightly-next/next/ feed gets a signed
announcement card that points a stray cmux NEXT install to cmux NIGHTLY.

Fixes #33050

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-90f71867d2222e9eecd0
@github-actions

github-actions Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Review ed3c5dd: HQ feed card · gallery before/after runs · checks

App of this head (app.zip, kept 1 day)

gh workflow run reload-build.yml -R manaflow-ai/cmux --ref feat-cmux-next -f tag=pr36287 -f ref=nightly-sign-once-as-nightly -f platform=macos -f backend_mode=local

@teamleaderleo teamleaderleo added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@blacksmith-sh

This comment has been minimized.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6-astra high, router-review): BLOCK at 71259b4

BLOCK at 71259b4

Visual verdict: no images; Leo tries the build

  • [P1] [blocking] [category:correctness] [finding:F1] [outcome:pending] nightly-next-verdict.yml:53,160 configures nightly-next-guard.py with NIGHTLY_NEXT_FEED_BASE=https://files-next.cmux.com/nightly-next/next, but the appcast downloaded and retagged at line 151 comes from the bare /nightly-next feed and contains enclosure URLs under that bare prefix. The guard therefore rejects every changed appcast as “not a nightly-next download” before uploading the ring update, so canary verdict promotion fails whenever channels need changing. Use the bare feed base for the appcast guard while retaining /next for verdict lookups.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
The one feed names its DMGs under nightly-next/, so the appcast guard of the retagged
feed takes NIGHTLY_NEXT_LEGACY_FEED_BASE; against the verdicts' nightly-next/next/ base
it refused every promotion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-90f71867d2222e9eecd0
test_gh_budget_wait.py's publish path no longer lists the removed copy jobs, and
test_publish_release_assets_callers.py reads the names script's versioned DMG and
its --immutable/--alias call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-90f71867d2222e9eecd0
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (Claude Opus 5.5 subagent, correctness first): SHIP at 07ce809

Scope: the full diff at this head, read through the API. Nothing was run locally.

Changes since cb6708c. That review found two reds. The compare shows a single commit (07ce809) that touches only the two tests behind them:

  • tests/test_gh_budget_wait.py:348: PUBLISH_PATH no longer lists legacy-identity-target, sign-nightly-identity-legacy or publish-nightly-identity-legacy. Those jobs are gone from nightly.yml, which caused the KeyError. Every other publish-path job is still checked.
  • tests/test_publish_release_assets_callers.py:91: the pin follows the script's new shape. It still requires the versioned DMG as --immutable, and it now also requires --alias "$rolling". That is stricter than before, not weaker, and it matches github-names-publish-rolling-name.sh at this head.

Correctness of the whole change

  • Needs and outputs: I parsed nightly.yml at this head (44 jobs). Every needs: entry names a job that exists. Every needs.X. expression names a job in that job's own needs. Nothing references legacy_* outputs, steps.legacy-*, NIGHTLY_NEXT_LEGACY_* or the removed jobs. The &ring-token anchor moved into republish-nightly-deltas and has no alias earlier in the file.
  • Signing: in decide, nightly-next now uses com.cmuxterm.app.nightly, cmux.nightly.entitlements, sign_channel=nightly and profile_channel=NIGHTLY. The embed step then picks APPLE_NIGHTLY_PROVISIONING_PROFILE_BASE64. The removed copy steps used that same secret in the release-next environment, so the secret is available there. The metadata checks in notarize-nightly-dmg.sh (lines 752 and 782) use CMUX_CHANNEL=nightly and so expect com.cmuxterm.app.nightly. What gets signed and notarized is exactly what ships.
  • Feed for the installed cmux NIGHTLY: SUFeedURL is NIGHTLY_NEXT_FEED_BASE/appcast-arm64.xml, which is files-next.cmux.com/nightly-next/appcast-arm64.xml, the feed those installs already poll. The Sparkle key is the same; the removed copy job also signed with SPARKLE_NEXT_PRIVATE_KEY. The items keep the cmux-next / cmux-next-dev channel tags, which nightly-next-guard.py requires and the legacy installs accept. The guard's feed-url, appcast and r2-key checks all pass with the new base and prefix (nightly-next/). The older -nightlyid- DMG names still match cmux-nightly-next-macos-.
  • The feed keeps the build before the newest: publish-next-feed-keep-nightly.sh now also runs with the ring off. current=() is reset on each pass, and only ring-off turns an unreadable feed into a warning. On the first publish after merge, the earlier item kept is the last legacy-signed build, so a rollback has a build to serve.
  • Verdicts and pulled.json stay in nightly-next/next/, and verdict.yml still writes a copy beside the one feed. Rollback and canary follow the new base, and the retag in verdict.yml targets the one feed.
  • Runners and concurrency: no runs-on or concurrency changed on any remaining job. The sign, recover, publish and names jobs keep their routes. pick-nightly-sign-runner now runs only when prepare-nightly-variant succeeds, which is correct because the legacy-only reason for it is gone. No cancel-in-progress was added.
  • Prerequisite: ci(nightly-next): the watchers read the one cmux NIGHTLY feed #36228 (the watchers on main) merged at 2026-11-05T07:15:24Z.

Non-blocking

  1. Transition only. recover-continue-pending-notarization.sh:49: a pending submission from before the merge is a cmux NEXT-identity build (com.cmuxterm.app.nightly.cmux-next). If it is recovered after the merge, notarize-nightly-dmg.sh:752 will refuse it with CMUX_CHANNEL=nightly. For an Accepted submission that refusal logs an ::error ("a check after acceptance failed"), and the candidate is skipped. It fails safe: no other-identity DMG reaches the feed. The annotation is noise until those pending artifacts expire.
  2. Naming in nightly-next-verdict.yml:52-58: NIGHTLY_NEXT_FEED_BASE there now means the verdicts folder, and NIGHTLY_NEXT_LEGACY_FEED_BASE means the one feed. The step "Move the old feed's builds whose canary passed to Nightly" now writes the live feed. The behaviour is right, but the names invert their meaning in nightly.yml. Worth renaming in the follow-up.
  3. In nightly.yml, NIGHTLY_NEXT_RETIRED_FEED_BASE and NIGHTLY_NEXT_VERDICTS_BASE hold the same value. That is fine today; keep them separate on purpose if the verdicts ever move.

agent-identity footer: claude-sr2-coord review subagent

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex gpt-6.1-sol xhigh, router-review): BLOCK at 07ce809

BLOCK at 07ce809

Visual verdict: no images; Leo tries the build

  1. [P1] [blocking] [category:reliability] [finding:F1] [outcome:pending] .github/workflows/nightly-next-canary.yml:41,142 now derives durable verdicts from /nightly-next/verdicts/, but nightly-next-verdict.yml writes them under /nightly-next/next/verdicts/. Scheduled canary runs therefore never see a readable verdict, so every five-minute tick reruns the canary and dispatches duplicate verdict jobs. Read from the retired verdicts path and update the canary test fixture accordingly.

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
The canary reads the one feed, but verdicts stay in nightly-next/next/verdicts/, where
nightly-next-verdict.yml writes them. Deriving the path from the feed found no verdict
on any build, so every scheduled tick re-ran the canary and dispatched a verdict again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-90f71867d2222e9eecd0
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (Claude Opus 5.5 subagent, correctness first): SHIP at ed3c5dd

Scope: compare 07ce809...ed3c5dd is one commit, ahead 1 / behind 0, two files (.github/workflows/nightly-next-canary.yml +4/-1, tests/test_nightly_next_canary_mode.py +7/-2). Nothing else moved, so the earlier SHIP at 07ce809 still covers the rest of the diff.

Checks:

  • Path match. nightly-next-canary.yml:44 sets NIGHTLY_NEXT_VERDICTS_BASE: https://files-next.cmux.com/nightly-next/next, and :145 reads ${NIGHTLY_NEXT_VERDICTS_BASE}/verdicts/${BUILD}.json. nightly-next-verdict.yml:52-54,95 writes key nightly-next/next/verdicts/${BUILD}.json to bucket cmux-nightly-next, served at https://files-next.cmux.com/nightly-next/next/verdicts/<build>.json. Bucket, prefix, file name and build key all match. The build key is the same steps.feed.outputs.build the canary dispatches with (-f build="$BUILD"), and the reader also checks that d["build"] == BUILD and that the verdict is pass or fail.
  • One value everywhere. nightly.yml:184 NIGHTLY_NEXT_VERDICTS_BASE, nightly-next-verdict.yml:52 NIGHTLY_NEXT_FEED_BASE (used as --verdicts-base for the retag at :153), nightly_next_train.py VERDICTS and the canary now all point at .../nightly-next/next. The retag call sites in nightly.yml, publish-next-feed-retag.sh and publish-next-feed-keep-nightly.sh all pass --verdicts-base "$NIGHTLY_NEXT_VERDICTS_BASE". Rollback writes pulled.json under NIGHTLY_NEXT_PULLED_PREFIX: nightly-next/next/, the same folder. Nothing that reads verdicts still uses the old ${FEED%/*} derivation.
  • Notes stay on the bare feed. nightly-next-canary.yml:168 --notes-base "${NIGHTLY_NEXT_FEED%/*}/notes" gives nightly-next/notes, which is where release-notes-upload-to-r2.sh writes (NIGHTLY_NEXT_R2_PREFIX=nightly-next/). That is correct.
  • The test is not a tautology. The curl stub records every https argument. The test feeds the step the env values read from the workflow YAML, then compares the fetched URL against the literal https://files-next.cmux.com/nightly-next/next/verdicts/4100.json?. It fails if the workflow's base value changes or if the step goes back to deriving the path from the feed. If curl is never called, the missing urls file raises, so that case fails too.

Notes (non-blocking):

  • nightly-next-verdict.yml:116-125 also makes a best-effort copy at nightly-next/verdicts/<build>.json, so the old derivation hit that copy whenever the copy succeeded. "Never readable" overstated the earlier bug. Even so, reading the canonical copy (written under set -e, so a failed write fails the run) is the right fix, because the copy only warns when it fails.

agent-identity footer: claude-sr2-coord review subagent

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Review (codex, router-review): SHIP at ed3c5dd

Reviewer: codex gpt-6-astra high, fast tier.

SHIP at ed3c5dd

Visual verdict: unchanged by this diff

Targeted nightly identity, feed, ring, publication, canary, rollback, recovery, and train tests pass (235 tests); changed shell and Python files also pass syntax checks.

Follow-ups: none

@teamleaderleo teamleaderleo removed the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026
@github-actions github-actions Bot added the risk:high Risk tier (docs/ci/risk-tiers.md) label Nov 5, 2026

@cmux-bot cmux-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cmux-bot approves ed3c5dd: an independent review passed this exact head (router-review receipt by teamleaderleo). A push needs a new review; a later BLOCK at this head dismisses this approval.

@cmux-bot
cmux-bot Bot merged commit b823a2b into feat-cmux-next Nov 5, 2026
224 of 228 checks passed
@cmux-bot
cmux-bot Bot deleted the nightly-sign-once-as-nightly branch November 5, 2026 11:04
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
- cef-embedder.sh (#36421): a comment line named both kill(0) and the name
  lookup, which the pkill lint counts as a name-lookup kill; reword it.
- journal_store.rs (#35866): SEAL_WAIT and LOCK_WAIT get a ratchet entry
  that says what each bounds.
- nightly.yml (#36287): drop the gh-app-install and ring-token anchors no
  alias uses any more (actionlint).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c21fa64be7cee139675a
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
* ci(guards): clear the guard steps red at the feat-cmux-next tip

A push that touches what the guards read (#36321 added a workflow) judges the
guard group at its tip with no base comparison, so every step already red there
failed it. Each red, its cause and the fix:

- no-shallow-fetch: base_red_excuse.py and agent-loop.sh fetch only into a
  repository they just made (`git init`); listed in OWN_REPOSITORY with that.
- short-timeout ratchet: acpmux's ls relay deadline, harness read and rename
  pacing, and the cloud client's slack over its own timeout are bounds that
  refuse no send; listed with their reasons. server/mod.rs counts 2 now.
- fleet lints, pkill: cloud-journey.ts recorded no stub pid and stopped the
  stub by name; it now records the pid at launch and signals only that pid while
  it still runs the stub. side-instance.sh already stopped the pid recorded at
  launch; the launch lookup and the stop now use their own names, so the lint
  no longer reads the stop as a name lookup. vm.sh quits the same two
  first-login apps inside its throwaway guest in two commands; one definition
  now.
- fleet lints, personal host: TabMachineBadgeColorTests (#36295) named a personal
  machine as its sample three times; it uses my-vm, as its allowlist entry asked.
- actionlint: nightly.yml kept the &pub-runner anchor after #35918 dropped its
  last alias.
- cu-driver-eval.yml (#36252): its eval job gets its runners.json class (the
  build-fleet gateway runner, as screen-stream-eval), and its report artifact
  keeps 3 days, the guard's limit; the next daily run reads it.
- visual gate wiring: the verdict job's timeout became
  `vars.CI_NIGHTLY_MINI != '' && 220 || 100` in 132f7ce; the test pins both.
- registry blast radius: the test inherited the runner's GITHUB_EVENT_NAME=push,
  which adds --warn-only; it now sets the event and covers the push case.

The signing-keychain guard's nightly reds go in their own PR.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c21fa64be7cee139675a

* ci: clear three guard reds the base gained since this branch

- cef-embedder.sh (#36421): a comment line named both kill(0) and the name
  lookup, which the pkill lint counts as a name-lookup kill; reword it.
- journal_store.rs (#35866): SEAL_WAIT and LOCK_WAIT get a ratchet entry
  that says what each bounds.
- nightly.yml (#36287): drop the gh-app-install and ring-token anchors no
  alias uses any more (actionlint).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-c21fa64be7cee139675a

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
…/next/ (#36686)

* scripts: read the one nightly-next feed, not the retired nightly-next/next/

#36287 put every nightly-next build on nightly-next/appcast-arm64.xml,
signed as cmux NIGHTLY, and retired nightly-next/next/. The signed-host
check, the daemon swap check, the premain launch bench, three measure
scripts and the perf-bench fetcher still read the retired feed, so they
checked or measured a build that no longer moves.

cua_signed_host_check.py now checks the one feed's newest build as
com.cmuxterm.app.nightly and nothing else; daemon-swap-check and
premain-launch say the feed is unreadable instead of reading an empty
answer as "no builds". tests/test_nightly_next_one_feed_readers.py keeps
scripts/ off the retired feed (red on the base: 2 failures; green here).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-8707001042ea634e8771

* scripts: the signed-host check's move goes to its own PR; the feed test is a manual lane

Pointed at the one feed, cua_signed_host_check.py checks the live cmux NIGHTLY
(4204712406201) and fails: "signed app pid 1326 never kept an accepted host
connection" on the legacy helper's socket (run 42054552983). That is a finding
about the shipped build, so the check's move goes to its own PR with it, and
this one keeps the readers that pass.

tests/test_nightly_next_one_feed_readers.py runs by hand (lane manual): the
linux-guard lane runs in no pull-request workflow, and no new CI tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-8707001042ea634e8771

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
… read beside this app's feed

Review of f46c8ae (Codex gpt-6.1-sol): the Install op checked only the one-second
gesture window, so a page could ask for a second build on the same click; it now
spends the click (ImportHistoryPageProvider's spentGesture). And since #36287 the
one feed publishes DMGs to /nightly-next/ while verdicts and pulls stay in
/nightly-next/next/, so the fallback for a build off the feed read the wrong folder;
it now reads the folder of this app's own feed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef
teamleaderleo added a commit that referenced this pull request Nov 5, 2026
…parkle update (#36375)

* updater: Install this version checks the DMG's EdDSA signature, like every Sparkle update

A Switch to a build this Mac did not keep (Settings > Channels, Install an Earlier
Nightly…, `cmux nightly install`, and next What's New) downloaded the DMG and
trusted it on the code signature alone. Sparkle checks every update's EdDSA
signature against SUPublicEDKey first; this path now does the same, before the
image is even mounted:

- AppcastItem carries the enclosure's sparkle:edSignature and length.
- The nightly publisher writes <dmg>.signature.json (the feed's signature and
  length) and uploads it beside the DMG, so a build the two-item feed has
  dropped still has its signature. It needs no trust of its own: the key is
  the app's.
- BuildSwitchDownload refuses a download whose bytes or length do not match,
  and switchToBuild refuses a non-kept build with no signature anywhere (built
  before this file existed), saying why. A kept build swaps as before.

Checked against the live feed: build 4198271655001's edSignature verifies as
plain Ed25519 over the DMG bytes with the cmux NEXT key; one changed byte fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New: each day's Versions lists its builds, each with its commit, its changes and Install

Leo, 2026-11-05: "Like literally with a click to a past nightly. Like that would be huge. Like
just having that list of like, okay, you can just go and through the What's New page, you can
just get back to a prior version if you wanted to."

The Sheet's day header gets "Versions" (with the day's build count) at its end, at the header's
own line height so nothing moves. Open, it lists the day's builds: version, its commit (linked),
"Changes" (the compare from the build before), and Install (Installed for the running build).
Install asks once ("Restart into it"), then cmux.changelog.install runs
UpdaterService.switchToBuild: the download's EdDSA signature and team checked (previous commit),
the stores check, the running build kept, a restart with every session. The op
(ChangelogInstallProvider, CmuxNextUpdater, routed ahead of the notes) takes only the person's
own click on What's New; a refusal (pulled, unsigned, a store the build cannot read) shows under
its build. `installable` says whether to list Install: none on a DEV build, which follows no feed.

Tests: ChangelogInstallTests (the op's gesture and page rules, refusal), whats-new-install.test.tsx
(links, ask-once, refusal under its build).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* updater: on a build the person picked, a newer build waits for their click

Coordinator's call on the What's New rollback ask (Leo, 2026-11-05: "just get back to a prior
version if you wanted to"): a rolled-back user is offered the newest again, but it never installs
straight back without asking.

A rollback or Install this version (both run UpdaterService.rollback) remembers the build it went
to (cmux.next.updates.pickedBuild). While that build runs, UpdateController.asksBeforeInstalling
makes a found build wait in the update card for the person's click even with background install
on; any other running build (they installed forward) clears it, and updates install as set again.
The skip of the build left behind is unchanged.

Tests: BackgroundInstallTests.skippedBuildsAreNotOfferedAgain (a newer build waits, then installs
on accept; without the hold it installs as before), RollbackPickedBuildTests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New Install: type the install closure (the compile check's diagnostic failure); gallery states sheet-versions and sheet-versions-confirm

The ternary over an async closure gave Swift no diagnostic at all (fleet compile check
d73123de0e19c875c5aee570); an explicit closure type compiles. The gallery's mock host lists
Install and answers installable, and two Sheet states show a day's Versions open and Install's
one question, over the real nightly-next index fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* release notes: What's New installs an earlier version with a click

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* settings: updates.whatsNewInstall (off by default) gates What's New Install

The Install button in What's New ships off until it is proven on a real
nightly; the EdDSA check and the re-offer hold stay on for every path.
Agents can read the setting but only the person turns it on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New gallery: open the second day's Versions (the first holds only the installed build)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* BuildSwitchDownloadTests: the image the test download writes is nonisolated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New gallery: Versions is a disclosure, so the rows below it move (layout-shift allowance with its reason)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New gallery: format

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New Install: one click installs once; a dropped build's DMG is read beside this app's feed

Review of f46c8ae (Codex gpt-6.1-sol): the Install op checked only the one-second
gesture window, so a page could ask for a second build on the same click; it now
spends the click (ImportHistoryPageProvider's spentGesture). And since #36287 the
one feed publishes DMGs to /nightly-next/ while verdicts and pulls stay in
/nightly-next/next/, so the fallback for a build off the feed read the wrong folder;
it now reads the folder of this app's own feed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* What's New Install: import Foundation for the spent click's TimeInterval

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* action exports: regenerated for updates.whatsNewInstall

scripts/cmux-next/regenerate-action-exports.sh on the fleet at 92d571a. The new
toggle adds two settings actions (147 to 149). The workspace count (189 to 193)
is base drift the gated export checks never caught on base; regenerating fixes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* ArchiveSignature: a signature that names no length is refused

Review follow-up at fb77cb7: a missing or malformed enclosure length skipped the
size check. Every signature the publisher writes carries one, so none is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

* settings exports: regenerated for updates.whatsNewInstall

scripts/cmux-next/regenerate-settings-exports.sh on the fleet at fb77cb7 (the
.patch form for a PR that changes CmuxNextSettings sources), applied without the
bot-only defaults snapshot. Review of fb77cb7: the schema lacked the new row, so
the CLI and MCP could not see or set it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-f0070936d9cbebdedaef

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch was previously deployed

1 inactive deployment
ci-routing-agent — ed3c5ddb Deployed Nov 5, 2026 by teamleaderleo via cmux-tui tree preflight #115651
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk:high Risk tier (docs/ci/risk-tiers.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant