Repository navigation
chore(ci): pin every workflow action to a commit SHA - #130
Open
aliasfoxkde wants to merge 3 commits into
Open
aliasfoxkde wants to merge 3 commits into
aliasfoxkde wants to merge 3 commits into
Conversation
The release workflow builds the artifacts and creates the draft release, and it named its actions by mutable reference: `actions/checkout@v4`, `dtolnay/rust-toolchain@stable`, `actions/cache@v4`, `actions/upload-artifact@v4`, `actions/download-artifact@v4` and `cloudflare/wrangler-action@v3`. A tag can be moved and a branch moves on its own, so the release path executed code that could change with no diff to review here. All 19 action references now name a 40-hex commit SHA, with the version kept in a trailing comment. The rule is enforced, not reviewed: `scripts/validate_workflow_pins.py` walks the workflow directory and fails with the file and line of every unpinned reference, and it runs in the backup CI's `changes` job — the job every push and pull request executes, and the one `fmt` and `gate` depend on. `scripts/validate_workflow_pins-selftest.sh` demonstrates each failure mode with one deliberately broken input (a mutable tag, a mutable branch, a `docker://` image, a short SHA) and the two inputs the validator must accept. The deploy workflow triggered on a push that touched the workflow file itself, so every edit to it published the marketing site. The trigger is now the content it publishes — `site/**` and `wrangler.toml` — and a deliberate redeploy is `workflow_dispatch`. Decision 0099; ticket T27-57. Co-Authored-By: Claude Code <noreply@anthropic.com>
Contributor
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 6 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
The release workflow decided what a tag meant nowhere: it derived the artifact names from the tag, created every release as an ordinary draft, and had no notion of a platform. The aggregate validation that stands between a build and a release was a hundred lines of inline YAML that ran once, on a tag, with nothing around it to tell it apart from a run that was merely quiet. Three seams are now scripts with self-tests, and all three run in the backup CI's `changes` job: * `scripts/classify_release_mode.sh` maps `vX.Y.Z` to a production release and `vX.Y.Z-dryrun.N` to a rehearsal, and refuses every other shape — a two-component version, a missing `v` prefix, an unknown suffix, a `-dryrun` without a number, a non-numeric number, a trailing hyphen, and a tag that names another version. A manual `workflow_dispatch` run may only rehearse; cutting a production release still requires a tag push. * `scripts/validate_release_aggregate.sh` holds the fail-closed gate. It now also rejects an artifact the release does not publish, and requires every required platform to be represented in the release manifest — a skipped build was indistinguishable from a build nobody asked for. * `packaging/release-assets.sh` takes a mode. A production release is a draft titled by the version and described by its tracked release notes; a rehearsal is a prerelease titled `TEST <version>` whose notes carry a banner saying it must not be published. The rehearsal's disposable tag is created at the exact commit the build ran on, so the artifacts and the tag bind to one SHA, and an existing tag is never overwritten. The artifacts are named by the version the build produced, not by the tag: the tag says what is being released, the version says what was built. Decision 0100 records the reasoning; the ticket, the execution board, the task ledger, RELEASING.md and HANDOFF.md carry the new shape. Co-Authored-By: Claude Code <noreply@anthropic.com>
This was referenced Nov 12, 2026
The release gate now refuses a production release whose description is not a tracked `docs/releases/<tag>.md` note, but the release-workflow tests still staged a bare `notes.md` — so the three tests that drive a production `gh release create` failed on the new rule. The fixtures now stage the notes where a real release keeps them. Co-Authored-By: Claude Code <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.
What this does. Hardens the release workflows (ticket T27-57, issue #97) in three parts: every GitHub Action is pinned to a commit SHA and the rule fails closed in CI; a release tag is classified before it can cut anything, and the two accepted shapes are a production release and a rehearsal; and the fail-closed aggregate gate moves out of inline YAML into a script that is exercised by its own self-test.
Why. The release workflow builds the published artifacts and creates the draft release, and it named its actions by mutable reference —
actions/checkout@v4,dtolnay/rust-toolchain@stable,actions/cache@v4,actions/upload-artifact@v4,actions/download-artifact@v4,cloudflare/wrangler-action@v3. A tag can be moved and a branch moves on its own, so the release path could execute code that changed with no diff to review here.The same workflow also decided nowhere what a tag meant. It derived every artifact name from the tag, created every release as an ordinary draft, and had no notion of a platform at all — a skipped build was indistinguishable from a build nobody asked for. The rehearsal recorded in issue #99 (
v0.2.5-dryrun.3) produced a draft whose title was0.2.5-dryrun.3and whoseisPrereleaseflag was false: nothing in the tooling marked it as a rehearsal.The change — pinning (decision 0099).
ci.yml,release.ymlanddeploy-site.ymlname a 40-hex commit SHA; the version is kept in a trailing comment so the pin is auditable without a network call.scripts/validate_workflow_pins.py(stdlib-only) walks the workflow directory and fails with the file and line of every unpinned reference. It runs in the backup CI'schangesjob — the job every push and pull request executes, and the onefmtandgatedepend on — so an unpinned action fails the workflow before anything builds or publishes.scripts/validate_workflow_pins-selftest.shdemonstrates each failure mode with one deliberately broken input (a mutable tag, a mutable branch, adocker://image, a short SHA) and the two inputs the validator must accept (a pinned SHA, a local action).deploy-site.ymlno longer triggers on a push that touches the workflow file itself: the trigger is the content it publishes (site/**,wrangler.toml), and a deliberate redeploy isworkflow_dispatch. Editing the deploy workflow used to publish the marketing site.The change — release mode and a tested gate (decision 0100).
scripts/classify_release_mode.shmapsvX.Y.Zto a production release andvX.Y.Z-dryrun.Nto a rehearsal, and refuses every other shape: a two-component version, a tag without thevprefix, an unknown prerelease suffix (-rc.1), a-dryrunwithout a number, a non-numeric number, a trailing hyphen, and a tag that names another version than the tree builds. The release job fails closed when the classifier refuses.workflow_dispatchtrigger that may only rehearse: a manual run whose tag classifies as a production release is refused, because cutting a production release requires a tag push.packaging/release-assets.shtakes a mode. A production release is a draft titled by the version and described by its tracked release notes underdocs/releases/— a checksum dump is not a release description. A rehearsal is a prerelease titledTEST <version>whose notes carry a banner saying it must not be published.gh release createwould create the tag at the default branch), and an existing tag is never overwritten.cargo metadata.scripts/validate_release_aggregate.shholds the aggregate gate. It now also rejects an artifact the release does not publish (a stale file from an earlier version rode along unnoticed), and requires every required platform (x86_64-unknown-linux-gnu,wasm32-unknown-unknown) to be represented in the release manifest.Verification. All five run locally on this tree and in the backup CI's
changesjob:Every failure mode is demonstrated with one deliberately broken input, including the discriminating case for the platform check — an artifact set where every file is present and every digest agrees, which still fails because nothing carries the wasm platform's target triple.
Not in this change. The rehearsal path has not been run end to end through
workflow_dispatch: it needs this workflow onmainfirst, and the manual rehearsal in issue #99 predates theTESTtitle and the prerelease flag, so it does not yet demonstrate this mode. The checks are in the GitHub backup CI; mirroring them into the GitForge pipeline waits for the:2pipeline shape (PR #123, PR #129).Decisions 0099 and 0100; ticket T27-57.
🤖 Generated with Claude Code