Skip to content

feat: add a binary swift release pipeline - #418

Merged
webern merged 4 commits into
mainfrom
claude/mx-swift-delivery-design-33hvxk
Aug 23, 2026
Merged

feat: add a binary swift release pipeline#418
webern merged 4 commits into
mainfrom
claude/mx-swift-delivery-design-33hvxk

Conversation

@webern

@webern webern commented Aug 23, 2026

Copy link
Copy Markdown
Owner

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: a workflow_dispatch build 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: validates sha/version inputs, fans out to the target workflow, then writes release-manifest.json and commits B (binary-default Package.swift + manifest, parent A) and R (restores the source manifest) on release-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 with xcodebuild -create-xcframework), checksums it, and interpolates the release manifest from the new .github/release/Package.swift.template.
  • release-publish.yml: on a v* tag push, re-verifies the tag/version match, commit parentage, ancestry to main, the declared file paths, the pinned build run, and the checksums, then publishes the release. Never rebuilds.
  • Package.swift drops the dead MX_BINARY_RELEASE fatal-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

  • All workflow YAML parses; every embedded shell script passes bash -n
  • Full release lifecycle simulated locally against the real template and manifest logic: the correct flow passes every publish check; tagging the restore commit, the merge commit, or the base commit each fail the check that's supposed to catch it; a tag/version mismatch fails; a squash-merged release recovers via the documented history-only merge; a file smuggled outside the declared paths is rejected
  • Not run: the actual macOS slice builds and xcodebuild -create-xcframework (needs a real macOS runner — the first dispatch of release-build is the test of that step)

References

  • Progresses Publish mx with Conan #73: the reusable-workflow contract this PR establishes (inputs, artifact, metadata, allowed paths) is written so a Conan target can implement it as a second release target without changing the publish or validation logic

webern added 3 commits August 23, 2026 15:44
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).
@webern webern changed the title feat: binary Swift releases — the full release-build / release-publish system feat: add a binary swift release pipeline Aug 23, 2026
@webern webern added non-breaking fixes or implementation that do not require breaking changes ai Issues opened by, or through, a coding agent. labels Aug 23, 2026 — with Claude
mx-comments flags "load-bearing" as an overused, invented phrase.
Reword to plain language.
@webern
webern merged commit 4aff4d0 into main Aug 23, 2026
8 checks passed
@webern
webern deleted the claude/mx-swift-delivery-design-33hvxk branch August 23, 2026 18:47
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai Issues opened by, or through, a coding agent. non-breaking fixes or implementation that do not require breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant