From de35570b990e256c7443af5a06b0aef12535418a Mon Sep 17 00:00:00 2001 From: Stephane Segning Lambou Date: Tue, 18 Aug 2026 12:15:08 +0200 Subject: [PATCH] CI: install the cratestack CLI via cratestack's own action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both places CI installs the CLI ran `cargo install cratestack-cli --version --locked`, which compiles an ~18MB binary from source on every run. cratestack ships a composite action for exactly this (.github/actions/install-cratestack-cli): it downloads the prebuilt binary from that repo's own GitHub Releases, verifies it against the published .sha256 sidecar, and puts it on PATH — no Rust toolchain involved. That is both faster and a supply-chain check `cargo install` never performed. Pinned to @v0.8.3 rather than @main, per this repo's own rule against unpinned dependencies inside a pipeline. v0.8.3 is also the first tag carrying the installer's retry hardening (cratestack#578/#618) — v0.8.0 and earlier ship an older script that does not distinguish a transient connection failure from a permanent 404. The action ref and the `version:` input are deliberately independent, and this is NOT the duplicated-value drift AGENTS.md's release-engineering notes warn about: GitHub forbids expressions in `uses:`, so the ref cannot be derived from the pin even in principle. `cargo xtask cratestack-pin` remains the single source of truth for which version is installed; the ref only selects which revision of the installer script runs. Both comment blocks say so, so the next reader does not "fix" them into agreement. The `js` job still shells out to cargo, but only to read the pin — a second, toolchain-free parser there would recreate exactly the triplicated extraction #204 closed. Verified by reproducing the action's own download path for the exact asset ubuntu-latest will fetch: cratestack-cli-x86_64-unknown-linux- gnu-v0.8.3.tar.gz and its .sha256 both return HTTP 200, the checksum matches (25429742f9c6adf7...), and the archive contains exactly the bare `cratestack` binary the action's extract step expects, as a linux x86-64 ELF. actionlint clean (local and via docker); `cargo xtask workflow-paths` passes. Also corrects two stale paths in a comment block this change already touches: schema/migrations/... -> backends/migrations/..., and schema/schema.cstack -> schemas/vsms.cstack. Co-Authored-By: Claude Opus 5 --- .github/workflows/ci.yml | 66 ++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d98eda5..6188e3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,9 +65,9 @@ jobs: runs-on: ubuntu-latest # #204: none of the four adjacent gates this repo already had actually - # checked that schema/migrations/postgres/0001_init/{up,down}.sql is + # checked that backends/migrations/postgres/0001_init/{up,down}.sql is # what `cratestack migrate diff` produces from the *current* - # schema/schema.cstack — the `migrations` job above only proves the + # schemas/vsms.cstack — the `migrations` job above only proves the # committed SQL is *valid* (a migration missing a column still applies # fine), and neither `rust`'s `cargo check` nor # `cargo xtask sdk-schema-check` ever touches generated SQL at all. @@ -101,12 +101,31 @@ jobs: # A CLI newer (or older) than the pinned library emits DDL the # compiled library never produces — AGENTS.md records this biting # twice already (a stale global 0.7.4 CLI against a =0.6.7 pin, and - # later a drifted 0.7.4-vs-0.7.10 mismatch). Installed fresh every - # run, matching the `js` job's own existing approach for this exact - # same install below — no caching was added there, so none is - # invented here either. + # later a drifted 0.7.4-vs-0.7.10 mismatch). + # + # cratestack ships its own composite action for this, which both + # jobs now use instead of `cargo install cratestack-cli --locked`. + # It downloads the prebuilt binary from that repo's own GitHub + # Releases and verifies it against the published `.sha256` sidecar + # before putting it on PATH — so this step no longer compiles an + # ~18MB binary from source on every run, and gains a supply-chain + # check `cargo install` never performed. + # + # The action ref (`@v0.8.3`) and the `version:` input are + # deliberately independent, and this is NOT the duplicated-value + # drift AGENTS.md's release-engineering notes warn about: GitHub + # forbids expressions in `uses:`, so the ref cannot be derived from + # the pin even in principle. `cargo xtask cratestack-pin` remains + # the single source of truth for *which version gets installed*; the + # ref only says which revision of the installer script runs, and the + # two need not match. Pinned rather than floating on `@main` per this + # repo's own rule against unpinned dependencies inside a pipeline — + # v0.8.3 is the first tag carrying the installer's retry hardening + # (cratestack#578/#618); v0.8.0 and earlier ship an older script. - name: Install cratestack CLI (matched to the library pin) - run: cargo install cratestack-cli --version "${{ steps.pin.outputs.version }}" --locked + uses: cratestack/cratestack/.github/actions/install-cratestack-cli@v0.8.3 + with: + version: ${{ steps.pin.outputs.version }} - name: Regenerate 0001_init and diff it against the committed migration run: cargo xtask migrations-current @@ -312,14 +331,15 @@ jobs: - name: Biome (format + lint) run: pnpm biome ci . - # `packages/sms-client` is generated, not committed, so it must be - # produced before anything typechecks or builds against it. The CLI is - # installed from crates.io rather than built from a checkout: a client - # generated by a locally-built binary is reproducible on exactly one - # machine, which defeats the point of generating it in CI at all. - # Version-locked to the library pin — a mismatched CLI emits a client - # calling routes the compiled server does not serve, which is what the - # route gate below exists to catch. + # `frontends/packages/sms-client` is generated, not committed, so it + # must be produced before anything typechecks or builds against it. + # The CLI is a released artifact rather than something built from a + # checkout: a client generated by a locally-built binary is + # reproducible on exactly one machine, which defeats the point of + # generating it in CI at all. Version-locked to the library pin — a + # mismatched CLI emits a client calling routes the compiled server + # does not serve, which is what the route gate below exists to + # catch. # # Read through `cargo xtask cratestack-pin` rather than hardcoded # here — this step used to be the only place in this workflow with a @@ -331,14 +351,22 @@ jobs: # step, and the old `ci/assert-migrations-current.sh`. # `cargo xtask cratestack-pin` is now the one place that parses # Cargo.toml for this; both remaining call sites use it. This step - # needed no new `dtolnay/rust-toolchain@stable` — this job's own next - # step already assumed `cargo` is on `PATH` for `cargo install - # cratestack-cli`, on GitHub's own ubuntu-latest runner default. + # still needs no `dtolnay/rust-toolchain@stable`: it uses the `cargo` + # already on GitHub's own ubuntu-latest runner. Note this is the only + # reason this job touches Rust at all — the install step below no + # longer needs a toolchain, but reading the pin does, and a second, + # toolchain-free parser here would recreate exactly the triplicated + # extraction the paragraph above describes closing. - name: Read the pinned cratestack version from Cargo.toml id: pin run: echo "version=$(cargo xtask cratestack-pin)" >> "$GITHUB_OUTPUT" + # See the `schema-drift` job above for why this uses cratestack's own + # composite action, and why its `@v0.8.3` ref is independent of the + # `version:` input rather than a duplicated value that can drift. - name: Install cratestack CLI (matched to the library pin) - run: cargo install cratestack-cli --version "${{ steps.pin.outputs.version }}" --locked + uses: cratestack/cratestack/.github/actions/install-cratestack-cli@v0.8.3 + with: + version: ${{ steps.pin.outputs.version }} # `just` is not preinstalled on GitHub runners, and no other job in this # workflow needed it — they call cargo and ci/*.sh directly. Installing # it keeps one definition of what generation and the route gate actually