Repository navigation
Release bundles, smoke test, Homebrew formula and release workflow - #36
Merged
Merged
Conversation
packaging/build.sh macos14|macos26 builds sys1rust against the pinned MLX wheel for that macOS target, points its rpath at lib/ next to bin/, signs it ad hoc, checks linkage, minimum macOS, signature and --version, and tars it with libmlx, libjaccl, the metallib and the license files. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Starts a bundle's sys1rust serve, sends the 24 smoke requests and checks the answers against the fp32 CPU reference with compare.py's rules, then requires a clean SIGINT shutdown. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The formula picks the macOS 26 bundle on 26.2 and later and the macOS 14 bundle otherwise, installs into libexec with a bin symlink, and declares a brew services entry for sys1rust serve. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The smoke test now checks each answer against the reference's type and fields before it allows a near-tie disagreement, and it requires probabilities wherever the reference has them so the drift check always runs. Each request runs in a worker thread with a 120 s deadline, and a transport error, timeout or bad JSON records a FAIL line with its stage. SIGINT and SIGTERM handlers route through the cleanup, which waits up to 10 s after SIGKILL and reports a server that is still alive. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
packaging/licenses holds pinned license texts for the third-party code that MLX 0.32.2 compiles into libmlx.dylib, libjaccl.dylib and mlx.metallib. build.sh copies them into the bundle's licenses/, appends packaging/licenses/NOTICE to the bundle NOTICE and fails if a file is missing. build.sh also reads the whole LC_RPATH path, so a checkout path with spaces builds, requires the Rust version in packaging/rust-toolchain-version and prints tarball sizes in decimal MB. third_party.py runs cargo metadata only with that Rust version. render_formula.sh escapes \, & and | so BASE_URL and the version land in the formula literally. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…test malformed() now fails an answer whose probability map lacks a label from the reference's map. compare.py counts a missing label as 0, so dropping a low-probability label stayed under the drift limit. Extra labels still pass, as in compare.py. A SIGINT or SIGTERM that arrives while Popen starts the server now waits until `proc` holds the child, so the cleanup in `finally` stops it. The docstrings now name the actor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A v* tag builds both bundles on macos-26 with the Rust in packaging/rust-toolchain-version, pulls typed-decisions and smoke-tests them on macOS 14, 15 and 26 runners, and publishes the release with SHA256SUMS. A final tag also pushes the rendered formula to krishhgg/homebrew-tap with TAP_TOKEN. An -rc tag publishes a prerelease only. Pull requests that touch packaging run the build and smoke jobs. The publish job clones the tap before it creates the release, so an empty or expired token stops it before anything goes public. A rerun keeps a release that holds the same bundles and skips the tap commit when the formula is unchanged. packaging/RELEASE.md is the checklist, including the lock file refresh after a version bump. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lib/ carries more adapted third-party code than the first inventory listed. packaging/licenses now holds pinned texts for 8 more sources, packaging/licenses/NOTICE names each one, and build.sh fails if any is missing. - erf and erfinv by njuffa on Stack Overflow, in Metal erf.h and the CPU erfinv. MLX's constants match answer 35148199 revision 4 (2022) and answer 49743348 revision 3 (2021), both CC BY-SA 4.0 with no separate license. - PyTorch, for the fp8 e4m3 conversion in Metal fp8.h and CPU unary_ops.h and for the CPU erf. - supervised-lda's NumpyFormat.hpp, for the .npy reader and writer in io/load.cpp. - Julien Pommier's neon_mathfun.h (zlib) with simd_utils' FMA change (BSD 2-Clause), for the CPU sincos, which libmlx.dylib also keeps as C++ source in its CPU JIT preamble. - fastermath, for the CPU exp. - NumPy, TensorFlow and JAX, whose einsum path search, cumulative logsumexp gradient and truncated normal sampler MLX translates. NOTICE ends with what needs no notice and why. fmt, ThreadPool and Cephes are listed there, along with the papers and algorithms MLX implements in its own code. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Smoke-test the macOS 14 bundle on macos-15 only. GitHub retires the macos-14 image on 2026-11-02 and its brownouts already fail jobs. macos-15 runs that bundle on an OS older than 26.2, the case it exists for, and the macOS 26 bundle stays on macos-26. The publish job now: - deletes, by id, every draft release of the tag that a cancelled gh release create left, then creates the release again. A rerun accepts a published release only if both bundles and SHA256SUMS match this run's files. - runs git push --dry-run against the tap before it creates the release. The public tap clones without the token, so the clone never checked it. The dry run asks GitHub for git-receive-pack, which GitHub serves only to a token it accepts with push access to the tap. It sends no commits and does not check branch rules. - leaves the tap alone with a notice when its formula has a newer version (sort -V), so a retried older tag cannot downgrade it. Every tag's publish job shares the tap-update concurrency group. RELEASE.md pulls the 3 pinned models into a fresh HF_HUB_CACHE under packaging/.work before the strict suite. The laya-mlx tests load the snapshot that refs/main names, else the only cached one, and sys1rust pull writes no refs/main, so a shared cache could test another revision. The candidate tag lives in rc, so tagging, the SHA256SUMS download and the test formula all follow a bump to rc2. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The publish job deleted, by id, every draft release of the tag that a cancelled gh release create left. Publishing a draft keeps its id, so a maintainer who published the draft between the list and the delete would lose a live release, and the concurrency group cannot coordinate with a manual publish. The job now stops on such a draft, prints its URL and asks for a manual delete before a rerun. The published-release guard stays as it was. Also drop the last comment that mentioned the macos-14 runner. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…suite's GPU smoke.py now checks a reply's shape the way live.rs does. Question ids, answer fields and probability labels must match the reference's lists in order, and the legend must equal the reference's. noul, the confidences, act_probability and every probability must be numbers in [0, 1], and confidence, answer_confidence and act_probability must stay within MAX_DRIFT. A reply without confidence, answer_confidence, act_probability or legend used to pass. RELEASE.md says the strict suite needs an M5-class GPU on macOS 26.2 or later, because the laya-mlx settings tests need MLX's NAX gemms, and that the smoke tests run on any Apple silicon Mac. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.
PR 3 of 4 for packaging. PRs 1 (#34) and 2 (#35) are merged, so this targets main. It builds the release bundles, tests them, renders the Homebrew formula and adds the workflow that publishes a release from a tag.
What a user gets
brew installpicks one fromMacOS.full_version.bin/sys1rust,lib/withlibmlx.dylib,libjaccl.dylibandmlx.metallibfrom the official mlx-metal 0.32.2 wheels, plusLICENSE,NOTICEandlicenses/. The binary finds its libraries through@executable_path/../lib, so it runs through Homebrew's bin symlink.licenses/carries the Rust crate licenses, MLX's license and 16 pinned texts for third-party code compiled into the MLX libraries. Among them are nlohmann/json, gguflib, metal-cpp, PocketFFT and PyTorch's fp8 conversion.NOTICEnames each text and explains the 2 exclusions. fmt's license exempts object code, and ThreadPool's zlib terms ask for a notice only in source copies.brew servicesentry that runssys1rust serveat login.What build.sh checks
packaging/build.sh macos26|macos14 OUTfails unless:packaging/rust-toolchain-version(1.95.0).packaging/mlx-wheels.lock.NOTICEnames it.codesign --verifypasses.--versionprints the expected string.The smoke test
packaging/smoke.pystarts the bundle'sserve --offline --port 0and sends the 24 smoke requests (120 questions). It compares each answer withbench/reference/typed-decisions/smoke.jsonlusingbench/harness/compare.py's rules. Each reply must match the reference's shape, as inlive.rs: the same question ids, answer fields, probability labels and legend, in order. Probabilities and confidences must be finite and in [0, 1], and confidences must stay within the same 0.05 drift limit. It also requires a clean shutdown on SIGINT, and it never leaves a server running, even when interrupted during startup.On a base M5, both bundles had 0 failures. The largest probability drift was 0.0008 (macOS 26 bundle) and 0.0009 (macOS 14 bundle).
Homebrew
packaging/sys1rust.rb.inplusrender_formula.shrender the formula for a tag and the 2 tarball hashes.brew install,brew testand the smoke test through Homebrew's path.brew styleandbrew audit --strict --onlineare clean.The release workflow
.github/workflows/release.ymlruns onv*tags. A pull request that touchespackaging/or the workflow runs only the build and smoke jobs.HF_HUB_CACHE, then runs smoke.py. GitHub retires its macos-14 runner on 2026-11-02, so no job uses it. macos-15 still runs the macOS 14 bundle on an OS older than 26.2, which is the case that bundle exists for.TAP_TOKENcan push to the tap, withgit push --dry-run, before anything goes public.SHA256SUMS. A-rcNtag makes a prerelease and leaves the tap alone.SHA256SUMSmatch this run. A tap with a newer version is left alone. Publish jobs from different tags share a concurrency group.packaging/RELEASE.mdis the checklist. It covers the local strict suite in a dedicated cache holding only the pinned snapshots, smoke on both bundles, an rc tag, a throwaway local tap install, the final tag, and the expired-token recovery.Prerequisites (done)
TAP_TOKENsecret is set, a fine-grained token with contents read and write on that repo only.Tests
ghand a local git server, 10 release cases plus the tap cases:render_formula.shgives identical output with GNU and BSD tools, including URLs that contain&,|and\.🤖 Generated with Claude Code
The PR appears safe to merge; both previous findings are addressed and no new blocking issue was found.
What we checked:
[0, 1]. The new bounds match those calculations.Summary
Adds two macOS release bundles, license notices, a Homebrew formula, and a workflow that builds, smoke-tests, and publishes tagged releases.
smoke.pycheck reply shape, missing fields, and confidence differences.Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR Trigger["Packaging PR or version tag"] --> Build["Build both macOS bundles"] Build --> Smoke14["Smoke macOS 14 bundle on macOS 15"] Build --> Smoke26["Smoke macOS 26 bundle on macOS 26"] Smoke14 --> Gate{"Tag and both tests pass?"} Smoke26 --> Gate Gate -->|Yes| Publish["Publish release and checksums"] Publish --> Final{"Final release?"} Final -->|Yes| Tap["Update Homebrew formula"] Final -->|No| RC["Keep prerelease out of tap"]Reviews (2) · Last reviewed commit: "Require every reference field in the smo..."