You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by ja...@apache.org on 2026/08/26 17:07:30 UTC
(grails-core) branch ci/atr-release-automation created (now ae4f3c4697)
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a change to branch ci/atr-release-automation
in repository https://gitbox.apache.org/repos/asf/grails-core.git
at ae4f3c4697 ci: add opt-in Apache Trusted Releases automation
This branch includes the following new commits:
new ae4f3c4697 ci: add opt-in Apache Trusted Releases automation
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(grails-core) 01/01: ci: add opt-in Apache Trusted Releases automation
Posted by ja...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
jamesfredley pushed a commit to branch ci/atr-release-automation
in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit ae4f3c4697259142dc1d0d77b4a4f17270dc35d9
Author: James Fredley <ja...@users.noreply.github.com>
AuthorDate: Wed Aug 26 13:07:08 2026 -0400
ci: add opt-in Apache Trusted Releases automation
Map ASF Tooling Actions onto Grails release MANUAL steps without replacing
the default dist/dev plus Nexus path. Experimental ATR Alpha 3 workflows
cover compose, vote resolve, one announce@apache.org announcement, and
Maven distribution recording. Add a protected workflow_dispatch to promote
a closed Nexus staging repository, matching the Apache Polaris TLP
post-vote GitHub Actions precedent.
Document ASF policy and TLP citations for what stays manual: the 72-hour
PMC vote, dist/release SVN, Reporter, and extra announcement lists.
---
.github/scripts/releaseJarFiles.sh | 8 +-
.github/workflows/release-atr-announce.yml | 158 ++++++++++++++++++++++++++++
.github/workflows/release-atr-record.yml | 91 ++++++++++++++++
.github/workflows/release-atr-resolve.yml | 95 +++++++++++++++++
.github/workflows/release-atr-upload.yml | 130 +++++++++++++++++++++++
.github/workflows/release-close.yml | 24 +++++
.github/workflows/release-promote-jars.yml | 84 +++++++++++++++
.github/workflows/release.yml | 138 ++++++++++++++++++++++++-
RELEASE.md | 160 ++++++++++++++++++++++++++---
9 files changed, 868 insertions(+), 20 deletions(-)
diff --git a/.github/scripts/releaseJarFiles.sh b/.github/scripts/releaseJarFiles.sh
index a18f7c54c4..e83ab4feb2 100755
--- a/.github/scripts/releaseJarFiles.sh
+++ b/.github/scripts/releaseJarFiles.sh
@@ -30,8 +30,10 @@ fi
STAGING_DESCRIPTION="$1"
NEXUS_USER="$2"
-read -r -s -p "Password: " NEXUS_PASS
-echo
+if [[ -z "${NEXUS_PASS:-}" ]]; then
+ read -r -s -p "Password: " NEXUS_PASS
+ echo
+fi
if [[ -z "${STAGING_DESCRIPTION}" ]]; then
echo "ERROR: Staging Description must not be empty." >&2
@@ -140,4 +142,4 @@ echo "Dropping staging repository ${repoId}…"
nexusApi POST "/staging/profiles/${profileId}/drop" \
--data "$(jq -n --arg r "$repoId" --arg d "${STAGING_DESCRIPTION}" '{data:{stagedRepositoryId:$r,description:$d}}')"
-echo "Done. Released ${repoId}."
\ No newline at end of file
+echo "Done. Released ${repoId}."
diff --git a/.github/workflows/release-atr-announce.yml b/.github/workflows/release-atr-announce.yml
new file mode 100644
index 0000000000..a960aadf45
--- /dev/null
+++ b/.github/workflows/release-atr-announce.yml
@@ -0,0 +1,158 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Experimental Apache Trusted Releases (ATR) announce step.
+# Sends ONE announcement through ATR. The first successful announce promotes
+# the candidate from preview to released, so this workflow must not be rerun
+# for additional mailing lists. The recipient is restricted to announce@apache.org.
+# ATR documentation and the official workflow example:
+# https://tooling.apache.org/
+# https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/announce-release-on-atr.yaml
+# After it succeeds, send the remaining lists by hand from your @apache.org
+# address: dev@grails.apache.org, users@grails.apache.org, and
+# dev@groovy.apache.org.
+#
+# Prerequisites (see RELEASE.md "Experimental: Apache Trusted Releases"):
+# * This workflow listed under the project's ATR finish workflow paths
+# * Vote already resolved as passed on ATR
+name: "Release - ATR Announce (experimental)"
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release version without the v prefix (e.g. 7.0.0 or 8.0.0-M1)'
+ required: true
+ type: string
+ previous_version:
+ description: 'Previous released version without the v prefix, used in the compare URL'
+ required: true
+ type: string
+ announce_revision:
+ description: 'ATR revision number being announced (the voted-on revision)'
+ required: true
+ type: string
+ announce_email_to:
+ description: 'Must be announce@apache.org. Do not rerun this workflow for other lists.'
+ required: true
+ type: string
+ default: announce@apache.org
+ announce_path_suffix:
+ description: 'Compatibility-only action input. Vote start selects the Alpha publication path.'
+ required: false
+ type: string
+ default: ''
+ announce_body:
+ description: 'Optional fully-rendered announcement body. Leave empty to render .github/vote_templates/announce.txt'
+ required: false
+ type: string
+ default: ''
+ atr_host:
+ description: 'ATR host (Alpha default is release-test.apache.org)'
+ required: true
+ type: string
+ default: release-test.apache.org
+permissions: { }
+env:
+ PROJECT_DESC: >
+ Grails is a powerful Groovy-based web application framework for the JVM,
+ built on top of Spring Boot, and supported by a rich ecosystem of plugins
+ that extend its functionality.
+ PROJECT_NAME: Apache Grails
+ REPO_SLUG: ${{ github.repository }}
+ VERSION: ${{ inputs.version }}
+jobs:
+ announce:
+ name: "Announce release on ATR"
+ environment: release
+ runs-on: ubuntu-24.04
+ permissions:
+ id-token: write # required for GitHub OIDC → ATR
+ contents: read
+ steps:
+ - name: "📝 Establish tag, recipient, and compatibility path suffix"
+ env:
+ ANNOUNCE_EMAIL_TO_INPUT: ${{ inputs.announce_email_to }}
+ PATH_SUFFIX_INPUT: ${{ inputs.announce_path_suffix }}
+ run: |
+ set -euo pipefail
+ if [ "${ANNOUNCE_EMAIL_TO_INPUT}" != "announce@apache.org" ]; then
+ echo "::error::ATR announcements must be sent to announce@apache.org. Send project-list follow-ups manually."
+ exit 1
+ fi
+ echo "TAG=v${VERSION}" >> "$GITHUB_ENV"
+ echo "ANNOUNCE_EMAIL_TO=${ANNOUNCE_EMAIL_TO_INPUT}" >> "$GITHUB_ENV"
+ if [ -n "${PATH_SUFFIX_INPUT}" ]; then
+ echo "ANNOUNCE_PATH_SUFFIX=${PATH_SUFFIX_INPUT}" >> "$GITHUB_ENV"
+ else
+ echo "ANNOUNCE_PATH_SUFFIX=core/${VERSION}" >> "$GITHUB_ENV"
+ fi
+ - name: "📥 Checkout repository"
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ ref: v${{ inputs.version }}
+ persist-credentials: false
+ - name: "🛠️ Install envsubst"
+ run: sudo apt-get update -y && sudo apt-get install -y gettext-base
+ - name: "📝 Build announcement body"
+ id: body
+ env:
+ ANNOUNCE_BODY_OVERRIDE: ${{ inputs.announce_body }}
+ PREVIOUS_VERSION: ${{ inputs.previous_version }}
+ run: |
+ set -euo pipefail
+ if [ -n "${ANNOUNCE_BODY_OVERRIDE}" ]; then
+ body="${ANNOUNCE_BODY_OVERRIDE}"
+ else
+ body="$(cat .github/vote_templates/announce.txt | envsubst)"
+ if [[ "${VERSION}" == *M* || "${VERSION}" == *RC* ]]; then
+ body="$(printf '%s\n' "${body}" | sed '/<IF MILESTONE>/d; /<\/IF MILESTONE>/d')"
+ else
+ body="$(printf '%s\n' "${body}" | sed '/<IF MILESTONE>/,/<\/IF MILESTONE>/d')"
+ fi
+ body="${body//<PREVIOUS_VERSION>/${PREVIOUS_VERSION}}"
+ fi
+ if grep -q -e '<IF MILESTONE>' -e '</IF MILESTONE>' -e '<PREVIOUS_VERSION>' <<<"${body}"; then
+ echo "::error::Announcement body still contains template placeholders. Pass a fully rendered announce_body or a previous_version."
+ exit 1
+ fi
+ delimiter="ATRBODY_$(openssl rand -hex 8)"
+ {
+ echo "value<<${delimiter}"
+ printf '%s\n' "${body}"
+ echo "${delimiter}"
+ } >> "$GITHUB_OUTPUT"
+ - name: "📢 Announce release on ATR"
+ # Keep announce-path-suffix for the pinned action contract only. The current
+ # Alpha announce endpoint ignores it; the vote-start suffix selected the path.
+ # Pin to a full SHA; apache/tooling-actions does not publish tags.
+ # SHA is apache/tooling-actions@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5 (2026-08-24)
+ uses: apache/tooling-actions/release-on-atr@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5
+ with:
+ version: ${{ inputs.version }}
+ atr-host: ${{ inputs.atr_host }}
+ announce: 'true'
+ announce-revision: ${{ inputs.announce_revision }}
+ announce-email-to: ${{ env.ANNOUNCE_EMAIL_TO }}
+ announce-body: ${{ steps.body.outputs.value }}
+ announce-path-suffix: ${{ env.ANNOUNCE_PATH_SUFFIX }}
+ - name: "📧 Remaining announcement lists"
+ run: |
+ echo "::group::Manual follow-up announcements"
+ echo "ATR accepted the announcement to ${ANNOUNCE_EMAIL_TO}."
+ echo "Do not rerun this workflow. Send the same body from your @apache.org address to:"
+ echo " dev@grails.apache.org"
+ echo " users@grails.apache.org"
+ echo " dev@groovy.apache.org"
+ echo "::endgroup::"
diff --git a/.github/workflows/release-atr-record.yml b/.github/workflows/release-atr-record.yml
new file mode 100644
index 0000000000..f754f14caa
--- /dev/null
+++ b/.github/workflows/release-atr-record.yml
@@ -0,0 +1,91 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Experimental Apache Trusted Releases (ATR) distribution-record step.
+# Tells ATR that a finished release was published to an external channel
+# (Maven Central, SDKMAN!, GitHub Releases). Does not perform the publish.
+# ATR documentation and the official workflow example:
+# https://tooling.apache.org/
+# https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/record-distribution-on-atr.yaml
+#
+# Typical Grails usage after promoting the Nexus staging repository:
+# platform=MAVEN (ATR DistributionPlatform is case-sensitive)
+# distribution_owner_namespace=org.apache.grails
+# distribution_package=grails-bom
+# distribution_version=<same as the release version>
+#
+# Prerequisites (see RELEASE.md "Experimental: Apache Trusted Releases"):
+# * This workflow listed under the project's ATR finish workflow paths
+# * Vote already resolved as passed on ATR
+name: "Release - ATR Record Distribution (experimental)"
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'ATR release version without the v prefix (e.g. 7.0.0 or 8.0.0-M1)'
+ required: true
+ type: string
+ platform:
+ description: 'ATR DistributionPlatform member name (case-sensitive). Use MAVEN for Maven Central.'
+ required: true
+ type: string
+ default: MAVEN
+ distribution_owner_namespace:
+ description: 'Owner namespace (Maven groupId, empty for some platforms)'
+ required: false
+ type: string
+ default: org.apache.grails
+ distribution_package:
+ description: 'Distribution package (Maven artifactId)'
+ required: true
+ type: string
+ default: grails-bom
+ distribution_version:
+ description: 'Version on that platform. Leave empty to use the ATR version.'
+ required: false
+ type: string
+ default: ''
+ staging:
+ description: 'Record a staging (not yet released) distribution'
+ required: true
+ type: boolean
+ default: false
+ atr_host:
+ description: 'ATR host (Alpha default is release-test.apache.org)'
+ required: true
+ type: string
+ default: release-test.apache.org
+permissions: { }
+jobs:
+ record:
+ name: "Record distribution on ATR"
+ environment: release
+ runs-on: ubuntu-24.04
+ permissions:
+ id-token: write # required for GitHub OIDC → ATR
+ contents: read
+ steps:
+ - name: "📦 Record distribution on ATR"
+ # Pin to a full SHA; apache/tooling-actions does not publish tags.
+ # SHA is apache/tooling-actions@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5 (2026-08-24)
+ uses: apache/tooling-actions/record-atr-distribution@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5
+ with:
+ version: ${{ inputs.version }}
+ atr-host: ${{ inputs.atr_host }}
+ platform: ${{ inputs.platform }}
+ distribution-owner-namespace: ${{ inputs.distribution_owner_namespace }}
+ distribution-package: ${{ inputs.distribution_package }}
+ distribution-version: ${{ inputs.distribution_version != '' && inputs.distribution_version || inputs.version }}
+ staging: ${{ inputs.staging }}
diff --git a/.github/workflows/release-atr-resolve.yml b/.github/workflows/release-atr-resolve.yml
new file mode 100644
index 0000000000..4ff153ca3a
--- /dev/null
+++ b/.github/workflows/release-atr-resolve.yml
@@ -0,0 +1,95 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Experimental Apache Trusted Releases (ATR) vote-resolve step.
+# Replaces the manual "[RESULT][VOTE]" email: ATR records the outcome and
+# posts the result to the vote thread.
+# ATR documentation and the official workflow examples:
+# https://tooling.apache.org/
+# https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/resolve-vote-on-atr.yaml
+#
+# Prerequisites (see RELEASE.md "Experimental: Apache Trusted Releases"):
+# * This workflow listed under the project's ATR vote workflow paths
+# * A vote already started on ATR for the given version
+name: "Release - ATR Resolve Vote (experimental)"
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release version without the v prefix (e.g. 7.0.0 or 8.0.0-M1)'
+ required: true
+ type: string
+ resolve_resolution:
+ description: 'Vote resolution'
+ required: true
+ type: choice
+ options:
+ - passed
+ - failed
+ default: passed
+ atr_host:
+ description: 'ATR host (Alpha default is release-test.apache.org)'
+ required: true
+ type: string
+ default: release-test.apache.org
+permissions: { }
+jobs:
+ resolve:
+ name: "Resolve ATR vote"
+ environment: release
+ runs-on: ubuntu-24.04
+ permissions:
+ id-token: write # required for GitHub OIDC → ATR
+ contents: read
+ steps:
+ - name: "🗳 Resolve vote on ATR"
+ # Pin to a full SHA; apache/tooling-actions does not publish tags.
+ # SHA is apache/tooling-actions@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5 (2026-08-24)
+ uses: apache/tooling-actions/release-on-atr@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5
+ with:
+ version: ${{ inputs.version }}
+ atr-host: ${{ inputs.atr_host }}
+ resolve: 'true'
+ resolve-resolution: ${{ inputs.resolve_resolution }}
+ - name: "📧 Next steps"
+ env:
+ RESOLVE_RESOLUTION: ${{ inputs.resolve_resolution }}
+ VERSION: ${{ inputs.version }}
+ run: |
+ echo "::group::After resolving the vote"
+ if [ "${RESOLVE_RESOLUTION}" = "passed" ]; then
+ echo "Vote passed. During Alpha, ATR does not commit to dist/atr until the release is announced."
+ echo "Resolve plus announce complete the finish. Remaining steps, in this order:"
+ echo " * 1. Promote Nexus staging through Actions → Release - Promote Nexus Staging"
+ echo " with version ${VERSION}. ATR does not publish Maven Central."
+ echo " * 2. Once Maven Central is live, run 'Release - ATR Record Distribution (experimental)' (platform=MAVEN)."
+ echo " * 3. Run 'Release - ATR Announce (experimental)' ONCE. During Alpha this is what causes ATR to publish to dist/atr."
+ echo " announce-path-suffix on the announce action is compatibility-only; the current ATR announce endpoint ignores it."
+ echo " * 4. THEN move exactly"
+ echo " https://dist.apache.org/repos/dist/atr/grails/core/${VERSION}"
+ echo " to"
+ echo " https://dist.apache.org/repos/dist/release/grails/core/${VERSION}"
+ echo " using svn/svnmucc. Do NOT move the committee root dist/atr/grails."
+ echo " Do NOT run releaseDistributions.sh (that promotes dist/dev)."
+ echo " * 5. Remaining Grails-specific: Forge, docs, SDKMAN!, website, and blog as in RELEASE.md."
+ echo "When starting the ATR vote, set the download path suffix to core/${VERSION}"
+ echo "(or core/{{VERSION}} if the UI uses that token). The version-tree move occurs"
+ echo "manually after the successful announce, as listed above."
+ else
+ echo "Vote failed. Drop the Nexus staging repo and dist/dev copy with"
+ echo " Release - Abort Release"
+ echo "and discard the ATR candidate from the ATR UI."
+ fi
+ echo "::endgroup::"
diff --git a/.github/workflows/release-atr-upload.yml b/.github/workflows/release-atr-upload.yml
new file mode 100644
index 0000000000..44513888b6
--- /dev/null
+++ b/.github/workflows/release-atr-upload.yml
@@ -0,0 +1,130 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Experimental Apache Trusted Releases (ATR) compose step.
+# Uploads the signed source and binary distributions from a GitHub Release to
+# ATR using OIDC (apache/tooling-actions/upload-to-atr). This is opt-in and
+# does not replace the existing dist.apache.org/dev SVN upload.
+# ATR documentation and the official workflow example:
+# https://tooling.apache.org/
+# https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/build-and-rsync-to-atr.yaml
+#
+# Layout matches Grails dist.apache.org convention:
+# sources/ apache-grails-<version>-src.zip{,.asc,.sha512}
+# distribution/ apache-grails-<version>-bin.zip{,.asc,.sha512}
+# apache-grails-wrapper-<version>-bin.zip{,.asc,.sha512}
+# rsync destination is /<project>/<version>/, so voters see the same
+# sources/ and distribution/ split used on dist/dev/grails/core/<version>/.
+#
+# Prerequisites (see RELEASE.md "Experimental: Apache Trusted Releases"):
+# * Grails project registered on ATR with Trusted Publishing enabled
+# * This workflow listed under the project's ATR compose workflow paths
+# * Repository name on ATR set to "grails-core"
+name: "Release - ATR Upload (experimental)"
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release version without the v prefix (e.g. 7.0.0 or 8.0.0-M1)'
+ required: true
+ type: string
+ project:
+ description: 'ATR project slug (must match the project registered on ATR)'
+ required: true
+ type: string
+ default: grails
+ atr_host:
+ description: 'ATR host (Alpha default is release-test.apache.org)'
+ required: true
+ type: string
+ default: release-test.apache.org
+ ssh_port:
+ description: 'ATR rsync SSH port'
+ required: true
+ type: string
+ default: '2222'
+permissions: { }
+env:
+ DIST_NAME: apache-grails
+ REPO_SLUG: ${{ github.repository }}
+ VERSION: ${{ inputs.version }}
+jobs:
+ upload:
+ name: "Upload distributions to ATR"
+ runs-on: ubuntu-24.04
+ permissions:
+ id-token: write # required for GitHub OIDC → ATR
+ contents: read
+ steps:
+ - name: "📝 Establish tag"
+ run: echo "TAG=v${VERSION}" >> "$GITHUB_ENV"
+ - name: "📥 Download GitHub Release distributions"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -euo pipefail
+ mkdir -p atr-dist/sources atr-dist/distribution
+ download_into() {
+ local dest="$1"
+ shift
+ local asset
+ for asset in "$@"; do
+ echo "Downloading ${asset} -> ${dest}"
+ gh release download "${TAG}" \
+ --repo "${REPO_SLUG}" \
+ --pattern "${asset}" \
+ --dir "${dest}" \
+ --clobber
+ done
+ }
+ download_into atr-dist/sources \
+ "${DIST_NAME}-${VERSION}-src.zip" \
+ "${DIST_NAME}-${VERSION}-src.zip.sha512" \
+ "${DIST_NAME}-${VERSION}-src.zip.asc"
+ download_into atr-dist/distribution \
+ "${DIST_NAME}-${VERSION}-bin.zip" \
+ "${DIST_NAME}-${VERSION}-bin.zip.sha512" \
+ "${DIST_NAME}-${VERSION}-bin.zip.asc" \
+ "${DIST_NAME}-wrapper-${VERSION}-bin.zip" \
+ "${DIST_NAME}-wrapper-${VERSION}-bin.zip.sha512" \
+ "${DIST_NAME}-wrapper-${VERSION}-bin.zip.asc"
+ echo "Downloaded the following files:"
+ find atr-dist -type f -print | sort
+ - name: "📤 Upload to ATR"
+ # Pin to a full SHA; apache/tooling-actions does not publish tags.
+ # SHA is apache/tooling-actions@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5 (2026-08-24)
+ uses: apache/tooling-actions/upload-to-atr@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5
+ with:
+ project: ${{ inputs.project }}
+ version: ${{ env.VERSION }}
+ src: atr-dist
+ atr-host: ${{ inputs.atr_host }}
+ ssh-port: ${{ inputs.ssh_port }}
+ - name: "📧 Next step — start the ATR vote"
+ env:
+ ATR_HOST: ${{ inputs.atr_host }}
+ ATR_PROJECT: ${{ inputs.project }}
+ run: |
+ echo "::group::Start the vote on ATR"
+ echo "Compose is complete. ATR has no GitHub Action for starting a vote."
+ echo "Start the vote from the ATR UI or the ATR client, then wait at least 72 hours:"
+ echo " https://${ATR_HOST}/"
+ echo " atr vote start ${ATR_PROJECT} ${VERSION} <REVISION> -m \"<ASF_UID>@apache.org\""
+ echo "When starting the vote, set the download path suffix to core/${VERSION}"
+ echo "(or core/{{VERSION}} if the UI uses that token). During Alpha, a committer moves"
+ echo "the exact version tree from dist/atr to dist/release after the successful announce."
+ echo "Discard any dist/dev [VOTE] email from the main Release workflow; ATR starts the sole vote."
+ echo "After the vote passes, run workflow 'Release - ATR Resolve Vote (experimental)'."
+ echo "::endgroup::"
diff --git a/.github/workflows/release-close.yml b/.github/workflows/release-close.yml
index 70e3223f31..c0c3157454 100644
--- a/.github/workflows/release-close.yml
+++ b/.github/workflows/release-close.yml
@@ -79,6 +79,9 @@ jobs:
- name: '📧 Announcement Email'
run: |
echo "::group::Announcement Email"
+ echo "If this candidate was staged on ATR, do NOT send this mail to announce@apache.org"
+ echo "(ATR already announced there once). Send only the extra lists. If this candidate did"
+ echo "not use ATR, send to announce@ plus the project lists as shown."
echo ""
echo "TO:"
echo "*************************************************"
@@ -95,3 +98,24 @@ jobs:
cat .github/vote_templates/announce.txt | envsubst
echo "*************************************************"
echo "::endgroup::"
+ - name: "📧 Optional ATR announce (experimental)"
+ # Alpha behavior and the action contract are documented at
+ # https://release-test.apache.org/docs/ and https://github.com/apache/tooling-actions.
+ run: |
+ echo "::group::Apache Trusted Releases (opt-in)"
+ echo "If this candidate was staged on ATR, send the announcement via:"
+ echo " Actions → Release - ATR Announce (experimental) → version ${VERSION}"
+ echo "Run that workflow once (default announce@apache.org). Do not rerun it for"
+ echo "other lists — ATR promotes the release on the first announce. Then send"
+ echo "dev@grails.apache.org, users@grails.apache.org, and dev@groovy.apache.org"
+ echo "by hand from your @apache.org address."
+ echo "After that successful announce, use svn/svnmucc to move exactly:"
+ echo " https://dist.apache.org/repos/dist/atr/grails/core/${VERSION}"
+ echo "to:"
+ echo " https://dist.apache.org/repos/dist/release/grails/core/${VERSION}"
+ echo "Move the version tree, not dist/atr/grails. Do not run releaseDistributions.sh"
+ echo "or copy dist/dev for an ATR candidate."
+ echo "After Maven Central is live, also run:"
+ echo " Actions → Release - ATR Record Distribution (experimental) with platform=MAVEN"
+ echo "See RELEASE.md section 'Experimental: Apache Trusted Releases'."
+ echo "::endgroup::"
diff --git a/.github/workflows/release-promote-jars.yml b/.github/workflows/release-promote-jars.yml
new file mode 100644
index 0000000000..8c694d63a3
--- /dev/null
+++ b/.github/workflows/release-promote-jars.yml
@@ -0,0 +1,84 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Apache Polaris, a TLP, promotes a closed staging repository after its vote:
+# https://github.com/apache/polaris/blob/7866ce5c009d64e4c1454b47bca3df9873c6c0b2/.github/workflows/release-4-publish-release.yml
+# Grails already uses the same NEXUS_STAGE_DEPLOYER_* secrets in
+# release-drop-staging.yml. This workflow never writes dist.apache.org/release:
+# https://infra.apache.org/github-actions-policy.html
+name: "Release - Promote Nexus Staging"
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Release version without the v prefix (e.g. 8.0.0-M1)'
+ required: true
+ type: string
+ staging_description:
+ description: 'Nexus staging repository description (defaults to <repository>:<version>)'
+ required: false
+ type: string
+ default: ''
+ repository:
+ description: 'Repository name used only for the default staging description'
+ required: false
+ type: string
+ default: grails-core
+permissions:
+ contents: read
+jobs:
+ promote:
+ name: "Promote Nexus staging repository"
+ environment: release
+ # Keep the release environment restricted to trusted refs.
+ runs-on: ubuntu-24.04
+ steps:
+ - name: "📥 Checkout repository"
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ ref: v${{ inputs.version }}
+ persist-credentials: false
+ - name: "📝 Establish staging description"
+ env:
+ VERSION: ${{ inputs.version }}
+ STAGING_DESCRIPTION_INPUT: ${{ inputs.staging_description }}
+ REPOSITORY: ${{ inputs.repository }}
+ run: |
+ set -euo pipefail
+ if [ -z "${VERSION}" ]; then
+ echo "::error::Version must not be empty."
+ exit 1
+ fi
+ if [ -n "${STAGING_DESCRIPTION_INPUT}" ]; then
+ echo "STAGING_DESCRIPTION=${STAGING_DESCRIPTION_INPUT}" >> "$GITHUB_ENV"
+ else
+ echo "STAGING_DESCRIPTION=${REPOSITORY}:${VERSION}" >> "$GITHUB_ENV"
+ fi
+ - name: "🚀 Promote Nexus staging repository"
+ env:
+ NEXUS_USER: ${{ secrets.NEXUS_STAGE_DEPLOYER_USER }}
+ NEXUS_PASS: ${{ secrets.NEXUS_STAGE_DEPLOYER_PW }}
+ STAGING_DESCRIPTION: ${{ env.STAGING_DESCRIPTION }}
+ run: |
+ set -euo pipefail
+ if [ -z "${NEXUS_USER}" ] || [ -z "${NEXUS_PASS}" ]; then
+ echo "::error::NEXUS_STAGE_DEPLOYER_USER and NEXUS_STAGE_DEPLOYER_PW must be configured."
+ exit 1
+ fi
+ bash .github/scripts/releaseJarFiles.sh "$STAGING_DESCRIPTION" "$NEXUS_USER"
+ - name: "📦 Next steps"
+ run: |
+ echo "If ATR was used, once Maven Central is live run Actions → Release - ATR Record Distribution (experimental) with platform=MAVEN."
+ echo "SDKMAN! still waits for Maven Central."
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0110a4c29f..5e0cc176c4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -551,6 +551,9 @@ jobs:
export DIST_SVN_REVISION=$(awk '/Last Changed Rev:/ {print $4}' dev-repo/DIST_SVN_REVISION.txt)
echo "::group::Grails PMC Vote Email"
+ echo "If this candidate will be voted on ATR, discard this dist/dev vote email."
+ echo "Do not send it. ATR starts the sole vote against the ATR candidate after upload."
+ echo "If this candidate is the default dist/dev path, send the generated email below."
echo ""
echo "TO:"
echo "*************************************************"
@@ -566,6 +569,91 @@ jobs:
cat ${REPO_NAME}/.github/vote_templates/staged.txt | envsubst
echo "*************************************************"
echo "::endgroup::"
+ - name: "📧 Optional ATR vote (experimental)"
+ run: |
+ echo "::group::Apache Trusted Releases (opt-in)"
+ echo "To stage this candidate on ATR as well as dist/dev, either set repository"
+ echo "variable ATR_ENABLED=true (the atr_upload job then runs automatically) or"
+ echo "run workflow 'Release - ATR Upload (experimental)' with version ${VERSION}."
+ echo "ATR has no GitHub Action for starting a vote — after upload, start it from"
+ echo "the ATR UI (https://release-test.apache.org/) or the ATR client."
+ echo "When starting the vote, set the download path suffix to core/${VERSION}"
+ echo "(or core/{{VERSION}} if the UI uses that token)."
+ echo "If you are voting on ATR, discard the printed dist/dev [VOTE] email above;"
+ echo "do not start a second PMC vote against dist/dev."
+ echo "See RELEASE.md section 'Experimental: Apache Trusted Releases'."
+ echo "::endgroup::"
+ atr_upload:
+ name: "Optional - Upload distributions to ATR (experimental)"
+ # Off by default. Set repository variable ATR_ENABLED=true after Trusted
+ # Publishing is configured on ATR. See https://tooling.apache.org/ and
+ # https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/build-and-rsync-to-atr.yaml.
+ if: ${{ vars.ATR_ENABLED == 'true' }}
+ needs: [ publish, source ]
+ runs-on: ubuntu-24.04
+ permissions:
+ id-token: write # required for GitHub OIDC → ATR
+ contents: read
+ env:
+ ATR_HOST: ${{ vars.ATR_HOST != '' && vars.ATR_HOST || 'release-test.apache.org' }}
+ ATR_PROJECT: ${{ vars.ATR_PROJECT != '' && vars.ATR_PROJECT || 'grails' }}
+ ATR_SSH_PORT: ${{ vars.ATR_SSH_PORT != '' && vars.ATR_SSH_PORT || '2222' }}
+ steps:
+ - name: "📝 Establish release version"
+ run: echo "VERSION=${TAG#v}" >> "$GITHUB_ENV"
+ - name: "📥 Download GitHub Release distributions"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ set -euo pipefail
+ mkdir -p atr-dist/sources atr-dist/distribution
+ download_into() {
+ local dest="$1"
+ shift
+ local asset
+ for asset in "$@"; do
+ echo "Downloading ${asset} -> ${dest}"
+ gh release download "${TAG}" \
+ --repo "${REPO_SLUG}" \
+ --pattern "${asset}" \
+ --dir "${dest}" \
+ --clobber
+ done
+ }
+ download_into atr-dist/sources \
+ "${DIST_NAME}-${VERSION}-src.zip" \
+ "${DIST_NAME}-${VERSION}-src.zip.sha512" \
+ "${DIST_NAME}-${VERSION}-src.zip.asc"
+ download_into atr-dist/distribution \
+ "${DIST_NAME}-${VERSION}-bin.zip" \
+ "${DIST_NAME}-${VERSION}-bin.zip.sha512" \
+ "${DIST_NAME}-${VERSION}-bin.zip.asc" \
+ "${DIST_NAME}-wrapper-${VERSION}-bin.zip" \
+ "${DIST_NAME}-wrapper-${VERSION}-bin.zip.sha512" \
+ "${DIST_NAME}-wrapper-${VERSION}-bin.zip.asc"
+ echo "Downloaded the following files:"
+ find atr-dist -type f -print | sort
+ - name: "📤 Upload to ATR"
+ # Pin to a full SHA; apache/tooling-actions does not publish tags.
+ # SHA is apache/tooling-actions@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5 (2026-08-24)
+ uses: apache/tooling-actions/upload-to-atr@e8594ee9cc5ef5fbb48c367a9ab87042c0ccbbc5
+ with:
+ project: ${{ env.ATR_PROJECT }}
+ version: ${{ env.VERSION }}
+ src: atr-dist
+ atr-host: ${{ env.ATR_HOST }}
+ ssh-port: ${{ env.ATR_SSH_PORT }}
+ - name: "📧 Next step — start the ATR vote"
+ run: |
+ echo "::group::Start the vote on ATR"
+ echo "Compose is complete. Start the vote from the ATR UI or the ATR client:"
+ echo " https://${ATR_HOST}/"
+ echo " atr vote start ${ATR_PROJECT} ${VERSION} <REVISION> -m \"<ASF_UID>@apache.org\""
+ echo "When starting the vote, set the download path suffix to core/${VERSION}"
+ echo "(or core/{{VERSION}} if the UI uses that token). During Alpha, a committer moves"
+ echo "the exact version tree from dist/atr to dist/release after the successful announce."
+ echo "After the vote passes, run workflow 'Release - ATR Resolve Vote (experimental)'."
+ echo "::endgroup::"
release:
environment: release
name: "VOTE SUCCEEDED - Release Artifacts"
@@ -612,15 +700,39 @@ jobs:
cat .github/vote_templates/vote_succeeded.txt | envsubst
echo "*************************************************"
echo "::endgroup::"
+ - name: "📧 Optional ATR resolve (experimental)"
+ run: |
+ echo "::group::Apache Trusted Releases (opt-in)"
+ echo "If this candidate was staged on ATR, resolve the vote instead of sending"
+ echo "the RESULT email by hand:"
+ echo " Actions → Release - ATR Resolve Vote (experimental) → version ${VERSION}"
+ echo "Promote Nexus staging through Actions → Release - Promote Nexus Staging"
+ echo "with version ${VERSION}; then, once Maven Central is live, run ATR Record Distribution (platform=MAVEN)."
+ echo "Do NOT run releaseDistributions.sh for an ATR candidate — that promotes dist/dev,"
+ echo "which is not the set of files under vote. After the one successful ATR announce,"
+ echo "move exactly dist/atr/grails/core/${VERSION} to dist/release/grails/core/${VERSION}."
+ echo "See RELEASE.md section 'Experimental: Apache Trusted Releases'."
+ echo "::endgroup::"
- name: "🚀 MANUAL - Release JAR files"
run: |
echo "::group::Manual Jar Promotion"
- echo "Run .github/scripts/releaseJarFiles.sh ${REPO_NAME}:${VERSION} <ASF_USER>"
+ echo "Prefer Actions → Release - Promote Nexus Staging with version ${VERSION}"
+ echo "(environment: release). Local fallback:"
+ echo " .github/scripts/releaseJarFiles.sh ${REPO_NAME}:${VERSION} <ASF_USER>"
echo "::endgroup::"
- - name: "🚀 MANUAL - Release distribution artifacts"
+ - name: "🚀 MANUAL - Promote distribution artifacts"
+ # ATR Alpha promotion guidance: https://release-test.apache.org/docs/promoting-to-release
run: |
echo "::group::Manual ASF Artifact Promotion"
- echo "Run .github/scripts/releaseDistributions.sh ${TAG} ${SVN_FOLDER} <ASF_USER>"
+ echo "If this candidate was staged on ATR (standalone ATR Upload workflow or atr_upload / ATR_ENABLED),"
+ echo "after the one successful ATR announce, use svn/svnmucc to move exactly:"
+ echo " https://dist.apache.org/repos/dist/atr/grails/core/${VERSION}"
+ echo "to:"
+ echo " https://dist.apache.org/repos/dist/release/grails/core/${VERSION}"
+ echo "Move the version tree, not dist/atr/grails. Do not run releaseDistributions.sh"
+ echo "or copy dist/dev for an ATR candidate."
+ echo "If this candidate used dist/dev (the default), run:"
+ echo " .github/scripts/releaseDistributions.sh ${TAG} ${SVN_FOLDER} <ASF_USER>"
echo "::endgroup::"
- name: "✅ MANUAL - Update ASF Reporter"
run: |
@@ -805,6 +917,9 @@ jobs:
- name: '📧 Announcement Email'
run: |
echo "::group::Announcement Email"
+ echo "If this candidate was staged on ATR, do NOT send this mail to announce@apache.org"
+ echo "(ATR already announced there once). Send only the extra lists. If this candidate did"
+ echo "not use ATR, send to announce@ plus the project lists as shown."
echo ""
echo "TO:"
echo "*************************************************"
@@ -821,3 +936,20 @@ jobs:
cat .github/vote_templates/announce.txt | envsubst
echo "*************************************************"
echo "::endgroup::"
+ - name: "📧 Optional ATR announce (experimental)"
+ # Alpha behavior and the action contract are documented at https://tooling.apache.org/
+ # and https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/announce-release-on-atr.yaml.
+ run: |
+ echo "::group::Apache Trusted Releases (opt-in)"
+ echo "If this candidate was staged on ATR, send the announcement via:"
+ echo " Actions → Release - ATR Announce (experimental) → version ${VERSION}"
+ echo "Run that workflow once (default announce@apache.org). Do not rerun it for"
+ echo "other lists — ATR promotes the release on the first announce. Then send"
+ echo "dev@grails.apache.org, users@grails.apache.org, and dev@groovy.apache.org"
+ echo "by hand from your @apache.org address."
+ echo "After that successful announce, move exactly dist/atr/grails/core/${VERSION}"
+ echo "to dist/release/grails/core/${VERSION}; do not use releaseDistributions.sh."
+ echo "After Maven Central is live, also run:"
+ echo " Actions → Release - ATR Record Distribution (experimental) with platform=MAVEN"
+ echo "See RELEASE.md section 'Experimental: Apache Trusted Releases'."
+ echo "::endgroup::"
diff --git a/RELEASE.md b/RELEASE.md
index 2262066011..040ce00f35 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -274,7 +274,8 @@ The release vote is conducted on the [Grails dev mailing list](https://lists.apa
The vote runs for a minimum of 72 hours.
The vote email is automatically generated as part of the release process — see the `upload` job in the `Release`
-workflow for details.
+workflow for details. If the candidate is staged on ATR, discard that generated `dist/dev` vote email
+and start the sole vote from ATR so there is only one thread, covering the ATR artifacts.
## 5. Releasing
@@ -289,29 +290,43 @@ Confirm the Grails PMC votes passed with a +1 from at least 3 PMC members. The `
### Release the Staged Jar files
-The `release` job in the `Release` workflow has a step entitled `🚀 Release JAR files - MANUAL`. You can release the jar
-files by one of 2 ways:
+After the PMC vote has passed and the protected `release` environment is approved, prefer the opt-in
+`Release - Promote Nexus Staging` workflow. Run it from Actions with the version without `v`; leave
+`staging_description` empty to use `grails-core:<version>`. This is manually dispatched after the vote, following the
+[Apache Polaris TLP precedent](https://github.com/apache/polaris/blob/7866ce5c009d64e4c1454b47bca3df9873c6c0b2/.github/workflows/release-4-publish-release.yml).
-1. On https://repository.apache.org/#stagingRepositories, the staged artifacts must be released by opening the `grails-core` staging repository and
- clicking the `Release` button. It took almost 2 hours for the initial ASF release to publish these jars to Maven
- Central.
-2. Alternatively, the `release` job in the `Release` workflow will output an example command line to release the staging
- repository via the scripts checked into grails-core. Execute the script & set the placeholder values to release the
- jar files.
+The following remain fallbacks:
+
+1. On https://repository.apache.org/#stagingRepositories, release the staged artifacts by opening the `grails-core` staging repository and
+ clicking the `Release` button. It took almost 2 hours for the initial ASF release to publish these jars to Maven
+ Central.
+2. Run `.github/scripts/releaseJarFiles.sh grails-core:<version> <ASF_USER>`. The script prompts for a password unless
+ `NEXUS_PASS` is already set, which is how the protected workflow supplies its secret.
### Move the distributions from `dev` to `release`
-On dist.apache.org, the staged source distribution & binary distributions must be moved from `https://dist.apache.org/repos/dist/dev/grails/` to `https://dist.apache.org/repos/dist/release/grails/`. Per ASF
-infrastructure, this must be performed manually, and we are not allowed to automate it via a gated approval workflow.
-Either move them via your SVN client or use the checked in script to perform these actions as your user.
+If this candidate was staged on ATR, skip this `dist/dev` move. After the one ATR announce, move exactly
+`dist/atr/grails/core/<version>` → `dist/release/grails/core/<version>` as documented in
+[Experimental: Apache Trusted Releases](#experimental-apache-trusted-releases-atr). Do not run
+`releaseDistributions.sh` for an ATR candidate.
+
+Otherwise, on dist.apache.org, the staged source distribution & binary distributions must be moved from
+`https://dist.apache.org/repos/dist/dev/grails/` to `https://dist.apache.org/repos/dist/release/grails/`.
+[ASF GitHub Actions policy](https://infra.apache.org/github-actions-policy.html) forbids automated services
+from pushing official release data without prior Infrastructure authorization, so this `svn move` stays
+manual. [Apache Polaris leaves the same `svn mv` manual](https://github.com/apache/polaris/blob/7866ce5c009d64e4c1454b47bca3df9873c6c0b2/.github/workflows/release-4-publish-release.yml)
+because its CI credentials cannot write `dist/release`. Either move the tree via your SVN client or use
+the checked-in script as your user.
-The `release` job in the `Release` workflow has a step entitled `🚀 Move Distributions - MANUAL`. This step will output
+The `release` job in the `Release` workflow has a step entitled `🚀 MANUAL - Release distribution artifacts`. This step will output
an example call to the checked in script to move the distributions.
### Update ASF Reporter
After moving the distributions, you will receive an email from the ASF reporter. Click the link in the email to mark the
-release as published or go to https://reporter.apache.org/addrelease.html?grails. The `release` job in the `Release` workflow has a step to remind you of this.
+release as published or go to https://reporter.apache.org/addrelease.html?grails. This stays manual: Reporter is a
+login-protected form with no documented public API, and no Apache TLP GitHub Actions precedent was found. The `release`
+job in the `Release` workflow has a step to remind you of this.
For example, if the release is out of core with version `7.0.0-M4`, then the release name will be `CORE-7.0.0-M4`. Enter
the date you moved the distribution artifacts and report the release.
@@ -369,6 +384,120 @@ Announcements should come from your apache email address (see https://infra.apac
expected format. The announcement should be sent to `dev@grails.apache.org`, `dev@groovy.apache.org`, `users@grails.apache.org`, &
`announce@apache.org`. See the `close` job for a generated email.
+### What GitHub Actions may automate
+
+The following boundaries preserve PMC authority and use only opt-in, protected workflows. Apache's release policy permits
+automated release infrastructure to sign artifacts, while the vote remains a community email process.
+
+| Activity | GitHub Actions boundary and evidence |
+|----------|--------------------------------------|
+| ATR compose, resolve, one announce, and distribution record | Experimental, opt-in ATR workflows map to [ASF Tooling](https://tooling.apache.org/), the [ATR documentation](https://release-test.apache.org/docs/), and the official examples: [compose](https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/build-and-rsync-to-atr.yaml), [resolve](https://github.com/apache/tooling-asf-example/blob/main/.github/workflows/resolve-vote-on-atr.yaml), [announce](https://github. [...]
+| Signing in GitHub Actions | [Apache release policy](https://www.apache.org/legal/release-policy.html) requires release artifacts to be signed by the Release Manager or automated release infrastructure. ATR documents its [Trusted Publishing](https://release-test.apache.org/docs/trusted-publishing) model. |
+| Documentation and website content | [GitHub Actions policy](https://infra.apache.org/github-actions-policy.html) permits work on website content and other non-released data. Grails already deploys documentation from GitHub Actions. |
+| Promote closed Nexus staging after a passed vote | `Release - Promote Nexus Staging` is `workflow_dispatch` plus the protected `release` environment. It follows the [Apache Polaris TLP promotion workflow](https://github.com/apache/polaris/blob/7866ce5c009d64e4c1454b47bca3df9873c6c0b2/.github/workflows/release-4-publish-release.yml) and uses the existing `NEXUS_STAGE_DEPLOYER_USER` / `NEXUS_STAGE_DEPLOYER_PW` secrets used to drop staging. |
+| Skip `dist/dev` only when using ATR | ATR candidates may skip the traditional staging location as described in [Staging and voting](https://release-test.apache.org/docs/staging-and-voting). The `dist/dev` plus Nexus path remains Grails' official default until the PMC opts in to ATR. |
+
+The following must remain manual:
+
+| Activity | Why it remains manual |
+|----------|-----------------------|
+| Start and hold the binding PMC vote for at least 72 hours | [Apache release policy](https://www.apache.org/legal/release-policy.html) says release votes SHOULD remain open for at least 72 hours. [ATR](https://tooling.apache.org/trusted-releases.html) keeps community participation on release votes via email. |
+| Move official distributions to `dist/release` | [GitHub Actions policy](https://infra.apache.org/github-actions-policy.html) prohibits automated services from pushing data to a repository or branch subject to an official release without prior Infra authorization. ATR Alpha requires a committer to [move the exact version tree](https://release-test.apache.org/docs/promoting-to-release) from `dist/atr/grails/core/<version>` to `dist/release/grails/core/<version>` after the successful anno [...]
+| Report the release to ASF Reporter | [ASF Reporter addrelease](https://reporter.apache.org/addrelease.html) has no documented public API and no TLP GitHub Actions precedent was found. |
+| Send lists beyond the one ATR announcement | ATR rejects a second announcement for the same release. Send the remaining project lists manually after the single ATR announce. |
+
+ATR is Alpha 3 on `release-test.apache.org`; do not treat Beta as generally available while the
+[Beta announcement remains open](https://github.com/apache/tooling-trusted-releases/issues/1520).
+
+## Experimental: Apache Trusted Releases (ATR)
+
+[ASF Tooling](https://tooling.apache.org/) is building [Apache Trusted Releases](https://release-test.apache.org/)
+to automate compose, vote, and finish. ATR is **Alpha 3** software on `release-test.apache.org`. The existing
+`dist/dev` → `dist/release` and Nexus staging path remains the official Grails process until the PMC opts in.
+See the [ATR documentation](https://release-test.apache.org/docs/) and
+[apache/tooling-actions](https://github.com/apache/tooling-actions) for the experimental automation used below.
+
+The `apache/tooling-actions` GitHub Actions that map onto Grails manual steps:
+
+| Manual step today | Tooling action | Grails workflow |
+|-------------------|----------------|-----------------|
+| Upload source/binary zips to `dist/dev` | `apache/tooling-actions/upload-to-atr` | `Release - ATR Upload (experimental)`, or the `atr_upload` job in `release.yml` when `ATR_ENABLED=true` |
+| Send `[VOTE]` email | *none* (start the vote from the [ATR UI](https://release-test.apache.org/) or `atr vote start`) | — |
+| Send `[RESULT][VOTE]` email | `apache/tooling-actions/release-on-atr` (`resolve: true`) | `Release - ATR Resolve Vote (experimental)` |
+| Promote Nexus staging → Maven Central | `Release - Promote Nexus Staging` after the passed vote (or Nexus UI / `releaseJarFiles.sh` fallback) | Then, once Maven Central is live, `Release - ATR Record Distribution (experimental)` with `platform=MAVEN` |
+| Send `[ANNOUNCE]` email | `apache/tooling-actions/release-on-atr` (`announce: true`) | `Release - ATR Announce (experimental)` once, to `announce@apache.org`. Remaining lists (`dev@grails.apache.org`, `users@grails.apache.org`, `dev@groovy.apache.org`) stay manual — ATR rejects a second announce for the same version. |
+| Promote the ATR candidate after announcing | On Alpha, the first successful `release-on-atr` announce writes `dist/atr/`. A committer must then [move the exact version tree to `dist/release`](https://release-test.apache.org/docs/promoting-to-release): `dist/atr/grails/core/<version>` → `dist/release/grails/core/<version>`. ASF Infra still forbids automating that move from GitHub Actions. | Manual `svn move` after `Release - ATR Announce (experimental)`; do not use `releaseDistributions [...]
+| ASF Reporter, Forge, docs, SDKMAN!, website, blog | *none* | unchanged |
+
+Tooling-actions must be pinned to a full commit SHA (`apache/tooling-actions` does not tag releases). `apache/*`
+actions are auto-approved by the ASF allowlist, so `validateActions` accepts these pins.
+
+### Opt-in setup
+
+1. Join the ATR Alpha: log in at https://release-test.apache.org/ with ASF credentials and confirm the Grails project
+ is visible. Discuss on [dev@tooling.apache.org](https://lists.apache.org/list.html?dev@tooling.apache.org) /
+ [#apache-trusted-releases](https://the-asf.slack.com/archives/C049WADAAQG).
+2. Configure [Trusted Publishing](https://release-test.apache.org/docs/trusted-publishing) for `grails-core`:
+ * Repository name: `grails-core`
+ * Compose workflows:
+ * `.github/workflows/release.yml`
+ * `.github/workflows/release-atr-upload.yml`
+ * Vote workflows:
+ * `.github/workflows/release-atr-resolve.yml`
+ * Finish workflows:
+ * `.github/workflows/release-atr-announce.yml`
+ * `.github/workflows/release-atr-record.yml`
+3. Confirm the committee signing key used in `release.yml` matches ATR's automated-release UID convention
+ (`Automated Release Signing <pr...@grails.apache.org>`), or register that key with ATR. Grails already
+ signs in GitHub Actions with a project GPG key from ASF Infra.
+4. Optional: set repository variables so the main `Release` workflow also uploads to ATR after the source
+ distribution exists:
+ * `ATR_ENABLED=true`
+ * `ATR_PROJECT=grails` (must match the ATR project slug)
+ * `ATR_HOST=release-test.apache.org` (optional; this is the default)
+ * `ATR_SSH_PORT=2222` (optional; this is the default)
+
+### Using ATR alongside a Grails release
+
+Keep the current `Release` workflow as the source of signed artifacts. After the `source` job has uploaded
+the zips to the GitHub Release:
+
+1. **Compose.** Run `Release - ATR Upload (experimental)` (or rely on `atr_upload` when `ATR_ENABLED=true`).
+ This rsyncs the signed source zip into `sources/` and the CLI/wrapper zips into `distribution/`
+ (plus `.asc` / `.sha512`) on ATR, matching the Grails `dist/dev/grails/core/<version>/` layout.
+ Do **not** treat `dist/dev` as the files under vote if ATR is the staging location — see
+ [Staging and voting](https://release-test.apache.org/docs/staging-and-voting).
+2. **Vote.** Start the vote on ATR (UI or `atr vote start`). There is no Tooling GitHub Action for this.
+ When starting the vote, you **must** set the download path suffix to `core/<version>` (or
+ `core/{{VERSION}}` if the UI uses that token). This fixes the Alpha publication path so the later
+ announce creates `dist/atr/grails/core/<version>`. Discard the `dist/dev` `[VOTE]` email printed by
+ the `upload` job — do not start a second PMC vote against `dist/dev`. Keep the 72-hour PMC vote.
+ The GitHub `release` environment approval is still the gate for later jobs.
+3. **Resolve.** After the vote passes, run `Release - ATR Resolve Vote (experimental)` with `passed`.
+ That replaces sending the `[RESULT][VOTE]` email by hand.
+4. **Maven Central.** Run `Release - Promote Nexus Staging` with the version (or use the Nexus UI or
+ `.github/scripts/releaseJarFiles.sh` fallback). Once Maven Central is live, run
+ `Release - ATR Record Distribution (experimental)` with `platform=MAVEN`,
+ `distribution_owner_namespace=org.apache.grails`, `distribution_package=grails-bom`.
+5. **Announce.** Run `Release - ATR Announce (experimental)` **once**. ATR promotes the candidate on
+ the first announce, so a second run for another list is rejected. The recipient must be
+ `announce@apache.org`. `announce-path-suffix` remains in the pinned action contract for compatibility,
+ but the current ATR announce endpoint ignores it and it does not select the Alpha publication path:
+ the path was fixed when the vote started. Leave `announce_body` empty to render
+ `.github/vote_templates/announce.txt` with the required `previous_version` input (milestone wording
+ is included only for `M` / `RC` versions).
+ Do not use a second `release-on-atr` announce for extra lists. Send the same body from your `@apache.org` address to `dev@grails.apache.org`,
+ `users@grails.apache.org`, and `dev@groovy.apache.org`.
+6. **dist/release.** Only after the successful ATR announce, use SVN to move exactly
+ `https://dist.apache.org/repos/dist/atr/grails/core/<version>` to
+ `https://dist.apache.org/repos/dist/release/grails/core/<version>` per the
+ [ATR promoting-to-release documentation](https://release-test.apache.org/docs/promoting-to-release).
+ Move the version tree, not `dist/atr/grails`. Do not use `releaseDistributions.sh` or a `dist/dev` copy
+ for an ATR candidate.
+
+Grails-specific steps that ATR does not cover remain as in section 5: ASF Reporter, Forge deploys,
+documentation, SDKMAN!, the close-release PR, the static website, and the blog post.
+
# Rollback
In the event a staged artifact needs rolled back, kick off the `Release - Abort Release` workflow. This can only be done for steps prior to the VOTE.
@@ -614,6 +743,9 @@ the following workflows:
have the `release-abort.yml` workflow.
10. `release-notes.yml` - A workflow that configures Release Drafter so that release notes are automatically generated
as pull requests are merged.
+11. `release-atr-*.yml` - Optional Apache Trusted Releases (ATR) workflows. See
+ [Experimental: Apache Trusted Releases (ATR)](#experimental-apache-trusted-releases-atr). These do not replace
+ `release.yml` while ATR is in Alpha.
The key workflows above are: `gradle.yml`, `release.yml`, and `release-abort.yml`.