You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by xu...@apache.org on 2026/09/08 08:23:49 UTC
(opendal) branch main updated: fix(ci): dispatch weekly RC builds and notify release managers (#8241)
This is an automated email from the ASF dual-hosted git repository.
Xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git
The following commit(s) were added to refs/heads/main by this push:
new 90cab80d0a fix(ci): dispatch weekly RC builds and notify release managers (#8241)
90cab80d0a is described below
commit 90cab80d0ac6beff9e5a2e6eba31659a21cf0936
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Tue Sep 8 16:23:42 2026 +0800
fix(ci): dispatch weekly RC builds and notify release managers (#8241)
* fix(ci): dispatch weekly RC builds and notify release managers
* docs(release): align release guidance with weekly candidate automation
---
.agents/skills/opendal-release/SKILL.md | 24 +++---
.../opendal-release/references/official-release.md | 19 +++++
.agents/skills/opendal-release/references/rc.md | 36 ++++++++-
.../opendal-release/references/troubleshooting.md | 14 ++++
.agents/skills/opendal-release/references/vote.md | 9 ++-
.github/workflows/docs.yml | 8 +-
.github/workflows/release_ruby.yml | 5 +-
.github/workflows/weekly_release.yml | 80 ++++++++++++++++++++
website/community/release/release.md | 19 +++++
website/community/release/weekly.md | 87 ++++++++++++++++++++--
10 files changed, 272 insertions(+), 29 deletions(-)
diff --git a/.agents/skills/opendal-release/SKILL.md b/.agents/skills/opendal-release/SKILL.md
index 4c1471a25e..78490574c1 100644
--- a/.agents/skills/opendal-release/SKILL.md
+++ b/.agents/skills/opendal-release/SKILL.md
@@ -1,6 +1,6 @@
---
name: opendal-release
-description: Execute and verify Apache OpenDAL release-manager work, including RC tagging, required GitHub Actions checks, ASF SVN dist uploads, Nexus staging close/release, vote discussions, language package readiness, GitHub release, announcements, and release postmortems.
+description: Execute and verify Apache OpenDAL release-manager work, including weekly ATR candidates, RC tagging, required GitHub Actions checks, ASF SVN dist uploads, Nexus staging close/release, vote discussions, language package readiness, GitHub release, announcements, and release postmortems.
---
# OpenDAL Release
@@ -16,9 +16,9 @@ The primary repository runbook is `website/community/release/release.md`. The sp
- Use `gh` for GitHub PRs, issues, discussions, checks, and Actions logs.
- Do not use web search for repository state. Query live GitHub, SVN, Nexus, crates.io, PyPI, npm, and Maven URLs directly.
- Do not claim any step succeeded until the external system confirms it.
-- Treat an RC as bound to its signed tag, target commit, and generated artifacts. Do not create a new RC merely because `main` advances after tagging.
+- Treat an RC as bound to its tag, target commit, and signed source artifacts. Manual RC tags are signed; weekly preparation creates a lightweight tag and compose signs the artifacts. Do not create a new RC merely because `main` advances after tagging.
- Do not assume bindings or integrations share the top-level OpenDAL version. Each released binding or integration can have its own version.
-- Do not start a public vote with broken links, open Maven staging, missing SVN artifacts, or incomplete required workflows.
+- Do not start a public vote with broken links, open Maven staging, missing source artifacts at the selected staging location, or incomplete required workflows.
- Do not conflate Nexus `Close` before voting with Nexus `Release` after the vote passes.
- Do not over-block on unrelated/noncritical CI if the release gate is explicitly narrowed.
- Do not put SVN, Nexus, or mail credentials in commands, files, issue text, PRs, or release notes. Read them from environment variables or an interactive prompt and avoid shell history when possible.
@@ -47,16 +47,18 @@ Use these states explicitly when reporting status:
1. `planning`: release discussion/tracking issue/version bump not done.
2. `bump-pr`: version/changelog/upgrade/dependency updates are in PR.
3. `crates-bootstrapped`: the bootstrap workflow has authenticated every Rust crate in the scanned `main` publish plan, and every name exists with the exact Trusted Publisher and `trustpub_only` enabled.
-4. `rc-tagged`: signed RC tag exists and was pushed.
-5. `rc-ci`: tag-triggered release workflows are still running or failed.
-6. `artifacts-built`: `just release` generated local ASF source artifacts.
-7. `dist-dev-uploaded`: artifacts are committed to ASF SVN `dist/dev`.
+4. `rc-tagged`: RC tag exists and was pushed at the intended commit.
+5. `rc-ci`: required release workflows, triggered by tag push or explicit dispatch, are still running or failed.
+6. `artifacts-built`: local release tooling or source compose generated ASF source artifacts.
+7. `source-staged`: signed artifacts are uploaded to ATR compose or ASF SVN `dist/dev`; record which location and revision will be verified.
8. `nexus-closed`: Java staging repo is closed and publicly accessible.
-9. `vote-open`: GitHub Discussion vote is open.
+9. `vote-open`: the formal vote is open and identifies the verified candidate and source location.
10. `vote-passed`: at least 72 hours elapsed and binding vote requirements are met.
-11. `official-release`: final tag, `dist/release`, package repositories, GitHub release, and announcement are complete.
+11. `official-release`: final tag, approved source publication, package repositories, GitHub release, and announcement are complete.
-If a release fails before `official-release`, abandon that RC, clean up wrong staged artifacts where needed, drop the Maven staging repo, and create the next RC.
+Weekly preparation replaces the manual bump-PR path and can compose sources while downstream builds run. A candidate-ready Discussion confirms upload and dispatch, not readiness to vote.
+
+Retry transient failures against the same candidate and reuse existing signed artifacts. Create a new RC when source or workflow changes must be included, or the candidate is rejected; do not regenerate an RC merely to recover a missing dispatch or a staging permission failure.
## Choose the Current Phase
@@ -64,7 +66,7 @@ Load only the references needed for the requested phase or unresolved issue. Con
- [Preparation](references/preparation.md): start a release or prepare version, changelog, and upgrade updates.
- [Rust crate bootstrap](references/rust-bootstrap.md): reserve crate names or inspect the protected bootstrap prerequisites.
-- [Release candidate](references/rc.md): tag an RC, inspect its required CI, build source artifacts, upload to SVN, or close Nexus staging.
+- [Release candidate](references/rc.md): prepare a weekly ATR candidate or manual RC, inspect required CI, recover missing dispatches, stage source artifacts, or close Nexus staging.
- [Go readiness](references/go.md): release or verify Go bindings and service modules when Go is in scope.
- [Rust readiness](references/rust.md): inspect the publish plan, packaging constraints, or release helper CI.
- [Vote](references/vote.md): check pre-vote readiness, prepare the vote, or determine and publish its result.
diff --git a/.agents/skills/opendal-release/references/official-release.md b/.agents/skills/opendal-release/references/official-release.md
index 7e825759b9..8408d4716c 100644
--- a/.agents/skills/opendal-release/references/official-release.md
+++ b/.agents/skills/opendal-release/references/official-release.md
@@ -4,6 +4,25 @@ Repository paths and shell commands are relative to the repository root.
## Official Release
+After the vote passes, the release manager initiates publication. The weekly
+workflow does not observe the vote result or create a final tag. Use the exact
+commit approved in the vote, even when main has advanced.
+
+A final tag pushed with the release manager's credentials triggers the existing
+release workflows. Rust, Python, Node.js, Ruby and .NET publish through their
+final-tag paths; Java stages artifacts and still needs Nexus promotion. Dart
+builds artifacts. Verify workflow results and package-specific registry versions.
+Creating a GitHub Release is a separate step, not the trigger described here.
+
+Do not replace this push with a `GITHUB_TOKEN` push and assume the same result:
+that token suppresses downstream push triggers. Final-tag automation would need
+its own dispatch design, including Ruby's push-only publishing condition. The
+weekly RC dispatch flags are not a general final-publication recipe.
+
+For sources staged in ATR, follow the live ATR voting/publication path for the
+approved candidate revision. The SVN commands below apply to SVN-staged sources;
+do not rebuild approved archives to switch between the two paths.
+
After the vote passes:
1. Push final release tag:
diff --git a/.agents/skills/opendal-release/references/rc.md b/.agents/skills/opendal-release/references/rc.md
index 2900885aea..c70903d63f 100644
--- a/.agents/skills/opendal-release/references/rc.md
+++ b/.agents/skills/opendal-release/references/rc.md
@@ -2,7 +2,30 @@
Repository paths and shell commands are relative to the repository root.
-## RC Tagging
+## Weekly ATR Preparation
+
+Read `website/community/release/weekly.md` and the selected revision of
+`.github/workflows/weekly_release.yml` before operating this path. Scheduled
+preparation uses the Friday cutoff; manual dispatch pins the selected commit.
+The workflow prepares package versions, pushes a candidate branch and lightweight
+RC tag, and calls `release-compose.yml` to build, sign and upload source archives.
+Do not add a manual version PR or rebuild an already staged candidate by default.
+
+Check that the dispatch implementation is on the revision actually running.
+Merging a workflow fix does not change earlier runs or backfill their downstream
+jobs. The weekly `builds` job dispatches the existing workflows automatically;
+no human needs to trigger each build in a normal run. The `notify` job waits for
+compose and dispatch, then posts a preparation notice. It does not wait for all
+downstream results, evaluate votes, or publish a final release.
+
+For ATR OIDC, register `.github/workflows/weekly_release.yml` as an allowed compose
+caller alongside `.github/workflows/release-compose.yml`. Inspect the actual ATR
+permission error and initiating actor before changing artifacts or source code.
+After correcting external configuration, rerun failed jobs on the original run
+when its candidate and completed outputs remain usable. Rerunning all jobs can
+run preparation again and create another candidate.
+
+## Manual RC Tagging
Before creating an RC tag:
@@ -31,7 +54,7 @@ If a new commit lands after an RC tag and before the release is final, do not mo
## Required CI Gate
-After pushing the RC tag, inspect tag-triggered workflows with `gh`.
+After preparing the RC, inspect runs for its tag and verify their head SHA. Include both `push` and `workflow_dispatch` events; source compose success alone does not establish downstream success.
Default required gate:
@@ -49,7 +72,7 @@ If the release manager explicitly narrows or expands the gate, follow that instr
Useful commands:
```bash
-gh run list --repo apache/opendal --branch "v${release_version}" --event push --limit 50 \
+gh run list --repo apache/opendal --branch "v${release_version}" --limit 100 \
--json name,status,conclusion,databaseId,url
gh run view "${run_id}" --repo apache/opendal --json status,conclusion,jobs
@@ -68,7 +91,12 @@ Rules:
## Build ASF Source Artifacts
-Only build artifacts after the required RC workflows are green.
+For weekly ATR candidates, reuse the signed artifacts from the successful compose
+run and verify their candidate SHA, signatures, hashes and ATR revision. Source
+composition and downstream builds run independently; both must satisfy the agreed
+pre-vote gate. Do not rerun `just release` merely to obtain local copies.
+
+For the manual source-build path, build after the required RC workflows are green.
```bash
git checkout "v${release_version}"
diff --git a/.agents/skills/opendal-release/references/troubleshooting.md b/.agents/skills/opendal-release/references/troubleshooting.md
index 25f600bb44..6d222e45de 100644
--- a/.agents/skills/opendal-release/references/troubleshooting.md
+++ b/.agents/skills/opendal-release/references/troubleshooting.md
@@ -4,6 +4,20 @@ Repository paths and shell commands are relative to the repository root.
## Common Failure Patterns
+### An RC tag exists but release workflows did not start
+
+A tag pushed with `GITHUB_TOKEN` does not trigger downstream push workflows.
+Inspect the tag SHA, weekly run revision and `builds` job before diagnosing an
+individual binding. `Release Python Binding` is dispatched through
+`release_python.yml`; it is not implied by source compose success.
+
+Use the recovery procedure in `website/community/release/weekly.md`. Query runs
+without restricting the event to `push`, then dispatch only missing workflows at
+the existing RC tag. Reuse an existing failed run for retries. Read the workflow
+at that tag: adding `workflow_dispatch` on main does not add it to an old tag.
+An unsupported old-tag workflow requires a separate recovery decision, not moving
+the RC tag. A new weekly run prepares a new candidate and is not a backfill.
+
### Required CI is green except unrelated workflows
Use the agreed gate. For 0.56.0, the blocking gate was Rust / Java / Python / NodeJS. Dotnet RC NuGet publishing was outside that gate and was fixed separately.
diff --git a/.agents/skills/opendal-release/references/vote.md b/.agents/skills/opendal-release/references/vote.md
index 2f4150e81f..dc0f8bb2b6 100644
--- a/.agents/skills/opendal-release/references/vote.md
+++ b/.agents/skills/opendal-release/references/vote.md
@@ -9,8 +9,8 @@ Run this checklist immediately before creating the vote discussion:
- RC tag exists and points to the intended commit.
- A newer `main` SHA does not invalidate an existing RC by itself; the vote is on the RC tag and uploaded artifacts.
- Required RC workflows are `completed/success`.
-- `dist/dev/opendal/${release_version}/` exists and contains all generated source artifacts.
-- The artifact filenames in `dist/dev` match the package-specific versions from `dev/src/release/package.rs`.
+- The selected ATR revision or `dist/dev/opendal/${release_version}/` contains the signed source artifacts being proposed.
+- Artifact filenames match the package-specific versions from `dev/src/release/package.rs`; signatures and hashes have been independently verified.
- `KEYS` URL is reachable: `https://downloads.apache.org/opendal/KEYS`.
- Maven staging URL returns success and is not an open/hidden staging repo.
- TestPyPI project URL is reachable: `https://test.pypi.org/project/opendal/`.
@@ -24,6 +24,11 @@ If TestPyPI publish failed only because a file already exists from the same RC
attempt, distinguish that from missing artifacts. Report the exact duplicate
filename and proceed only with an explicit release-manager waiver.
+A weekly candidate-ready Discussion is a preparation notice, not a vote. For
+ATR candidates, inspect ATR checks and pin the verified candidate revision; use
+the live ATR vote path and its artifact links. Do not substitute an unpopulated
+SVN directory in the vote text. The template below applies to SVN-staged votes.
+
## Start Vote Discussion
Create the discussion in the `General` category of `apache/opendal`.
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index c1e8396e7b..cf0e1a59a3 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -37,6 +37,10 @@ on:
paths: *paths
workflow_dispatch:
inputs:
+ deploy-nightlies:
+ description: "Deploy built documentation to nightlies"
+ type: boolean
+ default: true
release_version:
description: "Release tag to deploy to nightlies"
required: true
@@ -642,7 +646,7 @@ jobs:
OPENDAL_WEBSITE_NOT_LATEST: true
- name: Deploy to nightlies for tagged version
- if: ${{ (startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'rc')) || github.event_name == 'workflow_dispatch' }}
+ if: ${{ (github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'rc')) || (github.event_name == 'workflow_dispatch' && inputs.deploy-nightlies) }}
shell: bash
env:
NIGHTLIES_RSYNC_HOST: ${{ secrets.NIGHTLIES_RSYNC_HOST }}
@@ -670,7 +674,7 @@ jobs:
OPENDAL_WEBSITE_BASE_URL: /opendal/opendal-docs-stable/
- name: Deploy to nightlies for stable version
- if: ${{ (startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'rc')) || github.event_name == 'workflow_dispatch' }}
+ if: ${{ (github.event_name != 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/') && !contains(github.ref, 'rc')) || (github.event_name == 'workflow_dispatch' && inputs.deploy-nightlies) }}
shell: bash
env:
NIGHTLIES_RSYNC_HOST: ${{ secrets.NIGHTLIES_RSYNC_HOST }}
diff --git a/.github/workflows/release_ruby.yml b/.github/workflows/release_ruby.yml
index a88ce0d461..2e9134d4ad 100644
--- a/.github/workflows/release_ruby.yml
+++ b/.github/workflows/release_ruby.yml
@@ -34,7 +34,8 @@ on:
# a release process. For now, we can rely on artifacts for testing pre-releases.
# In the future, we could consider a GitHub private registry for pre-releases.
#
- # omits `workflow_dispatch` to tighten this release workflow.
+ # Manual dispatch supports candidate builds only; publishing still requires a tag push.
+ workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}
@@ -196,7 +197,7 @@ jobs:
publish:
# RC tags only build artifacts for release verification. RubyGems does not
# provide a staging registry, so publish only from the final release tag.
- if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-')
+ if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-')
needs: [build, smoke-test, build-native]
runs-on: ubuntu-latest
diff --git a/.github/workflows/weekly_release.yml b/.github/workflows/weekly_release.yml
index d5fdeb266a..47a87eacec 100644
--- a/.github/workflows/weekly_release.yml
+++ b/.github/workflows/weekly_release.yml
@@ -118,3 +118,83 @@ jobs:
rc: ${{ needs.prepare.outputs.rc }}
secrets:
GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
+
+ builds:
+ needs: prepare
+ runs-on: ubuntu-latest
+ permissions:
+ actions: write
+ contents: read
+ steps:
+ - name: Dispatch existing RC workflows
+ env:
+ GH_TOKEN: ${{ github.token }}
+ RC: ${{ needs.prepare.outputs.rc }}
+ SHA: ${{ needs.prepare.outputs.sha }}
+ run: |
+ # GITHUB_TOKEN pushes do not trigger the existing tag workflows.
+ for workflow in ci_bindings_{c,cpp,d,dart,dotnet,go,haskell,java,lua,nodejs,ocaml,php,ruby,swift,zig}.yml release_{dart,dotnet,java,nodejs,python,ruby,rust}.yml docs.yml; do
+ # Reuse runs already accepted before a partial dispatch failure.
+ count=$(gh run list --repo apache/opendal --workflow "$workflow" --commit "$SHA" --event workflow_dispatch --limit 1 --json databaseId --jq length)
+ if [ "$count" -gt 0 ]; then continue; fi
+ args=()
+ case "$workflow" in
+ release_nodejs.yml) args=(-f nodejs-publish=false -f nodejs-publish-dry-run=false) ;;
+ release_dotnet.yml) args=(-f release_type=none) ;;
+ docs.yml) args=(-f release_version="v$RC" -f deploy-nightlies=false) ;;
+ esac
+ gh workflow run "$workflow" --repo apache/opendal --ref "v$RC" "${args[@]}"
+ done
+
+ notify:
+ needs: [prepare, compose, builds]
+ runs-on: ubuntu-latest
+ permissions:
+ discussions: write
+ contents: read
+ steps:
+ - name: Announce staged candidate for RM verification
+ env:
+ GH_TOKEN: ${{ github.token }}
+ RC: ${{ needs.prepare.outputs.rc }}
+ SHA: ${{ needs.prepare.outputs.sha }}
+ run: |
+ title="Source candidate ready: $RC"
+ # GraphQL variables must reach the API without shell expansion.
+ # shellcheck disable=SC2016
+ gh api graphql --paginate --slurp -f query='
+ query($endCursor: String) {
+ repository(owner: "apache", name: "opendal") {
+ id
+ discussionCategories(first: 100) { nodes { id name } }
+ discussions(first: 100, after: $endCursor) {
+ nodes { title url }
+ pageInfo { hasNextPage endCursor }
+ }
+ }
+ }' > "$RUNNER_TEMP/discussions.json"
+ existing=$(jq -r --arg title "$title" '[.[].data.repository.discussions.nodes[] | select(.title == $title) | .url][0] // empty' "$RUNNER_TEMP/discussions.json")
+ if [ -n "$existing" ]; then
+ echo "$existing" >> "$GITHUB_STEP_SUMMARY"
+ exit 0
+ fi
+ repository=$(jq -er '.[0].data.repository.id' "$RUNNER_TEMP/discussions.json")
+ category=$(jq -er '.[0].data.repository.discussionCategories.nodes[] | select(.name == "General") | .id' "$RUNNER_TEMP/discussions.json")
+ cat > "$RUNNER_TEMP/candidate-notice.md" <<EOF
+ Source candidate **$RC** has been built, signed and uploaded to ATR.
+
+ - [ATR candidate](https://releases.apache.org/compose/opendal/$RC)
+ - [RC tag](https://github.com/apache/opendal/releases/tag/v$RC), commit: \`$SHA\`
+ - [Source build and signed artifacts](https://github.com/apache/opendal/actions/runs/$GITHUB_RUN_ID)
+ - [RC builds and binding checks](https://github.com/apache/opendal/actions?query=branch%3Av$RC)
+
+ RC build workflows have been dispatched; this notice does not mean they have passed.
+ Release manager: check their results and staging outputs, inspect ATR checks, and independently verify the candidate before starting the formal vote. This Discussion is a preparation notice, not a vote.
+ EOF
+ # shellcheck disable=SC2016
+ gh api graphql -f query='
+ mutation($repository: ID!, $category: ID!, $title: String!, $body: String!) {
+ createDiscussion(input: {repositoryId: $repository, categoryId: $category, title: $title, body: $body}) {
+ discussion { url }
+ }
+ }' -f repository="$repository" -f category="$category" -f title="$title" -F body=@"$RUNNER_TEMP/candidate-notice.md" --jq '.data.createDiscussion.discussion.url' >> "$GITHUB_STEP_SUMMARY"
diff --git a/website/community/release/release.md b/website/community/release/release.md
index 0633a16fa0..2e425b98e7 100644
--- a/website/community/release/release.md
+++ b/website/community/release/release.md
@@ -474,6 +474,14 @@ Example: <https://lists.apache.org/thread/xk5myl10mztcfotn59oo59s4ckvojds6>
## Official Release
+The release manager initiates this phase after the vote passes. Weekly candidate
+preparation does not monitor vote results or publish the final release. Use the
+approved RC commit even if `main` has advanced.
+
+For a candidate staged in ATR, complete source publication through ATR using the
+approved revision. The SVN instructions below apply to candidates staged in
+`dist/dev`; preserve the approved archives in either path.
+
### Push the release git tag
```shell
@@ -485,6 +493,17 @@ git tag -s v${release_version}
git push origin v${release_version}
```
+Push the final tag with the release manager's credentials. This triggers the
+existing tag workflows: Rust, Python, Node.js, Ruby and .NET take their formal
+publication paths; Java stages to Nexus and requires the promotion below. Dart
+builds artifacts. Check the resulting runs and package repositories before
+claiming publication succeeded.
+
+Tags pushed with `GITHUB_TOKEN` do not trigger those push workflows. The weekly
+RC dispatcher does not automate final publication; its inputs preserve RC
+behavior, and Ruby publishing requires a tag push. Creating the GitHub Release
+later is separate from this trigger.
+
### Publish artifacts to SVN RELEASE branch
```shell
diff --git a/website/community/release/weekly.md b/website/community/release/weekly.md
index d605b961ec..280505d3cf 100644
--- a/website/community/release/weekly.md
+++ b/website/community/release/weekly.md
@@ -31,7 +31,8 @@ changes may require editing the inventory before running preparation again.
Each attempt commits the version, dependency and changelog updates on a fresh
`release-candidates/weekly-<run>-<attempt>` branch. It pushes that branch and its RC
-tag together, then passes the resulting SHA to compose in the same workflow run.
+lightweight tag together, then passes the resulting SHA to compose in the same workflow run.
+Compose signs the source archives; the lightweight tag itself is unsigned.
The RC suffix identifies the Actions run and attempt. Compatibility or preparation
failures stop the run before compose; correct the cause and start another run.
@@ -47,11 +48,81 @@ retains its caller's identity. The actor initiating a manual run or owning the
schedule must have the required ASF-linked project permissions. An RM should
maintain the cron expression, which determines the scheduled actor.
-The workflow ends at ATR compose. It does not start votes, announce releases,
-create final tags, synchronize main or publish language packages. These remain
-steps in the existing release procedure. Keep the candidate branch and signed
-artifacts until that procedure is complete.
+## Builds and preparation notice
-Installing this workflow on main activates the schedule. The initiating actor and
-reusable workflow's ATR OIDC identity still require a live rehearsal; local checks
-and this PR do not establish a successful remote upload.
+After pushing the RC tag, the workflow explicitly dispatches the existing tag
+builds, binding checks and documentation workflow against that tag. GitHub does
+not trigger downstream push workflows for tags created with `GITHUB_TOKEN`.
+These dispatches retain the RC behavior: Java stages to Nexus, Python uses
+TestPyPI, NodeJS performs a publish dry run, Ruby and .NET retain artifacts, and
+Rust does not publish. Documentation retains RC staging without deploying to
+nightlies. A retry skips workflows already dispatched for the candidate commit;
+rerun failed downstream jobs from their own runs.
+
+After ATR upload and dispatch succeed, the workflow posts a candidate preparation
+notice in GitHub Discussions with the ATR, tag, source artifact and build links.
+It reuses an existing notice for the same RC when retried. This notice does not
+assert that downstream builds or ATR checks passed. The RM verifies those results
+and the candidate before starting the formal vote.
+
+The workflow does not start votes, announce final releases, create final tags or
+synchronize main. These remain steps in the existing release procedure. Keep the
+candidate branch and signed artifacts until that procedure is complete.
+
+## Recovering an existing candidate
+
+Inspect the weekly run's revision and jobs before assuming automatic dispatch is
+available. Workflow changes must be present in the revision used by the run;
+merging a fix does not backfill older candidates. List runs for the existing tag
+without filtering to `push`, since automatic dispatch produces
+`workflow_dispatch` events:
+
+```bash
+gh run list --repo apache/opendal --branch "v${release_candidate_version}" \
+ --limit 100 --json name,event,headSha,status,conclusion,databaseId,url
+```
+
+Check each run's head SHA against the candidate commit. Rerun failed downstream
+jobs from their existing runs. If a workflow has no run, dispatch it against the
+same RC tag. For example, to recover a missing Python release build:
+
+```bash
+gh workflow run release_python.yml --repo apache/opendal \
+ --ref "v${release_candidate_version}"
+```
+
+This RC path publishes to TestPyPI. Read the selected workflow at the RC tag
+before dispatching: it must support `workflow_dispatch` there, not only on main.
+Use the workflow list and inputs in the weekly `builds` job as the maintained
+reference for other missing builds. In particular, NodeJS uses
+`nodejs-publish=false` and `nodejs-publish-dry-run=false` (the RC tag enables its
+dry run), .NET uses `release_type=none`, and Docs uses the RC tag as
+`release_version` with `deploy-nightlies=false`. An old tag without a required
+input or dispatch trigger needs a separate recovery decision; do not move it.
+
+A successful dispatch only confirms acceptance. Find the resulting run and follow
+it to completion. A candidate notice does not replace checking all required
+builds, registry staging and ATR checks.
+
+For a compose permission failure, correct ATR's allowed caller or actor
+configuration and rerun failed jobs on the original run when its completed
+outputs remain usable. Reuse existing signed artifacts. Rerunning preparation or
+starting a new weekly run creates a new candidate rather than recovering the old
+one.
+
+## After the vote passes
+
+The RM follows [Official Release](release.md#official-release): create the final
+signed tag at the approved RC commit and push it with the RM's credentials. That
+tag push triggers the existing final-release workflows. Weekly automation does
+not evaluate the vote or create this tag. Nexus promotion, source publication,
+GitHub Release and the announcement remain part of the release procedure.
+
+A final tag pushed with `GITHUB_TOKEN` would also suppress downstream push
+workflows. The RC dispatch mechanism is not a final-release dispatcher, and Ruby
+publishing still requires a final-tag push. Any future automation of final tags
+must account for those conditions.
+
+For ATR-staged sources, publish the approved candidate revision through ATR.
+The SVN move commands in the release procedure apply to SVN-staged candidates.
+Preserve the approved source archives through publication.