You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hugegraph.apache.org by ji...@apache.org on 2026/10/09 13:54:34 UTC

(hugegraph-toolchain) branch master updated: refactor: support 1.8.0 release SDK packaging (#794)

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

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph-toolchain.git


The following commit(s) were added to refs/heads/master by this push:
     new 38fcddc6e refactor: support 1.8.0 release SDK packaging (#794)
38fcddc6e is described below

commit 38fcddc6edf197472581e28b968eff14be129042
Author: imbajin <ji...@apache.org>
AuthorDate: Fri Oct 9 21:54:29 2026 +0800

    refactor: support 1.8.0 release SDK packaging (#794)
    
    Toolchain now builds with Java 17 and the 1.8.0 SDK while testing current Server 1.8.0 and released Server 1.7.0 separately. Historical Server compatibility no longer changes the SDK used to compile Toolchain.
---
 .gitattributes                                     |   3 +-
 .github/actions/setup-candidate-sdk/action.yml     |  16 +-
 .github/scripts/ci-policy.py                       |   5 +-
 .github/scripts/test_candidate_sdk.py              | 322 ++++++++++++++++++---
 .github/scripts/test_ci_policy.py                  |  64 +++-
 .github/scripts/test_java17_image.py               |  16 +-
 .github/scripts/verify_candidate_image_sdk.py      | 122 ++++++--
 .github/workflows/ci.yml                           |  63 ++--
 .github/workflows/client-go-ci.yml                 |  38 ++-
 .github/workflows/java17-images-ci.yml             |   2 +-
 .github/workflows/spark-connector-ci.yml           |  10 +
 .github/workflows/upstream-compatibility.yml       |   7 -
 README.md                                          |  20 +-
 docs/ci.md                                         |   8 +-
 docs/java17-migration.md                           |  38 ++-
 hugegraph-dist/pom.xml                             |  14 +-
 hugegraph-dist/release-docs/LICENSE                |  56 +++-
 hugegraph-dist/release-docs/NOTICE                 |  12 +-
 .../scripts/dependency/known-dependencies.txt      |  29 +-
 hugegraph-hubble/Dockerfile                        |  10 +-
 hugegraph-hubble/README.md                         |   4 +-
 hugegraph-hubble/hubble-dist/pom.xml               |   4 +
 hugegraph-loader/Dockerfile                        |   6 +-
 hugegraph-loader/README.md                         |   2 +-
 hugegraph-loader/pom.xml                           |   6 +
 hugegraph-spark-connector/README.md                |   6 +-
 hugegraph-tools/pom.xml                            |   4 +
 pom.xml                                            |   6 +-
 28 files changed, 704 insertions(+), 189 deletions(-)

diff --git a/.gitattributes b/.gitattributes
index ef04141f9..2638b09a5 100755
--- a/.gitattributes
+++ b/.gitattributes
@@ -8,7 +8,8 @@ apache-release.sh export-ignore
 .licenserc.yaml export-ignore
 
 # ignored directory
-.github/ export-ignore
+.github/workflows/ export-ignore
+.github/outdated/ export-ignore
 hugegraph-dist/scripts/ export-ignore
 .serena/ export-ignore
 # only exclude the root
diff --git a/.github/actions/setup-candidate-sdk/action.yml b/.github/actions/setup-candidate-sdk/action.yml
index 02a52e556..849486966 100644
--- a/.github/actions/setup-candidate-sdk/action.yml
+++ b/.github/actions/setup-candidate-sdk/action.yml
@@ -1,5 +1,5 @@
 name: 'Setup same-source candidate SDK'
-description: 'Build a locked Java 17 server reactor outside the workspace in an isolated Maven repository'
+description: 'Build a same-source Java 17 SDK outside the workspace in an isolated Maven repository'
 
 inputs:
   repository:
@@ -7,10 +7,10 @@ inputs:
     default: 'apache/hugegraph'
   ref:
     description: 'Immutable candidate source commit'
-    default: 'e62c961e00221569d4f955abbadf60faee45b283'
+    default: '68855199031d5801edb4fe41b2bacbacffe8fe68'
   expected-commit:
     description: 'Locked candidate source commit'
-    default: 'e62c961e00221569d4f955abbadf60faee45b283'
+    default: '68855199031d5801edb4fe41b2bacbacffe8fe68'
 
 outputs:
   repository:
@@ -53,6 +53,7 @@ runs:
         SERVER_REPOSITORY: ${{ steps.source.outputs.repository }}
         SERVER_FETCH_REF: ${{ steps.source.outputs.fetch_ref }}
       run: |
+        set -euo pipefail
         candidate_repo=$(mktemp -d "$RUNNER_TEMP/hugegraph-candidate-m2.XXXXXX")
         server_build=$(mktemp -d "$RUNNER_TEMP/hugegraph-candidate-build.XXXXXX")
         source_dir="$server_build/source"
@@ -76,17 +77,14 @@ runs:
           printf 'Candidate SDK installer did not produce a provenance manifest\n' >&2
           exit 1
         fi
+        CANDIDATE_SOURCE_COMMIT="$SERVER_COMMIT" \
+          python3 "$GITHUB_WORKSPACE/.github/scripts/verify_candidate_image_sdk.py" "$candidate_repo" \
+          --configure-upstream-env "$GITHUB_ENV"
         manifest_artifact_id=$(python3 -c 'import uuid; print(uuid.uuid4().hex)')
         archive_sha=$(sha256sum "${archives[0]}" | cut -d ' ' -f 1)
         printf 'archive-sha256=%s\nmanifest=%s\n' "$archive_sha" \
           "$manifest" >> "$GITHUB_OUTPUT"
         printf 'manifest-artifact-id=%s\n' "$manifest_artifact_id" >> "$GITHUB_OUTPUT"
-        # Keep subprocess Maven repository isolation aligned with CLI arguments.
-        {
-          printf 'MAVEN_OPTS=%s -Dmaven.repo.local=%s\n' "${MAVEN_OPTS:-}" "$candidate_repo"
-          printf 'MAVEN_ARGS=%s -Dmaven.repo.local=%s\n' "${MAVEN_ARGS:-}" "$candidate_repo"
-          printf 'CANDIDATE_SOURCE_COMMIT=%s\n' "$SERVER_COMMIT"
-        } >> "$GITHUB_ENV"
         printf 'archive=%s\nmaven-repo=%s\n' "${archives[0]}" "$candidate_repo" >> "$GITHUB_OUTPUT"
     - name: Retain candidate SDK provenance
       uses: actions/upload-artifact@v7
diff --git a/.github/scripts/ci-policy.py b/.github/scripts/ci-policy.py
index 46e6b60c0..43b09e77b 100644
--- a/.github/scripts/ci-policy.py
+++ b/.github/scripts/ci-policy.py
@@ -469,8 +469,9 @@ def gate(plan, results, fetch=None):
         if set(plan["expected"]).intersection(MODULES["toolchain"]):
             if results.get("fixture", {}).get("result") != "success":
                 raise ValueError("selected tests lack successful fixture")
-        if "hubble" in plan["expected"] and results.get("hubble-fixture", {}).get("result") != "success":
-            raise ValueError("selected Hubble tests lack successful baseline fixture")
+        if (set(plan["expected"]).intersection({"hubble", "go"}) and
+                results.get("current-fixture", {}).get("result") != "success"):
+            raise ValueError("selected tests lack successful current Server fixture")
     require_current_pr(plan, fetch or api)
     return result_report(plan, results)
 
diff --git a/.github/scripts/test_candidate_sdk.py b/.github/scripts/test_candidate_sdk.py
index 8d93d298c..6ce5024d2 100644
--- a/.github/scripts/test_candidate_sdk.py
+++ b/.github/scripts/test_candidate_sdk.py
@@ -20,11 +20,13 @@
 import copy
 import json
 import os
+import re
 from unittest.mock import patch
 from pathlib import Path
 import shutil
 import subprocess
 import tempfile
+import textwrap
 import unittest
 import uuid
 import xml.etree.ElementTree as ET
@@ -55,7 +57,7 @@ class CandidateParentModelTest(unittest.TestCase):
                 root_pom = (header + '<parent><groupId>org.apache</groupId><artifactId>apache</artifactId>'
                             '<version>23</version></parent><groupId>' + group + '</groupId>'
                             '<artifactId>root</artifactId><version>${revision}</version><packaging>pom</packaging>'
-                            '<properties><revision>1.7.0</revision></properties><modules><module>dependency</module>'
+                            '<properties><revision>1.8.0</revision></properties><modules><module>dependency</module>'
                             '<module>consumer</module></modules><build><plugins><plugin>'
                             '<artifactId>maven-remote-resources-plugin</artifactId><version>3.3.0</version>'
                             '</plugin></plugins></build></project>')
@@ -67,7 +69,7 @@ class CandidateParentModelTest(unittest.TestCase):
                     directory.mkdir()
                     body = flatten if module == "dependency" else ""
                     dependency = (('<dependencies><dependency><groupId>' + group + '</groupId>'
-                                   '<artifactId>dependency</artifactId><version>1.7.0</version>'
+                                   '<artifactId>dependency</artifactId><version>1.8.0</version>'
                                    '</dependency></dependencies>') if module == "consumer" else "")
                     (directory / "pom.xml").write_text(header + parent + '<artifactId>' + module + '</artifactId>'
                                                       + dependency + '<build><plugins>' + body
@@ -99,7 +101,7 @@ class CandidateParentModelTest(unittest.TestCase):
                     self.assertNotIn("Failed to build parent project", output)
                     version = ET.parse(source / ".flattened-pom.xml").getroot().findtext(
                         "{http://maven.apache.org/POM/4.0.0}version")
-                    self.assertEqual("1.7.0", version)
+                    self.assertEqual("1.8.0", version)
                 else:
                     self.assertIn(literal_request, output)
                 for path, original in original_poms.items():
@@ -116,18 +118,18 @@ class CandidateParentModelTest(unittest.TestCase):
             group = "org.example.candidatesdk.g" + uuid.uuid4().hex
             repository = Path.home() / ".m2/repository" / group.replace(".", "/")
             self.addCleanup(shutil.rmtree, repository, True)
-            artifact = repository / "sdk/1.8.0"
+            artifact = repository / "sdk/1.9.0"
             artifact.mkdir(parents=True)
             header = '<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion>'
-            (artifact / "sdk-1.8.0.pom").write_text(
+            (artifact / "sdk-1.9.0.pom").write_text(
                 header + '<groupId>' + group + '</groupId><artifactId>sdk</artifactId>'
-                '<version>1.8.0</version></project>')
-            with zipfile.ZipFile(artifact / "sdk-1.8.0.jar", "w") as jar:
+                '<version>1.9.0</version></project>')
+            with zipfile.ZipFile(artifact / "sdk-1.9.0.jar", "w") as jar:
                 jar.writestr("sdk-source.txt", "selected upstream revision")
-            (artifact / "_remote.repositories").write_text("sdk-1.8.0.pom>=\nsdk-1.8.0.jar>=\n")
+            (artifact / "_remote.repositories").write_text("sdk-1.9.0.pom>=\nsdk-1.9.0.jar>=\n")
             pom = root / "pom.xml"
             pom.write_text(header + '<groupId>org.example.consumer</groupId><artifactId>consumer</artifactId>'
-                           '<version>1</version><properties><hugegraph.version>1.7.0</hugegraph.version>'
+                           '<version>1</version><properties><hugegraph.version>1.8.0</hugegraph.version>'
                            '</properties><dependencies><dependency><groupId>' + group + '</groupId>'
                            '<artifactId>sdk</artifactId><version>${hugegraph.version}</version></dependency>'
                            '</dependencies></project>')
@@ -136,7 +138,7 @@ class CandidateParentModelTest(unittest.TestCase):
             command = [os.environ["CANDIDATE_SDK_MAVEN_TEST"], "-o", "-ntp", "--settings", str(settings),
                        "-f", str(pom), "org.apache.maven.plugins:maven-dependency-plugin:3.1.1:build-classpath",
                        "-Dmdep.outputFile=" + str(root / "classpath")]
-            env = dict(os.environ, MAVEN_ARGS="-Dhugegraph.version=1.8.0")
+            env = dict(os.environ, MAVEN_ARGS="-Dhugegraph.version=1.9.0")
             # Warm only Maven's public plugin dependencies when this opt-in
             # regression runs on a fresh machine; coordinate checks stay offline.
             prepared = subprocess.run([arg for arg in command if arg != "-o"], env=env,
@@ -146,11 +148,33 @@ class CandidateParentModelTest(unittest.TestCase):
             env["MAVEN_ARGS"] = ""
             wrong = subprocess.run(command, env=env, capture_output=True, text=True, timeout=90)
             self.assertNotEqual(0, wrong.returncode, wrong.stdout + wrong.stderr)
-            self.assertIn(group + ":sdk:jar:1.7.0", wrong.stdout + wrong.stderr)
-            env["MAVEN_ARGS"] = "-Dhugegraph.version=1.8.0"
+            self.assertIn(group + ":sdk:jar:1.8.0", wrong.stdout + wrong.stderr)
+            env["MAVEN_ARGS"] = "-Dhugegraph.version=1.9.0"
             selected = subprocess.run(command, env=env, capture_output=True, text=True, timeout=90)
             self.assertEqual(0, selected.returncode, selected.stdout + selected.stderr)
-            self.assertEqual(str(artifact / "sdk-1.8.0.jar"), (root / "classpath").read_text().strip())
+            self.assertEqual(str(artifact / "sdk-1.9.0.jar"), (root / "classpath").read_text().strip())
+
+    @unittest.skipUnless(os.environ.get("CANDIDATE_SDK_MAVEN_TEST"),
+                         "Set CANDIDATE_SDK_MAVEN_TEST for the aggregate-coordinate regression")
+    def test_toolchain_dist_coordinate_keeps_the_release_archive_name(self):
+        # Exercise Maven's real reactor selector and interpolation without packaging the full distribution.
+        with tempfile.TemporaryDirectory() as temporary:
+            output = Path(temporary) / "effective-pom.xml"
+            source = Path(__file__).resolve().parents[2]
+            result = subprocess.run([os.environ["CANDIDATE_SDK_MAVEN_TEST"], "-f", str(source / "pom.xml"),
+                                     "-pl", ":hugegraph-toolchain-dist", "-ntp", "-B",
+                                     "org.apache.maven.plugins:maven-help-plugin:3.2.0:effective-pom",
+                                     "-Doutput=" + str(output)], capture_output=True, text=True, timeout=90)
+            self.assertEqual(0, result.returncode, result.stdout + result.stderr)
+            model = ET.parse(output).getroot()
+            ns = "{http://maven.apache.org/POM/4.0.0}"
+            self.assertEqual("org.apache.hugegraph", model.findtext(ns + "groupId"))
+            self.assertEqual("hugegraph-toolchain-dist", model.findtext(ns + "artifactId"))
+            self.assertEqual("1.8.0", model.findtext(ns + "version"))
+            properties = model.find(ns + "properties")
+            self.assertEqual("hugegraph-toolchain", properties.findtext(ns + "release.name"))
+            self.assertEqual("apache-hugegraph-toolchain-1.8.0", properties.findtext(ns + "final.name"))
+            self.assertEqual("hugegraph-toolchain-dist-1.8.0", model.findtext(ns + "build/" + ns + "finalName"))
 
 
 class CandidateDistributionTest(unittest.TestCase):
@@ -160,7 +184,7 @@ class CandidateDistributionTest(unittest.TestCase):
         context.start()
         self.addCleanup(context.stop)
 
-    def fixture(self, root, revision="1.7.0"):
+    def fixture(self, root, revision="1.8.0"):
         repository = root / "m2"
         artifacts, modules = [], []
         for source_pom in sorted(sdk.REQUIRED_MODULES):
@@ -189,17 +213,24 @@ class CandidateDistributionTest(unittest.TestCase):
     def test_floating_sdk_version_preserves_verified_coordinates_and_maven_arguments(self):
         with tempfile.TemporaryDirectory() as temporary:
             root = Path(temporary)
-            repository, manifest = self.fixture(root, "1.8.0")
+            repository, manifest = self.fixture(root, "1.9.0-SNAPSHOT")
             environment = root / "github-env"
             # Fixed-baseline validation must still reject the newer revision.
             with self.assertRaises(RuntimeError):
                 sdk.validate_sdk(repository)
-            original = "--settings settings.xml -Dmaven.repo.local=" + str(repository)
-            with patch.dict(os.environ, {"MAVEN_ARGS": original}):
+            original = "--settings settings.xml -Dmaven.repo.local=/old -Dhugegraph.version=1.8.0"
+            with patch.dict(os.environ, {"MAVEN_ARGS": original, "MAVEN_OPTS": "-Xmx1g -Dsdk.validation.mode=release"}):
                 sdk.configure_upstream(repository, environment)
             lines = dict(line.split("=", 1) for line in environment.read_text().splitlines())
-            self.assertEqual("1.8.0", lines["CANDIDATE_SDK_VERSION"])
-            self.assertEqual(original + " -Dhugegraph.version=1.8.0", lines["MAVEN_ARGS"])
+            self.assertEqual("1.9.0-SNAPSHOT", lines["CANDIDATE_SDK_VERSION"])
+            for key in ("MAVEN_ARGS", "MAVEN_OPTS"):
+                self.assertIn("-Dmaven.repo.local=" + str(repository), lines[key])
+                self.assertIn("-Dhugegraph.version=1.9.0-SNAPSHOT", lines[key])
+                self.assertIn("-Dsdk.validation.mode=candidate", lines[key])
+                for property_name in ("maven.repo.local", "hugegraph.version", "sdk.validation.mode"):
+                    self.assertEqual(1, lines[key].count("-D" + property_name + "="))
+            self.assertIn("--settings settings.xml", lines["MAVEN_ARGS"])
+            self.assertIn("-Xmx1g", lines["MAVEN_OPTS"])
             with patch.dict(os.environ, lines):
                 sdk.validate_sdk(repository)
                 distribution = self.distribution(root, repository, manifest)
@@ -228,6 +259,16 @@ class CandidateDistributionTest(unittest.TestCase):
             self.assertIn("cannot be combined", result.stderr)
             self.assertFalse(environment.exists())
 
+    def test_floating_distribution_cli_uses_its_explicit_sdk_version(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            root = Path(temporary)
+            repository, manifest = self.fixture(root, "1.9.0-SNAPSHOT")
+            distribution = self.distribution(root, repository, manifest)
+            result = subprocess.run(["python3", sdk.__file__, str(repository), "--version", "1.9.0-SNAPSHOT",
+                                     "--distribution", str(distribution), "--module", "hubble"],
+                                    capture_output=True, text=True, timeout=10)
+            self.assertEqual(0, result.returncode, result.stderr)
+
     def write_manifest(self, repository, manifest):
         (repository / "candidate-sdk-manifest.json").write_text(json.dumps(manifest))
 
@@ -250,7 +291,7 @@ class CandidateDistributionTest(unittest.TestCase):
             directory = self.distribution(root, repository, manifest)
             for module in ("loader", "tools", "hubble"):
                 self.assertEqual(9, len(sdk.validate_distribution(repository, directory, module)))
-            common = directory / "lib/hugegraph-common-1.7.0.jar"
+            common = directory / "lib/hugegraph-common-1.8.0.jar"
             common.write_bytes(b"Central artifact with the same GAV")
             with self.assertRaisesRegex(RuntimeError, "changed packaged"):
                 sdk.validate_distribution(repository, directory, "loader")
@@ -261,7 +302,7 @@ class CandidateDistributionTest(unittest.TestCase):
                 root = Path(temporary)
                 repository, manifest = self.fixture(root)
                 directory = self.distribution(root, repository, manifest)
-                common = directory / "lib/hugegraph-common-1.7.0.jar"
+                common = directory / "lib/hugegraph-common-1.8.0.jar"
                 if scenario == "missing":
                     common.unlink()
                 elif scenario in ("old_version", "duplicate"):
@@ -278,7 +319,7 @@ class CandidateDistributionTest(unittest.TestCase):
                     (directory / "candidate-sdk-manifest.json").unlink()
                 else:
                     common.unlink()
-                    common.symlink_to(repository / "org/apache/hugegraph/hugegraph-common/1.7.0/hugegraph-common-1.7.0.jar")
+                    common.symlink_to(repository / "org/apache/hugegraph/hugegraph-common/1.8.0/hugegraph-common-1.8.0.jar")
                 with self.assertRaises(RuntimeError):
                     sdk.validate_distribution(repository, directory, "loader")
 
@@ -287,7 +328,7 @@ class CandidateDistributionTest(unittest.TestCase):
             root = Path(temporary)
             repository, manifest = self.fixture(root)
             directory = self.distribution(root, repository, manifest)
-            (directory / "lib/hugegraph-core-1.7.0.jar").unlink()
+            (directory / "lib/hugegraph-core-1.8.0.jar").unlink()
             sdk.validate_distribution(repository, directory, "tools")
             with self.assertRaisesRegex(RuntimeError, "hugegraph-core"):
                 sdk.validate_distribution(repository, directory, "hubble")
@@ -298,7 +339,7 @@ class CandidateDistributionTest(unittest.TestCase):
                 root = Path(temporary)
                 repository, manifest = self.fixture(root)
                 directory = self.distribution(root, repository, manifest)
-                pom = repository / "org/apache/hugegraph/hugegraph/1.7.0/hugegraph-1.7.0.pom"
+                pom = repository / "org/apache/hugegraph/hugegraph/1.8.0/hugegraph-1.8.0.pom"
                 if scenario == "wrong_source":
                     manifest["commit"] = "0" * 40
                 elif scenario == "false_origin":
@@ -318,13 +359,8 @@ class CandidateDistributionTest(unittest.TestCase):
             root = Path(temporary)
             repository, manifest = self.fixture(root)
             outputs = []
-            server_dist = repository / "org/apache/hugegraph/hugegraph-dist/1.7.0/hugegraph-dist-1.7.0.jar"
-            server_dist.parent.mkdir(parents=True)
-            server_dist.write_bytes(b"locked Server distribution")
-            manifest["artifacts"].append({"path": str(server_dist.relative_to(repository)),
-                                          "sha256": sdk.digest(server_dist), "source_reactor_install": True})
             for artifact, extension in (("hugegraph-toolchain", "pom"),
-                                        ("hugegraph-dist", "jar"),
+                                        ("hugegraph-toolchain-dist", "jar"),
                                         ("hugegraph-client", "jar"),
                                         ("hugegraph-loader", "jar"),
                                         ("hubble-be", "jar")):
@@ -334,28 +370,48 @@ class CandidateDistributionTest(unittest.TestCase):
                 manifest["artifacts"].append({"path": str(path.relative_to(repository)),
                                               "sha256": sdk.digest(path), "source_reactor_install": True})
                 outputs.append(path)
+            # The distinct Server dist coordinate is an immutable source input.
+            server_dist = repository / "org/apache/hugegraph/hugegraph-dist/1.8.0/hugegraph-dist-1.8.0.jar"
+            server_dist.parent.mkdir(parents=True, exist_ok=True)
+            server_dist.write_bytes(b"locked Server distribution")
+            manifest["artifacts"].append({"path": str(server_dist.relative_to(repository)),
+                                          "sha256": sdk.digest(server_dist), "source_reactor_install": True})
             self.write_manifest(repository, manifest)
             directory = self.distribution(root, repository, manifest)
-            # The aggregate Toolchain dist JAR is not a module runtime library.
+            # Aggregate dist JARs are not module runtime libraries.
             (directory / "lib/hugegraph-dist-1.8.0.jar").unlink()
+            (directory / "lib/hugegraph-toolchain-dist-1.8.0.jar").unlink()
             before = (repository / "candidate-sdk-manifest.json").read_bytes()
             for path in outputs:
                 # Simulate parent/Client install, then Loader install, then Hubble install.
                 path.write_bytes(b"new reactor output after compile")
-                if path.suffix == ".jar" and path.name != "hugegraph-dist-1.8.0.jar":
+                if path.suffix == ".jar" and path.name != "hugegraph-toolchain-dist-1.8.0.jar":
                     shutil.copyfile(path, directory / "lib" / path.name)
                 for module in ("loader", "tools", "hubble"):
                     sdk.validate_distribution(repository, directory, module)
             self.assertEqual(before, (repository / "candidate-sdk-manifest.json").read_bytes())
+            self.assertEqual(b"locked Server distribution", server_dist.read_bytes())
             server_dist.write_bytes(b"overwritten Server distribution")
             with self.assertRaisesRegex(RuntimeError, "SDK artifact hash mismatch"):
                 sdk.validate_sdk(repository)
             server_dist.write_bytes(b"locked Server distribution")
-            common = repository / "org/apache/hugegraph/hugegraph-common/1.7.0/hugegraph-common-1.7.0.jar"
+            common = repository / "org/apache/hugegraph/hugegraph-common/1.8.0/hugegraph-common-1.8.0.jar"
             common.write_bytes(b"replaced Server SDK input")
             with self.assertRaisesRegex(RuntimeError, "SDK artifact hash mismatch"):
                 sdk.validate_distribution(repository, directory, "hubble")
 
+    def test_legacy_dist_output_at_another_revision_does_not_change_server_inputs(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            repository, manifest = self.fixture(Path(temporary), "1.9.0-SNAPSHOT")
+            legacy = repository / "org/apache/hugegraph/hugegraph-dist/1.8.0/hugegraph-dist-1.8.0.jar"
+            legacy.parent.mkdir(parents=True)
+            legacy.write_bytes(b"legacy Toolchain output")
+            manifest["artifacts"].append({"path": str(legacy.relative_to(repository)),
+                                          "sha256": sdk.digest(legacy), "source_reactor_install": True})
+            self.write_manifest(repository, manifest)
+            legacy.write_bytes(b"updated legacy Toolchain output")
+            sdk.validate_sdk(repository, "1.9.0-SNAPSHOT")
+
     def test_source_context_preserves_full_identity_checks(self):
         with tempfile.TemporaryDirectory() as temporary:
             repository, manifest = self.fixture(Path(temporary))
@@ -424,5 +480,205 @@ class CandidateDistributionTest(unittest.TestCase):
             self.assertNotIn("secret", result.stderr)
 
 
+class ReleaseDistributionTest(unittest.TestCase):
+    def fixture(self, root, version="1.8.0"):
+        repository, directory = root / "m2", root / "distribution"
+        library = directory / "lib"
+        library.mkdir(parents=True)
+        for artifact in ("hugegraph-common", "hg-pd-common", "hg-pd-client", "hg-pd-grpc", "hugegraph-core"):
+            parent = repository / "org/apache/hugegraph" / artifact / version
+            parent.mkdir(parents=True)
+            pom = ('<project xmlns="http://maven.apache.org/POM/4.0.0"><parent>'
+                   '<groupId>org.apache.hugegraph</groupId><artifactId>hugegraph</artifactId>'
+                   '<version>' + version + '</version></parent><artifactId>' + artifact + '</artifactId></project>')
+            (parent / (artifact + "-" + version + ".pom")).write_text(pom)
+            jar = parent / (artifact + "-" + version + ".jar")
+            jar.write_bytes(artifact.encode())
+            shutil.copyfile(jar, library / jar.name)
+        (library / "hugegraph-client-1.8.0.jar").write_bytes(b"Toolchain output")
+        return repository, directory
+
+    def test_release_uses_exact_maven_artifacts_without_a_candidate_manifest(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            repository, directory = self.fixture(Path(temporary))
+            for module in ("loader", "tools", "hubble"):
+                self.assertEqual(5, len(sdk.validate_release_sdk(repository, "1.8.0", directory, module)))
+            command = ["python3", str(Path(sdk.__file__)), str(repository), "--mode", "release",
+                       "--version", "1.8.0", "--distribution", str(directory), "--module", "hubble"]
+            result = subprocess.run(command, capture_output=True, text=True, timeout=5)
+            self.assertEqual(0, result.returncode, result.stderr)
+            self.assertIn("release SDK 1.8.0", result.stdout)
+
+    def test_release_rejects_missing_mixed_and_replaced_sdk_inputs(self):
+        scenarios = ("missing_jar", "missing_pom", "missing_library", "mixed_version", "duplicate_version",
+                     "replaced_library", "stale_repo_manifest", "stale_package_manifest", "wrong_pom_version",
+                     "invalid_pom", "repository_symlink", "package_symlink", "library_directory_symlink")
+        for scenario in scenarios:
+            with self.subTest(scenario=scenario), tempfile.TemporaryDirectory() as temporary:
+                root = Path(temporary)
+                repository, directory = self.fixture(root)
+                common = repository / "org/apache/hugegraph/hugegraph-common/1.8.0/hugegraph-common-1.8.0.jar"
+                packaged = directory / "lib" / common.name
+                pom = common.with_suffix(".pom")
+                if scenario == "missing_jar":
+                    common.unlink()
+                elif scenario == "missing_pom":
+                    pom.unlink()
+                elif scenario == "missing_library":
+                    packaged.unlink()
+                elif scenario in ("mixed_version", "duplicate_version"):
+                    old = packaged.with_name("hugegraph-common-1.7.0.jar")
+                    shutil.copyfile(packaged, old)
+                    if scenario == "mixed_version":
+                        packaged.unlink()
+                elif scenario == "replaced_library":
+                    packaged.write_bytes(b"Other repository's same GAV")
+                elif scenario in ("stale_repo_manifest", "stale_package_manifest"):
+                    target = repository if scenario == "stale_repo_manifest" else directory
+                    (target / "candidate-sdk-manifest.json").write_text("{}")
+                elif scenario == "wrong_pom_version":
+                    pom.write_text(pom.read_text().replace("1.8.0", "1.7.0"))
+                elif scenario == "invalid_pom":
+                    pom.write_text("<project>")
+                elif scenario == "repository_symlink":
+                    common.unlink()
+                    common.symlink_to(packaged)
+                elif scenario == "library_directory_symlink":
+                    external = root / "external-library"
+                    (directory / "lib").rename(external)
+                    (directory / "lib").symlink_to(external, target_is_directory=True)
+                else:
+                    packaged.unlink()
+                    packaged.symlink_to(common)
+                with self.assertRaises(RuntimeError):
+                    sdk.validate_release_sdk(repository, "1.8.0", directory, "loader")
+
+    def test_release_hubble_requires_core_but_loader_does_not(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            repository, directory = self.fixture(Path(temporary))
+            (directory / "lib/hugegraph-core-1.8.0.jar").unlink()
+            sdk.validate_release_sdk(repository, "1.8.0", directory, "loader")
+            with self.assertRaisesRegex(RuntimeError, "hugegraph-core"):
+                sdk.validate_release_sdk(repository, "1.8.0", directory, "hubble")
+
+    def test_release_requires_transitive_pd_common_library(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            repository, directory = self.fixture(Path(temporary))
+            (directory / "lib/hg-pd-common-1.8.0.jar").unlink()
+            for module in ("loader", "tools", "hubble"):
+                with self.subTest(module=module), self.assertRaisesRegex(RuntimeError, "hg-pd-common"):
+                    sdk.validate_release_sdk(repository, "1.8.0", directory, module)
+
+    def test_sdk_rejects_unresolved_and_unspecified_versions(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            repository, directory = self.fixture(Path(temporary))
+            with self.assertRaisesRegex(RuntimeError, "concrete non-SNAPSHOT version"):
+                sdk.validate_release_sdk(repository, "${revision}", directory, "loader")
+            result = subprocess.run(["python3", str(Path(sdk.__file__)), str(repository), "--mode", "release"],
+                                    capture_output=True, text=True, timeout=5)
+            self.assertNotEqual(0, result.returncode)
+            self.assertIn("--version is required", result.stderr)
+
+    def test_release_rejects_snapshot_even_with_matching_artifacts(self):
+        with tempfile.TemporaryDirectory() as temporary:
+            repository, directory = self.fixture(Path(temporary), "1.9.0-SNAPSHOT")
+            with self.assertRaisesRegex(RuntimeError, "concrete non-SNAPSHOT version"):
+                sdk.validate_release_sdk(repository, "1.9.0-SNAPSHOT", directory, "hubble")
+            command = ["python3", str(Path(sdk.__file__)), str(repository), "--mode", "release",
+                       "--version", "1.9.0-SNAPSHOT", "--distribution", str(directory), "--module", "hubble"]
+            result = subprocess.run(command, capture_output=True, text=True, timeout=5)
+            self.assertNotEqual(0, result.returncode)
+            self.assertIn("concrete non-SNAPSHOT version", result.stderr)
+
+
+class SDKActionTest(unittest.TestCase):
+    def test_source_build_defaults_agree_without_changing_historical_runtime_fixture(self):
+        root = Path(__file__).resolve().parents[2]
+        action = (root / ".github/actions/setup-candidate-sdk/action.yml").read_text()
+        defaults = re.findall(r"default: '([0-9a-f]{40})'", action)
+        self.assertEqual([sdk.COMMIT, sdk.COMMIT], defaults)
+        for name in (".github/workflows/ci.yml", ".github/workflows/java17-images-ci.yml",
+                     "hugegraph-loader/Dockerfile", "hugegraph-hubble/Dockerfile"):
+            self.assertIn(sdk.COMMIT, (root / name).read_text())
+        for name in ("hugegraph-loader/Dockerfile", "hugegraph-hubble/Dockerfile"):
+            self.assertIn("-Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate", (root / name).read_text())
+        upstream = (root / ".github/workflows/upstream-compatibility.yml").read_text()
+        self.assertIn("ref: master", upstream)
+        self.assertNotIn("validation-mode: release", upstream)
+        release = (root / ".github/actions/setup-hugegraph-server/release.py").read_text()
+        self.assertIn('VERSION = "1.7.0"', release)
+        self.assertIn('apache-hugegraph-incubating-1.7.0.tar.gz', release)
+
+    def run_action(self, root, revision, corrupt=None):
+        fixture = root / "fixture"
+        candidate = CandidateDistributionTest()
+        candidate.source_commit = sdk.COMMIT if revision == "1.8.0" else "b" * 40
+        repository, manifest = candidate.fixture(fixture, revision)
+        if corrupt == "source":
+            manifest["commit"] = "0" * 40
+        elif corrupt == "module":
+            manifest["required_sdk_modules"].pop()
+        elif corrupt == "hash":
+            (repository / manifest["artifacts"][0]["path"]).write_bytes(b"changed SDK")
+        elif corrupt == "origin":
+            manifest["artifacts"][0]["source_reactor_install"] = False
+        candidate.write_manifest(repository, manifest)
+        workspace = root / "workspace"
+        installer = workspace / "hugegraph-client/assembly/travis/install-candidate-sdk.sh"
+        installer.parent.mkdir(parents=True)
+        installer.write_text('#!/bin/bash\nset -euo pipefail\n'
+                             'cp -R "$SDK_FIXTURE/." "$3/"\n'
+                             'mkdir -p "$2/hugegraph-server"\n'
+                             'printf archive > "$2/hugegraph-server/apache-hugegraph-fixture.tar.gz"\n')
+        verifier = workspace / ".github/scripts/verify_candidate_image_sdk.py"
+        verifier.parent.mkdir(parents=True)
+        shutil.copyfile(sdk.__file__, verifier)
+        runner = root / "runner"
+        runner.mkdir()
+        home = root / "home"
+        home.mkdir()
+        environment, output = root / "env", root / "output"
+        action = (Path(__file__).resolve().parents[2] / ".github/actions/setup-candidate-sdk/action.yml").read_text()
+        script = textwrap.dedent(action.split("      run: |\n", 1)[1].split("\n    - name:", 1)[0])
+        env = dict(os.environ, HOME=str(home), RUNNER_TEMP=str(runner), GITHUB_WORKSPACE=str(workspace),
+                   GITHUB_ENV=str(environment), GITHUB_OUTPUT=str(output), SDK_FIXTURE=str(repository),
+                   SERVER_COMMIT=candidate.source_commit, SERVER_REPOSITORY=sdk.REPOSITORY,
+                   SERVER_FETCH_REF=candidate.source_commit, MAVEN_OPTS="-Xmx1g",
+                   MAVEN_ARGS="-ntp --settings settings.xml")
+        result = subprocess.run(["bash", "-c", script], env=env, capture_output=True, text=True, timeout=10)
+        variables = dict(line.split("=", 1) for line in environment.read_text().splitlines()) if environment.exists() else {}
+        outputs = dict(line.split("=", 1) for line in output.read_text().splitlines()) if output.exists() else {}
+        return result, variables, outputs
+
+    def test_action_verifies_pinned_and_floating_sdk_before_selecting_consumer_coordinates(self):
+        for revision in ("1.8.0", "1.9.0", "1.9.0-SNAPSHOT"):
+            with self.subTest(revision=revision), tempfile.TemporaryDirectory() as temporary:
+                result, variables, outputs = self.run_action(Path(temporary), revision)
+                self.assertEqual(0, result.returncode, result.stdout + result.stderr)
+                for key in ("MAVEN_ARGS", "MAVEN_OPTS"):
+                    self.assertIn("-Dmaven.repo.local=" + outputs["maven-repo"], variables[key])
+                    self.assertIn("-Dhugegraph.version=" + revision, variables[key])
+                    self.assertIn("-Dsdk.validation.mode=candidate", variables[key])
+                self.assertEqual(revision, variables["CANDIDATE_SDK_VERSION"])
+                manifest = Path(outputs["manifest"])
+                self.assertTrue(manifest.is_file())
+                self.assertEqual(revision, json.loads(manifest.read_text())["source_revision"])
+                self.assertEqual(manifest.parent, Path(outputs["maven-repo"]))
+                # The distribution uses exactly the SDK version configured by this action.
+                with patch.dict(os.environ, variables):
+                    sdk.validate_sdk(outputs["maven-repo"])
+                self.assertEqual(sdk.digest(Path(outputs["archive"])), outputs["archive-sha256"])
+                with self.assertRaises(RuntimeError):
+                    sdk.validate_release_sdk(outputs["maven-repo"], revision)
+
+    def test_action_rejects_unresolved_version_or_unverified_source_without_exporting_environment(self):
+        for revision, corrupt in (("${revision}", None), ("1.8.0", "source"), ("1.8.0", "module"),
+                                  ("1.9.0-SNAPSHOT", "hash"), ("1.9.0-SNAPSHOT", "origin")):
+            with self.subTest(revision=revision, corrupt=corrupt), tempfile.TemporaryDirectory() as temporary:
+                result, variables, _ = self.run_action(Path(temporary), revision, corrupt)
+                self.assertNotEqual(0, result.returncode)
+                self.assertEqual({}, variables)
+
+
 if __name__ == "__main__":
     unittest.main()
diff --git a/.github/scripts/test_ci_policy.py b/.github/scripts/test_ci_policy.py
index 2f722f6bc..9f604c0af 100644
--- a/.github/scripts/test_ci_policy.py
+++ b/.github/scripts/test_ci_policy.py
@@ -21,6 +21,7 @@ import os
 from pathlib import Path
 import subprocess
 import tempfile
+import textwrap
 import unittest
 from unittest.mock import patch
 
@@ -194,6 +195,39 @@ class PolicyTest(unittest.TestCase):
                 policy, "packaged_readme_state", return_value="valid"):
             return policy.create_plan("toolchain", event, "apache/t", fetch)
 
+    def test_go_and_spark_only_plans_keep_runtime_fixtures(self):
+        for module, path in [("go", "hugegraph-client-go/client.go"),
+                             ("spark", "hugegraph-spark-connector/src/test/Test.scala")]:
+            with self.subTest(module=module):
+                plan = self.pr_plan([path])
+                self.assertEqual([module], plan["selected"])
+                self.assertTrue(plan["needsFixture"])
+                self.assertTrue(plan[module])
+
+    def test_external_inputs_record_current_go_runtime_without_java_sdk(self):
+        workflow = (Path(__file__).resolve().parents[2] / ".github/workflows/ci.yml").read_text()
+        script = textwrap.dedent(workflow.split("          python3 - <<'PYINPUTS'\n", 1)[1].split(
+            "          PYINPUTS", 1)[0])
+        for selected in (["go"], ["spark"], ["hubble"], policy.MODULES["toolchain"], []):
+            with self.subTest(selected=selected), tempfile.TemporaryDirectory() as directory:
+                root = Path(directory)
+                (root / "scope.json").write_text(json.dumps({"selected": selected}))
+                env = dict(os.environ, NEEDS_SERVER=str(bool(selected)).lower(),
+                           NEEDS_CURRENT_SERVER=str(bool(set(selected) & {"go", "hubble"})).lower(),
+                           SERVER_REPOSITORY="apache/hugegraph", SERVER_COMMIT="released", SERVER_REF="1.7.0",
+                           CURRENT_REPOSITORY="apache/hugegraph", CURRENT_COMMIT="locked", CURRENT_REF="locked")
+                subprocess.run([os.sys.executable, "-c", script], cwd=root, env=env, check=True)
+                inputs = json.loads((root / "inputs.json").read_text())
+                self.assertEqual(any(module != "go" for module in selected), "candidateSDK" in inputs)
+                self.assertEqual(bool(set(selected) & {"go", "hubble"}), "currentServer" in inputs)
+                if "currentServer" in inputs:
+                    self.assertEqual("locked", inputs["currentServer"]["commit"])
+                    self.assertEqual("17", inputs["currentServer"]["jdk"])
+                if selected:
+                    self.assertEqual("11", inputs["server"]["jdk"])
+                else:
+                    self.assertEqual({}, inputs)
+
     def test_cumulative_pr_docs_update_runs_affected_consumers_again(self):
         for document in ["README.md", "hugegraph-hubble/README.md"]:
             with self.subTest(document=document):
@@ -282,15 +316,19 @@ class PolicyTest(unittest.TestCase):
         with self.assertRaises(ValueError):
             policy.gate(self.plan(), {"plan": {"result": "success"}, "client": {"result": "success"}})
 
-    def test_hubble_gate_requires_current_and_released_fixtures(self):
-        plan = dict(self.plan(), expected=["hubble"])
-        for producer in ("fixture", "hubble-fixture"):
-            for state in (None, "failure", "cancelled", "skipped"):
-                results = {"plan": {"result": "success"}, "hubble": {"result": "success"},
-                           "fixture": {"result": "success"}, "hubble-fixture": {"result": "success"}}
-                results[producer] = {"result": state}
-                with self.subTest(producer=producer, state=state), self.assertRaises(ValueError):
-                    policy.gate(plan, results)
+    def test_hubble_and_go_gates_require_current_and_released_fixtures(self):
+        for suite in ("hubble", "go"):
+            plan = dict(self.plan(), expected=[suite])
+            for producer in ("fixture", "current-fixture"):
+                for state in (None, "failure", "cancelled", "skipped"):
+                    results = {"plan": {"result": "success"}, suite: {"result": "success"},
+                               "fixture": {"result": "success"}, "current-fixture": {"result": "success"}}
+                    results[producer] = {"result": state}
+                    with self.subTest(suite=suite, producer=producer, state=state), self.assertRaises(ValueError):
+                        policy.gate(plan, results)
+            results = {"plan": {"result": "success"}, suite: {"result": "success"},
+                       "fixture": {"result": "success"}, "current-fixture": {"result": "success"}}
+            self.assertEqual([suite], policy.gate(plan, results)["executed"])
 
     def test_real_git_doc_modes_and_packaged_readme_contract(self):
         with tempfile.TemporaryDirectory() as directory:
@@ -358,7 +396,7 @@ class PolicyTest(unittest.TestCase):
                          tuple(plan[key] for key in ["pr", "source", "branch", "base", "head"]))
         results = {suite: {"result": "success"} for suite in plan["expected"]}
         results.update(plan={"result": "success"}, fixture={"result": "success"},
-                       **{"hubble-fixture": {"result": "success"}})
+                       **{"current-fixture": {"result": "success"}})
         with self.assertRaises(subprocess.CalledProcessError):
             policy.gate(plan, results, fail)
         self.assertEqual(plan["expected"], policy.gate(plan, results)["executed"])
@@ -397,7 +435,7 @@ class PolicyTest(unittest.TestCase):
                 plan = policy.create_plan("toolchain", event, "apache/t", lambda _: live)
                 results = {suite: {"result": "success"} for suite in plan["expected"]}
                 results.update(plan={"result": "success"}, fixture={"result": "success"},
-                               **{"hubble-fixture": {"result": "success"}})
+                               **{"current-fixture": {"result": "success"}})
                 self.assertEqual(plan["expected"], policy.gate(plan, results, lambda _: live)["executed"])
                 for section, field, value in [("head", "sha", advanced_head),
                                               ("head", "ref", "other-branch"),
@@ -472,7 +510,7 @@ class PolicyTest(unittest.TestCase):
                 self.assertEqual(["hubble", "loader"], plan["selected"])
                 results = {suite: {"result": "success"} for suite in plan["expected"]}
                 results.update(plan={"result": "success"}, fixture={"result": "success"},
-                               **{"hubble-fixture": {"result": "success"}})
+                               **{"current-fixture": {"result": "success"}})
                 report = policy.gate(plan, results, fetch)
                 self.assertEqual(canonical, report["base"])
                 self.assertEqual("target", report["baseBranch"])
@@ -568,7 +606,7 @@ class PolicyTest(unittest.TestCase):
                 self.assertEqual(["hubble", "loader"], plan["selected"])
                 results = {suite: {"result": "success"} for suite in plan["expected"]}
                 results.update(plan={"result": "success"}, fixture={"result": "success"},
-                               **{"hubble-fixture": {"result": "success"}})
+                               **{"current-fixture": {"result": "success"}})
                 report = policy.gate(plan, results, fetch)
                 self.assertEqual(canonical, report["base"])
                 self.assertEqual("candidate", report["baseBranch"])
diff --git a/.github/scripts/test_java17_image.py b/.github/scripts/test_java17_image.py
index ab6efb62a..eb128c4b2 100644
--- a/.github/scripts/test_java17_image.py
+++ b/.github/scripts/test_java17_image.py
@@ -67,8 +67,8 @@ class ImageEvidenceTest(unittest.TestCase):
             packaging = "pom" if (source_pom.count("/") == 1 and artifact != "hugegraph-struct") or source_pom == "pom.xml" else "jar"
             files = []
             for extension in (["pom", "jar"] if packaging == "jar" else ["pom"]):
-                filename = artifact + "-1.7.0." + extension
-                path = repository / "org/apache/hugegraph" / artifact / "1.7.0" / filename
+                filename = artifact + "-1.8.0." + extension
+                path = repository / "org/apache/hugegraph" / artifact / "1.8.0" / filename
                 path.parent.mkdir(parents=True, exist_ok=True)
                 path.write_bytes(filename.encode())
                 with (path.parent / "_remote.repositories").open("a") as origins:
@@ -77,10 +77,10 @@ class ImageEvidenceTest(unittest.TestCase):
                 files.append(relative)
                 artifacts.append({"path": relative, "sha256": sdk.digest(path), "source_reactor_install": True})
             modules.append({"group_id": "org.apache.hugegraph", "artifact_id": artifact,
-                            "version": "1.7.0", "packaging": packaging,
+                            "version": "1.8.0", "packaging": packaging,
                             "source_pom": source_pom, "files": files})
         manifest = {"repository": sdk.REPOSITORY, "commit": self.source_commit,
-                    "source_revision": "1.7.0", "java_version": "17",
+                    "source_revision": "1.8.0", "java_version": "17",
                     "required_sdk_modules": modules, "artifacts": artifacts}
         self.write_manifest(repository, manifest)
         return repository, manifest
@@ -93,7 +93,7 @@ class ImageEvidenceTest(unittest.TestCase):
             repository, manifest = self.fixture(Path(directory))
             _, expected = sdk.validate_sdk(repository)
             self.assertEqual(expected, images.verify_jars("hubble", expected, expected))
-            wrong = repository / "org/apache/hugegraph/hugegraph-common/1.7.0/hugegraph-common-1.7.0.jar"
+            wrong = repository / "org/apache/hugegraph/hugegraph-common/1.8.0/hugegraph-common-1.8.0.jar"
             wrong.write_bytes(b"central artifact with the same version")
             with self.assertRaisesRegex(RuntimeError, "hash mismatch"):
                 sdk.validate_sdk(repository)
@@ -117,7 +117,7 @@ class ImageEvidenceTest(unittest.TestCase):
                 with self.subTest(change=change), self.assertRaises(RuntimeError):
                     sdk.validate_sdk(repository)
             self.write_manifest(repository, manifest)
-            pom = repository / "org/apache/hugegraph/hugegraph/1.7.0/hugegraph-1.7.0.pom"
+            pom = repository / "org/apache/hugegraph/hugegraph/1.8.0/hugegraph-1.8.0.pom"
             (pom.parent / "_remote.repositories").write_text(pom.name + ">central=\n")
             with self.assertRaisesRegex(RuntimeError, "not installed from source"):
                 sdk.validate_sdk(repository)
@@ -143,8 +143,8 @@ class ImageEvidenceTest(unittest.TestCase):
                 images.prepare("loader", self.source_commit, repository, archive, expected, work, evidence)
 
     def test_runtime_hash_and_required_libraries_cannot_be_waived(self):
-        expected = {artifact + "-1.7.0.jar": "candidate" for artifact in (*images.REQUIRED, "hugegraph-core")}
-        for wrong in ({**expected, "hugegraph-common-1.7.0.jar": "central"},
+        expected = {artifact + "-1.8.0.jar": "candidate" for artifact in (*images.REQUIRED, "hugegraph-core")}
+        for wrong in ({**expected, "hugegraph-common-1.8.0.jar": "central"},
                       {key: value for key, value in expected.items() if not key.startswith("hugegraph-core-")},
                       {**expected, "hugegraph-common-1.5.0.jar": "old"},
                       {**expected, "hg-store-client-1.5.0.jar": "old"}):
diff --git a/.github/scripts/verify_candidate_image_sdk.py b/.github/scripts/verify_candidate_image_sdk.py
index aa7cb52ee..66e2f107d 100644
--- a/.github/scripts/verify_candidate_image_sdk.py
+++ b/.github/scripts/verify_candidate_image_sdk.py
@@ -15,17 +15,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-"""Validate the locked SDK and the actual libraries in a candidate distribution."""
+"""Validate SDK libraries against a locked candidate or the release Maven repository."""
 
 import argparse
 import hashlib
 import json
 import os
 import re
+import shlex
+import xml.etree.ElementTree as ET
 from pathlib import Path
 
 REPOSITORY = "apache/hugegraph"
-COMMIT = "e62c961e00221569d4f955abbadf60faee45b283"
+COMMIT = "68855199031d5801edb4fe41b2bacbacffe8fe68"
 REQUIRED_MODULES = {
     "pom.xml", "hugegraph-commons/pom.xml", "hugegraph-server/pom.xml",
     "hugegraph-pd/pom.xml", "hugegraph-store/pom.xml",
@@ -41,7 +43,8 @@ REQUIRED_MODULES = {
 # Older retained manifests may include them because they described a shared Maven cache.
 TOOLCHAIN_ARTIFACTS = {
     "hugegraph-toolchain", "hugegraph-client", "hugegraph-loader", "hugegraph-tools",
-    "hugegraph-spark-connector", "hugegraph-hubble", "hugegraph-dist", "hubble-be", "hubble-fe", "hubble-dist",
+    "hugegraph-spark-connector", "hugegraph-hubble", "hugegraph-toolchain-dist",
+    "hugegraph-dist", "hubble-be", "hubble-fe", "hubble-dist",
 }
 
 
@@ -77,7 +80,7 @@ def _validate_sdk(repository, expected_revision=None):
     java_version = manifest["java_version"]
     if not isinstance(java_version, str) or not java_version.isdecimal():
         raise ValueError("Invalid Java version format")
-    revision = expected_revision or os.environ.get("CANDIDATE_SDK_VERSION", "1.7.0")
+    revision = expected_revision or os.environ.get("CANDIDATE_SDK_VERSION", "1.8.0")
     if not isinstance(revision, str) or not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_.+-]*", revision):
         raise ValueError("Invalid candidate SDK version")
     if manifest["source_revision"] != revision or java_version != "17":
@@ -111,7 +114,8 @@ def _validate_sdk(repository, expected_revision=None):
         if (Path(relative).is_absolute() or ".." in parts or len(parts) != 6 or
                 parts[:3] != ("org", "apache", "hugegraph")):
             raise RuntimeError(f"Invalid SDK artifact path: {relative}")
-        # Server and Toolchain share the hugegraph-dist name at different revisions.
+        # Legacy Toolchain manifests used hugegraph-dist at a different revision.
+        # The selected Server revision remains locked even when its dist is not a required SDK module.
         if parts[3] in TOOLCHAIN_ARTIFACTS and (parts[3] != "hugegraph-dist" or
                                               parts[4] != manifest["source_revision"]):
             continue
@@ -133,8 +137,8 @@ def _validate_sdk(repository, expected_revision=None):
     return manifest, jars
 
 
-def validate_distribution(repository, directory, module):
-    manifest, expected = validate_sdk(repository)
+def validate_distribution(repository, directory, module, expected_revision=None):
+    manifest, expected = validate_sdk(repository, expected_revision)
     directory = Path(directory)
     try:
         packaged = json.loads((directory / "candidate-sdk-manifest.json").read_text())
@@ -143,9 +147,7 @@ def validate_distribution(repository, directory, module):
                            type(error).__name__ + ")") from None
     if packaged != manifest:
         raise RuntimeError("Packaged SDK manifest differs from the build repository")
-    required = {"hugegraph-common", "hg-pd-client", "hg-pd-grpc"}
-    if module == "hubble":
-        required.add("hugegraph-core")
+    required = required_libraries(module)
     actual = {}
     # Check only distribution libraries, never runtime data or user plugins.
     for path in (directory / "lib").glob("*.jar"):
@@ -166,32 +168,116 @@ def validate_distribution(repository, directory, module):
 def configure_upstream(repository, environment):
     """Select consumer coordinates only after verifying this run's SDK artifacts."""
     repository = Path(repository)
-    revision = json.loads((repository / "candidate-sdk-manifest.json").read_text())["source_revision"]
+    try:
+        revision = json.loads((repository / "candidate-sdk-manifest.json").read_text())["source_revision"]
+    except (OSError, ValueError, KeyError, TypeError) as error:
+        raise RuntimeError("Invalid candidate SDK manifest (" + type(error).__name__ + ")") from None
     manifest, jars = validate_sdk(repository, revision)
     with Path(environment).open("a") as stream:
+        stream.write(f"CANDIDATE_SOURCE_COMMIT={manifest['commit']}\n")
         stream.write(f"CANDIDATE_SDK_VERSION={revision}\n")
-        stream.write(f"MAVEN_ARGS={os.environ.get('MAVEN_ARGS', '')} -Dhugegraph.version={revision}\n")
+        for variable in ("MAVEN_OPTS", "MAVEN_ARGS"):
+            arguments = shlex.split(os.environ.get(variable, ""))
+            properties = ("-Dmaven.repo.local=", "-Dhugegraph.version=", "-Dsdk.validation.mode=")
+            arguments = [argument for argument in arguments if not argument.startswith(properties)]
+            arguments.extend((f"-Dmaven.repo.local={repository.absolute()}",
+                              f"-Dhugegraph.version={revision}", "-Dsdk.validation.mode=candidate"))
+            stream.write(f"{variable}={shlex.join(arguments)}\n")
     return manifest, jars
 
 
+def required_libraries(module):
+    required = {"hugegraph-common", "hg-pd-common", "hg-pd-client", "hg-pd-grpc"}
+    if module == "hubble":
+        required.add("hugegraph-core")
+    return required
+
+
+def release_artifact(repository, artifact, version):
+    directory = repository / "org/apache/hugegraph" / artifact / version
+    paths = [directory / f"{artifact}-{version}.{extension}" for extension in ("pom", "jar")]
+    for path in paths:
+        if path.is_symlink() or not path.is_file() or not path.resolve().is_relative_to(repository.resolve()):
+            raise RuntimeError("Missing or invalid release SDK artifact: " + path.name)
+    try:
+        pom = ET.parse(paths[0]).getroot()
+        namespace = "{http://maven.apache.org/POM/4.0.0}"
+        group = pom.findtext(namespace + "groupId") or pom.findtext(namespace + "parent/" + namespace + "groupId")
+        revision = pom.findtext(namespace + "version") or pom.findtext(namespace + "parent/" + namespace + "version")
+        if (group, pom.findtext(namespace + "artifactId"), revision) != ("org.apache.hugegraph", artifact, version):
+            raise RuntimeError("Unexpected release SDK POM coordinates: " + paths[0].name)
+    except (OSError, ET.ParseError):
+        raise RuntimeError("Invalid release SDK POM: " + paths[0].name) from None
+    return digest(paths[1])
+
+
+def validate_release_sdk(repository, version, directory=None, module=None):
+    """Check the explicitly selected SDK version and bytes, without approving a release."""
+    repository = Path(repository)
+    if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", version):
+        raise RuntimeError("Release SDK validation requires a concrete non-SNAPSHOT version")
+    if (repository / "candidate-sdk-manifest.json").exists():
+        raise RuntimeError("Candidate SDK manifest is not allowed in a release repository")
+    required = required_libraries(module)
+    expected = {f"{artifact}-{version}.jar": release_artifact(repository, artifact, version)
+                for artifact in required}
+    if directory is None:
+        return expected
+    directory = Path(directory)
+    if (directory / "candidate-sdk-manifest.json").exists():
+        raise RuntimeError("Candidate SDK manifest is not allowed in a release distribution")
+    library = directory / "lib"
+    if library.is_symlink() or not library.is_dir():
+        raise RuntimeError("Missing or invalid release SDK library directory")
+    actual = {}
+    for path in library.glob("*.jar"):
+        if path.name.startswith(("hugegraph-client-", "hugegraph-loader-", "hugegraph-tools-")):
+            continue
+        if not path.name.startswith(("hugegraph-", "hg-")):
+            continue
+        if (path.is_symlink() or not path.resolve().is_relative_to(directory.resolve()) or
+                not path.name.endswith(f"-{version}.jar")):
+            raise RuntimeError("Unexpected release SDK library version: " + path.name)
+        artifact = path.name[:-len(f"-{version}.jar")]
+        expected_hash = release_artifact(repository, artifact, version)
+        if digest(path) != expected_hash:
+            raise RuntimeError("Changed packaged release SDK library: " + path.name)
+        actual[path.name] = expected_hash
+    for name in expected:
+        if name not in actual:
+            raise RuntimeError("Missing packaged release SDK library: " + name)
+    return actual
+
+
 if __name__ == "__main__":
     parser = argparse.ArgumentParser(description=__doc__)
     parser.add_argument("repository", type=Path)
     parser.add_argument("--distribution", type=Path)
     parser.add_argument("--configure-upstream-env", type=Path)
     parser.add_argument("--module", choices=("loader", "tools", "hubble"))
+    parser.add_argument("--mode", choices=("candidate", "release"), default="candidate")
+    parser.add_argument("--version", help="Expected release SDK version (required in release mode)")
     args = parser.parse_args()
     if bool(args.distribution) != bool(args.module):
         parser.error("--distribution and --module must be used together")
     if args.configure_upstream_env and args.distribution:
         parser.error("--configure-upstream-env cannot be combined with --distribution")
+    if args.configure_upstream_env and args.mode != "candidate":
+        parser.error("--configure-upstream-env requires candidate mode")
+    if args.mode == "release" and not args.version:
+        parser.error("--version is required in release mode")
     try:
-        if args.configure_upstream_env:
-            manifest, jars = configure_upstream(args.repository, args.configure_upstream_env)
+        if args.mode == "release":
+            jars = validate_release_sdk(args.repository, args.version, args.distribution, args.module)
+            identity = "release SDK " + args.version
         else:
-            manifest, jars = validate_sdk(args.repository)
-        if args.distribution:
-            validate_distribution(args.repository, args.distribution, args.module)
+            if args.configure_upstream_env:
+                manifest, jars = configure_upstream(args.repository, args.configure_upstream_env)
+            else:
+                manifest, jars = validate_sdk(args.repository, args.version)
+            if args.distribution:
+                validate_distribution(args.repository, args.distribution, args.module, args.version)
+            identity = f"{manifest['repository']}@{manifest['commit'][:6]}"
     except RuntimeError as error:
         parser.exit(1, str(error) + "\n")
-    print(f"Verified {manifest['repository']}@{manifest['commit'][:6]}: {len(jars)} candidate JARs")
+    print(f"Verified {identity}: {len(jars)} SDK JARs")
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1813c0214..b6baf3e03 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -41,9 +41,9 @@ jobs:
       server-repository: ${{ steps.baseline.outputs.repository }}
       server-commit: ${{ steps.baseline.outputs.commit_id }}
       server-ref: ${{ steps.baseline.outputs.fetch_ref }}
-      hubble-commit: ${{ steps.hubble-baseline.outputs.commit_id }}
-      hubble-repository: ${{ steps.hubble-baseline.outputs.repository }}
-      hubble-ref: ${{ steps.hubble-baseline.outputs.fetch_ref }}
+      current-commit: ${{ steps.current-baseline.outputs.commit_id }}
+      current-repository: ${{ steps.current-baseline.outputs.repository }}
+      current-ref: ${{ steps.current-baseline.outputs.fetch_ref }}
       client: ${{ steps.select.outputs.client }}
       loader: ${{ steps.select.outputs.loader }}
       tools: ${{ steps.select.outputs.tools }}
@@ -81,23 +81,23 @@ jobs:
           ref: refs/tags/1.7.0
           expected-commit: b12425c2032bf0d21a97b8221f42a18055c2982f
       - name: Resolve locked Java17 candidate baseline
-        id: hubble-baseline
+        id: current-baseline
         if: steps.scope.outputs.needsFixture == 'true'
         uses: ./.github/actions/get-hugegraph-commit
         with:
           repository: apache/hugegraph
-          ref: e62c961e00221569d4f955abbadf60faee45b283
-          expected-commit: e62c961e00221569d4f955abbadf60faee45b283
+          ref: 68855199031d5801edb4fe41b2bacbacffe8fe68
+          expected-commit: 68855199031d5801edb4fe41b2bacbacffe8fe68
       - name: Record external test inputs
         env:
           NEEDS_SERVER: ${{ steps.scope.outputs.needsFixture }}
-          NEEDS_HUBBLE: ${{ steps.scope.outputs.hubble }}
+          NEEDS_CURRENT_SERVER: ${{ steps.scope.outputs.hubble == 'true' || steps.scope.outputs.go == 'true' }}
           SERVER_REPOSITORY: ${{ steps.baseline.outputs.repository }}
           SERVER_COMMIT: ${{ steps.baseline.outputs.commit_id }}
           SERVER_REF: ${{ steps.baseline.outputs.fetch_ref }}
-          HUBBLE_REPOSITORY: ${{ steps.hubble-baseline.outputs.repository }}
-          HUBBLE_REF: ${{ steps.hubble-baseline.outputs.fetch_ref }}
-          HUBBLE_COMMIT: ${{ steps.hubble-baseline.outputs.commit_id }}
+          CURRENT_REPOSITORY: ${{ steps.current-baseline.outputs.repository }}
+          CURRENT_REF: ${{ steps.current-baseline.outputs.fetch_ref }}
+          CURRENT_COMMIT: ${{ steps.current-baseline.outputs.commit_id }}
         run: |
           python3 - <<'PYINPUTS'
           import json, os
@@ -107,13 +107,18 @@ jobs:
                   selected = json.load(stream)['selected']
               inputs['toolchainJDK'] = {module: ['17']
                                         for module in selected if module != 'go'}
-              inputs['config'] = 'candidate-release-java17-ci-v3'
+              inputs['config'] = 'candidate-release-java17-ci-v4'
               inputs['server'] = {'repository': os.environ['SERVER_REPOSITORY'],
                                   'commit': os.environ['SERVER_COMMIT'],
                                   'ref': os.environ['SERVER_REF'], 'jdk': '11'}
-          if os.environ['NEEDS_SERVER'] == 'true':
-              inputs['candidateSDK'] = {'repository': os.environ['HUBBLE_REPOSITORY'], 'commit': os.environ['HUBBLE_COMMIT'],
-                                  'ref': os.environ['HUBBLE_REF'], 'jdk': '17'}
+          if os.environ['NEEDS_SERVER'] == 'true' and any(module != 'go' for module in selected):
+              inputs['candidateSDK'] = {'repository': os.environ['CURRENT_REPOSITORY'],
+                                        'commit': os.environ['CURRENT_COMMIT'],
+                                        'ref': os.environ['CURRENT_REF'], 'jdk': '17'}
+          if os.environ['NEEDS_CURRENT_SERVER'] == 'true':
+              inputs['currentServer'] = {'repository': os.environ['CURRENT_REPOSITORY'],
+                                         'commit': os.environ['CURRENT_COMMIT'],
+                                         'ref': os.environ['CURRENT_REF'], 'jdk': '17'}
           with open('inputs.json', 'w') as stream:
               json.dump(inputs, stream, sort_keys=True)
           PYINPUTS
@@ -164,9 +169,9 @@ jobs:
           fetch-ref: ${{ needs.plan.outputs.server-ref }}
           java-version: '11'
 
-  hubble-fixture:
+  current-fixture:
     needs: plan
-    if: needs.plan.outputs.hubble == 'true'
+    if: needs.plan.outputs.hubble == 'true' || needs.plan.outputs.go == 'true'
     runs-on: ubuntu-latest
     outputs:
       artifact: ${{ steps.identity.outputs.artifact }}
@@ -176,7 +181,7 @@ jobs:
           persist-credentials: false
       - name: Name immutable Server artifact
         id: identity
-        run: echo "artifact=hubble-fixture-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"
+        run: echo "artifact=current-fixture-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"
       - uses: ./.github/actions/setup-java-env
         with:
           java-version: '17'
@@ -186,9 +191,9 @@ jobs:
         with:
           mode: build
           artifact-name: ${{ steps.identity.outputs.artifact }}
-          repository: ${{ needs.plan.outputs.hubble-repository }}
-          commit-id: ${{ needs.plan.outputs.hubble-commit }}
-          fetch-ref: ${{ needs.plan.outputs.hubble-ref }}
+          repository: ${{ needs.plan.outputs.current-repository }}
+          commit-id: ${{ needs.plan.outputs.current-commit }}
+          fetch-ref: ${{ needs.plan.outputs.current-ref }}
           java-version: '17'
 
   client:
@@ -248,14 +253,14 @@ jobs:
       contents: read
       actions: read
       id-token: write
-    needs: [plan, fixture, hubble-fixture]
+    needs: [plan, fixture, current-fixture]
     if: ${{ needs.plan.outputs.hubble == 'true' }}
     uses: ./.github/workflows/hubble-ci.yml
     with:
-      current-artifact-name: ${{ needs.hubble-fixture.outputs.artifact }}
-      current-commit: ${{ needs.plan.outputs.hubble-commit }}
-      current-repository: ${{ needs.plan.outputs.hubble-repository }}
-      current-ref: ${{ needs.plan.outputs.hubble-ref }}
+      current-artifact-name: ${{ needs.current-fixture.outputs.artifact }}
+      current-commit: ${{ needs.plan.outputs.current-commit }}
+      current-repository: ${{ needs.plan.outputs.current-repository }}
+      current-ref: ${{ needs.plan.outputs.current-ref }}
       artifact-name: ${{ needs.fixture.outputs.artifact }}
       server-repository: ${{ needs.plan.outputs.server-repository }}
       server-commit: ${{ needs.plan.outputs.server-commit }}
@@ -263,10 +268,14 @@ jobs:
     secrets: inherit
 
   go:
-    needs: [plan, fixture]
+    needs: [plan, fixture, current-fixture]
     if: ${{ needs.plan.outputs.go == 'true' }}
     uses: ./.github/workflows/client-go-ci.yml
     with:
+      current-artifact-name: ${{ needs.current-fixture.outputs.artifact }}
+      current-commit: ${{ needs.plan.outputs.current-commit }}
+      current-repository: ${{ needs.plan.outputs.current-repository }}
+      current-ref: ${{ needs.plan.outputs.current-ref }}
       artifact-name: ${{ needs.fixture.outputs.artifact }}
       server-repository: ${{ needs.plan.outputs.server-repository }}
       server-commit: ${{ needs.plan.outputs.server-commit }}
@@ -289,7 +298,7 @@ jobs:
 
   affected-module-tests:
     name: affected-module-tests
-    needs: [plan, fixture, hubble-fixture, client, loader, tools, spark, hubble, go, loader_image, hubble_image, dependency-audit]
+    needs: [plan, fixture, current-fixture, client, loader, tools, spark, hubble, go, loader_image, hubble_image, dependency-audit]
     if: ${{ !cancelled() }}
     runs-on: ubuntu-latest
     steps:
diff --git a/.github/workflows/client-go-ci.yml b/.github/workflows/client-go-ci.yml
index 1873f8890..fe715f07a 100644
--- a/.github/workflows/client-go-ci.yml
+++ b/.github/workflows/client-go-ci.yml
@@ -3,6 +3,18 @@ name: "go-client-ci"
 on:
   workflow_call:
     inputs:
+      current-artifact-name:
+        type: string
+        required: true
+      current-repository:
+        type: string
+        required: true
+      current-ref:
+        type: string
+        required: true
+      current-commit:
+        type: string
+        required: true
       server-repository:
         type: string
         default: apache/hugegraph
@@ -24,7 +36,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        JAVA_VERSION: ['17']
+        SERVER_BASELINE: ['candidate', 'release-1.7']
 
     steps:
       - name: Fetch Code
@@ -32,30 +44,24 @@ jobs:
         with:
           fetch-depth: 2
 
-      - name: Install released Server JVM
+      - name: Setup Server JVM
         id: server-java
         uses: actions/setup-java@v5
         with:
-          java-version: '11'
+          java-version: ${{ matrix.SERVER_BASELINE == 'candidate' && '17' || '11' }}
           distribution: temurin
           overwrite-settings: false
 
-      - name: Setup Java environment
-        uses: ./.github/actions/setup-java-env
-        with:
-          java-version: ${{ matrix.JAVA_VERSION }}
-          distribution: 'zulu'
-
       - name: Setup HugeGraph server
         uses: ./.github/actions/setup-hugegraph-server
         with:
           travis-dir: ${{ env.TRAVIS_DIR }}
-          commit-id: ${{ inputs.server-commit }}
-          repository: ${{ inputs.server-repository }}
-          fetch-ref: ${{ inputs.server-ref }}
-          artifact-name: ${{ inputs.artifact-name }}
+          commit-id: ${{ matrix.SERVER_BASELINE == 'candidate' && inputs.current-commit || inputs.server-commit }}
+          repository: ${{ matrix.SERVER_BASELINE == 'candidate' && inputs.current-repository || inputs.server-repository }}
+          fetch-ref: ${{ matrix.SERVER_BASELINE == 'candidate' && inputs.current-ref || inputs.server-ref }}
+          artifact-name: ${{ matrix.SERVER_BASELINE == 'candidate' && inputs.current-artifact-name || inputs.artifact-name }}
           mode: start
-          java-version: '11'
+          java-version: ${{ matrix.SERVER_BASELINE == 'candidate' && '17' || '11' }}
           java-home: ${{ steps.server-java.outputs.path }}
 
       - name: Init Go env
@@ -71,4 +77,6 @@ jobs:
           sudo sysctl -w vm.swappiness=1
           sudo sysctl -w fs.file-max=262144
           sudo sysctl -w vm.max_map_count=262144
-          cd hugegraph-client-go && make test
+          cd hugegraph-client-go
+          # Each live Server baseline must execute requests rather than replay cached test results.
+          make test GOTEST='GO111MODULE=on go test -race -count=1 -timeout 30s -gcflags="-N -l"'
diff --git a/.github/workflows/java17-images-ci.yml b/.github/workflows/java17-images-ci.yml
index 84011e014..0d9606541 100644
--- a/.github/workflows/java17-images-ci.yml
+++ b/.github/workflows/java17-images-ci.yml
@@ -63,7 +63,7 @@ jobs:
         uses: ./.github/actions/setup-candidate-sdk
         with:
           repository: apache/hugegraph
-          expected-commit: e62c961e00221569d4f955abbadf60faee45b283
+          expected-commit: 68855199031d5801edb4fe41b2bacbacffe8fe68
       - name: Build same-source dependencies and the real image
         env:
           IMAGE_EVIDENCE: ${{ runner.temp }}/java17-image-${{ matrix.module }}-evidence
diff --git a/.github/workflows/spark-connector-ci.yml b/.github/workflows/spark-connector-ci.yml
index 202a63988..8145813b3 100644
--- a/.github/workflows/spark-connector-ci.yml
+++ b/.github/workflows/spark-connector-ci.yml
@@ -22,8 +22,10 @@ jobs:
     env:
       TRAVIS_DIR: hugegraph-spark-connector/assembly/travis
     strategy:
+      fail-fast: false
       matrix:
         JAVA_VERSION: ['17']
+        SERVER_BASELINE: ['candidate', 'release-1.7']
 
     steps:
       - name: Fetch Code
@@ -32,6 +34,7 @@ jobs:
           fetch-depth: 2
 
       - name: Install released Server JVM
+        if: matrix.SERVER_BASELINE == 'release-1.7'
         id: released-java
         uses: actions/setup-java@v5
         with:
@@ -54,6 +57,7 @@ jobs:
           mvn install -pl hugegraph-client,hugegraph-spark-connector -am -Dmaven.javadoc.skip=true -DskipTests -ntp
 
       - name: Setup HugeGraph server
+        if: matrix.SERVER_BASELINE == 'release-1.7'
         uses: ./.github/actions/setup-hugegraph-server
         with:
           travis-dir: ${{ env.TRAVIS_DIR }}
@@ -65,6 +69,12 @@ jobs:
           artifact-name: ${{ inputs.artifact-name }}
           java-version: '11'
 
+      - name: Start candidate Server fixture
+        if: matrix.SERVER_BASELINE == 'candidate'
+        env:
+          CANDIDATE_ARCHIVE: ${{ steps.candidate-sdk.outputs.archive }}
+        run: bash hugegraph-client/assembly/travis/start-hugegraph-servers.sh "$CANDIDATE_ARCHIVE"
+
       - name: Run test
         run: |
           cd hugegraph-spark-connector && ls
diff --git a/.github/workflows/upstream-compatibility.yml b/.github/workflows/upstream-compatibility.yml
index e2e95d360..19fdee8a6 100644
--- a/.github/workflows/upstream-compatibility.yml
+++ b/.github/workflows/upstream-compatibility.yml
@@ -53,13 +53,6 @@ jobs:
           ref: ${{ steps.upstream.outputs.commit_id }}
           expected-commit: ${{ steps.upstream.outputs.commit_id }}
 
-      - name: Select verified SDK version for all consumer builds and tests
-        env:
-          CANDIDATE_REPO: ${{ steps.sdk.outputs.maven-repo }}
-        run: >-
-          python3 .github/scripts/verify_candidate_image_sdk.py "$CANDIDATE_REPO"
-          --configure-upstream-env "$GITHUB_ENV"
-
       - name: Compile core consumers
         run: |
           mvn install -pl hugegraph-client,hugegraph-loader,hugegraph-tools,hugegraph-spark-connector -am -DskipTests -Dmaven.javadoc.skip=true -ntp
diff --git a/README.md b/README.md
index a40b242c2..6e17d5ef7 100644
--- a/README.md
+++ b/README.md
@@ -122,7 +122,7 @@ graph TB
 |-------------|---------|-------|
 | JDK | 17+ | Build and run the core tools with Java 17 |
 | Maven | 3.6.3+ | For building from source |
-| HugeGraph Server | 1.7.0 or the locked Java 17 candidate | See the migration guide for Server 1.5 scope |
+| HugeGraph Server | Current 1.8 or official 1.7.0 | See the migration guide for Server 1.5 scope |
 
 For the Java 17 candidate SDK, upgrade procedure and compatibility boundary, see [the migration guide](docs/java17-migration.md).
 
@@ -364,16 +364,28 @@ Check [Maven Central](https://mvnrepository.com/artifact/org.apache.hugegraph) f
 
 ### Full Build
 
-Follow the [Java 17 bootstrap instructions](docs/java17-migration.md#build-the-locked-candidate) to install the locked SDK into an isolated Maven repository, then build with that repository:
+Normal builds use the 1.8.0 SDK and release packaging checks. Select the staging repository in Maven settings and use an isolated local repository:
 
 ```bash
-mvn -Dmaven.repo.local="$candidate_dir/m2" clean install -DskipTests -Dmaven.javadoc.skip=true -ntp
+release_settings=/absolute/path/to/settings-with-selected-staging.xml
+release_repository=$(mktemp -d /tmp/hugegraph-release-m2.XXXXXX)
+mvn --settings "$release_settings" -Dmaven.repo.local="$release_repository" clean install \
+  -DskipTests -Dmaven.javadoc.skip=true -ntp
 ```
 
-Distribution packaging requires Python 3.9 or newer and verifies the SDK manifest and bundled libraries before producing archives. Ordinary `compile` and `test` remain available with published dependencies; those checks do not validate a candidate distribution. Use the same `-Dmaven.repo.local` option in the module commands below when building candidates.
+Distribution packaging requires Python 3.9 or newer. Release mode checks required SDK POM/JARs and packaged library versions and bytes, and rejects
+candidate manifests. Add `-Papache-release` for the Apache release profile; see [release packaging](docs/java17-migration.md#release-packaging).
+To build the pinned same-source 1.8.0 SDK instead, follow [the bootstrap instructions](docs/java17-migration.md#build-the-locked-candidate) and supply
+`-Dmaven.repo.local="$candidate_dir/m2" -Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate` to every module build.
+
+The aggregate Maven module is `org.apache.hugegraph:hugegraph-toolchain-dist:1.8.0`, distinct from Server's `hugegraph-dist` coordinate.
+Its source directory remains `hugegraph-dist`; the release archive remains `apache-hugegraph-toolchain-1.8.0.tar.gz`.
 
 ### Module-Specific Builds
 
+Until the 1.8.0 SDK is available in Maven Central, apply the same `--settings "$release_settings"` and
+`-Dmaven.repo.local="$release_repository"` options from Full Build to each Maven command below.
+
 | Module | Build Command |
 |--------|---------------|
 | Client | `mvn -e compile -pl hugegraph-client -Dmaven.javadoc.skip=true -ntp` |
diff --git a/docs/ci.md b/docs/ci.md
index c3057ebc0..6e0192b01 100644
--- a/docs/ci.md
+++ b/docs/ci.md
@@ -36,11 +36,11 @@ Dockerfile-only changes run the image check without building Server fixtures. Hu
 
 Each image job builds the checkout's actual Dockerfile, records its image ID and starts a local run-scoped tag with pulling disabled. The container's image ID must match the build. Loader directly executes `./bin/hugegraph-loader.sh --help`, including its executable permission and shebang; its default idle container alone cannot pass. Hubble must remain running and return a valid `/about` JSON response with the application name and version. Its public root page must contain the React roo [...]
 
-Selected Client, Loader, Tools, Spark and Hubble tests compile and run on Java 17, including Loader's separate HDFS jobs. Core module tests cover the locked candidate and released Server 1.7 where configured; these runtime checks supplement affected-module selection without adding unrelated modules. Their results remain advisory.
+Selected Client, Loader, Tools, Spark and Hubble tests compile and run on Java 17, including Loader's separate HDFS jobs. Java module tests compile against SDK 1.8.0; Client, Loader, Tools, Hubble and Spark Connector test both current Server 1.8 and official Server 1.7. Go Client runs the same Server matrix without installing the Java SDK; these runtime checks supplement affected-module selection without adding unrelated modules. Their results remain advisory.
 
-The historical Server 1.7 fixture uses the official ASF 1.7.0 binary archive with a pinned official SHA-512 and runs on its own Java 11 JVM. Downloads fail without a source-build fallback. The locked ASF master Server uses Java 17 and TinkerPop 3.8.1. CI locks the complete immutable source commit (shown as `e62c96` here), so later master updates do not change the build inputs. The SDK action, CI fixtures, image defaults and packaging verifier use the same locked source commit.
+The historical Server 1.7 fixture uses the official ASF 1.7.0 binary archive with a pinned official SHA-512 and runs on its own Java 11 JVM. Downloads fail without a source-build fallback. The locked current Server uses SDK 1.8.0, Java 17 and TinkerPop 3.8.1. CI locks the complete immutable source commit `68855199031d5801edb4fe41b2bacbacffe8fe68`, so later master updates do not change the build inputs. The SDK action, CI fixtures, image defaults and packaging verifier use the same locked [...]
 
-The official release archive is downloaded once and cached; Hubble's candidate fixture is built once, with independent services per job. Reuse verifies source, commit, JDK, build inputs and archive checksum. Client, Loader and Tools candidate tests start the archive already produced by their SDK bootstrap. SDK generation remains job-local until distinct published SDK coordinates are available. The bootstrap flattens CI-friendly POM versions before installation, including the root parent, [...]
+The official release archive is downloaded once and cached; the current Server fixture shared by Hubble and Go is built once, with independent services per job. Reuse verifies source, commit, JDK, build inputs and archive checksum. Client, Loader, Tools and Spark candidate tests start the archive already produced by their SDK bootstrap. Same-source SDK generation remains job-local and retains complete source-install and artifact-hash evidence; release/staging checks use their own isolate [...]
 
 The shared Java initialization action requires Maven 3.9 or newer and supplies explicit settings through `MAVEN_ARGS`, preserving existing arguments and user settings. Dependencies are resolved from Central before ASF Stage, with Stage snapshots disabled; build plugins use the standard public repositories. Maven cache keys include repository settings and SDK preparation inputs. Only third-party dependencies seed the isolated SDK repository; cached HugeGraph artifacts are discarded, and H [...]
 
@@ -48,7 +48,7 @@ Loader validates its existing profiles against both configured Server baselines.
 
 The immutable Server packages, `ci-plan` and `ci-test-results` diagnostic artifacts are retained for seven days from their upload. Partial reruns need the original plan and fixture within that window. After an artifact expires, rerun the whole workflow to rebuild its plan and fixtures; extending only the result report cannot restore an expired package. Test reports and coverage artifacts keep their existing retention settings.
 
-Documentation paths use an explicit allowlist. A PR containing only plain prose and static documentation assets has no module tests. Selection uses the cumulative PR diff, so a documentation update to a PR that also changes source runs its selected consumers again. Source, type definitions, tests and CI configuration are never treated as docs. The gate checks the current workflow's actual selected job results and fixture producers. Its result report records the run, attempt and input met [...]
+Documentation paths use an explicit allowlist. A PR containing only plain prose and static documentation assets has no module tests. Selection uses the cumulative PR diff, so a documentation update to a PR that also changes source runs its selected consumers again. Source, type definitions, tests and CI configuration are never treated as docs. The gate checks the current workflow's actual selected job results and fixture producers. Its result report records the run, attempt and input met [...]
 
 PR planning records the event's head, base and source before querying live metadata. A known mismatch with the checkout merge, current head or source fails planning. The report records the actual tested base and merge; advancing master alone does not invalidate a completed result, consistent with non-strict branch protection. Unknown selection falls back to full validation. No cross-run success is reused.
 
diff --git a/docs/java17-migration.md b/docs/java17-migration.md
index cb0353625..23eea6f95 100644
--- a/docs/java17-migration.md
+++ b/docs/java17-migration.md
@@ -6,7 +6,10 @@ Toolchain builds, tests and applications use Java 17. The released Server 1.7 co
 
 ## Build the locked candidate
 
-The ASF Server master uses Java 17 and TinkerPop 3.8.1 while retaining the Maven version `1.7.0`. Its Common, PD, gRPC and Store artifacts must come from the same source commit; Maven Central artifacts with that version are not interchangeable with the candidate. CI uses the verified `apache/hugegraph` master baseline (shown as `e62c96` here), storing and fetching its complete immutable commit identity. Update the SDK action, verifier, Docker defaults and CI baseline together when advanc [...]
+The locked Java 17 Server source uses TinkerPop 3.8.1 and SDK version `1.8.0`. Its Common, PD, gRPC and Store artifacts must come from the
+same source commit; Maven Central artifacts with that version are not interchangeable with the candidate. Candidate CI uses the verified
+`apache/hugegraph` baseline `68855199031d5801edb4fe41b2bacbacffe8fe68`, storing and fetching its complete immutable commit identity. Update the SDK action, verifier,
+Docker defaults and CI baseline together when advancing that commit; moving master alone must not silently change packaged SDK provenance.
 
 Use an explicit JDK directory and check both Java and Maven. On systems where Java 17 is not registered, a system JDK selector can return another installed version. Checking the generated class version alone does not identify the JVM that built or tested it.
 
@@ -30,15 +33,42 @@ PY_COMMIT
 
 bash "$toolchain_root/hugegraph-client/assembly/travis/install-candidate-sdk.sh" \
   "$server_commit" "$candidate_dir/server" "$candidate_dir/m2"
-mvn -Dmaven.repo.local="$candidate_dir/m2" clean install \
+mvn -Dmaven.repo.local="$candidate_dir/m2" -Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate clean install \
   -DskipTests -Dmaven.javadoc.skip=true -ntp
 ```
 
 The SDK helper installs the locked Server distribution, PD client, Store client and Struct modules together with their Maven dependency closure, without starting a server. Unrelated Server modules are excluded. It requires a new external source directory and a dedicated Maven repository with no existing HugeGraph artifacts. It records source and artifact provenance in `candidate-sdk-manifest.json` inside that repository. Reuse the same explicit `-Dmaven.repo.local` option for subsequent  [...]
 
-Distribution packaging also requires Python 3.9 or newer. Before each module archive is written, the existing SDK verifier checks the locked source, required POM/JAR hashes and local installation origins, then checks the distribution's manifest and actual SDK libraries. The combined archive rechecks all three module directories before moving them. Source checks lock Server SDK artifacts; Toolchain artifacts such as the Client, Loader and Hubble backend are outputs of this reactor and may [...]
+Distribution packaging also requires Python 3.9 or newer. Before each module archive is written, the existing SDK verifier checks the locked source, required POM/JAR hashes and local installation origins, then checks the distribution's manifest and actual SDK libraries. The combined archive rechecks all three module directories before moving them. Source checks lock Server SDK artifacts; Toolchain artifacts such as the Client, Loader and Hubble backend are outputs of this reactor and may [...]
 
-These isolated builds reuse `1.7.0` coordinates only for source validation. Before proposing a release candidate or deploying Toolchain Maven artifacts for external consumers, every Java 17 SDK dependency must have coordinates that distinguish it from the previously released `1.7.0` artifacts and are available to those consumers. Update the Toolchain dependencies and rerun the module dependency and runtime matrices against those publishable identities. Server versioning and publication a [...]
+Normal Toolchain builds and fixed CI/Docker builds compile against SDK `1.8.0`. Same-source builds select
+`-Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate` together with the isolated Maven repository and retain its manifest.
+The compile-time SDK version is independent of the runtime compatibility target: core CI runs the same Client, Loader, Tools and Hubble
+against current Server 1.8 on Java 17 and the official Server 1.7 binary on its supported Java 11. The old Java 17 source SDK using 1.7.0
+coordinates is no longer a test baseline.
+
+The separate `upstream-master` workflow resolves master once, builds that exact source, validates its complete SDK manifest, hashes and local
+source-install origins, then selects the actual source version for every consumer. This also supports a future upstream version such as
+`1.9.0-SNAPSHOT`; it does not change the fixed 1.8.0 compile baseline or establish release/staging provenance.
+
+## Release packaging
+
+Build 1.8.0 release packages with Java 17, Python 3.9 or newer, and `-Papache-release`. Use a fresh Maven repository and settings that resolve the
+chosen Apache staging repository. The existing `stage` profile selects Apache's staging group; specific repository selection belongs in Maven
+settings. Do not preinstall local Server artifacts when validating remote staging dependencies.
+
+```bash
+release_settings=/absolute/path/to/settings-with-selected-staging.xml
+release_repository=$(mktemp -d /tmp/hugegraph-release-m2.XXXXXX)
+mvn --settings "$release_settings" -Dmaven.repo.local="$release_repository" -Papache-release \
+  clean install -DskipTests -Dmaven.javadoc.skip=true -ntp
+```
+
+Release mode requires the Common and PD Common/Client/gRPC 1.8.0 POM/JARs, plus Core for Hubble. Every bundled Server SDK library must have the selected
+version and match the JAR bytes in the build repository; missing, mixed or replaced libraries fail packaging. Candidate manifests in the Maven
+repository or distribution are rejected. This verifies dependency/package consistency, while staging origin is established separately by fresh
+resolution and retained build logs. For unsigned source prevalidation only, add `-Dgpg.skip=true` and record the local dependency origin explicitly.
+Passing that build does not establish RC signatures or constitute an ASF release. Staging upload and formal publication remain separate actions.
 
 ## Upgrade the core tools
 
diff --git a/hugegraph-dist/pom.xml b/hugegraph-dist/pom.xml
index f5efcbbde..acb784ae8 100644
--- a/hugegraph-dist/pom.xml
+++ b/hugegraph-dist/pom.xml
@@ -27,7 +27,7 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-    <artifactId>hugegraph-dist</artifactId>
+    <artifactId>hugegraph-toolchain-dist</artifactId>
 
     <properties>
         <release.name>${project.parent.artifactId}</release.name>
@@ -76,6 +76,10 @@
                                 <exec executable="python3" failonerror="true">
                                     <arg value="${project.basedir}/../.github/scripts/verify_candidate_image_sdk.py"/>
                                     <arg value="${settings.localRepository}"/>
+                                    <arg value="--mode"/>
+                                    <arg value="${sdk.validation.mode}"/>
+                                    <arg value="--version"/>
+                                    <arg value="${hugegraph.version}"/>
                                     <arg value="--distribution"/>
                                     <arg value="${project.basedir}/../hugegraph-loader/apache-hugegraph-loader-${project.version}"/>
                                     <arg value="--module"/>
@@ -84,6 +88,10 @@
                                 <exec executable="python3" failonerror="true">
                                     <arg value="${project.basedir}/../.github/scripts/verify_candidate_image_sdk.py"/>
                                     <arg value="${settings.localRepository}"/>
+                                    <arg value="--mode"/>
+                                    <arg value="${sdk.validation.mode}"/>
+                                    <arg value="--version"/>
+                                    <arg value="${hugegraph.version}"/>
                                     <arg value="--distribution"/>
                                     <arg value="${project.basedir}/../hugegraph-tools/apache-hugegraph-tools-${project.version}"/>
                                     <arg value="--module"/>
@@ -92,6 +100,10 @@
                                 <exec executable="python3" failonerror="true">
                                     <arg value="${project.basedir}/../.github/scripts/verify_candidate_image_sdk.py"/>
                                     <arg value="${settings.localRepository}"/>
+                                    <arg value="--mode"/>
+                                    <arg value="${sdk.validation.mode}"/>
+                                    <arg value="--version"/>
+                                    <arg value="${hugegraph.version}"/>
                                     <arg value="--distribution"/>
                                     <arg value="${project.basedir}/../hugegraph-hubble/apache-hugegraph-hubble-${project.version}"/>
                                     <arg value="--module"/>
diff --git a/hugegraph-dist/release-docs/LICENSE b/hugegraph-dist/release-docs/LICENSE
index 71412b9d6..2ce0fbe90 100644
--- a/hugegraph-dist/release-docs/LICENSE
+++ b/hugegraph-dist/release-docs/LICENSE
@@ -916,15 +916,53 @@ https://github.com/h2database/h2database/tree/version-2.3.232
         Apache-2.0 terms above
 
 ========================================================================
-Retained published baseline license coverage
+SDK runtime dependency license coverage
 ========================================================================
 
-This version was already present in the original published runtime classpaths.
-Its Apache-2.0 terms are reproduced at the beginning of this LICENSE.
+These versions are selected by the SDK 1.8.0 runtime dependencies.
+Apache-2.0 terms are reproduced at the beginning of this LICENSE; the
+existing gRPC native-code grants and NOTICE remain unchanged.
 
-    (Apache-2.0) io.grpc:grpc-core:1.39.0
+    (Apache-2.0) io.grpc:grpc-api:1.55.3
+    (Apache-2.0) io.grpc:grpc-context:1.55.3
+    (Apache-2.0) io.grpc:grpc-core:1.55.3
+    (Apache-2.0) io.grpc:grpc-netty-shaded:1.55.3
+    (Apache-2.0) io.grpc:grpc-protobuf:1.55.3
+    (Apache-2.0) io.grpc:grpc-protobuf-lite:1.55.3
+    (Apache-2.0) io.grpc:grpc-stub:1.55.3
+    (Apache-2.0) io.perfmark:perfmark-api:0.25.0
+    (Apache-2.0) com.google.api.grpc:proto-google-common-protos:2.9.0
         Apache-2.0 terms above
 
+    (BSD-3-Clause) com.google.protobuf:protobuf-java:3.22.3
+        licenses/LICENSE-spark-java17-third-party.txt
+        Matching upstream grant: https://raw.githubusercontent.com/protocolbuffers/protobuf/v22.3/LICENSE
+
+    (MIT) org.codehaus.mojo:animal-sniffer-annotations:1.21
+        Full copyright notice and grant reproduced below
+
+The MIT License
+
+Copyright (c) 2009 codehaus.org.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
 ========================================================================
 Spark Connector runtime dependency updates
 ========================================================================
@@ -974,11 +1012,11 @@ reproduced in the main distribution NOTICE. JavaParser uses its Apache-2.0
 option; Cucumber components use MIT and ANTLR/TreeLayout use BSD-3-Clause.
 These grants do not replace the terms of other bundled components.
 
-The HugeGraph Common/Core and PD client/common/grpc SDK payloads retain the
-1.7.0 Maven coordinates while originating from the verified same-source
-Java 17 candidate build. Their payload identity is recorded with its source
-revision and SHA256 in the accompanying build/audit metadata; they are not
-represented as byte-identical Maven Central 1.7.0 artifacts.
+The HugeGraph Common/Core and PD client/common/grpc SDK payloads use 1.8.0
+Maven coordinates. Same-source Java 17 builds record their source revision
+and SHA256 in the accompanying build/audit metadata. Release builds verify
+the selected staging artifacts independently; source builds do not establish
+byte identity with a published Maven artifact.
 
     (Apache-2.0) com.github.javaparser:javaparser-core:3.28.0
         Apache-2.0 terms above
diff --git a/hugegraph-dist/release-docs/NOTICE b/hugegraph-dist/release-docs/NOTICE
index 3e8a3d110..54dcce5f6 100644
--- a/hugegraph-dist/release-docs/NOTICE
+++ b/hugegraph-dist/release-docs/NOTICE
@@ -5173,7 +5173,7 @@ The Apache Software Foundation (http://www.apache.org/).
 
 
 ========================================================================
-org.apache.hugegraph:hg-pd-client:1.7.0 / META-INF/NOTICE
+org.apache.hugegraph:hg-pd-client:1.8.0 / META-INF/NOTICE
 ========================================================================
 
 
@@ -5187,7 +5187,7 @@ The Apache Software Foundation (http://www.apache.org/).
 
 
 ========================================================================
-org.apache.hugegraph:hg-pd-common:1.7.0 / META-INF/NOTICE
+org.apache.hugegraph:hg-pd-common:1.8.0 / META-INF/NOTICE
 ========================================================================
 
 
@@ -5201,7 +5201,7 @@ The Apache Software Foundation (http://www.apache.org/).
 
 
 ========================================================================
-org.apache.hugegraph:hg-pd-grpc:1.7.0 / META-INF/NOTICE
+org.apache.hugegraph:hg-pd-grpc:1.8.0 / META-INF/NOTICE
 ========================================================================
 
 
@@ -5215,7 +5215,7 @@ The Apache Software Foundation (http://www.apache.org/).
 
 
 ========================================================================
-org.apache.hugegraph:hugegraph-common:1.7.0 / META-INF/NOTICE
+org.apache.hugegraph:hugegraph-common:1.8.0 / META-INF/NOTICE
 ========================================================================
 
 
@@ -5229,7 +5229,7 @@ The Apache Software Foundation (http://www.apache.org/).
 
 
 ========================================================================
-org.apache.hugegraph:hugegraph-core:1.7.0 / META-INF/NOTICE
+org.apache.hugegraph:hugegraph-core:1.8.0 / META-INF/NOTICE
 ========================================================================
 
 
@@ -5287,7 +5287,7 @@ This product includes software developed at
 The Apache Software Foundation (https://www.apache.org/).
 
 ========================================================================
-io.grpc:grpc-netty-shaded:1.39.0 / META-INF/NOTICE.txt
+io.grpc:grpc-netty-shaded:1.55.3 / META-INF/NOTICE.txt
 ========================================================================
 
                           The Netty Project
diff --git a/hugegraph-dist/scripts/dependency/known-dependencies.txt b/hugegraph-dist/scripts/dependency/known-dependencies.txt
index e7e022733..5e3f781e2 100644
--- a/hugegraph-dist/scripts/dependency/known-dependencies.txt
+++ b/hugegraph-dist/scripts/dependency/known-dependencies.txt
@@ -8,7 +8,7 @@ activation-1.1.1.jar
 aircompressor-0.10.jar
 aircompressor-0.21.jar
 angus-activation-2.0.3.jar
-animal-sniffer-annotations-1.19.jar
+animal-sniffer-annotations-1.21.jar
 annotations-13.0.jar
 annotations-17.0.0.jar
 annotations-24.0.1.jar
@@ -150,15 +150,15 @@ groovy-jsr223-4.0.32.jar
 groovy-swing-4.0.32.jar
 groovy-templates-4.0.32.jar
 groovy-xml-4.0.32.jar
-grpc-api-1.39.0.jar
-grpc-context-1.39.0.jar
-grpc-core-1.39.0.jar
+grpc-api-1.55.3.jar
+grpc-context-1.55.3.jar
+grpc-core-1.55.3.jar
 grpc-grpclb-1.39.0.jar
 grpc-netty-1.39.0.jar
-grpc-netty-shaded-1.39.0.jar
-grpc-protobuf-1.39.0.jar
-grpc-protobuf-lite-1.39.0.jar
-grpc-stub-1.39.0.jar
+grpc-netty-shaded-1.55.3.jar
+grpc-protobuf-1.55.3.jar
+grpc-protobuf-lite-1.55.3.jar
+grpc-stub-1.55.3.jar
 gson-2.8.9.jar
 guava-30.0-jre.jar
 guice-4.0.jar
@@ -206,13 +206,12 @@ hbase-shaded-netty-2.2.1.jar
 hbase-shaded-protobuf-2.2.1.jar
 hbase-zookeeper-2.2.3.jar
 hessian-3.3.6.jar
-hg-pd-client-1.7.0.jar
-hg-pd-common-1.7.0.jar
-hg-pd-grpc-1.7.0.jar
+hg-pd-client-1.8.0.jar
+hg-pd-common-1.8.0.jar
+hg-pd-grpc-1.8.0.jar
 hive-classification-3.1.3.jar
 hive-common-3.1.3.jar
 hive-exec-3.1.3-core.jar
-hive-exec-3.1.3.jar
 hive-llap-client-3.1.3.jar
 hive-llap-common-3.1.3.jar
 hive-llap-tez-3.1.3.jar
@@ -571,16 +570,16 @@ paranamer-2.3.jar
 parboiled-core-1.1.8.jar
 parboiled-core-1.2.0.jar
 parquet-hadoop-bundle-1.10.0.jar
-perfmark-api-0.23.0.jar
+perfmark-api-0.25.0.jar
 plexus-utils-3.3.0.jar
 plexus-utils-3.5.1.jar
 postgresql-42.4.1.jar
 postgresql-42.7.11.jar
-proto-google-common-protos-2.0.1.jar
+proto-google-common-protos-2.9.0.jar
 protobuf-java-2.5.0.jar
-protobuf-java-3.17.2.jar
 protobuf-java-3.17.3.jar
 protobuf-java-3.19.6.jar
+protobuf-java-3.22.3.jar
 protobuf-java-util-3.17.2.jar
 query-13.6.0.jar
 re2j-1.1.jar
diff --git a/hugegraph-hubble/Dockerfile b/hugegraph-hubble/Dockerfile
index a57653ef0..2184b7825 100644
--- a/hugegraph-hubble/Dockerfile
+++ b/hugegraph-hubble/Dockerfile
@@ -40,16 +40,20 @@ RUN mkdir -p /opt/candidate-m2 \
     && if [ ! -f /opt/candidate-m2/candidate-sdk-manifest.json ]; then \
          SERVER_REPOSITORY=apache/hugegraph \
          bash /pkg/hugegraph-client/assembly/travis/install-candidate-sdk.sh \
-           e62c961e00221569d4f955abbadf60faee45b283 /opt/candidate-source /opt/candidate-m2; \
+           68855199031d5801edb4fe41b2bacbacffe8fe68 /opt/candidate-source /opt/candidate-m2; \
        fi \
     && python3 /pkg/.github/scripts/verify_candidate_image_sdk.py /opt/candidate-m2
 
 COPY . /pkg
 
 RUN set -x \
-    && mvn install $MAVEN_ARGS -Dmaven.repo.local=/opt/candidate-m2 -pl hugegraph-client,hugegraph-loader -am -Dmaven.javadoc.skip=true -DskipTests -ntp \
+    && mvn install $MAVEN_ARGS -Dmaven.repo.local=/opt/candidate-m2 \
+       -Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate \
+       -pl hugegraph-client,hugegraph-loader -am -Dmaven.javadoc.skip=true -DskipTests -ntp \
     && cd /pkg/hugegraph-hubble/ \
-    && mvn package $MAVEN_ARGS -Dmaven.repo.local=/opt/candidate-m2 -e -B -ntp -DskipTests -Dmaven.javadoc.skip=true \
+    && mvn package $MAVEN_ARGS -Dmaven.repo.local=/opt/candidate-m2 \
+       -Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate \
+       -e -B -ntp -DskipTests -Dmaven.javadoc.skip=true \
     && python3 /pkg/.github/scripts/verify_candidate_image_sdk.py /opt/candidate-m2 \
     && set -- /pkg/hugegraph-hubble/target/apache-hugegraph-hubble-*.tar.gz \
     && test "$#" -eq 1 \
diff --git a/hugegraph-hubble/README.md b/hugegraph-hubble/README.md
index c7cedf264..9338594eb 100644
--- a/hugegraph-hubble/README.md
+++ b/hugegraph-hubble/README.md
@@ -200,8 +200,8 @@ For the Java 17 candidate, prepare the locked SDK using the [bootstrap instructi
 ```bash
 cd hugegraph-toolchain
 python3 -m pip install -r hugegraph-hubble/hubble-dist/assembly/travis/requirements.txt
-mvn -Dmaven.repo.local="$candidate_dir/m2" install -pl hugegraph-client,hugegraph-loader -am -Dmaven.javadoc.skip=true -DskipTests -ntp
-(cd hugegraph-hubble && mvn -Dmaven.repo.local="$candidate_dir/m2" package -Dmaven.javadoc.skip=true -DskipTests -ntp)
+mvn -Dmaven.repo.local="$candidate_dir/m2" -Dsdk.validation.mode=candidate install -pl hugegraph-client,hugegraph-loader -am -Dmaven.javadoc.skip=true -DskipTests -ntp
+(cd hugegraph-hubble && mvn -Dmaven.repo.local="$candidate_dir/m2" -Dsdk.validation.mode=candidate package -Dmaven.javadoc.skip=true -DskipTests -ntp)
 cd hugegraph-hubble/apache-hugegraph-hubble-*
 ```
 
diff --git a/hugegraph-hubble/hubble-dist/pom.xml b/hugegraph-hubble/hubble-dist/pom.xml
index 9c9104ad0..1902d606b 100644
--- a/hugegraph-hubble/hubble-dist/pom.xml
+++ b/hugegraph-hubble/hubble-dist/pom.xml
@@ -162,6 +162,10 @@
                                 <exec executable="python3" failonerror="true">
                                     <arg value="${repo.root.dir}/.github/scripts/verify_candidate_image_sdk.py"/>
                                     <arg value="${settings.localRepository}"/>
+                                    <arg value="--mode"/>
+                                    <arg value="${sdk.validation.mode}"/>
+                                    <arg value="--version"/>
+                                    <arg value="${hugegraph.version}"/>
                                     <arg value="--distribution"/>
                                     <arg value="${top.level.dir}/${final.name}"/>
                                     <arg value="--module"/>
diff --git a/hugegraph-loader/Dockerfile b/hugegraph-loader/Dockerfile
index 1985afe64..04a2462a8 100644
--- a/hugegraph-loader/Dockerfile
+++ b/hugegraph-loader/Dockerfile
@@ -36,14 +36,16 @@ RUN mkdir -p /opt/candidate-m2 \
     && if [ ! -f /opt/candidate-m2/candidate-sdk-manifest.json ]; then \
          SERVER_REPOSITORY=apache/hugegraph \
          bash /pkg/hugegraph-client/assembly/travis/install-candidate-sdk.sh \
-           e62c961e00221569d4f955abbadf60faee45b283 /opt/candidate-source /opt/candidate-m2; \
+           68855199031d5801edb4fe41b2bacbacffe8fe68 /opt/candidate-source /opt/candidate-m2; \
        fi \
     && python3 /pkg/.github/scripts/verify_candidate_image_sdk.py /opt/candidate-m2
 
 COPY . /pkg
 
 RUN set -x \
-    && mvn clean package $MAVEN_ARGS -Dmaven.repo.local=/opt/candidate-m2 -pl hugegraph-client,hugegraph-loader -am \
+    && mvn clean package $MAVEN_ARGS -Dmaven.repo.local=/opt/candidate-m2 \
+       -Dhugegraph.version=1.8.0 -Dsdk.validation.mode=candidate \
+       -pl hugegraph-client,hugegraph-loader -am \
        -Dmaven.javadoc.skip=true -DskipTests -ntp \
     && python3 /pkg/.github/scripts/verify_candidate_image_sdk.py /opt/candidate-m2
 
diff --git a/hugegraph-loader/README.md b/hugegraph-loader/README.md
index 44c346198..9b6342b06 100644
--- a/hugegraph-loader/README.md
+++ b/hugegraph-loader/README.md
@@ -59,7 +59,7 @@ mvn install:install-file -Dfile=./ojdbc8.jar -DgroupId=com.oracle -DartifactId=o
 For the Java 17 candidate, prepare the locked SDK using the [bootstrap instructions](../docs/java17-migration.md#build-the-locked-candidate), then compile from the Toolchain repository root with the same isolated Maven repository:
 
 ```bash
-mvn -Dmaven.repo.local="$candidate_dir/m2" clean package -pl hugegraph-client,hugegraph-loader -am -DskipTests -ntp
+mvn -Dmaven.repo.local="$candidate_dir/m2" -Dsdk.validation.mode=candidate clean package -pl hugegraph-client,hugegraph-loader -am -DskipTests -ntp
 ```
 
 ### 2.3 Use docker image (Convenient for Test/Dev)
diff --git a/hugegraph-loader/pom.xml b/hugegraph-loader/pom.xml
index 50d4ec1ca..4db7eaa90 100644
--- a/hugegraph-loader/pom.xml
+++ b/hugegraph-loader/pom.xml
@@ -427,6 +427,8 @@
             <groupId>org.apache.hive</groupId>
             <artifactId>hive-exec</artifactId>
             <version>${hive.version}</version>
+            <!-- Keep Hive's embedded old Guava out of the shared Loader classpath. -->
+            <classifier>core</classifier>
             <exclusions>
                 <exclusion>
                     <groupId>log4j</groupId>
@@ -734,6 +736,10 @@
                                 <exec executable="python3" failonerror="true">
                                     <arg value="${project.basedir}/../.github/scripts/verify_candidate_image_sdk.py"/>
                                     <arg value="${settings.localRepository}"/>
+                                    <arg value="--mode"/>
+                                    <arg value="${sdk.validation.mode}"/>
+                                    <arg value="--version"/>
+                                    <arg value="${hugegraph.version}"/>
                                     <arg value="--distribution"/>
                                     <arg value="${project.basedir}/${final.name}"/>
                                     <arg value="--module"/>
diff --git a/hugegraph-spark-connector/README.md b/hugegraph-spark-connector/README.md
index 84f927a16..224780d45 100644
--- a/hugegraph-spark-connector/README.md
+++ b/hugegraph-spark-connector/README.md
@@ -29,7 +29,7 @@ Required:
 - Spark 3.5.8 with Scala 2.12 (built with Scala 2.12.18)
 - Maven 3.6.3+
 
-The default Maven commands below use published Common 1.7.0, whose request bodies depend on the JVM default charset. Retain the UTF-8 submission options below when running this build on Java 17. The isolated same-source candidate SDK used by CI supplies a Common implementation that uses the declared request-body charset and defaults to UTF-8.
+The default Maven commands below compile against Common 1.8.0, which uses the declared request-body charset and defaults to UTF-8. Retain the UTF-8 submission options below to keep Spark drivers and executors consistent. The compile-time SDK version is independent of the Server version used by the runtime compatibility tests.
 
 To build without executing tests:
 
@@ -43,7 +43,7 @@ To build with default tests:
 mvn clean package
 ```
 
-The integration tests clear the configured graph before writing vertices and edges through Spark. Use a disposable server and graph. The defaults are `http://127.0.0.1:8080` and `hugegraph`; set `-Dhugegraph.test.url=... -Dhugegraph.test.graph=...` to select another target. The Surefire JVM starts with `-Dfile.encoding=UTF-8` so these fixtures do not depend on the caller's locale. The default Spark master is `local[2]`. `-Dspark.test.master=...` accepts only `local` or `local[...]`, for  [...]
+The integration tests clear the configured graph before writing vertices and edges through Spark. Use a disposable server and graph. The defaults are `http://127.0.0.1:8080` and `hugegraph`; set `-Dhugegraph.test.url=... -Dhugegraph.test.graph=...` to select another target. The Surefire JVM starts with `-Dfile.encoding=UTF-8` so these fixtures do not depend on the caller's locale. The default Spark master is `local[2]`. `-Dspark.test.master=...` accepts only `local` or `local[...]`, for  [...]
 
 HTTPS tests require an explicit trust store shared by the readback client and local Spark writers:
 
@@ -79,7 +79,7 @@ spark-submit --deploy-mode client --driver-class-path "$DRIVER_GUAVA_JAR" \
   /path/to/your-application.jar
 ```
 
-Omit the UTF-8 driver and executor options only when the connector and Java Client were built using the isolated same-source candidate SDK. The default Maven build still requires them.
+The explicit UTF-8 driver and executor options above remain the recommended submission configuration.
 
 For cluster deploy mode, provision Guava on the remote driver and use its readable absolute path in `--driver-class-path`; copying it to executors does not provision the driver.
 
diff --git a/hugegraph-tools/pom.xml b/hugegraph-tools/pom.xml
index f403a471c..101e1c22e 100644
--- a/hugegraph-tools/pom.xml
+++ b/hugegraph-tools/pom.xml
@@ -213,6 +213,10 @@
                                 <exec executable="python3" failonerror="true">
                                     <arg value="${project.basedir}/../.github/scripts/verify_candidate_image_sdk.py"/>
                                     <arg value="${settings.localRepository}"/>
+                                    <arg value="--mode"/>
+                                    <arg value="${sdk.validation.mode}"/>
+                                    <arg value="--version"/>
+                                    <arg value="${hugegraph.version}"/>
                                     <arg value="--distribution"/>
                                     <arg value="${project.basedir}/${final.name}"/>
                                     <arg value="--module"/>
diff --git a/pom.xml b/pom.xml
index 793b3cbc9..489c9ff32 100644
--- a/pom.xml
+++ b/pom.xml
@@ -100,7 +100,8 @@
     <properties>
         <revision>1.8.0</revision>
         <!-- Unified release line for HugeGraph common, server/core, PD and store artifacts -->
-        <hugegraph.version>1.7.0</hugegraph.version>
+        <hugegraph.version>1.8.0</hugegraph.version>
+        <sdk.validation.mode>release</sdk.validation.mode>
         <release.name>${project.artifactId}</release.name>
         <final.name>apache-${release.name}-${project.version}</final.name>
         <assembly.dir>${project.basedir}/assembly</assembly.dir>
@@ -340,6 +341,9 @@
     <profiles>
         <profile>
             <id>apache-release</id>
+            <properties>
+                <sdk.validation.mode>release</sdk.validation.mode>
+            </properties>
             <build>
                 <pluginManagement>
                     <plugins>