Repository navigation
build(dist): ship attested cross-platform binaries, all built on Linux - #77
Conversation
CLOUD-173 Attach attested, cross-platform binaries to every GitHub release
Why Every release to date ships zero assets. That blocks dogfooding, which is the point: Batten is its own consumer #1, and there is no compiled
Scope This issue is the artifact half only: build, archive, attest, upload. Package-manager distribution and Definition of done
Acceptance
Ready — refinement specialization
Cost note: the repo is private, so macOS runners bill at 10× and Windows at 2×. A release build is a few minutes per target and runs only on release, not per PR. |
Every release so far shipped zero assets. `mise run release` is release-plz only — tag plus a release entry, never a build — and cross-check runs `cargo check` (codegen to metadata, no linking), so a green `cross` proved compilation without ever producing a runnable binary. There was nothing to install, which blocks dogfooding Batten as consumer #1 and left house-style §12 prose with no mechanism. mise-tasks/dist builds --profile dist (the profile Cargo.toml already declared and nothing used) for one target and stages a named archive. The release workflow calls that same task per matrix leg, so the archive CI uploads is the one a maintainer reproduces locally. The archive name is a contract, not a convenience: CLOUD-65's binstall path resolves assets by name, so naming and per-platform format are pinned in tests/dist.bats rather than left to whatever the last release emitted. Provenance binds to the binary, not the archive, so repackaging cannot launder it. mise-action runs with cache: false — a poisoned cache entry would land inside a signed artifact and be faithfully attested. Refs CLOUD-173.
…ows runners The first cut of this workflow ran macOS and Windows legs, which bill at 10x and 2x on a private repo. Replace them: every leg now runs on ubuntu-latest, with non-native targets built by cross, which compiles inside a per-target container carrying that target's linker and sysroot. `rustup target add` supplies only the standard library — the linker is what actually blocks a cross-build. Targets: linux gnu and musl on x86_64 and aarch64, plus windows-gnu. musl is the statically linked binary §12 asks for, portable across glibc versions. Windows ships the gnu triple because it links from Linux; msvc would need a Windows runner. macOS produces no artifact and cannot: linking Darwin needs Apple's SDK, which no container can carry, so it requires a macOS runner. That is a spending decision, recorded on the issue rather than silently incurred. Refs CLOUD-173.
Darwin was recorded as the one target requiring a paid macOS runner. That was wrong, and testing it here disproved it: cargo-zigbuild links both apple-darwin targets from Linux, producing a real Mach-O 64-bit arm64/x86_64 executable carrying LC_CODE_SIGNATURE — the ad-hoc signature Apple Silicon requires before it will exec an arm64 binary. No Apple SDK, no container image, no macOS runner. zig ships the Darwin linker support, which is the piece rustup does not provide: rustup target add installs only the standard library, and the linker is what blocks a cross-build. The whole matrix is now ubuntu-latest: linux gnu and musl on x86_64 and aarch64, windows-gnu, and both macOS targets. The caveat is dependency drift, not the toolchain. A crate linking a system framework (CoreFoundation, Security) would need SDKROOT and a real macOS SDK, which is where Apple's licensing question returns. Nothing in the tree does today. Refs CLOUD-173.
The macOS artifacts are linked on Linux by zig with no Apple SDK, which holds only while nothing links a macOS system framework. Nothing catches that today: cross-check runs cargo check, which stops at codegen and never links, so the first symptom would be the release workflow failing after a tag was already cut. macos-link-check resolves the graph with --filter-platform for aarch64-apple-darwin, so a macOS-only transitive dep is visible from a Linux host, and fails on either a package declaring a native links key (general, needs no list) or a named framework-linking crate (covers the build-script case the first rule cannot see). Verified in both directions: green on the tree as it stands, and adding native-tls — the crate a default-featured HTTP client pulls in — fails it naming all five offending packages. The named list is incomplete by construction; the residual gap closes by actually linking the Darwin targets, which is the next step on the issue. Refs CLOUD-173.
…l path macos-link-check reads a list of framework-linking crates, so it is incomplete by construction. Close that gap the only way with no false negatives: link the target. It either links or it does not. Structured to cost no wall-clock. It is its own CI job with one target per matrix leg, so the legs run concurrently with each other and with ci/cross, and nothing was added to mise run ci or the pre-commit hook. Measured 7s per leg against a critical path of 46-63s. 7s, not the 35s an optimized build takes: linking is what is under test, so the dev profile proves the same thing without LTO. A test asserts the task never grows a dist profile, so that cannot regress quietly. Net CI work goes down. Both Darwin triples leave cross-check, where they were cargo check'd — which never links and so never caught this class of break anyway. cross-check now covers windows-gnu alone. A file task, not an inline one: mise appends CLI args to inline task bodies, which passed a stray positional to cargo. Refs CLOUD-173.
a11bb6a to
6730729
Compare
|
/fast-forward |
Implements CLOUD-173. Every release to date shipped zero assets —
mise run releaseisrelease-plz release, which tags and creates the release entry but never builds. There was nothing to install, which blocks dogfooding Batten as consumer #1 and left house-style §12 prose with no mechanism.What ships
mise-tasks/distbuilds--profile dist(the profileCargo.tomlalready declared and nothing used) for one target and stages a named archive.release-artifacts.ymlcalls that same task per matrix leg onrelease: published, attests each binary, and uploads it — so the archive CI publishes is one a maintainer reproduces locally.Every leg runs on
ubuntu-latest. No macOS runner, no Windows runner.rustup target addinstalls only the standard library; the linker is what blocks a cross-build.crosssupplies one per target via containers. For Darwin, zig does — verified on this repo before adopting: both triples link from Linux and produceMach-O 64-bit arm64/x86_64 executablecarryingLC_CODE_SIGNATURE, the ad-hoc signature Apple Silicon requires before it will exec an arm64 binary at all. No Apple SDK involved.Windows ships the gnu triple rather than msvc because gnu links from Linux; msvc can only be linked on Windows and would cost a 2×-billed runner.
Provenance binds to the binary, not the archive, so repackaging cannot launder it.
mise-actionruns withcache: false— zizmor flags cache-poisoning as high severity on artifact-publishing workflows, and a poisoned entry would land inside a signed artifact that the attestation would then faithfully vouch for.The dependency-drift risk, and two gates for it
The SDK-free macOS build holds only while nothing links a macOS system framework. Nothing caught that before:
cross-checkrunscargo check, which stops at codegen-to-metadata and never links, so the first symptom would have been the release workflow failing after a tag was cut.macos-link-check(in the hk gate, ~1s) resolves the graph with--filter-platform aarch64-apple-darwin— load-bearing, since a macOS-only transitive dep is otherwise invisible from a Linux host. Fails on any package declaring a nativelinkskey (general, no list) or a named framework crate (covers the build-script case the first rule can't see). Verified both directions: green as-is, and addingnative-tlsfails it naming all five offending packages.darwin-link(its own parallel CI job) closes the residual gap the only way with no false negatives — by actually linking. One target per matrix leg, concurrent withciandcross.CI cost
Net work goes down. Both Darwin triples left
cross-check, where they werecargo checked and never caught this anyway;cross-checknow covers windows-gnu alone, so thecrossjob — the current critical path at ~62s — sheds two thirds of its work.darwin-linkmeasured 7s per leg (dev profile: linking is what's under test, so LTO proves nothing extra), against a critical path of 46–63s. A test asserts it never grows adistprofile.Tests
tests/dist.bats,tests/macos-link-check.bats,tests/darwin-link.bats— 38 bats tests total, plus actionlint and zizmor clean. Proven end to end locally: a 494 KB archive extracting to a workingbattenreporting0.0.14, and both Darwin targets linking.Known gaps
fail-fast: falsekeeps one bad leg from taking the others down, andworkflow_dispatchre-runs against an existing tag.rust-cachewill be slower; the per-target cache key should settle it. If it lands over the critical path, drop to a single Darwin target.curl/misefetches (no quarantine attribute); a browser download would need Developer ID signing and notarization. That belongs with CLOUD-65, which remains blocked by CLOUD-22 on licensing.Refs CLOUD-173.
Generated by Claude Code