feat: add a binary swift release pipeline - #418
Merged
Conversation
Restructure the release design so the metadata commit B lives on a machine-owned release branch instead of merging to main, pin build provenance in a manifest committed inside B, and close the smaller gaps: version as a dispatch input, actions: read permissions, checksum re-verification at publish, and per-target artifact names. Section 13 logs each of the seven flaws and its fix.
Owner requirement: tagged release commits must be part of main's history. The release PR returns with two machine-written commits -- B (binary manifest + provenance, the tag target) and R (restores the source manifest) -- so a merge-commit merge places B in main's ancestry while every main tip stays a source package. B's parent is still A, preserving the no-frozen-main and single-binary-commit properties from the previous revision. Publish now also enforces that B is an ancestor of main before releasing.
…blish) The workflows specified by docs/ai/design/release-process.md: - _release-target-swift.yml: builds the Mx XCFramework from commit A (three CMake+clang slices merged with libtool, stitched by -create-xcframework), checksums it, and emits the release artifact plus the metadata (interpolated release Package.swift + contract fragment) that the prep job commits. - release-build.yml: workflow_dispatch (sha + version); fans out to the target, then writes release-manifest.json, commits B (binary manifest + provenance) and R (source-manifest restore) on release-prep/v<version>, and opens the release PR with the tag instructions. - release-publish.yml: on v* tag push, re-verifies every manifest claim from primary sources (checks 1-6: tag/version match, B sits directly on A, A and B ancestors of main, diff within the declared paths under a hardcoded ceiling, the pinned run is a successful release-build in this repo, recomputed sha256 == manifest == Package.swift) and publishes the already-built artifacts. - .github/release/Package.swift.template: the binary-by-default release manifest with non-Apple source fallback and MX_SOURCE_BUILD opt-out. Package.swift drops the dead MX_BINARY_RELEASE fatalError arm (main never switches to binary mode under the final scheme), and the design doc is synced with the implementation (revision 13c records the drift).
mx-comments flags "load-bearing" as an overused, invented phrase. Reword to plain language.
5 tasks
webern
added a commit
that referenced
this pull request
Aug 23, 2026
## Human Summary Allows a suffix to be added to a release version and tag. ## Summary Release-candidate versions like 0.6.0-rc1 were rejected. release-build validated its version input against three plain numbers, so a candidate never got past the first job. Two changes make one work: - release-build validates the version as SemVer 2.0 with the optional pre-release suffix. Build metadata (+meta) is still rejected, because SemVer ignores it when comparing versions, so two releases differing only there would look like one version to a consumer. - release-publish passes --prerelease to gh release create when the version carries a suffix. The repository's latest release keeps pointing at the newest finished version, and a candidate is never the default download. The release PR body now says the same thing to whoever reviews and tags it. Nothing else needed to change. The v* tag trigger, the six publish checks, the asset URL and the two-commit PR shape were already written in terms of the version string rather than its shape, so they took a candidate as-is. A consumer reaches a candidate by pinning it: ```swift .package(url: "https://github.com/webern/mx.git", exact: "0.6.0-rc1") ``` Section 7 of docs/ai/design/release-process.md covers the ordering rules, what publishing does differently, and that pinning line. The revision log has a new entry (d). ## Testing This is workflow YAML, so it was verified locally rather than on a runner. - [x] Both workflows parse as YAML, and every run script passes bash -n - [x] The validate job's regex, extracted from the workflow and driven directly, over 23 versions. Accepted: 1.4.0, 0.6.0-rc1, 0.6.0-rc.1, 0.6.0-alpha.beta.1, 1.0.0-0, 1.0.0-x-y-z.0. Rejected: v0.6.0-rc1, 0.6.0-, "0.6.0-rc 1", 0.6.0+build, 01.2.3, 0.6.0-01, 0.6.0.1, 0.6.0_rc1, empty, and a two-line string - [x] The git-lifecycle simulation from #418 re-run end to end with version 0.6.0-rc1: branch release-prep/v0.6.0-rc1, tag v0.6.0-rc1, the B and R commit shape, publish checks 1 through 4, the baked asset URL and checksum, and the squash-merge recovery path all behave the way they do for 1.4.0 - [x] The same simulation still passes for 1.4.0 - [x] Both sides of each new conditional: --prerelease present for 0.6.0-rc1 and absent for 1.4.0; the PR body note appears for a candidate, and its placeholder line is removed for a final release Not covered locally: the macOS build itself and the real gh calls. Those first run when a release is dispatched. ## References - Follows #418, which built the release pipeline
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.
Human Summary
Fable built this release process so the the swift packaging system can pull built release binaries from GitHub releases.
Summary
Implements the binary Swift release system from
docs/ai/design/release-process.md: aworkflow_dispatchbuild phase that turns a chosen commit into a checksummed XCFramework and opens a machine-written release PR, and a tag-triggered publish phase that re-verifies every claim from primary sources before publishing a GitHub Release.release-build.yml: validatessha/versioninputs, fans out to the target workflow, then writesrelease-manifest.jsonand commits B (binary-defaultPackage.swift+ manifest, parent A) and R (restores the source manifest) onrelease-prep/v<version>, and opens the release PR._release-target-swift.yml: builds the XCFramework (macOS, iOS device, iOS simulator slices via CMake/clang, stitched withxcodebuild -create-xcframework), checksums it, and interpolates the release manifest from the new.github/release/Package.swift.template.release-publish.yml: on av*tag push, re-verifies the tag/version match, commit parentage, ancestry tomain, the declared file paths, the pinned build run, and the checksums, then publishes the release. Never rebuilds.Package.swiftdrops the deadMX_BINARY_RELEASEfatal-error arm; the design doc is synced with a couple of small implementation details that differ from the original text (CMake's built-in iOS cross-compilation instead of a third-party toolchain file, and the metadata-artifact channel between the target and the prep job).Testing
bash -nxcodebuild -create-xcframework(needs a real macOS runner — the first dispatch ofrelease-buildis the test of that step)References