From 7656cd5afc3f528a656b3dc951d48351ee0ff327 Mon Sep 17 00:00:00 2001 From: Rob Lyon Date: Fri, 24 Jul 2026 08:37:18 -0700 Subject: [PATCH 1/4] ci(enactr): add native git release flows --- .enactr/README.md | 40 +++++++ .enactr/flows/bump.yaml | 71 ++++++++++++ .enactr/flows/ci.yaml | 88 +++++++++++++++ .enactr/flows/main.yaml | 86 +++++++++++++++ .enactr/flows/release.yaml | 217 +++++++++++++++++++++++++++++++++++++ 5 files changed, 502 insertions(+) create mode 100644 .enactr/README.md create mode 100644 .enactr/flows/bump.yaml create mode 100644 .enactr/flows/ci.yaml create mode 100644 .enactr/flows/main.yaml create mode 100644 .enactr/flows/release.yaml diff --git a/.enactr/README.md b/.enactr/README.md new file mode 100644 index 0000000..cc181bc --- /dev/null +++ b/.enactr/README.md @@ -0,0 +1,40 @@ +# Enactr flows + +Flywheel's Enactr CI and release automation lives in `flows/`. These definitions +coexist with the GitHub Actions workflows; switching off the GitHub workflows is +a separate cutover decision. + +The flows expect these tenant secrets: + +| Secret | Used for | +| --- | --- | +| `CODECOV_TOKEN` | Uploading Rust coverage | +| `DOCKERHUB_USERNAME` | Authenticating to Docker Hub | +| `DOCKERHUB_TOKEN` | Pushing images and manifests to Docker Hub | +| `GH_TOKEN` | Creating GitHub releases | + +The `GH_TOKEN` credential needs write access to repository contents so GitHub +can create the release tag. Native Git actions use the connected repository's +short-lived installation token instead. Docker image builds run natively and +concurrently on the hosted `build-amd64` and `build-arm64` queues. Each build +pushes an architecture tag; the dependent manifest action combines those +images into the version tag and `latest`. + +The `bump-version` flow checks out the reusable `enactr/release-bump` branch, +commits the selected version increment with Enactr's native Git module, pushes +it, and reconciles one pull request to protected `main`. Merging that pull +request triggers the `release` flow. The release publishes the Helm index from +a native `gh-pages` worktree; the action packages the exact run commit from a +GitHub source archive so the worktree remains single-purpose. + +The original CI topology is represented by separate `ci` and `main` flows. +Format and Clippy fan out first; Test and the release build then run in +parallel, while Coverage waits specifically for Test. + +Two GitHub Actions behaviors do not yet have direct Enactr equivalents in these +flows: + +- CI does not cancel an older run for the same pull-request ref; Enactr + concurrency is flow-wide rather than grouped by ref. +- The Rust build cache is not persisted because no durable Enactr storage + backend has been selected for Cargo state. diff --git a/.enactr/flows/bump.yaml b/.enactr/flows/bump.yaml new file mode 100644 index 0000000..777c54d --- /dev/null +++ b/.enactr/flows/bump.yaml @@ -0,0 +1,71 @@ +name: bump-version + +on: + workflow_dispatch: {} + +concurrency: 1 + +agents: + queue: default + +actions: + - input: Version bump + prompt: Choose the semantic version component to increment. + fields: + - select: Bump + key: bump + required: true + options: + - label: Patch + value: patch + - label: Minor + value: minor + - label: Major + value: major + + - label: Bump version + key: bump + image: rust:1.94-bookworm + timeout: 20m + git: + ref: enactr/release-bump + create: true + create_from: main + fetch_depth: 0 + clean: true + commit: + message: "chore(release): bump version" + author_name: github-actions[bot] + author_email: github-actions[bot]@users.noreply.github.com + paths: + - Cargo.toml + - Cargo.lock + - charts/flywheel/Chart.yaml + push: + atomic: true + pull_request: + base: main + title: "chore(release): bump version" + body: "Prepare the next Flywheel release. CI will run on this pull request before it can merge to protected main." + command: | + set -eu + + current=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) + IFS=. read -r major minor patch <&2; exit 1 ;; + esac + + new="${major}.${minor}.${patch}" + echo "Bumping $current -> $new" + + sed -i "0,/^version = \".*\"/s//version = \"$new\"/" Cargo.toml + cargo update --workspace + sed -i "s/^version:.*/version: $new/" charts/flywheel/Chart.yaml + sed -i "s/^appVersion:.*/appVersion: \"$new\"/" charts/flywheel/Chart.yaml diff --git a/.enactr/flows/ci.yaml b/.enactr/flows/ci.yaml new file mode 100644 index 0000000..f933a70 --- /dev/null +++ b/.enactr/flows/ci.yaml @@ -0,0 +1,88 @@ +name: ci + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + workflow_dispatch: {} + +concurrency: 10 + +env: + CARGO_TERM_COLOR: always + +agents: + queue: default + +actions: + - group: Static checks + actions: + - label: Format + key: fmt + image: rust:1.94-bookworm + command: | + set -eu + rustup component add rustfmt + cargo fmt --check + + - label: Clippy + key: clippy + image: rust:1.94-bookworm + timeout: 30m + command: | + set -eu + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + rustup component add clippy + cargo clippy --all-targets --all-features -- -D warnings + + - group: Test, coverage, and build + actions: + - label: Test + key: test + image: rust:1.94-bookworm + timeout: 40m + command: | + set -eu + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + cargo test --all-targets + + - label: Coverage + key: coverage + image: rust:1.94-bookworm + depends_on: [test] + timeout: 60m + secrets: + CODECOV_TOKEN: enactr://CODECOV_TOKEN + command: | + set -eu + + apt-get update + apt-get install --yes --no-install-recommends curl libclang-dev + rm -rf /var/lib/apt/lists/* + rustup component add llvm-tools-preview + cargo install cargo-llvm-cov --locked + cargo llvm-cov --all-targets --lcov --output-path lcov.info + + curl -Os https://cli.codecov.io/latest/linux/codecov + chmod +x codecov + ./codecov --verbose upload-process \ + --disable-search \ + --fail-on-error \ + --token "$CODECOV_TOKEN" \ + --name "enactr-${ENACTR_RUN_ID}" \ + --file lcov.info + + - label: Release build + key: build + image: rust:1.94-bookworm + timeout: 40m + command: | + set -eu + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + cargo build --release diff --git a/.enactr/flows/main.yaml b/.enactr/flows/main.yaml new file mode 100644 index 0000000..c001eed --- /dev/null +++ b/.enactr/flows/main.yaml @@ -0,0 +1,86 @@ +name: main + +on: + push: + branches: [main] + +concurrency: 1 + +env: + CARGO_TERM_COLOR: always + +agents: + queue: default + +actions: + - group: Static checks + actions: + - label: Format + key: fmt + image: rust:1.94-bookworm + command: | + set -eu + rustup component add rustfmt + cargo fmt --check + + - label: Clippy + key: clippy + image: rust:1.94-bookworm + timeout: 30m + command: | + set -eu + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + rustup component add clippy + cargo clippy --all-targets --all-features -- -D warnings + + - group: Test, coverage, and build + actions: + - label: Test + key: test + image: rust:1.94-bookworm + timeout: 40m + command: | + set -eu + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + cargo test --all-targets + + - label: Coverage + key: coverage + image: rust:1.94-bookworm + depends_on: [test] + timeout: 60m + secrets: + CODECOV_TOKEN: enactr://CODECOV_TOKEN + command: | + set -eu + + apt-get update + apt-get install --yes --no-install-recommends curl libclang-dev + rm -rf /var/lib/apt/lists/* + rustup component add llvm-tools-preview + cargo install cargo-llvm-cov --locked + cargo llvm-cov --all-targets --lcov --output-path lcov.info + + curl -Os https://cli.codecov.io/latest/linux/codecov + chmod +x codecov + ./codecov --verbose upload-process \ + --disable-search \ + --fail-on-error \ + --token "$CODECOV_TOKEN" \ + --name "enactr-${ENACTR_RUN_ID}" \ + --file lcov.info + + - label: Release build + key: build + image: rust:1.94-bookworm + timeout: 40m + command: | + set -eu + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + cargo build --release diff --git a/.enactr/flows/release.yaml b/.enactr/flows/release.yaml new file mode 100644 index 0000000..b1e6d13 --- /dev/null +++ b/.enactr/flows/release.yaml @@ -0,0 +1,217 @@ +name: release + +on: + workflow_dispatch: {} + push: + branches: [main] + paths: + - Cargo.toml + - Cargo.lock + - charts/flywheel/Chart.yaml + +concurrency: 1 + +env: + CARGO_TERM_COLOR: always + IMAGE_REPOSITORY: docker.io/ctxsh/flywheel + +agents: + queue: default + +actions: + - label: Resolve release tag + key: resolve-tag + image: alpine:3.22 + command: | + set -eu + + version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) + tag="v${version}" + enactr metadata set tag "$tag" + echo "Resolved release tag: $tag" + + - label: Release checks + key: release-checks + image: rust:1.94-bookworm + timeout: 60m + command: | + set -eu + + apt-get update + apt-get install --yes --no-install-recommends libclang-dev + rm -rf /var/lib/apt/lists/* + rustup component add rustfmt clippy + cargo fmt --check + cargo clippy --all-targets --all-features -- -D warnings + cargo test --all-targets + + - group: Native image builds + actions: + - label: Build linux/amd64 image + key: image-amd64 + image: quay.io/buildah/stable:v1.43 + agents: + queue: build-amd64 + timeout: 90m + env: + STORAGE_DRIVER: vfs + BUILDAH_ISOLATION: chroot + secrets: + DOCKERHUB_USERNAME: enactr://DOCKERHUB_USERNAME + DOCKERHUB_TOKEN: enactr://DOCKERHUB_TOKEN + command: | + set -eu + + test "$(uname -m)" = "x86_64" + tag=$(enactr metadata get tag) + destination="${IMAGE_REPOSITORY}:${tag}-amd64" + + printf '%s' "$DOCKERHUB_TOKEN" | + buildah login --username "$DOCKERHUB_USERNAME" --password-stdin docker.io + buildah bud \ + --arch amd64 \ + --format docker \ + --isolation chroot \ + --tag "$destination" \ + . + buildah push "$destination" "docker://$destination" + + - label: Build linux/arm64 image + key: image-arm64 + image: quay.io/buildah/stable:v1.43 + agents: + queue: build-arm64 + timeout: 90m + env: + STORAGE_DRIVER: vfs + BUILDAH_ISOLATION: chroot + secrets: + DOCKERHUB_USERNAME: enactr://DOCKERHUB_USERNAME + DOCKERHUB_TOKEN: enactr://DOCKERHUB_TOKEN + command: | + set -eu + + test "$(uname -m)" = "aarch64" + tag=$(enactr metadata get tag) + destination="${IMAGE_REPOSITORY}:${tag}-arm64" + + printf '%s' "$DOCKERHUB_TOKEN" | + buildah login --username "$DOCKERHUB_USERNAME" --password-stdin docker.io + buildah bud \ + --arch arm64 \ + --format docker \ + --isolation chroot \ + --tag "$destination" \ + . + buildah push "$destination" "docker://$destination" + + - label: Publish multi-architecture manifests + key: image-manifest + image: quay.io/buildah/stable:v1.43 + depends_on: [image-amd64, image-arm64] + timeout: 20m + env: + STORAGE_DRIVER: vfs + secrets: + DOCKERHUB_USERNAME: enactr://DOCKERHUB_USERNAME + DOCKERHUB_TOKEN: enactr://DOCKERHUB_TOKEN + command: | + set -eu + + tag=$(enactr metadata get tag) + manifest="flywheel-${ENACTR_RUN_ID}" + + printf '%s' "$DOCKERHUB_TOKEN" | + buildah login --username "$DOCKERHUB_USERNAME" --password-stdin docker.io + buildah manifest create "$manifest" + buildah manifest add \ + --arch amd64 \ + "$manifest" \ + "docker://${IMAGE_REPOSITORY}:${tag}-amd64" + buildah manifest add \ + --arch arm64 \ + "$manifest" \ + "docker://${IMAGE_REPOSITORY}:${tag}-arm64" + buildah manifest push --all "$manifest" "docker://${IMAGE_REPOSITORY}:${tag}" + buildah manifest push --all "$manifest" "docker://${IMAGE_REPOSITORY}:latest" + + - label: Publish Helm chart + key: helm + image: alpine/helm:3.20.2 + depends_on: [image-manifest] + timeout: 20m + git: + ref: gh-pages + fetch_depth: 0 + clean: true + commit: + message: "chore(release): publish chart" + author_name: github-actions[bot] + author_email: github-actions[bot]@users.noreply.github.com + push: + atomic: true + command: | + set -eu + + source_dir=$(mktemp -d) + trap 'rm -rf "$source_dir"' EXIT + wget -qO- \ + "https://github.com/ctxswitch/flywheel/archive/${ENACTR_COMMIT}.tar.gz" | + tar -xz --strip-components=1 -C "$source_dir" + + helm lint "$source_dir/charts/flywheel" --strict + helm package "$source_dir/charts/flywheel" --destination "$source_dir" + + cp "$source_dir"/*.tgz . + touch .nojekyll + if [ -f index.yaml ]; then + helm repo index . \ + --url https://ctxswitch.github.io/flywheel \ + --merge index.yaml + else + helm repo index . --url https://ctxswitch.github.io/flywheel + fi + + - label: Publish GitHub release + key: github-release + image: alpine:3.22 + depends_on: [image-manifest, helm] + timeout: 20m + secrets: + GH_TOKEN: enactr://GH_TOKEN + command: | + set -eu + + apk add --no-cache github-cli + tag=$(enactr metadata get tag) + chart_version=$(sed -n 's/^version: *//p' charts/flywheel/Chart.yaml | head -1) + + if gh release view "$tag" --repo ctxswitch/flywheel >/dev/null 2>&1; then + echo "GitHub release $tag already exists." + else + notes=$(cat < Date: Sat, 25 Jul 2026 07:23:09 -0700 Subject: [PATCH 2/4] docs(enactr): explain git worktrees --- .enactr/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.enactr/README.md b/.enactr/README.md index cc181bc..16ec3ba 100644 --- a/.enactr/README.md +++ b/.enactr/README.md @@ -4,6 +4,32 @@ Flywheel's Enactr CI and release automation lives in `flows/`. These definitions coexist with the GitHub Actions workflows; switching off the GitHub workflows is a separate cutover decision. +## Git worktrees + +Every command action receives its own repository worktree. Actions without a +`git` block use Enactr's automatic checkout: the connected Flywheel repository +is cloned into `/workspace/src`, the run's exact `ENACTR_COMMIT` is checked out +detached, and nothing is published afterward. The `ci`, `main`, release-check, +image-build, manifest, and GitHub-release actions all use this read-only mode. + +Worktrees are isolated by action, including actions in the same group. A file +created by one action is not visible to another unless it is transferred +through Storage, a pushed Git ref, or another external artifact service. + +Only actions that need branch behavior or publication declare `git`: + +| Action | Worktree | After a successful command | +| --- | --- | --- | +| `bump-version` | Reusable `enactr/release-bump` branch | Commit version files, push, and reconcile one PR to `main` | +| Release `helm` | `gh-pages` branch | Commit the chart archive and index, then push | + +The Helm action can own only its `gh-pages` worktree, so it downloads the source +archive addressed by `ENACTR_COMMIT` before packaging the chart. Native Git +credentials come from the connected repository's short-lived installation +token and are redacted by the runtime. `GH_TOKEN` remains separate because +GitHub release creation is a provider API operation outside the native Git +module. + The flows expect these tenant secrets: | Secret | Used for | From f1933f653b7dc3776960cb50b97009fb1c0c0195 Mon Sep 17 00:00:00 2001 From: Rob Lyon Date: Sat, 25 Jul 2026 07:46:53 -0700 Subject: [PATCH 3/4] fix(release): prevent duplicate publication --- .enactr/README.md | 21 ++++++---- .enactr/flows/bump.yaml | 71 ---------------------------------- .enactr/flows/release.yaml | 57 ++++++++++++++++++++++++--- .github/workflows/release.yaml | 24 ++++++------ 4 files changed, 78 insertions(+), 95 deletions(-) delete mode 100644 .enactr/flows/bump.yaml diff --git a/.enactr/README.md b/.enactr/README.md index 16ec3ba..de7700f 100644 --- a/.enactr/README.md +++ b/.enactr/README.md @@ -20,7 +20,6 @@ Only actions that need branch behavior or publication declare `git`: | Action | Worktree | After a successful command | | --- | --- | --- | -| `bump-version` | Reusable `enactr/release-bump` branch | Commit version files, push, and reconcile one PR to `main` | | Release `helm` | `gh-pages` branch | Commit the chart archive and index, then push | The Helm action can own only its `gh-pages` worktree, so it downloads the source @@ -46,12 +45,20 @@ concurrently on the hosted `build-amd64` and `build-arm64` queues. Each build pushes an architecture tag; the dependent manifest action combines those images into the version tag and `latest`. -The `bump-version` flow checks out the reusable `enactr/release-bump` branch, -commits the selected version increment with Enactr's native Git module, pushes -it, and reconciles one pull request to protected `main`. Merging that pull -request triggers the `release` flow. The release publishes the Helm index from -a native `gh-pages` worktree; the action packages the exact run commit from a -GitHub source archive so the worktree remains single-purpose. +Version bumps remain owned by `.github/workflows/bump.yaml`. Native Git +configuration is literal, so an Enactr bump flow cannot yet create a fresh +version-named branch safely; reusing a branch can start a later bump from stale +or abandoned release state. + +The Enactr `release` flow is manual-only while the GitHub and Enactr +implementations coexist. Before doing any release work, it verifies that an +existing tag points to the run commit and exits without publication when the +GitHub release already exists. The push trigger should be enabled only in the +cutover that disables the GitHub release publisher. + +The release publishes the Helm index from a native `gh-pages` worktree; the +action packages the exact run commit from a GitHub source archive so the +worktree remains single-purpose. The original CI topology is represented by separate `ci` and `main` flows. Format and Clippy fan out first; Test and the release build then run in diff --git a/.enactr/flows/bump.yaml b/.enactr/flows/bump.yaml deleted file mode 100644 index 777c54d..0000000 --- a/.enactr/flows/bump.yaml +++ /dev/null @@ -1,71 +0,0 @@ -name: bump-version - -on: - workflow_dispatch: {} - -concurrency: 1 - -agents: - queue: default - -actions: - - input: Version bump - prompt: Choose the semantic version component to increment. - fields: - - select: Bump - key: bump - required: true - options: - - label: Patch - value: patch - - label: Minor - value: minor - - label: Major - value: major - - - label: Bump version - key: bump - image: rust:1.94-bookworm - timeout: 20m - git: - ref: enactr/release-bump - create: true - create_from: main - fetch_depth: 0 - clean: true - commit: - message: "chore(release): bump version" - author_name: github-actions[bot] - author_email: github-actions[bot]@users.noreply.github.com - paths: - - Cargo.toml - - Cargo.lock - - charts/flywheel/Chart.yaml - push: - atomic: true - pull_request: - base: main - title: "chore(release): bump version" - body: "Prepare the next Flywheel release. CI will run on this pull request before it can merge to protected main." - command: | - set -eu - - current=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) - IFS=. read -r major minor patch <&2; exit 1 ;; - esac - - new="${major}.${minor}.${patch}" - echo "Bumping $current -> $new" - - sed -i "0,/^version = \".*\"/s//version = \"$new\"/" Cargo.toml - cargo update --workspace - sed -i "s/^version:.*/version: $new/" charts/flywheel/Chart.yaml - sed -i "s/^appVersion:.*/appVersion: \"$new\"/" charts/flywheel/Chart.yaml diff --git a/.enactr/flows/release.yaml b/.enactr/flows/release.yaml index b1e6d13..351af90 100644 --- a/.enactr/flows/release.yaml +++ b/.enactr/flows/release.yaml @@ -2,12 +2,6 @@ name: release on: workflow_dispatch: {} - push: - branches: [main] - paths: - - Cargo.toml - - Cargo.lock - - charts/flywheel/Chart.yaml concurrency: 1 @@ -22,12 +16,33 @@ actions: - label: Resolve release tag key: resolve-tag image: alpine:3.22 + secrets: + GH_TOKEN: enactr://GH_TOKEN command: | set -eu + apk add --no-cache github-cli version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1) tag="v${version}" enactr metadata set tag "$tag" + + if tag_type=$(gh api "repos/ctxswitch/flywheel/git/ref/tags/${tag}" --jq '.object.type' 2>/dev/null); then + tag_sha=$(gh api "repos/ctxswitch/flywheel/git/ref/tags/${tag}" --jq '.object.sha') + if [ "$tag_type" = "tag" ]; then + tag_sha=$(gh api "repos/ctxswitch/flywheel/git/tags/${tag_sha}" --jq '.object.sha') + fi + if [ "$tag_sha" != "$ENACTR_COMMIT" ]; then + echo "Release tag $tag already points to $tag_sha, not $ENACTR_COMMIT." >&2 + exit 1 + fi + fi + + if gh release view "$tag" --repo ctxswitch/flywheel >/dev/null 2>&1; then + enactr metadata set release_required false + echo "GitHub release $tag already exists; publication is a no-op." + else + enactr metadata set release_required true + fi echo "Resolved release tag: $tag" - label: Release checks @@ -37,6 +52,11 @@ actions: command: | set -eu + if [ "$(enactr metadata get release_required)" != "true" ]; then + echo "Release already exists; skipping release checks." + exit 0 + fi + apt-get update apt-get install --yes --no-install-recommends libclang-dev rm -rf /var/lib/apt/lists/* @@ -62,6 +82,11 @@ actions: command: | set -eu + if [ "$(enactr metadata get release_required)" != "true" ]; then + echo "Release already exists; skipping linux/amd64 image." + exit 0 + fi + test "$(uname -m)" = "x86_64" tag=$(enactr metadata get tag) destination="${IMAGE_REPOSITORY}:${tag}-amd64" @@ -91,6 +116,11 @@ actions: command: | set -eu + if [ "$(enactr metadata get release_required)" != "true" ]; then + echo "Release already exists; skipping linux/arm64 image." + exit 0 + fi + test "$(uname -m)" = "aarch64" tag=$(enactr metadata get tag) destination="${IMAGE_REPOSITORY}:${tag}-arm64" @@ -118,6 +148,11 @@ actions: command: | set -eu + if [ "$(enactr metadata get release_required)" != "true" ]; then + echo "Release already exists; skipping image manifests." + exit 0 + fi + tag=$(enactr metadata get tag) manifest="flywheel-${ENACTR_RUN_ID}" @@ -153,6 +188,11 @@ actions: command: | set -eu + if [ "$(enactr metadata get release_required)" != "true" ]; then + echo "Release already exists; skipping Helm publication." + exit 0 + fi + source_dir=$(mktemp -d) trap 'rm -rf "$source_dir"' EXIT wget -qO- \ @@ -182,6 +222,11 @@ actions: command: | set -eu + if [ "$(enactr metadata get release_required)" != "true" ]; then + echo "Release already exists; skipping GitHub release." + exit 0 + fi + apk add --no-cache github-cli tag=$(enactr metadata get tag) chart_version=$(sed -n 's/^version: *//p' charts/flywheel/Chart.yaml | head -1) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7ec773f..abe12f5 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,6 +23,7 @@ jobs: outputs: tag: ${{ steps.tag.outputs.tag }} release: ${{ steps.tag.outputs.release }} + source_ref: ${{ steps.tag.outputs.source_ref }} steps: - uses: actions/checkout@v4 with: @@ -40,13 +41,11 @@ jobs: echo "release=false" >> "$GITHUB_OUTPUT" else echo "release=true" >> "$GITHUB_OUTPUT" + echo "source_ref=$tag" >> "$GITHUB_OUTPUT" fi else - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag "$tag" - git push origin "$tag" echo "release=true" >> "$GITHUB_OUTPUT" + echo "source_ref=${{ github.sha }}" >> "$GITHUB_OUTPUT" fi echo "tag=$tag" >> "$GITHUB_OUTPUT" @@ -60,7 +59,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ needs.resolve-tag.outputs.tag }} + ref: ${{ needs.resolve-tag.outputs.source_ref }} - name: Install native build dependencies run: sudo apt-get update && sudo apt-get install --yes --no-install-recommends libclang-dev - uses: dtolnay/rust-toolchain@stable @@ -79,7 +78,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ needs.resolve-tag.outputs.tag }} + ref: ${{ needs.resolve-tag.outputs.source_ref }} - uses: docker/login-action@v3 with: @@ -108,7 +107,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ needs.resolve-tag.outputs.tag }} + ref: ${{ needs.resolve-tag.outputs.source_ref }} - name: Install Helm uses: azure/setup-helm@v4 @@ -154,7 +153,7 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ needs.resolve-tag.outputs.tag }} + ref: ${{ needs.resolve-tag.outputs.source_ref }} fetch-depth: 0 - name: Read chart version @@ -166,11 +165,13 @@ jobs: - name: Generate changelog id: changelog run: | - prev_tag=$(git tag --sort=-v:refname | grep '^v' | sed -n '2p') + tag='${{ needs.resolve-tag.outputs.tag }}' + source_ref='${{ needs.resolve-tag.outputs.source_ref }}' + prev_tag=$(git tag --sort=-v:refname | grep '^v' | grep -Fvx "$tag" | sed -n '1p') if [ -z "$prev_tag" ]; then - log=$(git log --pretty=format:"- %s (%h)" '${{ needs.resolve-tag.outputs.tag }}') + log=$(git log --pretty=format:"- %s (%h)" "$source_ref") else - log=$(git log --pretty=format:"- %s (%h)" "${prev_tag}..${{ needs.resolve-tag.outputs.tag }}") + log=$(git log --pretty=format:"- %s (%h)" "${prev_tag}..${source_ref}") fi { @@ -183,6 +184,7 @@ jobs: uses: softprops/action-gh-release@v2 with: tag_name: ${{ needs.resolve-tag.outputs.tag }} + target_commitish: ${{ needs.resolve-tag.outputs.source_ref }} generate_release_notes: false body: | ## Docker Image From b6da0c3d40f743ebcc52e6591fe863101352e4ee Mon Sep 17 00:00:00 2001 From: Rob Lyon Date: Sat, 25 Jul 2026 07:49:09 -0700 Subject: [PATCH 4/4] refactor(release): simplify enactr preflight --- .enactr/README.md | 6 ++-- .enactr/flows/release.yaml | 58 ++++++++------------------------------ 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/.enactr/README.md b/.enactr/README.md index de7700f..12d3ee3 100644 --- a/.enactr/README.md +++ b/.enactr/README.md @@ -52,9 +52,9 @@ or abandoned release state. The Enactr `release` flow is manual-only while the GitHub and Enactr implementations coexist. Before doing any release work, it verifies that an -existing tag points to the run commit and exits without publication when the -GitHub release already exists. The push trigger should be enabled only in the -cutover that disables the GitHub release publisher. +existing tag points to the run commit and fails closed when the GitHub release +already exists. The push trigger should be enabled only in the cutover that +disables the GitHub release publisher. The release publishes the Helm index from a native `gh-pages` worktree; the action packages the exact run commit from a GitHub source archive so the diff --git a/.enactr/flows/release.yaml b/.enactr/flows/release.yaml index 351af90..2cf6af7 100644 --- a/.enactr/flows/release.yaml +++ b/.enactr/flows/release.yaml @@ -13,7 +13,7 @@ agents: queue: default actions: - - label: Resolve release tag + - label: Preflight release key: resolve-tag image: alpine:3.22 secrets: @@ -38,12 +38,10 @@ actions: fi if gh release view "$tag" --repo ctxswitch/flywheel >/dev/null 2>&1; then - enactr metadata set release_required false - echo "GitHub release $tag already exists; publication is a no-op." - else - enactr metadata set release_required true + echo "GitHub release $tag already exists; refusing to republish it." >&2 + exit 1 fi - echo "Resolved release tag: $tag" + echo "Release $tag is clear to publish from $ENACTR_COMMIT." - label: Release checks key: release-checks @@ -52,11 +50,6 @@ actions: command: | set -eu - if [ "$(enactr metadata get release_required)" != "true" ]; then - echo "Release already exists; skipping release checks." - exit 0 - fi - apt-get update apt-get install --yes --no-install-recommends libclang-dev rm -rf /var/lib/apt/lists/* @@ -82,11 +75,6 @@ actions: command: | set -eu - if [ "$(enactr metadata get release_required)" != "true" ]; then - echo "Release already exists; skipping linux/amd64 image." - exit 0 - fi - test "$(uname -m)" = "x86_64" tag=$(enactr metadata get tag) destination="${IMAGE_REPOSITORY}:${tag}-amd64" @@ -116,11 +104,6 @@ actions: command: | set -eu - if [ "$(enactr metadata get release_required)" != "true" ]; then - echo "Release already exists; skipping linux/arm64 image." - exit 0 - fi - test "$(uname -m)" = "aarch64" tag=$(enactr metadata get tag) destination="${IMAGE_REPOSITORY}:${tag}-arm64" @@ -148,11 +131,6 @@ actions: command: | set -eu - if [ "$(enactr metadata get release_required)" != "true" ]; then - echo "Release already exists; skipping image manifests." - exit 0 - fi - tag=$(enactr metadata get tag) manifest="flywheel-${ENACTR_RUN_ID}" @@ -188,11 +166,6 @@ actions: command: | set -eu - if [ "$(enactr metadata get release_required)" != "true" ]; then - echo "Release already exists; skipping Helm publication." - exit 0 - fi - source_dir=$(mktemp -d) trap 'rm -rf "$source_dir"' EXIT wget -qO- \ @@ -222,19 +195,11 @@ actions: command: | set -eu - if [ "$(enactr metadata get release_required)" != "true" ]; then - echo "Release already exists; skipping GitHub release." - exit 0 - fi - apk add --no-cache github-cli tag=$(enactr metadata get tag) chart_version=$(sed -n 's/^version: *//p' charts/flywheel/Chart.yaml | head -1) - if gh release view "$tag" --repo ctxswitch/flywheel >/dev/null 2>&1; then - echo "GitHub release $tag already exists." - else - notes=$(cat <