ci: reuse cached signed/notarized native binaries on release#775
Merged
Conversation
The release workflow rebuilt and re-notarized the macOS/Rust native encryption binaries on every publish, even when nothing under the native source changed. The Apple notarization round-trip in particular is slow and externally dependent, and re-running it for a byte-identical binary is wasted work. Gate the native build/notarize/Rust jobs on a content-addressed cache probe. The cache key covers the native source, the build/bundle scripts, and the workflows that drive them, plus a manual 'v1' salt for toolchain bumps the file hash can't see. On a cache hit the build and notarize jobs are skipped and the previously notarized bundle is reused. To never publish the wrong thing: - keys are content-addressed, so a restored binary always matches the source that produced it - caches restored on a release are main-scoped only (feature-branch caches are not visible), so a release can only reuse a binary a prior main run produced - a reused macOS bundle is re-verified on a macOS runner (codesign + notarization staple + functional smoke) before the publish step; if it can't be verified the release is blocked - the publish job fails loudly if any native binary is missing First release after this lands rebuilds once to seed the cache, then subsequent releases with unchanged native source reuse it.
The macOS .app reuse relied on the bundle being byte-identical across releases, but that was only true by accident: release.yaml happened not to pass a --version, so the bundle fell back to a constant default. Anyone wiring a real version into that call would have silently defeated the notarized-bundle cache with no error. Make it stable by construction. CFBundleVersion is vestigial — nothing reads it (Swift binary, Rust binary, varlock CLI, notarization) — so hardcode it to a fixed constant in build-swift.ts and remove the --version plumbing from the macOS build workflow and its callers. The bundle is now byte-stable in every path, not just the release one. First release after this rebuilds + re-notarizes once (the build script hash changed), then reuse kicks in.
The bundle version is now a fixed constant (build-swift.ts), so the binary-release path no longer threads a version into the macOS build. That left the debug job's version output unused and a vestigial 'needs: debug' on build-native-macos. Remove both; the context-printing debug step stays. The re-release flow (workflow_dispatch version input -> RELEASE_VERSION -> release upload / homebrew / docker) is unchanged.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The release workflow rebuilt and re-notarized the macOS / Rust native encryption binaries on every publish, even when nothing under the native source changed. The three native jobs were gated only on
mode == 'publish' && includes-varlock == 'true'— never on whether the binary-relevant source actually changed.The Apple notarization round-trip (
xcrun notarytool submit --wait) is the worst offender: it's slow, externally dependent, and occasionally flaky, and re-running it for a byte-identical binary is pure waste.What this does
Gates the
build-native-macos,notarize-native-macos, andbuild-native-rustjobs on a content-addressed cache probe in theplanjob. On a cache hit, the build + notarize jobs are skipped and the previously notarized bundle / Rust binaries are reused.v1salt to force a fleet-wide rebuild on toolchain bumps the file hash can't see (runner image / Xcode / Rust / UPX).git diffchange-detection — that's empty onmain. The cache key is the change detector..appis already version-independent in CI (the release path doesn't pass--version, so the bundle embeds a constant placeholder, not the npm version), which is what makes the notarized bundle byte-stable across releases. Documented inline."Never publish the wrong thing" safeguards
This was the explicit design constraint. Layers:
maincan only restore caches produced by priormainruns; feature-branch caches aren't visible. Combined with branch protection, that's the anti-poisoning boundary.codesign --verify --deep --strict+xcrun stapler validate+ a functionalstatussmoke) in a newverify-native-macosjob. If it can't be verified, the release is blocked (!failure()).fail-on-cache-miss: trueon the publish-side restores, so a mid-run eviction blocks rather than silently shipping an incomplete package.Rollout
The first release after this lands sees no cache under the new
v1keys, so it rebuilds + notarizes once to seed the cache. Subsequent releases with unchanged native source reuse it (skipping the macOS build + the Apple notarization round-trip, and the Rust matrix).Compatibility
notarize-native-macos.yamlgains an optionalcache-keyinput (default empty); all caching steps are gated on it being set. The other caller (binary-release.yaml) doesn't pass it and is unaffected.build-native-rust.yamlis unchanged — the release path just passes a salted, comprehensivesource-hashinto the existingsource-hashinput.Notes / caveats for review
.appround-tripping throughactions/cachewith its signature + stapled ticket intact. Those are regular files in the bundle (not xattrs), andtest.yamlalready round-trips the signed.appthrough cache today, so the path is exercised — but theverify-native-macosjob exists precisely to catch it if a future cache/runner change breaks it.AGENTS.mddoes not require a bump file.