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/14 06:23:51 UTC

(opendal) branch xuanwo/fix-release-discussion-sync created (now 76c4f9f953)

This is an automated email from the ASF dual-hosted git repository.

Xuanwo pushed a change to branch xuanwo/fix-release-discussion-sync
in repository https://gitbox.apache.org/repos/asf/opendal.git


      at 76c4f9f953 fix(release): sync lifecycle through discussion comments

This branch includes the following new commits:

     new 76c4f9f953 fix(release): sync lifecycle through discussion comments

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.



(opendal) 01/01: fix(release): sync lifecycle through discussion comments

Posted by xu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Xuanwo pushed a commit to branch xuanwo/fix-release-discussion-sync
in repository https://gitbox.apache.org/repos/asf/opendal.git

commit 76c4f9f95380e72c12bc05ce30a7e7aefc4728f3
Author: Xuanwo <gi...@xuanwo.io>
AuthorDate: Mon Sep 14 14:23:32 2026 +0800

    fix(release): sync lifecycle through discussion comments
---
 scripts/release_lifecycle.py        | 37 +++++++++++-------
 scripts/test_release_impact.py      |  5 ++-
 scripts/test_release_lifecycle.py   | 77 +++++++++++++++++++++++++++++++++++++
 website/community/release/weekly.md |  6 ++-
 4 files changed, 108 insertions(+), 17 deletions(-)

diff --git a/scripts/release_lifecycle.py b/scripts/release_lifecycle.py
index 2304098be5..1de912f23d 100644
--- a/scripts/release_lifecycle.py
+++ b/scripts/release_lifecycle.py
@@ -24,6 +24,7 @@ releases own their respective completion records; this module keeps no journal.
 import argparse
 import base64
 import dataclasses
+import hashlib
 import json
 import os
 import re
@@ -179,16 +180,9 @@ def discussion(title, body, category="General"):
     if len(matches) > 1:
         raise ValueError(f"multiple discussions named {title}")
     if matches:
-        current = matches[0]
-        if current["body"] != body:
-            api(
-                "graphql",
-                {
-                    "query": "mutation($id:ID!,$body:String!){updateDiscussion(input:{discussionId:$id,body:$body}){discussion{id}}}",
-                    "variables": {"id": current["id"], "body": body},
-                },
-            )
-        return current
+        # Actions tokens can create Discussions but cannot edit their bodies.
+        # Publish lifecycle changes as comments on the original entry point.
+        return matches[0]
     result = api(
         "graphql",
         {
@@ -284,7 +278,10 @@ def notice(candidate, release, status=None):
         if release["phase"] == "release"
         else candidate.branch
     )
-    body = f"""**{status}**
+    body = f"""**Release candidate: {rc}**
+
+ATR is the release authority. Follow the latest status replies below and the ATR
+vote page for current progress; this opening post contains candidate instructions.
 
 - [ATR checks]({ATR}/checks/opendal/{rc}) · [Download candidate]({ATR}/download/path/opendal/{rc}) · [Vote]({ATR}/vote/opendal/{rc})
 - [Candidate branch](https://github.com/{REPO}/tree/{branch}) · [RC tag](https://github.com/{REPO}/releases/tag/v{rc})
@@ -294,8 +291,8 @@ def notice(candidate, release, status=None):
 {version_notes(candidate)}
 ### Release manager: next actions
 
-1. Check required build results and language staging, including the closed Nexus repository. Dispatch acceptance alone is not build success.
-2. Independently download, verify signatures and checksums, inspect licenses, and build the source candidate. Review the announcement draft below.
+1. Review the candidate in ATR. Other builds and language package staging are optional and do not block starting the ATR vote.
+2. Review the source artifacts and announcement draft below.
 3. Start the vote on the verified revision. After the voting period, inspect the tally and resolve according to the result.
 
 ### CLI for release managers and agents
@@ -344,7 +341,19 @@ Download and verify the candidate. During voting, ASF committers can vote on ATR
         comment_once(
             current["id"],
             f"<!-- opendal-vote:{rc}:{seq} -->",
-            f"Voting is now open: [{rc}]({ATR}/vote/opendal/{rc}). The ATR page shows the deadline and participation instructions. Please verify the candidate and vote there or in the dev mailing-list thread.",
+            f"Voting is now open: [{rc}]({ATR}/vote/opendal/{rc}). The ATR page shows the deadline and participation instructions. Follow ATR and these status replies for current progress; the opening post records candidate preparation. Other builds and language package staging are optional and do not block this vote.",
+        )
+    else:
+        progress = f"""**{status}**
+
+- ATR revision: `{revision}`; [current ATR status]({ATR}/vote/opendal/{rc}).
+- [Source branch](https://github.com/{REPO}/tree/{branch}) · [Publication runs](https://github.com/{REPO}/actions/workflows/release_publish.yml).
+"""
+        digest = hashlib.sha256(progress.encode()).hexdigest()
+        comment_once(
+            current["id"],
+            f"<!-- opendal-status:{rc}:{release.get('current_vote_seq')}:{digest} -->",
+            progress,
         )
     return current
 
diff --git a/scripts/test_release_impact.py b/scripts/test_release_impact.py
index 20e908976c..a6cc9916c9 100644
--- a/scripts/test_release_impact.py
+++ b/scripts/test_release_impact.py
@@ -256,7 +256,10 @@ class ImpactTests(unittest.TestCase):
             self.assertIn(candidate.sha, api.call_args.args[0])
         with (
             patch.object(release, "version_notes", return_value=notes),
-            patch.object(release, "discussion", return_value={}) as discussion,
+            patch.object(
+                release, "discussion", return_value={"id": "discussion"}
+            ) as discussion,
+            patch.object(release, "comment_once"),
         ):
             release.notice(
                 candidate,
diff --git a/scripts/test_release_lifecycle.py b/scripts/test_release_lifecycle.py
index 36e790f296..04676bdde1 100644
--- a/scripts/test_release_lifecycle.py
+++ b/scripts/test_release_lifecycle.py
@@ -316,6 +316,83 @@ class LifecycleTests(unittest.TestCase):
             release.comment_once("discussion", marker, "Vote now")
             api.assert_not_called()
 
+    def test_sync_announces_vote_without_editing_existing_discussion(self):
+        discussion = {
+            "id": "discussion",
+            "title": f"Release candidate: {self.candidate.rc}",
+            "body": "Candidate uploaded; RM verification is next.",
+        }
+        comments = []
+
+        def github_read(*args):
+            return json.dumps([{"data": {"node": {"comments": {"nodes": comments}}}}])
+
+        def github_write(path, payload):
+            self.assertIn("addDiscussionComment(", payload["query"])
+            comments.append({"body": payload["variables"]["body"]})
+
+        with (
+            tempfile.TemporaryDirectory() as directory,
+            patch.dict(os.environ, {"GITHUB_OUTPUT": f"{directory}/outputs"}),
+            patch.object(release, "candidates", return_value=[self.candidate.rc]),
+            patch.object(release.Candidate, "load", return_value=self.candidate),
+            patch.object(
+                release.Candidate,
+                "atr",
+                return_value=self.atr
+                | {"phase": "release_candidate", "vote_resolved": None},
+            ),
+            patch.object(release, "version_notes", return_value="Package versions"),
+            patch.object(
+                release, "discussion_data", return_value=("repo", {}, [discussion])
+            ),
+            patch.object(release, "gh", side_effect=github_read),
+            patch.object(release, "api", side_effect=github_write),
+        ):
+            release.sync()
+            release.sync()
+        self.assertEqual(len(comments), 1)
+        self.assertIn("Voting is now open", comments[0]["body"])
+        self.assertEqual(
+            discussion["body"], "Candidate uploaded; RM verification is next."
+        )
+
+    def test_publication_status_comments_only_change_with_progress(self):
+        comments = []
+
+        def github_write(path, payload):
+            self.assertIn("addDiscussionComment(", payload["query"])
+            comments.append({"body": payload["variables"]["body"]})
+
+        with (
+            patch.object(release, "version_notes", return_value="Package versions"),
+            patch.object(release, "discussion", return_value={"id": "discussion"}),
+            patch.object(
+                release,
+                "gh",
+                side_effect=lambda *args: json.dumps(
+                    [{"data": {"node": {"comments": {"nodes": comments}}}}]
+                ),
+            ),
+            patch.object(release, "api", side_effect=github_write),
+        ):
+            release.notice(self.candidate, self.atr, "Publication is running.")
+            release.notice(self.candidate, self.atr, "Publication is running.")
+            release.notice(
+                self.candidate,
+                self.atr | {"phase": "release"},
+                "Publication is complete.",
+            )
+            release.notice(
+                self.candidate,
+                self.atr | {"phase": "release"},
+                "Publication is complete.",
+            )
+        self.assertEqual(len(comments), 2)
+        self.assertIn("Publication is running.", comments[0]["body"])
+        self.assertIn("Publication is complete.", comments[1]["body"])
+        self.assertIn("tree/releases/0.59.3)", comments[1]["body"])
+
     def test_baseline_does_not_require_git_ancestry(self):
         releases = [
             {
diff --git a/website/community/release/weekly.md b/website/community/release/weekly.md
index e3ce366e7a..3e92b728c7 100644
--- a/website/community/release/weekly.md
+++ b/website/community/release/weekly.md
@@ -152,8 +152,10 @@ credentials belong in the CLI's hidden prompt. The CLI is an interactive RM tool
 CI reads ATR's JSON API and uses its trusted-publisher announcement endpoint.
 
 `release_lifecycle.yml` runs hourly at minute 17 (GitHub can delay scheduled runs).
-It updates the same candidate Discussion and posts one reminder per ATR vote
-round. GitHub subscribers receive that comment through their notification
+It keeps the candidate Discussion's opening post unchanged and appends status
+changes as comments, including one reminder per ATR vote round. Unchanged status
+does not generate another comment. Follow the latest replies and ATR for current
+progress. GitHub subscribers receive comments through their notification
 settings. The official vote remains in ATR and the dev mailing-list thread; the
 Discussion does not create another ballot. To synchronize sooner: