Skip to content

CI: install the cratestack CLI via cratestack's own action - #322

Merged
stephane-segning merged 1 commit into
mainfrom
use-cratestack-install-action
Aug 18, 2026
Merged

CI: install the cratestack CLI via cratestack's own action#322
stephane-segning merged 1 commit into
mainfrom
use-cratestack-install-action

Conversation

@stephane-segning

Copy link
Copy Markdown
Contributor

Summary

Replaces both cargo install cratestack-cli --version <pin> --locked steps in CI with cratestack's own composite action, install-cratestack-cli. It downloads the prebuilt binary from that repo's GitHub Releases, verifies it against the published .sha256 sidecar, and puts it on PATH — no Rust toolchain, no source build.

Intent

Maintainer request: "cratestack has a gh action … let's use it."

cargo install cratestack-cli --locked compiles an ~18MB binary from source on every run, in two separate jobs. The action does a download + checksum instead — faster, and it adds a supply-chain check cargo install never performed.

Scope

Two steps in .github/workflows/ci.yml:

  • schema-drift (0001_init matches cratestack migrate diff)
  • js (pnpm biome, typecheck, build, test)

Both keep cargo xtask cratestack-pin as the version source. Nothing else changes.

Two decisions worth stating

Pinned @v0.8.3, not @main. This repo's own release-engineering notes treat an unpinned dependency inside a pipeline as real, not hypothetical, breakage. v0.8.3 is also the first tag carrying the installer's retry hardening (cratestack#578/#618) — checked, rather than assumed: v0.8.0 and v0.7.16 both point at an older blob (f8682c1), while v0.8.3 matches main (53033f5). That hardening is precisely the distinction between a transient connection failure and a permanent 404, which matters for a step that gates two jobs.

The action ref and the version: input are independent, and that is not drift. GitHub forbids expressions in uses:, so the ref cannot be derived from the pin even in principle. cargo xtask cratestack-pin stays 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 explicitly, so the next reader doesn't "fix" them into agreement and then wonder why bumping one didn't change the other.

Deliberately NOT done

  • The js job still shells out to cargo — only to read the pin. A second, toolchain-free parser there (a sed) would recreate exactly the triplicated extraction CI never checks that committed migrations match schema.cstack #204 closed, which had already drifted once. The install step no longer needs a toolchain; reading the pin still does.
  • No caching added. The old steps had none; a checksum-verified download doesn't obviously want one.
  • actionlint was not added to CI. Used locally to check this change; wiring it up as a gate is a separate decision.

Verification

actionlint clean — both the local install and rhysd/actionlint via Docker, exit 0. cargo xtask workflow-paths passes (36 paths). YAML re-parsed to confirm both steps resolve to the action with the expected with: block.

But actionlint only proves the YAML is well-formed, not that the action can fetch anything — the failure mode that actually matters here is an asset-name mismatch, which is invisible to a linter. So I reproduced the action's own download path for the exact asset ubuntu-latest will request, using its own curl flags:

fetching: cratestack-cli-x86_64-unknown-linux-gnu-v0.8.3.tar.gz
asset HTTP 200
sha256 HTTP 200
--- checksum (the action's step 4) ---
expected: 25429742f9c6adf721581a3c14897f9d8957e1bc1608476dc54b2978fab56407
actual:   25429742f9c6adf721581a3c14897f9d8957e1bc1608476dc54b2978fab56407
CHECKSUM MATCH
--- archive contents (the action's step 5 expects a bare 'cratestack') ---
cratestack
cratestack: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked

That covers steps 2–5 of the action (target-triple resolution, asset URL, checksum, extract layout). Step 1 (version resolution) is skipped entirely because we pass an explicit version rather than latest.

The real gate is this PR's own CI run — these two jobs are the change. schema-drift passing means the action delivered a working 0.8.3 CLI, since migrations-current regenerates DDL and byte-compares it; js passing means just client-gen/client-check got one too.

Screenshots / Evidence

Terminal output inline above.

Risk Assessment

What breaks if I'm wrong: two CI jobs fail loudly and immediately — schema-drift and js both invoke cratestack right after installing it. There is no silent-failure path: a missing asset is a hard ::error::, a checksum mismatch is a hard ::error::, and a CLI that installed but doesn't run fails at cargo xtask migrations-current or just client-gen. Nothing merges to main on a bad install.

The one non-obvious risk: this makes CI depend on cratestack's GitHub Releases rather than crates.io. If a release asset were ever deleted or renamed, these jobs break where cargo install would have kept working. Mitigated by the checksum sidecar being published alongside, by the pinned ref, and by both being verified above — but it is a genuine change in what CI depends on, not a pure speedup.

No production code, no runtime behaviour, no schema, no migrations touched.

AI Usage Declaration

Claude read the action's action.yml in full before adopting it, chose and justified the pinned ref against the actual blob SHAs at each tag, made the edits, and reproduced the download path. Every claim above is the output of a command that was run.

  • A human directed this change and is accountable for it.
  • Claims in this PR were verified against the repository or a running system, rather than assumed.

Reviewer Focus

  1. The @v0.8.3 ref. A commit SHA would be stricter supply-chain practice, but every other action in this workflow is tag-pinned (actions/checkout@v4, taiki-e/install-action@v2, EmbarkStudios/cargo-deny-action@v2), so I matched house convention. Happy to switch to @53033f5 if you'd rather.
  2. Whether the js job should keep touching cargo at all. Reading the pin is the only reason left. Alternatives all mean a second parser, which CI never checks that committed migrations match schema.cstack #204 explicitly closed — but if you'd accept, say, committing the pin to a small file that both cargo and bash can read, that's a different design worth its own change.

Checklist

  • docs/roadmap.md checked — no edit needed; this changes no milestone, gate, dependency or decision.
  • No framework/toolchain surprise to record — the action behaved exactly as its own action.yml documents.
  • No new R1 exceptions.

Both places CI installs the CLI ran `cargo install cratestack-cli
--version <pin> --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 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: de35570

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@stephane-segning
stephane-segning merged commit a4c5962 into main Aug 18, 2026
7 of 9 checks passed
@stephane-segning
stephane-segning deleted the use-cratestack-install-action branch August 18, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant