Skip to content

feat(ci): ship releases without Apple signing credentials - #292

Open
hyperb1iss wants to merge 6 commits into
mainfrom
nova/release-macos-optional
Open

hyperb1iss wants to merge 6 commits into
mainfrom
nova/release-macos-optional

Conversation

@hyperb1iss

@hyperb1iss hyperb1iss commented Sep 15, 2026

Copy link
Copy Markdown
Owner

What this changes

A stable release no longer needs Apple signing credentials to ship. The Release workflow reports missing APPLE_* secrets instead of refusing to tag, the tag lane builds the macOS standalone tarballs and DMGs only when every secret is present, and the Homebrew job tracks macOS only when the release published the complete signed set, carrying the tap's current macOS build forward otherwise. Re-dispatching the tag lane after the credentials land adds the signed macOS artifacts to the same GitHub Release and advances the tap.

The packaged systemd units also stop capping the daemon at CPUQuota=25% and MemoryMax=512M, and the install pages stop promising a DMG on every release.

Why

PR #285 made signed macOS a hard requirement of every release, and no Apple credentials exist yet (the Developer Program enrollment is pending), so 0.5.2 could not be cut at all. The dry run passed because the gate only ran on real cuts, so the failure would have surfaced after the rehearsal looked green.

CPUQuota=25% in systemd means a quarter of one core, not a quarter of the machine. That unit shipped through the release installer, the deb, the AUR package, and Homebrew (the NixOS module already omitted it), so every packaged install ran a 60fps compositor plus in-process Servo on a quarter core. The daemon idles near 330MB RSS, which leaves the 512M ceiling one busy scene away from an OOM kill. Performance ceilings are a product baseline, so the caps go rather than get raised.

The README, download page, and both install guides told macOS readers to download Hypercolor-<version>.dmg from the releases page, but no release since v0.3.2 carries a macOS asset and the tap cask is pinned there.

🛠️ How it works

release-credentials probes the seven secrets and selects both release matrices from .github/release-matrix.json. Entries marked signing: true (the four macOS lanes) are dropped when any secret is missing and the marker never reaches the matrix. build-native-app and build-release take their include from that output, so a credential-less run never schedules a macOS runner and never hits the signing wrapper that would exit 1. With every secret present the lanes are exactly what #285 defined.

update-homebrew asks the GitHub Release which assets exist. All four macOS assets (two standalone tarballs, two DMGs) advance the formula's on_macos block and the cask together; none keeps the tap's current macOS stanzas, read back from the live formula with their own version line; a partial set fails the job, so a broken macOS publish cannot advance the tap. The renderer enforces the same all-or-nothing rule on its CLI.

create-release adds only the assets an existing release does not already carry, so a re-run leaves the published Linux and Windows digests alone (a rebuild is not byte-identical, and the tap formula, AUR, and Nix pins point at the first run's checksums). The PyPI publish skips an already-published version and npm already does, so the tag lane is safe to re-dispatch with release_artifacts: full once the secrets exist. Public CI still never publishes an unsigned macOS artifact in either mode; the unsigned --no-sign build remains an oss-ci-* fixture.

🧪 Validation

  • node --test over release-workflow, macos-release, macos-ci-coverage, homebrew-formula, and resolve-release-version: 30 tests pass. The new tests execute the workflow shell directly. The credential probe runs against the real matrix file with secrets present and absent, and the test asserts which targets survive. The Homebrew checksum and render steps run against a fixture tap in three scenarios (full macOS set, Linux only, partial set) and the test checks the rendered formula, the untouched cask, and the refusal. The Release report step runs with complete and missing secrets.

  • cargo test -p hypercolor-app --test packaging_tests: 53 pass. Three of them asserted the old inline matrix and the placeholder-free template; they now read the matrix file and the carry-forward template. rust-test gates both release builders, so these had to move with the workflow.

  • actionlint on both workflows is clean; both parse as YAML.

  • node --check scripts/homebrew-formula.mjs passes.

  • just docs-build builds 95 pages and cd docs && zola check passes.

  • An independent verification agent reviewed the diff and re-ran the checks above. Its first pass failed on the three Rust assertions and on a re-run path that clobbered every asset; both are fixed in the last two commits and the notes below record the rest.

  • Not exercised: an actual tag run. The credential-less path is proven by executing the workflow shell locally, not by a GitHub run. The first real proof is the 0.5.2 dry run followed by the cut.

  • Added or updated tests

  • Added or updated docs (README, AGENTS.md, relevant spec, or guide)

  • just verify passes locally (not applicable; no Rust changes)

  • just deny passes (not applicable; no dependency changes)

  • just ui-test and just ui-build pass (not applicable)

  • just sdk-lint, just sdk-check, and just sdk-build pass (not applicable)

  • just python-verify passes (not applicable)

  • just compat-check passes (not applicable)

  • just docs-build passes (required for docs or README changes)

  • cd docs && zola check passes (required for docs link/content changes)

  • Packaging scripts were syntax-checked (required for scripts/ or packaging/)

  • just e2e-build passes with the normal Servo stack (not applicable)

  • just e2e-build-cpu passes when validating the CPU smoke fallback (not applicable)

  • just e2e passes against the Servo stack (not applicable)

  • Tested on real hardware, simulator, or e2e harness (not applicable; workflow, packaging, and docs only)

Notes for reviewers

Start with the probe step in release-credentials and the Download release tarballs and compute checksums step in update-homebrew; those two decide everything else. The matrix JSON keeps the exact entries #285 defined, including the multi-line artifact-path values, so the diff there is a move rather than a rewrite.

The re-run path was the verifier's other catch: the first version clobbered every asset, which would have replaced the Linux and Windows files with different digests after the tap, AUR, and Nix had already pinned the originals. Uploading only missing assets keeps a re-run additive.

Where signing happens is a separate decision this PR does not make. #285 signs in public CI with repository secrets; spec 67 section 2 says Apple credentials live only in the proprietary release system. Both paths work with this change, because a signed macOS set published on the release is all the tap job looks for.

🤖 Generated with Claude Code

https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou

@coderabbitai

coderabbitai Bot commented Sep 15, 2026

Copy link
Copy Markdown

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 5 days. After that, they cost $0.25 per reviewed file.

Or wait 26 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 12343785-dc7c-40b0-8df1-2a5364f7df03

📥 Commits

Reviewing files that changed from the base of the PR and between 187a28a and c5835e4.

📒 Files selected for processing (19)
  • .github/release-matrix.json
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • README.md
  • crates/hypercolor-app/tests/packaging_tests.rs
  • docs/content/download.md
  • docs/content/guide/choose-your-install.md
  • docs/content/guide/installation.md
  • docs/design/13-performance.md
  • docs/design/14-desktop-integration.md
  • docs/design/25-distribution-and-applet.md
  • docs/development/RELEASING.md
  • packaging/homebrew/hypercolor.rb
  • packaging/systemd/user/hypercolor.service
  • packaging/systemd/user/hypercolor.service.system
  • scripts/homebrew-formula.mjs
  • scripts/tests/homebrew-formula.test.mjs
  • scripts/tests/macos-release.test.mjs
  • scripts/tests/release-workflow.test.mjs

Comment @coderabbitai help to get the list of available commands.

hyperb1iss and others added 6 commits September 15, 2026 13:34
The renderer required all four platform digests plus both DMG digests, so
a stable tag without signed macOS artifacts could not update the tap at
all. Linux stanzas now always come from the published release; macOS
stanzas come from the same release when it shipped the complete signed
set, and are otherwise read back from the formula already in the tap
with their own version line, the shape the tap carried at 0.5.1. The
cask is only rendered when both DMGs exist.

A release that publishes only part of the macOS set is still rejected,
so the tap advances all of macOS or none of it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
…ials

The Release workflow refused to tag when any APPLE_* secret was missing,
and the tag lane's credential gate, macOS matrix entries, and Homebrew
job all assumed signed macOS artifacts exist. No Apple credentials are
configured yet, so every release was blocked on macOS.

release-credentials now probes the seven secrets and selects the release
matrices from .github/release-matrix.json: macOS entries build only when
every secret is present, and a missing one drops them with a warning
instead of failing. The Release workflow reports the same status on dry
runs and real cuts. update-homebrew reads the published assets and only
tracks macOS when the whole signed set shipped, carrying the tap's
current macOS build forward otherwise and refusing a partial set.

create-release uploads onto an existing GitHub Release and the PyPI
publish skips an already-published version, so re-dispatching the tag
lane after the credentials land adds macOS to a release that shipped
without it. Public CI still never publishes an unsigned macOS artifact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
…units

Both packaged units carried CPUQuota=25% and MemoryMax=512M, and
dist.sh copies that unit into the tarball, so the release installer,
the deb, the AUR package, and Homebrew all shipped it. CPUQuota=25% is a
quarter of one core, not a quarter of the machine, which starves a 60fps
compositor plus in-process Servo on every packaged install, and the
daemon idles near 330MB RSS, so the memory ceiling was an OOM kill
waiting for a busy scene. The NixOS module already omitted both.

Resource ceilings are a product baseline; the units now run unconstrained
and rely on the watchdog and Restart=on-failure for runaway recovery.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
The README, download page, and install guides told macOS readers to grab
Hypercolor-<version>.dmg from the releases page, but no release since
v0.3.2 has carried a macOS asset and the tap cask is pinned there. The
pages now say macOS builds ship only when the signed lane runs and can
lag Linux and Windows, point at the tap as the source of truth, and keep
the DMG instructions for releases that did ship one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
…plate

The hypercolor-app packaging tests asserted the macOS matrix entries
inline in ci.yml and that the formula template carried no macOS version
placeholder. Both moved: the entries live in .github/release-matrix.json
so the credential probe can filter them, and the on_macos block carries
its own version line so a Linux-only release can carry the tap's macOS
build forward. rust-test gates both release builders, so these three
assertions would have failed the very tag run the change exists to make
possible.

The template header now describes the carry-forward instead of claiming
every platform renders from the same release.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
A re-run that clobbered every artifact would replace the Linux and
Windows assets with rebuilt files carrying new digests, because dist.sh
does not produce byte-identical archives. The tap formula committed by
the first run, the AUR and Nix pins, and anyone who verified a download
would all break until a second publish wave landed. create-release now
uploads only the assets the release does not already carry, so a re-run
after the Apple credentials land adds the macOS set and leaves the rest
untouched. RELEASING.md describes that behaviour instead of claiming the
AUR and Nix jobs ignore a re-run.

The two design docs that still listed the systemd caps as shipping
config carry the retirement note.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013dXbUNEe4yxrGhQLXbzRou
@hyperb1iss
hyperb1iss force-pushed the nova/release-macos-optional branch from cd7b4ea to c5835e4 Compare September 15, 2026 20:34
@hyperb1iss
hyperb1iss marked this pull request as ready for review September 15, 2026 23:22
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