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

Kusuma04-dev pushed a commit to branch 10.3.x-test
in repository https://gitbox.apache.org/repos/asf/incubator-kie.git

commit e505005f8b240198a665e475eaca807ee4adb156
Author: Kusuma <[email protected]>
AuthorDate: Thu Oct 22 12:25:00 2026 +0530

    uppdated release scripts
---
 .ci/ci-image/entrypoint.sh                       |  20 ++
 .ci/ci-image/start-docker.sh                     |   8 +
 .ci/ci-image/wait-for-docker.sh                  |   4 +
 .ci/ci-image/wait-for-process.sh                 |  19 ++
 .ci/incubator-kie-ci-build.Dockerfile            | 229 ++++++++++++++++
 .ci/jenkins/Jenkinsfile.103xplus.deploy          | 315 +++++++++++++++++++++++
 .ci/jenkins/Jenkinsfile.103xplus.promote         | 308 ++++++++++++++++++++++
 .ci/jenkins/Jenkinsfile.ci-image-build           | 108 ++++++++
 .ci/jenkins/project/Jenkinsfile.103xplus.release | 218 ++++++++++++++++
 script/release/00-create-stream-branch.sh        | 172 +++++++++++++
 script/release/01-update-version.sh              | 108 ++++++++
 script/release/02-rc-commit.sh                   | 236 +++++++++++++++++
 script/release/03-build.sh                       | 100 +++++++
 script/release/04-deploy-to-staging.sh           | 179 +++++++++++++
 script/release/05-tag-release.sh                 | 134 ++++++++++
 script/release/README.md                         | 270 +++++++++++++++++++
 script/release/release-all.sh                    | 201 +++++++++++++++
 script/release/upload_filemgmt.sh                | 127 +++++++++
 18 files changed, 2756 insertions(+)

diff --git a/.ci/ci-image/entrypoint.sh b/.ci/ci-image/entrypoint.sh
new file mode 100755
index 00000000000..835472d83b9
--- /dev/null
+++ b/.ci/ci-image/entrypoint.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+# cgroup v2: enable nesting
+if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
+       echo "in cgroupv2 branch"
+       # move the processes from the root group to the /init group,
+       # otherwise writing subtree_control fails with EBUSY.
+       # An error during moving non-existent process (i.e., "cat") is ignored.
+       sudo mkdir -p /sys/fs/cgroup/init
+       sudo bash -c "xargs -rn1 < /sys/fs/cgroup/cgroup.procs > 
/sys/fs/cgroup/init/cgroup.procs || :"
+       # enable controllers
+       sudo bash -c "sed -e 's/ / +/g' -e 's/^/+/' < 
/sys/fs/cgroup/cgroup.controllers > /sys/fs/cgroup/cgroup.subtree_control"
+fi
+
+# Start docker and print logs
+start-docker.sh
+
+# process the command passed, after docker is started
+if [ $# -gt 0 ]; then
+       exec "$@"
+fi
\ No newline at end of file
diff --git a/.ci/ci-image/start-docker.sh b/.ci/ci-image/start-docker.sh
new file mode 100755
index 00000000000..930eb553775
--- /dev/null
+++ b/.ci/ci-image/start-docker.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+source /opt/bash-utils/logger.sh
+
+INFO "Starting supervisor"
+sudo bash -c "/usr/bin/supervisord >> /dev/null 2>&1" &
+
+INFO "Starting docker"
+bash -c "source wait-for-docker.sh > /dev/null && sudo chown root:docker 
/var/run/docker.sock" &
diff --git a/.ci/ci-image/wait-for-docker.sh b/.ci/ci-image/wait-for-docker.sh
new file mode 100755
index 00000000000..ee636f9c3ce
--- /dev/null
+++ b/.ci/ci-image/wait-for-docker.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+source wait-for-process.sh
+
+wait_for_process dockerd
\ No newline at end of file
diff --git a/.ci/ci-image/wait-for-process.sh b/.ci/ci-image/wait-for-process.sh
new file mode 100755
index 00000000000..49d4035e8d4
--- /dev/null
+++ b/.ci/ci-image/wait-for-process.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+source /opt/bash-utils/logger.sh
+
+function wait_for_process () {
+    local max_time_wait=30
+    local process_name="$1"
+    local waited_sec=0
+    while ! pgrep "$process_name" >/dev/null && ((waited_sec < 
max_time_wait)); do
+        INFO "Process $process_name is not running yet. Retrying in 1 seconds"
+        INFO "Waited $waited_sec seconds of $max_time_wait seconds"
+        sleep 1
+        ((waited_sec=waited_sec+1))
+        if ((waited_sec >= max_time_wait)); then
+            return 1
+        fi
+    done
+    INFO "Process $process_name successfully started."
+    return 0
+}
\ No newline at end of file
diff --git a/.ci/incubator-kie-ci-build.Dockerfile 
b/.ci/incubator-kie-ci-build.Dockerfile
new file mode 100644
index 00000000000..7a0200b12a6
--- /dev/null
+++ b/.ci/incubator-kie-ci-build.Dockerfile
@@ -0,0 +1,229 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# CI builder image for incubator-kie (Drools) Jenkins jobs.
+# Same content as apache-nodes/Dockerfile.kogito-ci-build 
(incubator-kie-kogito-pipelines, main-latest),
+# moved here so it can be published per stream by 
.ci/jenkins/Jenkinsfile.ci-image-build, e.g.
+#   docker.io/apache/incubator-kie-kogito-ci-build:10.3.x-latest
+#
+# Docker-in-Docker: the daemon is started by ENTRYPOINT entrypoint.sh (-> 
start-docker.sh),
+# so the container must run privileged (--privileged --group-add docker).
+#
+# Keep SDKMAN_MAVEN in sync with <version.org.apache.maven> in 
kie-parent/pom.xml
+# (enforced by maven-enforcer-plugin, rule requireMavenVersion).
+FROM cruizba/ubuntu-dind:jammy-26.1.4
+
+SHELL ["/bin/bash", "-c"]
+
+ARG SDKMAN_JAVA="17.0.11-tem"
+ARG SDKMAN_MAVEN="3.9.16"
+ARG PYTHON_MAJOR_VERSION="3"
+ARG PYTHON_MAJOR_MINOR_VERSION="3.11"
+ARG DOCKER_VERSION="24.0.7"
+
+# set locale to C.UTF-8
+ENV LANG='C.UTF-8'
+
+# set TZ to America/New_York
+ENV TZ='America/New_York'
+
+RUN apt update && apt upgrade -y && apt install -y \
+# skdman deps (BEGIN)
+git \
+findutils \
+tar \
+gzip \
+unzip \
+zip \
+# skdman deps (END)
+# python3 (BEGIN)
+python${PYTHON_MAJOR_MINOR_VERSION} \
+python${PYTHON_MAJOR_MINOR_VERSION}-dev \
+python${PYTHON_MAJOR_VERSION}-pip \
+python${PYTHON_MAJOR_VERSION}-gssapi \
+krb5-multidev \
+# python3 (END)
+# system (BEGIN)
+netcat \
+libvshadow-utils \
+sudo \
+wget \
+software-properties-common \
+# system (END)
+# drools (BEGIN)
+fontconfig \
+# drools (END)
+# kogito python integration (BEGIN)
+libglvnd0 \
+# kogito python integration (END)
+# Cypress dependencies install (BEGIN)
+xvfb \
+libgtk2.0-dev \
+libgtk-3-dev \
+libnotify-dev \
+libnss3-tools \
+libxss1 \
+libasound2 \
+# Cypress dependencies install (END)
+# kogito-images (BEGIN)
+jq \
+skopeo \
+subversion \
+# kogito-images (END)
+&& apt clean
+
+RUN groupadd -g 910 nonrootuser && useradd -u 910 -g 910 -s /bin/bash -m 
nonrootuser && \
+  echo "nonrootuser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
+
+# Docker
+RUN wget -O docker.tgz 
https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_VERSION}.tgz
  && \
+  tar --extract --file docker.tgz --strip-components 1 --directory 
/usr/local/bin/ && \
+  rm -rf docker.tgz && \
+  groupadd docker && \
+  usermod -aG docker nonrootuser && \
+  newgrp docker
+
+USER nonrootuser
+
+# Install pip of given version (BEGIN)
+RUN curl -sS https://bootstrap.pypa.io/get-pip.py | 
python${PYTHON_MAJOR_MINOR_VERSION}
+# Install pip of given version (END)
+
+RUN sudo update-alternatives --install /usr/local/bin/python python $(which 
python${PYTHON_MAJOR_MINOR_VERSION}) 1 && \
+  sudo update-alternatives --install /usr/local/bin/python3 python3 $(which 
python${PYTHON_MAJOR_MINOR_VERSION}) 1 && \
+  sudo update-alternatives --install /usr/local/bin/pip pip 
~/.local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} -1 && \
+  sudo update-alternatives --install /usr/local/bin/pip${PYTHON_MAJOR_VERSION} 
pip${PYTHON_MAJOR_VERSION} ~/.local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} -1 && \
+  sudo update-alternatives --install 
/usr/local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} 
pip${PYTHON_MAJOR_MINOR_VERSION} ~/.local/bin/pip${PYTHON_MAJOR_MINOR_VERSION} 
-1
+
+RUN curl -s "https://get.sdkman.io"; | bash && \
+  source "$HOME/.sdkman/bin/sdkman-init.sh" && \
+  sdk install java ${SDKMAN_JAVA} && \
+  sudo update-alternatives --install /usr/local/bin/java java $(which java) 1 
&& \
+  sdk install maven ${SDKMAN_MAVEN} && \
+  sudo update-alternatives --install /usr/local/bin/mvn mvn $(which mvn) 1 && \
+  sdk flush
+  
+RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | 
bash && \
+  export NVM_DIR="$HOME/.nvm" && \
+  [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" && \
+  [ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" && \
+  source ~/.bashrc && nvm install "v16.20.0" && \
+  sudo update-alternatives --install /usr/local/bin/node node $(which node) 1 
&& \
+  sudo update-alternatives --install /usr/local/bin/npm npm $(which npm) 1
+  
+RUN wget https://go.dev/dl/go1.21.6.linux-amd64.tar.gz -P /tmp && \
+  sudo mkdir /opt/golang && \
+  sudo tar -C /opt/golang -xzf /tmp/go*.tar.gz && \
+  sudo chown -R nonrootuser:nonrootuser /opt/golang/go && \
+  sudo chmod -R 755 /opt/golang/go && \
+  echo 'export GOPATH=${HOME}/go' | sudo tee /etc/profile.d/go.sh && \
+  echo 'export PATH=${PATH}:/opt/golang/go/bin:${GOPATH}/bin' | sudo tee -a 
/etc/profile.d/go.sh && \
+  echo "source /etc/profile.d/go.sh" >> $HOME/.bashrc && \
+  rm -rf /tmp/go* && \
+  sudo update-alternatives --install /usr/local/bin/go go 
/opt/golang/go/bin/go 1 && \
+  go install golang.org/x/tools/cmd/goimports@latest && \
+  sudo update-alternatives --install /usr/local/bin/goimports goimports 
~/go/bin/goimports 1
+
+# Install hub CLI (used for GitHub api operations)
+RUN wget 
https://github.com/mislav/hub/releases/download/v2.14.2/hub-linux-amd64-2.14.2.tgz
 -O /tmp/hub.tgz && \
+  (if ([ ! -d /opt/hub ]);then sudo mkdir /opt/hub;else rm -rf /opt/hub/*; 
fi;) && \
+  sudo tar -C /opt/hub -xzf /tmp/hub.tgz --strip-components=1 && \
+  sudo chown -R nonrootuser:nonrootuser /opt/hub/bin/hub && \
+  sudo chmod -R 755 /opt/hub/bin/hub && \
+  rm -rf /tmp/hub.tgz && \
+  sudo update-alternatives --install /usr/local/bin/hub hub /opt/hub/bin/hub 1
+
+# Cekit
+RUN pip${PYTHON_MAJOR_MINOR_VERSION} install cekit==4.11.0 behave lxml docker 
docker-squash elementPath pyyaml ruamel.yaml python-dateutil Jinja2 pykwalify 
colorlog click && \
+  sudo update-alternatives --install /usr/local/bin/cekit cekit 
~/.local/bin/cekit 1 && \
+  sudo update-alternatives --install /usr/local/bin/docker-squash 
docker-squash ~/.local/bin/docker-squash 1
+RUN wget 
https://github.com/openshift/source-to-image/releases/download/v1.3.8/source-to-image-v1.3.8-980ca195-linux-amd64.tar.gz
 -P /tmp && \
+  tmp_dir=$(mktemp -d) && \
+  tar -C ${tmp_dir} -xzvf 
/tmp/source-to-image-v1.3.8-980ca195-linux-amd64.tar.gz && \
+  ls ${tmp_dir} && \
+  sudo mv ${tmp_dir}/s2i /usr/local/bin/ && \
+  rm -rf ${tmp_dir} /tmp/source-to-image/*
+
+# gh cli
+RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | 
sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
+  && sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
+  && echo "deb [arch=$(dpkg --print-architecture) 
signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] 
https://cli.github.com/packages stable main" | sudo tee 
/etc/apt/sources.list.d/github-cli.list > /dev/null \
+  && sudo apt update \
+  && sudo apt install gh -y
+
+# Install kubectl
+RUN wget https://dl.k8s.io/release/$(curl -Ls 
https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl -P /tmp && \
+  chmod +x /tmp/kubectl && \
+  sudo mv /tmp/kubectl /usr/local/bin/kubectl
+
+# Install oc client
+RUN wget 
https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest-4.13/openshift-client-linux.tar.gz
 -P /tmp && \
+  tmp_dir=$(mktemp -d) && \
+  tar -C ${tmp_dir} -xvf /tmp/openshift-client-linux.tar.gz && \
+  sudo mv ${tmp_dir}/oc /usr/local/bin && \
+  rm -rf ${tmp_dir} /tmp/openshift-client*
+
+# Install ansible
+RUN sudo ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
+    echo $TZ | sudo tee /etc/timezone && \
+    sudo add-apt-repository --yes --update ppa:ansible/ansible && \
+    sudo apt update && \
+    sudo apt install -y ansible
+
+# Convenience script to account for using 'alternatives' in some places
+RUN sudo bash -c 'echo -e "#!/bin/bash\nupdate-alternatives \"\$@\"" > 
/usr/local/bin/alternatives' \
+  && sudo chmod +x /usr/local/bin/alternatives
+
+ENV HOME="/home/nonrootuser"
+
+ENV JAVA_HOME="${HOME}/.sdkman/candidates/java/current/"
+ENV MAVEN_HOME="${HOME}/.sdkman/candidates/maven/current/"
+ENV MAVEN_OPTS="-Xms1024m -Xmx12g"
+ENV NODE_HOME="${HOME}/.nvm/versions/node/v16.20.0"
+ENV NODE_OPTIONS="--max_old_space_size=4096"
+ENV GOPATH="${HOME}/go"
+ENV GOROOT="/opt/golang/go"
+ENV PATH="${PATH}:${GOROOT}/bin:${GOPATH}/bin"
+
+ENV CONTAINER_ENGINE="docker"
+ENV CONTAINER_ENGINE_TLS_OPTIONS=""
+
+WORKDIR /project/directory
+
+USER root
+COPY .ci/ci-image/start-docker.sh .ci/ci-image/wait-for-docker.sh 
.ci/ci-image/wait-for-process.sh .ci/ci-image/entrypoint.sh /usr/local/bin/
+RUN chmod +x /usr/local/bin/start-docker.sh \
+       /usr/local/bin/entrypoint.sh \
+  /usr/local/bin/wait-for-docker.sh \
+  /usr/local/bin/wait-for-process.sh
+USER nonrootuser
+
+RUN echo "source wait-for-docker.sh" >> ~/.bashrc
+
+# Use bash as default instead of dash (BEGIN)
+RUN sudo bash -c "echo \"dash dash/sh boolean false\" | debconf-set-selections"
+RUN sudo bash -c "DEBIAN_FRONTEND=noninteractive dpkg-reconfigure dash"
+# Use bash as default instead of dash (END)
+
+# Disable quarkus usage analytics (BEGIN)
+RUN mkdir ~/.redhat && (echo '{"disabled":true}' > 
~/.redhat/io.quarkus.analytics.localconfig)
+# Disable quarkus usage analytics (END)
+
+ENTRYPOINT ["entrypoint.sh"]
+CMD ["bash"]
diff --git a/.ci/jenkins/Jenkinsfile.103xplus.deploy 
b/.ci/jenkins/Jenkinsfile.103xplus.deploy
new file mode 100644
index 00000000000..a6f0bca54fc
--- /dev/null
+++ b/.ci/jenkins/Jenkinsfile.103xplus.deploy
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// 10.3.x+ deploy pipeline — no kogito-pipelines dependency.
+//   - agent { label 'ubuntu' } + tools block instead of docker { image 
AGENT_DOCKER_BUILDER_IMAGE }
+//   - Version update via script/release/01-update-version.sh (no MavenCommand 
shared library)
+//   - Build via direct mvn invocation (no MavenCommand shared library)
+//   - PR creation via 'gh' CLI instead of githubscm shared-library helpers
+//   - @Library removed entirely
+
+deployProperties = [:]
+prBranchHash     = ''
+
+pipeline {
+    agent {
+        label 'ubuntu'
+    }
+
+    tools {
+        maven 'maven_3.9.16'
+        jdk   'jdk_17_latest'
+    }
+
+    options {
+        timestamps()
+        timeout(time: 300, unit: 'MINUTES')
+    }
+
+    environment {
+        DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}")
+    }
+
+    stages {
+        stage('Initialize') {
+            steps {
+                script {
+                    cleanWs(disableDeferredWipeout: true)
+
+                    prBranchHash = sh(script: 'openssl rand -hex 5', 
returnStdout: true).trim()
+
+                    if (params.DISPLAY_NAME) {
+                        currentBuild.displayName = params.DISPLAY_NAME
+                    }
+
+                    if (isCreatePr()) {
+                        assert getProjectVersion() : 'PROJECT_VERSION is 
required when CREATE_PR is true'
+                    }
+
+                    dir(getRepoName()) {
+                        checkoutRepo()
+                    }
+                }
+            }
+            post {
+                success {
+                    script {
+                        setDeployPropertyIfNeeded('git.branch', 
getBuildBranch())
+                        setDeployPropertyIfNeeded('git.author', getGitAuthor())
+                        setDeployPropertyIfNeeded('project.version', 
getProjectVersion())
+                    }
+                }
+            }
+        }
+
+        stage('Prepare for PR') {
+            when {
+                expression { return isCreatePr() }
+            }
+            steps {
+                script {
+                    dir(getRepoName()) {
+                        withCredentials([usernamePassword(credentialsId: 
getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 
'GITHUB_TOKEN')]) {
+                            sh """#!/usr/bin/env bash
+                            set -euo pipefail
+                            git config user.email "${env.GIT_AUTHOR_EMAIL ?: 
'[email protected]'}"
+                            git config user.name  "${env.GIT_AUTHOR ?: 'Apache 
KIE Release Bot'}"
+                            git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+                            if git ls-remote --exit-code origin 
'${getPRBranch()}'; then
+                                git push origin --delete '${getPRBranch()}' || 
true
+                            fi
+                            git checkout -b '${getPRBranch()}'
+                            """
+                        }
+                    }
+                }
+            }
+        }
+
+        stage('Update project version') {
+            when {
+                expression { return getProjectVersion() }
+            }
+            steps {
+                script {
+                    dir(getRepoName()) {
+                        configFileProvider([configFile(fileId: 
env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) {
+                            sh """#!/usr/bin/env bash
+                            set -euo pipefail
+                            chmod +x script/release/01-update-version.sh
+                            MAVEN_SETTINGS="\${MAVEN_SETTINGS_FILE}" \
+                                ./script/release/01-update-version.sh 
'${getProjectVersion()}'
+                            """
+                        }
+                    }
+                }
+            }
+        }
+
+        stage('Build & Deploy repo') {
+            steps {
+                script {
+                    dir(getRepoName()) {
+                        configFileProvider([configFile(fileId: 
env.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) {
+                            withCredentials([usernamePassword(credentialsId: 
env.MAVEN_REPO_CREDS_ID, usernameVariable: 'REPOSITORY_USER', passwordVariable: 
'REPOSITORY_TOKEN')]) {
+                                def goal
+                                if (shouldDeployToRepository()) {
+                                    goal = "deploy -DdeployAtEnd 
-Dapache.repository.username=\${REPOSITORY_USER} 
-Dapache.repository.password=\${REPOSITORY_TOKEN} 
-DretryFailedDeploymentCount=5"
+                                } else {
+                                    goal = 'install'
+                                }
+
+                                def extraOpts = [
+                                    env.BUILD_MVN_OPTS,
+                                    env.DROOLS_BUILD_MVN_OPTS,
+                                    env.BUILD_MVN_OPTS_CURRENT
+                                ].findAll { it }.join(' ')
+
+                                def skipTestsFlag = params.SKIP_TESTS ? 
'-DskipTests=true' : '-DskipTests=false'
+
+                                sh """#!/usr/bin/env bash
+                                set -euo pipefail
+                                mvn -s "\${MAVEN_SETTINGS_FILE}" -B -ntp -fae \
+                                    -Dfull \
+                                    ${skipTestsFlag} \
+                                    -Dmaven.test.failure.ignore=true \
+                                    ${extraOpts} \
+                                    clean ${goal}
+                                """
+                            }
+                        }
+                    }
+                }
+            }
+            post {
+                always {
+                    script {
+                        dir(getRepoName()) {
+                            saveReports()
+                            archiveArtifacts(artifacts: 'build_logs/**', 
allowEmptyArchive: true)
+                        }
+                    }
+                }
+            }
+        }
+
+        stage('Create PR') {
+            when {
+                expression { return isCreatePr() }
+            }
+            steps {
+                script {
+                    dir(getRepoName()) {
+                        withCredentials([usernamePassword(credentialsId: 
getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 
'GITHUB_TOKEN')]) {
+                            def prLink = sh(script: """#!/usr/bin/env bash
+                            set -euo pipefail
+                            git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+
+                            # Stage only pom.xml and antora.yml changes 
(version bump files)
+                            git add -A -- '*.xml' '*/antora.yml' 2>/dev/null 
|| true
+                            if git diff --cached --quiet; then
+                                echo '[WARN] no changes to commit' >&2
+                                echo ''
+                                exit 0
+                            fi
+
+                            COMMIT_MSG="[${getBuildBranch()}] Update version 
to ${getProjectVersion()}"
+                            git commit -m "\${COMMIT_MSG}"
+                            git push origin '${getPRBranch()}'
+
+                            PR_BODY="Generated by build ${BUILD_TAG}: 
${BUILD_URL}.\\nPlease do not merge, it should be merged automatically."
+                            gh pr create \
+                                --repo '${getGitAuthor()}/${getRepoName()}' \
+                                --head '${getPRBranch()}' \
+                                --base '${getBuildBranch()}' \
+                                --title "\${COMMIT_MSG}" \
+                                --body "\${PR_BODY}" \
+                                --label 'skip-ci'
+                            """, returnStdout: true).trim()
+
+                            if (prLink) {
+                                deployProperties["${getRepoName()}.pr.link"] = 
prLink
+                            }
+                        }
+                    }
+                }
+            }
+            post {
+                success {
+                    script {
+                        
setDeployPropertyIfNeeded("${getRepoName()}.pr.source.uri", 
"https://github.com/${getGitAuthor()}/${getRepoName()}")
+                        
setDeployPropertyIfNeeded("${getRepoName()}.pr.source.ref", getPRBranch())
+                        
setDeployPropertyIfNeeded("${getRepoName()}.pr.target.uri", 
"https://github.com/${getGitAuthor()}/${getRepoName()}")
+                        
setDeployPropertyIfNeeded("${getRepoName()}.pr.target.ref", getBuildBranch())
+                    }
+                }
+            }
+        }
+    }
+    post {
+        always {
+            script {
+                setDeployPropertyIfNeeded("${getRepoName()}.deploy.build.run", 
true)
+                def propertiesStr = deployProperties.collect { entry -> 
"${entry.key}=${entry.value}" }.join('\n')
+                writeFile(text: propertiesStr, file: PROPERTIES_FILE_NAME)
+                archiveArtifacts(artifacts: PROPERTIES_FILE_NAME)
+            }
+        }
+        unsuccessful {
+            sendNotification()
+        }
+        cleanup {
+            cleanWs()
+        }
+    }
+}
+
+void saveReports() {
+    junit testResults: '**/target/surefire-reports/**/*.xml, 
**/target/failsafe-reports/**/*.xml, **/target/invoker-reports/**/TEST-*.xml', 
allowEmptyResults: true
+}
+
+void checkoutRepo() {
+    deleteDir()
+    checkout([$class: 'GitSCM',
+        branches: [[name: "*/${getBuildBranch()}"]],
+        userRemoteConfigs: [[
+            url: "https://github.com/${getGitAuthor()}/${getRepoName()}.git",
+            credentialsId: getGitAuthorCredsId()
+        ]]
+    ])
+    sh "git checkout ${getBuildBranch()}"
+}
+
+void sendNotification() {
+    if (params.SEND_NOTIFICATION) {
+        mail(
+            to: env.DROOLS_CI_EMAIL_TO,
+            subject: "[FAILURE] Deploy [${getBuildBranch()}] Drools - Build 
${BUILD_TAG}",
+            body: "Deploy pipeline failed.\n\nBuild URL: ${BUILD_URL}"
+        )
+    } else {
+        echo 'No notification sent per configuration'
+    }
+}
+
+boolean shouldDeployToRepository() {
+    return env.MAVEN_DEPLOY_REPOSITORY || getGitAuthor() == 'apache'
+}
+
+boolean isCreatePr() {
+    return params.CREATE_PR
+}
+
+boolean isStreamDevVersion() {
+    return getProjectVersion()?.contains('-SNAPSHOT') ?: false
+}
+
+String getRepoName() {
+    return env.REPO_NAME
+}
+
+String getGitAuthor() {
+    return "${GIT_AUTHOR}"
+}
+
+String getBuildBranch() {
+    return params.BUILD_BRANCH_NAME
+}
+
+String getProjectVersion() {
+    return params.PROJECT_VERSION
+}
+
+String getPRBranch() {
+    return params.DROOLS_PR_BRANCH ?: 
"drools-${getProjectVersion()}_${prBranchHash}"
+}
+
+String getGitAuthorCredsId() {
+    return env.GIT_AUTHOR_CREDS_ID
+}
+
+String getGitAuthorPushCredsId() {
+    return env.GIT_AUTHOR_PUSH_CREDS_ID ?: env.GIT_AUTHOR_CREDS_ID
+}
+
+void setDeployPropertyIfNeeded(String key, def value) {
+    if (value) {
+        deployProperties[key] = value
+    }
+}
diff --git a/.ci/jenkins/Jenkinsfile.103xplus.promote 
b/.ci/jenkins/Jenkinsfile.103xplus.promote
new file mode 100644
index 00000000000..33cdfc578bf
--- /dev/null
+++ b/.ci/jenkins/Jenkinsfile.103xplus.promote
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// 10.3.x+ promote pipeline — no kogito-pipelines dependency.
+//   - agent { label 'ubuntu' } + tools block instead of docker { image 
AGENT_DOCKER_BUILDER_IMAGE }
+//   - 'Tag final release' stage delegates to 05-tag-release.sh (no shared 
library)
+//   - 'Create release' stage uses 'gh' CLI instead of githubscm 
shared-library helpers
+//   - 'Upload drools binaries and documentation' stage uses 03-build.sh 
directly
+//   - @Library removed entirely
+
+deployProperties = [:]
+
+pipeline {
+    agent {
+        label 'ubuntu'
+    }
+
+    tools {
+        maven 'maven_3.9.16'
+        jdk   'jdk_17_latest'
+    }
+
+    options {
+        timestamps()
+        timeout(time: 180, unit: 'MINUTES')
+    }
+
+    environment {
+        DROOLS_CI_EMAIL_TO = credentials("${JENKINS_EMAIL_CREDS_ID}")
+    }
+
+    stages {
+        stage('Initialization') {
+            steps {
+                script {
+                    cleanWs()
+
+                    if (params.DISPLAY_NAME != '') {
+                        currentBuild.displayName = params.DISPLAY_NAME
+                    }
+
+                    readDeployProperties()
+
+                    assert getProjectVersion() : 'PROJECT_VERSION is required'
+                    // Build branch must match the release version stream 
(e.g. 10.3.0 -> 10.3.x)
+                    def expectedBranch = 
getProjectVersion().replaceFirst(/(\d+\.\d+)\.\d+.*/, '$1.x')
+                    assert getBuildBranch() == expectedBranch : \
+                        "BUILD_BRANCH_NAME '${getBuildBranch()}' does not 
match version '${getProjectVersion()}' (expected '${expectedBranch}')"
+                    // GIT_TAG must be the approved RC tag; the final tag is 
derived from it.
+                    assert getRcTag() ==~ /.+-rc[0-9]+/ : \
+                        "GIT_TAG must be the approved RC tag (e.g. 
10.3.0-rc2), got '${getRcTag()}'"
+                }
+            }
+        }
+
+        stage('Merge deploy PR') {
+            // NOTE: tagLatest() removed here intentionally.
+            // In the 103xplus flow the final release tag is created by the
+            // separate "Tag final release" stage below, which delegates to
+            // 05-tag-release.sh so the tag always points to the approved
+            // RC commit — NOT the current branch tip.  Calling tagLatest()
+            // here would create a duplicate tag at the wrong commit.
+            steps {
+                script {
+                    withCredentials([usernamePassword(credentialsId: 
getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 
'GITHUB_TOKEN')]) {
+                        def prLink = getDeployPrLink()
+                        if (prLink?.trim()) {
+                            sh """#!/usr/bin/env bash
+                            set -euo pipefail
+                            git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+                            gh pr review  '${prLink}' --approve
+                            gh pr merge   '${prLink}' --merge --auto
+                            """
+                        }
+                        dir(getRepoName()) {
+                            checkoutRepo()
+                        }
+                    }
+                }
+            }
+        }
+
+        stage('Tag final release') {
+            // Delegate final-tag creation to 05-tag-release.sh so the release 
tag
+            // always points to the approved RC commit — NOT the current 
branch tip.
+            // This matches the documented strategy: the R commit lives under 
the RC
+            // tag only; the development branch is never disturbed.
+            // Runs before 'Create release' so the GitHub release attaches to 
this
+            // tag instead of creating one at the branch tip.
+            steps {
+                withCredentials([
+                    usernamePassword(credentialsId: 
env.GIT_AUTHOR_PUSH_CREDS_ID, usernameVariable: 'GITHUB_USER', 
passwordVariable: 'GITHUB_TOKEN')
+                ]) {
+                    script {
+                        dir(getRepoName()) {
+                            sh """#!/usr/bin/env bash
+                            set -euo pipefail
+                            git config user.email "${env.GIT_AUTHOR_EMAIL ?: 
'[email protected]'}"
+                            git config user.name  "${env.GIT_AUTHOR ?: 'Apache 
KIE Release Bot'}"
+                            git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+                            # Fetch the RC tag so the script can resolve it.
+                            git fetch origin 
"refs/tags/${getRcTag()}:refs/tags/${getRcTag()}"
+                            chmod +x script/release/05-tag-release.sh
+                            ./script/release/05-tag-release.sh --rc-tag 
"${getRcTag()}" --push
+                            """
+                        }
+                    }
+                }
+            }
+        }
+
+        stage('Create release') {
+            steps {
+                script {
+                    withCredentials([usernamePassword(credentialsId: 
getGitAuthorPushCredsId(), usernameVariable: 'GITHUB_USER', passwordVariable: 
'GITHUB_TOKEN')]) {
+                        sh """#!/usr/bin/env bash
+                        set -euo pipefail
+                        git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+
+                        # Delete any pre-existing release so we can re-create 
it cleanly.
+                        if gh release view '${getGitTag()}' --repo 
'${getGitAuthor()}/${getRepoName()}' &>/dev/null; then
+                            gh release delete '${getGitTag()}' \
+                                --repo '${getGitAuthor()}/${getRepoName()}' \
+                                --yes
+                        fi
+
+                        # Derive the previous tag for release-note generation.
+                        PREV_TAG="\$(git tag --sort=-version:refname | grep -v 
'^${getGitTag()}\$' | head -1 || true)"
+
+                        gh release create '${getGitTag()}' \
+                            --repo '${getGitAuthor()}/${getRepoName()}' \
+                            --target '${getBuildBranch()}' \
+                            --title   '${getGitTag()}' \
+                            --generate-notes \
+                            \${PREV_TAG:+--notes-start-tag "\${PREV_TAG}"}
+                        """
+                    }
+                }
+            }
+        }
+
+        stage('Upload drools binaries and documentation') {
+            when {
+                expression { return isMainStream() }
+            }
+            steps {
+                script {
+                    dir(getRepoName()) {
+                        sh """#!/usr/bin/env bash
+                        set -euo pipefail
+                        chmod +x script/release/03-build.sh
+                        ./script/release/03-build.sh --skip-tests
+                        """
+
+                        if (isNotTestingBuild()) {
+                            sshagent(['drools-filemgmt']) {
+                                sh """#!/usr/bin/env bash
+                                set -euo pipefail
+                                chmod +x script/release/upload_filemgmt.sh
+                                ./script/release/upload_filemgmt.sh 
'${getProjectVersion()}'
+                                """
+                            }
+                        } else {
+                            echo 'No uploadFileMgmt due to testing build'
+                        }
+                    }
+                }
+            }
+        }
+    }
+    post {
+        unsuccessful {
+            sendNotification()
+        }
+        cleanup {
+            cleanWs()
+        }
+    }
+}
+
+void sendNotification() {
+    if (params.SEND_NOTIFICATION) {
+        mail(
+            to: env.DROOLS_CI_EMAIL_TO,
+            subject: "[FAILURE] Promote [${getBuildBranch()}] Drools - Build 
${BUILD_TAG}",
+            body: "Promote pipeline failed.\n\nBuild URL: ${BUILD_URL}"
+        )
+    } else {
+        echo 'No notification sent per configuration'
+    }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Deployment properties
+//////////////////////////////////////////////////////////////////////////////
+
+void readDeployProperties() {
+    String deployUrl = params.DEPLOY_BUILD_URL
+    if (deployUrl != '') {
+        if (!deployUrl.endsWith('/')) {
+            deployUrl += '/'
+        }
+        sh "wget ${deployUrl}artifact/${PROPERTIES_FILE_NAME} -O 
${PROPERTIES_FILE_NAME}"
+        deployProperties = readProperties file: PROPERTIES_FILE_NAME
+        // echo all properties
+        echo deployProperties.collect { entry -> "${entry.key}=${entry.value}" 
}.join('\n')
+    }
+}
+
+boolean hasDeployProperty(String key) {
+    return deployProperties[key] != null
+}
+
+String getDeployProperty(String key) {
+    if (hasDeployProperty(key)) {
+        return deployProperties[key]
+    }
+    return ''
+}
+
+String getParamOrDeployProperty(String paramKey, String deployPropertyKey) {
+    if (params[paramKey] != '') {
+        return params[paramKey]
+    }
+    return getDeployProperty(deployPropertyKey)
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Getter / Setter
+//////////////////////////////////////////////////////////////////////////////
+
+String getRepoName() {
+    return env.REPO_NAME
+}
+
+String getProjectVersion() {
+    return getParamOrDeployProperty('PROJECT_VERSION', 'project.version')
+}
+
+// Approved RC tag to promote (e.g. 10.3.0-rc2). Mandatory, validated in 
Initialization.
+String getRcTag() {
+    return params.GIT_TAG ?: ''
+}
+
+// Final release tag, derived from the RC tag (e.g. 10.3.0-rc2 -> 10.3.0).
+String getGitTag() {
+    return getRcTag().replaceFirst(/-rc[0-9]+$/, '')
+}
+
+String getBuildBranch() {
+    return params.BUILD_BRANCH_NAME
+}
+
+String getGitAuthor() {
+    return env.GIT_AUTHOR
+}
+
+String getGitAuthorCredsId() {
+    return env.GIT_AUTHOR_CREDS_ID
+}
+
+String getGitAuthorPushCredsId() {
+    return env.GIT_AUTHOR_PUSH_CREDS_ID
+}
+
+String getDeployPrLink() {
+    return getDeployProperty("${getRepoName()}.pr.link")
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Git
+//////////////////////////////////////////////////////////////////////////////
+
+void checkoutRepo() {
+    deleteDir()
+    checkout([$class: 'GitSCM',
+        branches: [[name: "*/${getBuildBranch()}"]],
+        userRemoteConfigs: [[
+            url: "https://github.com/${getGitAuthor()}/${getRepoName()}.git",
+            credentialsId: getGitAuthorCredsId()
+        ]]
+    ])
+    sh "git checkout ${getBuildBranch()}"
+}
+
+boolean isNotTestingBuild() {
+    return getGitAuthor() == 'apache'
+}
+
+boolean isMainStream() {
+    return env.DROOLS_STREAM == 'main'
+}
\ No newline at end of file
diff --git a/.ci/jenkins/Jenkinsfile.ci-image-build 
b/.ci/jenkins/Jenkinsfile.ci-image-build
new file mode 100644
index 00000000000..630695ce825
--- /dev/null
+++ b/.ci/jenkins/Jenkinsfile.ci-image-build
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Builds and publishes the CI builder image from 
.ci/incubator-kie-ci-build.Dockerfile.
+// One tag per stream (e.g. main-latest, 10.3.x-latest, 10.4.x-latest): set 
IMAGE_TAG to the stream name.
+// The 10.3.x+ deploy/promote pipelines no longer use this image — they run on
+// agent { label 'ubuntu' } with tools { maven 'maven_3.9.16' } directly.
+// This pipeline is retained for future streams that may still use the Docker 
agent path.
+
+pipeline {
+    agent {
+        label 'ubuntu'
+    }
+
+    parameters {
+        string(name: 'IMAGE_TAG', defaultValue: "${env.BRANCH_NAME}", 
description: 'Image tag, one per stream, e.g. 10.3.x-latest', trim: true)
+    }
+
+    environment {
+        DOCKER_CONFIG = "${WORKSPACE}/.docker"
+        IMAGE_REGISTRY = 'docker.io'
+        IMAGE_ACCOUNT = 'apache'
+        IMAGE_NAME = 'incubator-kie-kogito-ci-build'
+        DOCKERHUB_USER_CREDS_ID = 'DOCKERHUB_USER'
+        DOCKERHUB_TOKEN_CREDS_ID = 'DOCKERHUB_TOKEN'
+    }
+
+    options {
+        timestamps()
+        timeout(time: 120, unit: 'MINUTES')
+    }
+
+    stages {
+        stage('Initialize') {
+            steps {
+                script {
+                    assert params.IMAGE_TAG : 'IMAGE_TAG parameter is 
mandatory'
+                    env.FULL_IMAGE = 
"${env.IMAGE_REGISTRY}/${env.IMAGE_ACCOUNT}/${env.IMAGE_NAME}:${params.IMAGE_TAG}"
+                    currentBuild.displayName = params.IMAGE_TAG
+                }
+                cleanWs(deleteDirs: true, disableDeferredWipeout: true)
+            }
+        }
+
+        stage('Checkout') {
+            steps {
+                dir('kie') {
+                    checkout scm
+                }
+            }
+        }
+
+        stage('Build image') {
+            steps {
+                dir('kie') {
+                    sh "docker build -t ${env.FULL_IMAGE} -f 
.ci/incubator-kie-ci-build.Dockerfile ."
+                }
+            }
+        }
+
+        stage('Test built image') {
+            steps {
+                // Test before pushing so a broken image is never published.
+                // --privileged: the image entrypoint starts the 
Docker-in-Docker daemon.
+                sh """#!/bin/bash -el
+                    docker run --rm --privileged --group-add docker 
${env.FULL_IMAGE} bash -lc 'wait-for-docker.sh && java -version && mvn 
--version && git --version && gh --version && docker info > /dev/null && echo 
DOCKER_OK'
+                """.trim()
+            }
+        }
+
+        stage('Push image to registry') {
+            steps {
+                withCredentials([
+                    string(credentialsId: env.DOCKERHUB_USER_CREDS_ID, 
variable: 'REGISTRY_USER'),
+                    string(credentialsId: env.DOCKERHUB_TOKEN_CREDS_ID, 
variable: 'REGISTRY_TOKEN')
+                ]) {
+                    sh '''#!/bin/bash -e
+                        echo "${REGISTRY_TOKEN}" | docker login -u 
"${REGISTRY_USER}" --password-stdin "${IMAGE_REGISTRY}"
+                        docker push "${FULL_IMAGE}"
+                        docker logout "${IMAGE_REGISTRY}"
+                    '''
+                }
+            }
+        }
+    }
+
+    post {
+        always {
+            cleanWs(deleteDirs: true)
+        }
+    }
+}
diff --git a/.ci/jenkins/project/Jenkinsfile.103xplus.release 
b/.ci/jenkins/project/Jenkinsfile.103xplus.release
new file mode 100644
index 00000000000..aed2930e984
--- /dev/null
+++ b/.ci/jenkins/project/Jenkinsfile.103xplus.release
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// 10.3.x+ release pipeline — fully independent, no kogito-pipelines / 
jobs.groovy dependency.
+//   - No @Library, no DSL-injected env vars, no drools-deploy / 
drools-promote downstream jobs
+//   - Create a plain "Pipeline script from SCM" job pointing at this file and 
run it
+//     with a few parameters. Everything it needs is a parameter (with a 
default).
+//   - Runs script/release/release-all.sh (R commit + RC tag -> build -> Nexus 
staging)
+//
+// MANDATORY parameters : RELEASE_VERSION, TAG_NAME
+// Real RC run          : DEPLOY=true, PUSH_TAG=true, DRY_RUN=false
+// Branch cut (once per minor, before rc1 only):
+//                        CREATE_STREAM_BRANCH=true -> creates X.Y.x from 
STREAM_BASE_BRANCH,
+//                        sets X.Y.999-SNAPSHOT, pushes it, then continues the 
RC on X.Y.x.
+//                        Leave it false for rc2, final and patch releases 
(branch already exists).
+
+pipeline {
+    agent {
+        label 'ubuntu'
+    }
+
+    tools {
+        maven 'maven_3.9.16'
+        jdk   'jdk_17_latest'
+    }
+
+    options {
+        timestamps()
+        timeout(time: 360, unit: 'MINUTES')
+    }
+
+    parameters {
+        string(name: 'RELEASE_VERSION', defaultValue: '', description: 
'MANDATORY. Exact release version, e.g. 10.3.0', trim: true)
+        string(name: 'TAG_NAME', defaultValue: '', description: 'MANDATORY. RC 
tag name, e.g. 10.3.0-rc1 (must start with RELEASE_VERSION and be a new tag)', 
trim: true)
+        booleanParam(name: 'CREATE_STREAM_BRANCH', defaultValue: false, 
description: 'Branch cut: create X.Y.x (derived from RELEASE_VERSION) from 
STREAM_BASE_BRANCH, set X.Y.999-SNAPSHOT and push it BEFORE building the RC. 
Only for the first RC of a new minor (e.g. 10.3.0-rc1). Fails if X.Y.x already 
exists')
+        string(name: 'STREAM_BASE_BRANCH', defaultValue: 'main', description: 
'Used only when CREATE_STREAM_BRANCH=true. Branch the stream branch is cut 
from', trim: true)
+        booleanParam(name: 'SKIP_TESTS', defaultValue: true, description: 
'Skip running tests during reactor build')
+        booleanParam(name: 'DEPLOY', defaultValue: true, description: 'Deploy 
artifacts to Apache Nexus staging')
+        string(name: 'STAGING_URL', defaultValue: '', description: 'Custom 
Nexus staging URL (optional)')
+        booleanParam(name: 'PUSH_TAG', defaultValue: true, description: 'Push 
the git tag to origin')
+        string(name: 'EXTRA_MAVEN_OPTS', defaultValue: '', description: 'Extra 
Maven CLI options to pass to build.sh')
+        booleanParam(name: 'DRY_RUN', defaultValue: false, description: 'Dry 
run without making remote git or staging changes')
+        booleanParam(name: 'SEND_NOTIFICATION', defaultValue: false, 
description: 'Send an e-mail when the pipeline finishes (requires 
NOTIFICATION_EMAIL)')
+        string(name: 'NOTIFICATION_EMAIL', defaultValue: '', description: 
'Optional. Recipient for SEND_NOTIFICATION', trim: true)
+
+        // Jenkins IDs — defaults match the existing KIE Jenkins; override 
only if they differ.
+        string(name: 'MAVEN_SETTINGS_CONFIG_FILE_ID', defaultValue: 
'kie-release-authn-settings', description: 'Config File Provider ID of the 
Maven settings.xml (must define server "apache.releases.https")', trim: true)
+        string(name: 'GPG_KEY_CREDS_ID', defaultValue: 'GPG_KEY_FILE', 
description: 'Secret FILE credential ID holding the armored GPG private key', 
trim: true)
+        string(name: 'GPG_PASSPHRASE_CREDS_ID', defaultValue: '', description: 
'Optional. Secret TEXT credential ID holding the GPG key passphrase. Leave 
empty if the key has no passphrase (KIE release key has none)', trim: true)
+        string(name: 'GIT_PUSH_CREDS_ID', defaultValue: 
'84811880-2025-45b6-a44c-2f33bef30ad2', description: 'Username/password 
credential ID used to push the RC tag to GitHub', trim: true)
+        string(name: 'GIT_AUTHOR_NAME', defaultValue: 'Apache KIE Release 
Bot', description: 'git user.name for the R commit', trim: true)
+        string(name: 'GIT_AUTHOR_EMAIL', defaultValue: '[email protected]', 
description: 'git user.email for the R commit', trim: true)
+    }
+
+    stages {
+        stage('Initialize & Validate') {
+            steps {
+                script {
+                    if (!params.RELEASE_VERSION?.trim()) {
+                        error 'RELEASE_VERSION parameter is mandatory (e.g. 
10.3.0)'
+                    }
+                    if (!params.TAG_NAME?.trim()) {
+                        error 'TAG_NAME parameter is mandatory (e.g. 
10.3.0-rc1)'
+                    }
+                    if (!params.TAG_NAME.startsWith(params.RELEASE_VERSION)) {
+                        error "TAG_NAME '${params.TAG_NAME}' must start with 
RELEASE_VERSION '${params.RELEASE_VERSION}'"
+                    }
+                    currentBuild.displayName = "Release 
${params.RELEASE_VERSION} (${params.TAG_NAME})"
+                }
+            }
+        }
+
+        stage('Create Stream Branch') {
+            when {
+                expression { return params.CREATE_STREAM_BRANCH }
+            }
+            steps {
+                script {
+                    withCredentials([usernamePassword(credentialsId: 
params.GIT_PUSH_CREDS_ID, usernameVariable: 'GITHUB_USER', passwordVariable: 
'GITHUB_TOKEN')]) {
+                        def branchArgs = [
+                            './script/release/00-create-stream-branch.sh',
+                            '--version', params.RELEASE_VERSION,
+                            '--base',    params.STREAM_BASE_BRANCH,
+                            '--push'
+                        ]
+                        if (params.DRY_RUN) branchArgs << '--dry-run'
+
+                        sh """#!/usr/bin/env bash
+                        set -euo pipefail
+                        git config user.email "${params.GIT_AUTHOR_EMAIL}"
+                        git config user.name  "${params.GIT_AUTHOR_NAME}"
+                        git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+                        chmod +x script/release/*.sh
+                        ${branchArgs.collect { "'${it.replace("'", "'\\''")}'" 
}.join(' ')}
+                        """
+                    }
+                }
+            }
+        }
+
+        stage('Execute Release Scripts') {
+            steps {
+                // The Maven settings id is a Config File Provider id (not a 
secret) and is
+                // exposed as MAVEN_SETTINGS_FILE. GPG key/passphrase and the 
GitHub push
+                // credential are real credentials and use withCredentials.
+                script {
+                    def creds = [
+                        file(credentialsId: params.GPG_KEY_CREDS_ID, variable: 
'GPG_KEY_FILE_PATH'),
+                        usernamePassword(credentialsId: 
params.GIT_PUSH_CREDS_ID, usernameVariable: 'GITHUB_USER', passwordVariable: 
'GITHUB_TOKEN')
+                    ]
+                    // Passphrase is optional: the KIE release signing key has 
none.
+                    if (params.GPG_PASSPHRASE_CREDS_ID?.trim()) {
+                        creds << string(credentialsId: 
params.GPG_PASSPHRASE_CREDS_ID, variable: 'MAVEN_GPG_PASSPHRASE')
+                    }
+                    configFileProvider([configFile(fileId: 
params.MAVEN_SETTINGS_CONFIG_FILE_ID, variable: 'MAVEN_SETTINGS_FILE')]) {
+                        withCredentials(creds) {
+                            // Import the signing key so the apache-release 
profile can sign
+                            // artifacts. Passphrase is supplied later via 
MAVEN_GPG_PASSPHRASE.
+                            sh '''#!/usr/bin/env bash
+                            set -euo pipefail
+                            gpg --batch --pinentry-mode=loopback --import 
"$GPG_KEY_FILE_PATH"
+                            '''
+
+                            // Configure Git identity for the R commit and 
authenticate the
+                            // raw `git push` in 02-rc-commit.sh. The 
credential helper reads
+                            // the token from the environment at push time, 
never from disk.
+                            sh """#!/usr/bin/env bash
+                            set -euo pipefail
+                            git config user.email "${params.GIT_AUTHOR_EMAIL}"
+                            git config user.name  "${params.GIT_AUTHOR_NAME}"
+                            git config --local credential.helper '!f() { echo 
username=\$GITHUB_USER; echo password=\$GITHUB_TOKEN; }; f'
+                            """
+
+                            // Build the argv array safely — each parameter is 
a
+                            // separate shell word, no string concatenation or 
eval.
+                            def releaseArgs = [
+                                './script/release/release-all.sh',
+                                '--version', params.RELEASE_VERSION,
+                                '--tag',     params.TAG_NAME
+                            ]
+                            if (params.SKIP_TESTS)             releaseArgs << 
'--skip-tests'
+                            if (params.DEPLOY)                 releaseArgs << 
'--deploy'
+                            if (params.STAGING_URL?.trim())    releaseArgs += 
['--staging-url', params.STAGING_URL.trim()]
+                            if (params.PUSH_TAG)               releaseArgs << 
'--push-tag'
+                            if (params.EXTRA_MAVEN_OPTS?.trim()) releaseArgs 
+= ['--maven-opts', params.EXTRA_MAVEN_OPTS.trim()]
+                            if (params.DRY_RUN)                releaseArgs << 
'--dry-run'
+
+                            // MAVEN_SETTINGS_FILE is injected by 
configFileProvider above.
+                            // If a passphrase credential was given, 
MAVEN_GPG_PASSPHRASE is in
+                            // env and read by the GPG plugin directly (not a 
CLI arg).
+                            withEnv(["MAVEN_SETTINGS=${MAVEN_SETTINGS_FILE}"]) 
{
+                                sh """#!/usr/bin/env bash
+                                set -euo pipefail
+                                chmod +x script/release/*.sh
+                                ${releaseArgs.collect { "'${it.replace("'", 
"'\\''")}'" }.join(' ')}
+                                """
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    post {
+        always {
+            cleanWs()
+        }
+        success {
+            script {
+                sendNotification()
+            }
+        }
+        unsuccessful {
+            sendErrorNotification()
+        }
+    }
+}
+
+void sendNotification() {
+    if (params.SEND_NOTIFICATION && params.NOTIFICATION_EMAIL) {
+        mail(
+            to: params.NOTIFICATION_EMAIL,
+            subject: "[SUCCESS] Release ${params.RELEASE_VERSION} 
(${params.TAG_NAME}) Drools - Build ${BUILD_TAG}",
+            body: "Release pipeline succeeded.\n\nBuild URL: ${BUILD_URL}"
+        )
+    } else {
+        echo 'No notification sent per configuration'
+    }
+}
+
+void sendErrorNotification() {
+    if (params.SEND_NOTIFICATION && params.NOTIFICATION_EMAIL) {
+        mail(
+            to: params.NOTIFICATION_EMAIL,
+            subject: "[FAILURE] Release ${params.RELEASE_VERSION} 
(${params.TAG_NAME}) Drools - Build ${BUILD_TAG}",
+            body: "Release pipeline failed.\n\nBuild URL: ${BUILD_URL}"
+        )
+    } else {
+        echo 'No notification sent per configuration'
+    }
+}
diff --git a/script/release/00-create-stream-branch.sh 
b/script/release/00-create-stream-branch.sh
new file mode 100755
index 00000000000..c9f1ca63877
--- /dev/null
+++ b/script/release/00-create-stream-branch.sh
@@ -0,0 +1,172 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Branch cut (Automation A): creates the X.Y.x development stream branch from
+# the base branch (default: main) and moves it to the X.Y.999-SNAPSHOT dev
+# version (the "D commit").  Run ONCE per minor version, BEFORE the first RC.
+#
+#   main  ──── c ──── c ──── c ───────────────────────────>  999-SNAPSHOT
+#                            └── [D commit] ──── c ────>  X.Y.x 
(X.Y.999-SNAPSHOT)
+#
+# Usage:
+#   ./script/release/00-create-stream-branch.sh <version> [--base <branch>] 
[--push] [--dry-run]
+#   ./script/release/00-create-stream-branch.sh --version <version> [--base 
<branch>] [--push] [--dry-run]
+#
+# Examples:
+#   ./script/release/00-create-stream-branch.sh 10.3.0 --push
+#   ./script/release/00-create-stream-branch.sh --version 10.3 --base main 
--dry-run
+#
+# Options:
+#   <version> | --version <ver>   Release version (10.3.0) or minor (10.3).  
Only major.minor is used.
+#   --base <branch>               Branch to cut from (default: main)
+#   --remote <name>               Git remote (default: origin)
+#   --push                        Push the new branch to the remote (default: 
local only)
+#   --dry-run                     Print what would happen without making any 
changes
+#
+# After this script the working tree is left checked out on X.Y.x.
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+VERSION=""
+BASE_BRANCH="main"
+REMOTE="origin"
+PUSH=false
+DRY_RUN=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --version)  VERSION="${2:-}"; shift 2 ;;
+        --base)     BASE_BRANCH="${2:-}"; shift 2 ;;
+        --remote)   REMOTE="${2:-}"; shift 2 ;;
+        --push)     PUSH=true; shift ;;
+        --dry-run)  DRY_RUN=true; shift ;;
+        *)
+            if [[ -z "${VERSION}" && ! "$1" =~ ^- ]]; then
+                VERSION="$1"
+                shift
+            else
+                echo "Unknown option: $1"
+                echo "Usage: $0 [<version> | --version <version>] [--base 
<branch>] [--push] [--dry-run]"
+                exit 1
+            fi
+            ;;
+    esac
+done
+
+if [[ -z "${VERSION}" ]]; then
+    echo "ERROR: Version is required."
+    echo "Usage: $0 10.3.0 [--base main] [--push] [--dry-run]"
+    exit 1
+fi
+
+if [[ ! "${VERSION}" =~ ^([0-9]+)\.([0-9]+)(\.[0-9A-Za-z.-]+)?$ ]]; then
+    echo "ERROR: Cannot parse version '${VERSION}'. Expected e.g. 10.3.0 or 
10.3"
+    exit 1
+fi
+
+MAJOR_MINOR="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}"
+STREAM_BRANCH="${MAJOR_MINOR}.x"
+DEV_VERSION="${MAJOR_MINOR}.999-SNAPSHOT"
+
+cd "${REPO_ROOT}"
+
+echo "========================================"
+echo "Apache KIE repo — create stream branch"
+echo "Stream branch   : ${STREAM_BRANCH}"
+echo "Cut from        : ${REMOTE}/${BASE_BRANCH}"
+echo "Dev version     : ${DEV_VERSION}"
+echo "Push            : ${PUSH}"
+echo "Dry run         : ${DRY_RUN}"
+echo "========================================"
+
+# ── Guards ───────────────────────────────────────────────────────────────────
+if ! git diff --quiet || ! git diff --cached --quiet; then
+    echo "ERROR: There are uncommitted changes in the working tree."
+    exit 1
+fi
+if [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
+    echo "ERROR: There are untracked files in the working tree:"
+    git ls-files --others --exclude-standard | sed 's/^/  /'
+    exit 1
+fi
+
+if git ls-remote --exit-code --heads "${REMOTE}" "${STREAM_BRANCH}" 
&>/dev/null; then
+    echo "ERROR: Branch '${STREAM_BRANCH}' already exists on '${REMOTE}'."
+    echo "       The branch cut is done once per minor version. For rc2 / 
patch"
+    echo "       releases reuse the existing branch and skip this step."
+    exit 1
+fi
+
+if git rev-parse --verify "${STREAM_BRANCH}" &>/dev/null; then
+    echo "ERROR: Local branch '${STREAM_BRANCH}' already exists. Delete it 
first:"
+    echo "  git branch -D ${STREAM_BRANCH}"
+    exit 1
+fi
+
+if [[ "${DRY_RUN}" == "true" ]]; then
+    echo ""
+    echo "[DRY RUN] Would execute:"
+    echo "  git fetch ${REMOTE} 
+refs/heads/${BASE_BRANCH}:refs/remotes/${REMOTE}/${BASE_BRANCH}"
+    echo "  git checkout -b ${STREAM_BRANCH} ${REMOTE}/${BASE_BRANCH}"
+    echo "  ./script/release/01-update-version.sh ${DEV_VERSION}"
+    echo "  git add -A"
+    echo "  git commit -m \"Update version to ${DEV_VERSION}\""
+    if [[ "${PUSH}" == "true" ]]; then
+        echo "  git push ${REMOTE} ${STREAM_BRANCH}"
+    fi
+    exit 0
+fi
+
+# ── Branch cut ───────────────────────────────────────────────────────────────
+echo ""
+echo "--- Creating ${STREAM_BRANCH} from ${REMOTE}/${BASE_BRANCH} ---"
+git fetch "${REMOTE}" 
"+refs/heads/${BASE_BRANCH}:refs/remotes/${REMOTE}/${BASE_BRANCH}"
+git checkout -b "${STREAM_BRANCH}" "${REMOTE}/${BASE_BRANCH}"
+
+echo ""
+echo "--- Updating version to ${DEV_VERSION} ---"
+"${SCRIPT_DIR}/01-update-version.sh" "${DEV_VERSION}"
+
+echo ""
+echo "--- Committing D commit ---"
+git add -A
+git commit -m "Update version to ${DEV_VERSION}"
+
+if [[ "${PUSH}" == "true" ]]; then
+    echo ""
+    echo "--- Pushing ${STREAM_BRANCH} to ${REMOTE} ---"
+    git push "${REMOTE}" "${STREAM_BRANCH}"
+fi
+
+echo ""
+echo "========================================"
+echo "Stream branch ${STREAM_BRANCH} ready (${DEV_VERSION})."
+echo ""
+if [[ "${PUSH}" != "true" ]]; then
+    echo "Next step (not pushed yet):"
+    echo "  git push ${REMOTE} ${STREAM_BRANCH}"
+    echo ""
+fi
+echo "Next: run the RC release, e.g."
+echo "  ./script/release/release-all.sh --version ${MAJOR_MINOR}.0 --tag 
${MAJOR_MINOR}.0-rc1 --deploy --push-tag"
+echo "========================================"
diff --git a/script/release/01-update-version.sh 
b/script/release/01-update-version.sh
new file mode 100755
index 00000000000..1cb08f58a48
--- /dev/null
+++ b/script/release/01-update-version.sh
@@ -0,0 +1,108 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Updates the Maven version across the Apache KIE repo (drools + optaplanner +
+# kogito-runtimes + kogito-apps are all modules of the same root POM since the
+# 10.3.x consolidation).
+#
+# Usage:
+#   ./script/release/01-update-version.sh <version>
+#
+# Examples:
+#   ./script/release/01-update-version.sh 10.3.999-SNAPSHOT   # dev/stream 
version
+#   ./script/release/01-update-version.sh 10.3.0              # release version
+#
+# The script also updates the data-index ephemeral image tag property that 
lives
+# inside kogito-quarkus (was a separate step in the old multi-repo flow).
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+NEW_VERSION="${1:-}"
+
+if [[ -z "${NEW_VERSION}" ]]; then
+    echo "Usage: $0 <version>"
+    echo "  e.g. $0 10.3.999-SNAPSHOT"
+    echo "  e.g. $0 10.3.0"
+    exit 1
+fi
+
+# Derive the stream name (e.g. 10.3.0 → 10.3.x, 10.3.999-SNAPSHOT → 10.3.x).
+# Used for the data-index image tag property.
+# Special case: 999-SNAPSHOT is the main-stream version; the image tag is 
"main".
+if [[ "${NEW_VERSION}" == "999-SNAPSHOT" ]]; then
+    STREAM_NAME="main"
+else
+    STREAM_NAME="$(echo "${NEW_VERSION}" | sed 
's/^\([0-9]*\.[0-9]*\)\..*/\1.x/')"
+fi
+
+# Detect the current version from the root POM so we can pass -DoldVersion,
+# which skips the full reactor scan and is much faster on a large multi-module
+# repo like this one.
+CURRENT_VERSION="$(mvn -q help:evaluate -Dexpression=project.version 
-DforceStdout -f "${REPO_ROOT}/pom.xml" 2>/dev/null)"
+
+if [[ -z "${CURRENT_VERSION}" ]]; then
+    echo "ERROR: Could not determine current project version from pom.xml."
+    echo "       Make sure Maven is available and the POM is resolvable."
+    exit 1
+fi
+
+echo "========================================"
+echo "Apache KIE repo — version update"
+echo "Current version : ${CURRENT_VERSION}"
+echo "New version     : ${NEW_VERSION}"
+echo "Stream name     : ${STREAM_NAME}"
+echo "========================================"
+
+cd "${REPO_ROOT}"
+
+echo ""
+echo "--- Updating all Maven module versions ---"
+mvn versions:set \
+    -DoldVersion="${CURRENT_VERSION}" \
+    -DnewVersion="${NEW_VERSION}" \
+    -DprocessAllModules \
+    -DgenerateBackupPoms=false
+
+# The data-index ephemeral image tag property is controlled via a dedicated
+# property inside kogito-quarkus-workflow-common-deployment.  Update it to
+# track the stream name so nightly image pulls resolve correctly.
+DATA_INDEX_MODULE=":kogito-quarkus-workflow-common-deployment"
+if mvn -q help:evaluate -Dexpression=project.artifactId -pl 
"${DATA_INDEX_MODULE}" 2>/dev/null; then
+    echo ""
+    echo "--- Updating data-index ephemeral image tag (${STREAM_NAME}) ---"
+    mvn -pl "${DATA_INDEX_MODULE}" versions:set-property \
+        -Dproperty=data-index-ephemeral.image.tagVersion \
+        -DnewVersion="${STREAM_NAME}" \
+        -DgenerateBackupPoms=false
+else
+    echo "(skipping data-index image tag update — module not found)"
+fi
+
+echo ""
+echo "========================================"
+echo "Version update complete: ${NEW_VERSION}"
+echo "========================================"
+echo ""
+echo "Next steps:"
+echo "  git diff          # review changes"
+echo "  git add -A && git commit -m \"chore: update version to 
${NEW_VERSION}\""
diff --git a/script/release/02-rc-commit.sh b/script/release/02-rc-commit.sh
new file mode 100755
index 00000000000..aff6d8cecf1
--- /dev/null
+++ b/script/release/02-rc-commit.sh
@@ -0,0 +1,236 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Creates the "R commit" (release commit) and the corresponding RC git tag.
+#
+# The R commit contains only the version bump to the exact release version.
+# It is made on a short-lived local release branch that is never pushed — only
+# the tag is pushed.  This mirrors the branch strategy from release.txt:
+#
+#   10.2.x branch  ──── c ──── c ──── [D commit] ──── c ────> ...
+#                                         |
+#                                 (local release branch)
+#                                         └── [R commit] ──> tag: 10.3.0-rc1
+#
+# Usage:
+#   ./script/release/02-rc-commit.sh <version> [--tag <tag-name>] [--push] 
[--dry-run]
+#   ./script/release/02-rc-commit.sh --version <release-version> --tag 
<tag-name>
+#
+# Examples:
+#   ./script/release/02-rc-commit.sh 10.3.0 --tag 10.3.0-rc1
+#   ./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc2 --push
+#
+# Options:
+#   <version> | --version <ver>   Exact release version, e.g. 10.3.0
+#   --tag | --rc-tag <tag>        Git tag name, e.g. 10.3.0-rc1 (defaults to 
<version>-rc1)
+#   --rc                          Accepted for parity
+#   --push | --push-tag           Push the tag to origin (default: local only)
+#   --dry-run                     Print what would happen without making any 
changes
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+RELEASE_VERSION=""
+TAG_NAME=""
+PUSH=false
+DRY_RUN=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --version)
+            RELEASE_VERSION="${2:-}"
+            shift 2
+            ;;
+        --tag|--rc-tag)
+            TAG_NAME="${2:-}"
+            shift 2
+            ;;
+        --rc)
+            shift
+            ;;
+        --push|--push-tag)
+            PUSH=true
+            shift
+            ;;
+        --dry-run)
+            DRY_RUN=true
+            shift
+            ;;
+        *)
+            if [[ -z "${RELEASE_VERSION}" && ! "$1" =~ ^- ]]; then
+                RELEASE_VERSION="$1"
+                shift
+            else
+                echo "Unknown option: $1"
+                echo "Usage: $0 [<version> | --version <version>] [--tag 
<tag>] [--push] [--dry-run]"
+                exit 1
+            fi
+            ;;
+    esac
+done
+
+if [[ -z "${RELEASE_VERSION}" ]]; then
+    echo "ERROR: Version is required."
+    echo "Usage: $0 10.3.0 [--tag 10.3.0-rc1] [--push] [--dry-run]"
+    exit 1
+fi
+
+if [[ -z "${TAG_NAME}" ]]; then
+    TAG_NAME="${RELEASE_VERSION}-rc1"
+fi
+
+# Derive the expected development branch name (e.g. 10.3.0 → 10.3.x).
+STREAM_BRANCH="$(echo "${RELEASE_VERSION}" | sed 
's/^\([0-9]*\.[0-9]*\)\..*/\1.x/')"
+RELEASE_BRANCH="release/${TAG_NAME}"
+
+cd "${REPO_ROOT}"
+
+# Capture original ref: branch name when attached, commit SHA when detached
+# (Jenkins SCM checkouts are typically detached; `git rev-parse --abbrev-ref 
HEAD`
+#  returns "HEAD" in that case, which cannot be restored via `git checkout 
HEAD`
+#  because the branch is still pointing there.  We need the actual commit SHA.)
+if git symbolic-ref --quiet HEAD &>/dev/null; then
+    CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
+else
+    CURRENT_BRANCH="$(git rev-parse HEAD)"
+fi
+
+echo "========================================"
+echo "Apache KIE repo — RC commit + tag"
+echo "Release version : ${RELEASE_VERSION}"
+echo "Tag             : ${TAG_NAME}"
+echo "Release branch  : ${RELEASE_BRANCH} (local only)"
+echo "Push tag        : ${PUSH}"
+echo "Dry run         : ${DRY_RUN}"
+echo "Current branch  : ${CURRENT_BRANCH}"
+echo "========================================"
+
+# Warn if we're not on the expected stream branch (not a fatal error — someone
+# may legitimately be on a different commit).
+if [[ "${CURRENT_BRANCH}" != "${STREAM_BRANCH}" ]]; then
+    echo ""
+    echo "WARNING: Current branch '${CURRENT_BRANCH}' is not the expected 
stream"
+    echo "         branch '${STREAM_BRANCH}'. Proceed with caution."
+    echo ""
+fi
+
+# Check there are no uncommitted changes (staged, unstaged, or untracked) 
before
+# we do anything.  Untracked files matter here because git add -A below will
+# include them in the R commit.
+if ! git diff --quiet || ! git diff --cached --quiet; then
+    echo "ERROR: There are uncommitted changes in the working tree."
+    echo "       Commit or stash them before running this script."
+    exit 1
+fi
+if [[ -n "$(git ls-files --others --exclude-standard)" ]]; then
+    echo "ERROR: There are untracked files in the working tree:"
+    git ls-files --others --exclude-standard | sed 's/^/  /'
+    echo "       Remove or .gitignore them before running this script."
+    exit 1
+fi
+
+if [[ "${DRY_RUN}" == "true" ]]; then
+    echo ""
+    echo "[DRY RUN] Would execute:"
+    echo "  git checkout -b ${RELEASE_BRANCH}"
+    echo "  ./script/release/01-update-version.sh ${RELEASE_VERSION}"
+    echo "  git add -A"
+    echo "  git commit -m \"chore: release ${RELEASE_VERSION}\""
+    echo "  git tag -a ${TAG_NAME} -m \"Release ${TAG_NAME}\""
+    if [[ "${PUSH}" == "true" ]]; then
+        echo "  git push origin ${TAG_NAME}"
+    fi
+    echo "  git checkout ${CURRENT_BRANCH}"
+    echo "  git branch -D ${RELEASE_BRANCH}"
+    exit 0
+fi
+
+# ── Guard: stale release branch from a previous interrupted run ──────────────
+if git rev-parse --verify "${RELEASE_BRANCH}" &>/dev/null; then
+    echo ""
+    echo "ERROR: Branch '${RELEASE_BRANCH}' already exists from a previous"
+    echo "       (possibly interrupted) run."
+    echo ""
+    echo "To clean up and start fresh:"
+    echo "  git checkout ${CURRENT_BRANCH}"
+    echo "  git branch -D ${RELEASE_BRANCH}"
+    if git rev-parse --verify "${TAG_NAME}" &>/dev/null; then
+        echo "  git tag -d ${TAG_NAME}"
+    fi
+    exit 1
+fi
+
+# ── Guard: tag already exists 
─────────────────────────────────────────────────
+if git rev-parse --verify "${TAG_NAME}" &>/dev/null; then
+    echo ""
+    echo "ERROR: Tag '${TAG_NAME}' already exists."
+    echo "       Delete it first if you want to recreate it:"
+    echo "  git tag -d ${TAG_NAME}"
+    exit 1
+fi
+
+echo ""
+echo "--- Creating local release branch: ${RELEASE_BRANCH} ---"
+git checkout -b "${RELEASE_BRANCH}"
+
+echo ""
+echo "--- Updating version to ${RELEASE_VERSION} ---"
+"${SCRIPT_DIR}/01-update-version.sh" "${RELEASE_VERSION}"
+
+echo ""
+echo "--- Committing R commit ---"
+git add -A
+git commit -m "chore: release ${RELEASE_VERSION}"
+
+echo ""
+echo "--- Tagging ${TAG_NAME} ---"
+git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}"
+
+if [[ "${PUSH}" == "true" ]]; then
+    echo ""
+    echo "--- Pushing tag ${TAG_NAME} to origin ---"
+    git push origin "${TAG_NAME}"
+fi
+
+echo ""
+echo "--- Returning to ${CURRENT_BRANCH} ---"
+git checkout "${CURRENT_BRANCH}"
+
+echo ""
+echo "--- Deleting local release branch ${RELEASE_BRANCH} ---"
+git branch -D "${RELEASE_BRANCH}"
+
+echo ""
+echo "========================================"
+echo "RC commit + tag complete."
+echo ""
+echo "Tag '${TAG_NAME}' points to the R commit (version ${RELEASE_VERSION})."
+if [[ "${PUSH}" == "false" ]]; then
+    echo ""
+    echo "Tag is LOCAL only. To push when ready:"
+    echo "  git push origin ${TAG_NAME}"
+fi
+echo ""
+echo "Next steps:"
+echo "  ./script/release/03-build.sh --skip-tests"
+echo "  ./script/release/04-deploy-to-staging.sh --tag ${TAG_NAME}"
+echo "========================================"
diff --git a/script/release/03-build.sh b/script/release/03-build.sh
new file mode 100755
index 00000000000..f9507e2ab34
--- /dev/null
+++ b/script/release/03-build.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Builds the Apache KIE repo for a release (skipping tests by default).
+#
+# Because drools, optaplanner, kogito-runtimes, and kogito-apps now all live in
+# the same reactor, a single `mvn clean install` is enough.
+#
+# Usage:
+#   ./script/release/03-build.sh [--skip-tests] [--maven-opts <opts>]
+#
+# Flags:
+#   --skip-tests          Skip all tests (default: tests are run)
+#   --maven-opts <opts>   Extra Maven options appended to the command. Split on
+#                         whitespace only; shell-style quoting is NOT honoured,
+#                         so individual options must not contain spaces.
+#
+# Examples:
+#   ./script/release/03-build.sh --skip-tests
+#   ./script/release/03-build.sh --skip-tests --maven-opts "-T 4"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+SKIP_TESTS=false
+# Extra Maven opts stored as an array and expanded quoted, so no glob 
expansion,
+# eval, or command substitution happens. Options are split on whitespace only.
+EXTRA_MVN_OPTS=()
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --skip-tests)
+            SKIP_TESTS=true
+            shift
+            ;;
+        --maven-opts)
+            # Accept a single-argument "extra opts" string and split it on
+            # whitespace into array elements using read. Quotes are not parsed.
+            IFS=' ' read -r -a _extra <<< "${2:-}"
+            EXTRA_MVN_OPTS+=("${_extra[@]}")
+            shift 2
+            ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 [--skip-tests] [--maven-opts <opts>]"
+            exit 1
+            ;;
+    esac
+done
+
+cd "${REPO_ROOT}"
+
+# ── Assemble Maven flags as a Bash array 
──────────────────────────────────────
+
+MVN_FLAGS=("-Dfull")
+
+if [[ "${SKIP_TESTS}" == "true" ]]; then
+    MVN_FLAGS+=("-DskipTests")
+fi
+
+if [[ "${#EXTRA_MVN_OPTS[@]}" -gt 0 ]]; then
+    MVN_FLAGS+=("${EXTRA_MVN_OPTS[@]}")
+fi
+
+# ── Build 
──────────────────────────────────────────────────────────────────────
+
+echo "========================================"
+echo "Apache KIE repo — build"
+echo "Skip tests        : ${SKIP_TESTS}"
+echo "Maven flags       : ${MVN_FLAGS[*]}"
+echo "========================================"
+echo ""
+
+mvn clean install "${MVN_FLAGS[@]}"
+
+echo ""
+echo "========================================"
+echo "Build complete."
+echo ""
+echo "JARs installed to local Maven repository (~/.m2/repository)."
+echo "========================================"
diff --git a/script/release/04-deploy-to-staging.sh 
b/script/release/04-deploy-to-staging.sh
new file mode 100755
index 00000000000..ea9f4db3563
--- /dev/null
+++ b/script/release/04-deploy-to-staging.sh
@@ -0,0 +1,179 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Deploys the locally-built JARs (already in ~/.m2) to a Nexus staging
+# repository for Apache release voting.
+#
+# By default this script runs in DRY RUN mode and will NOT push anything
+# remotely.  Pass --deploy to actually upload to Nexus.
+#
+# Usage:
+#   ./script/release/04-deploy-to-staging.sh --tag <rc-tag> [--deploy] 
[--staging-url <url>]
+#
+# Examples:
+#   # dry run (safe — prints the Maven command that would be run)
+#   ./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1
+#
+#   # actually deploy to Apache Nexus staging
+#   ./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 --deploy
+#
+#   # deploy to a custom staging URL
+#   ./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 --deploy \
+#       --staging-url 
https://repository.apache.org/service/local/staging/deploy/maven2
+#
+# Environment variables consumed when --deploy is active:
+#   MAVEN_SETTINGS         Path to a settings.xml with Nexus credentials 
(required).
+#                          The settings file must define a server with id 
"apache.releases.https"
+#                          (or the id set via --server-id) carrying the 
deployer credentials.
+#   MAVEN_GPG_PASSPHRASE   GPG passphrase for signing (required for Apache 
releases).
+#                          Passed to the GPG agent via the gpg-agent pinentry 
loopback
+#                          mechanism; NOT exposed on the Maven command line.
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+TAG_NAME=""
+DEPLOY=false
+STAGING_URL="https://repository.apache.org/service/local/staging/deploy/maven2";
+SERVER_ID="apache.releases.https"
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --tag)
+            TAG_NAME="${2:-}"
+            shift 2
+            ;;
+        --deploy)
+            DEPLOY=true
+            shift
+            ;;
+        --staging-url)
+            STAGING_URL="${2:-}"
+            shift 2
+            ;;
+        --server-id)
+            SERVER_ID="${2:-}"
+            shift 2
+            ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 --tag <rc-tag> [--deploy] [--staging-url <url>] 
[--server-id <id>]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${TAG_NAME}" ]]; then
+    echo "ERROR: --tag is required."
+    echo "Usage: $0 --tag 10.3.0-rc1 [--deploy]"
+    exit 1
+fi
+
+cd "${REPO_ROOT}"
+
+# ── Resolve the version from the tag name ────────────────────────────────────
+# Tag format: <version>-rc<N>  (e.g. 10.3.0-rc1)
+RELEASE_VERSION="$(echo "${TAG_NAME}" | sed 's/-rc[0-9]*$//')"
+
+echo "========================================"
+echo "Apache KIE repo — deploy to staging"
+echo "Tag             : ${TAG_NAME}"
+echo "Release version : ${RELEASE_VERSION}"
+echo "Staging URL     : ${STAGING_URL}"
+echo "Server ID       : ${SERVER_ID}"
+echo "Deploy (real)   : ${DEPLOY}"
+echo "========================================"
+
+# ── Verify we are on / can resolve the RC tag ────────────────────────────────
+if ! git rev-parse "${TAG_NAME}" &>/dev/null; then
+    echo ""
+    echo "ERROR: Git tag '${TAG_NAME}' not found in this repository."
+    echo "       Run rc-commit.sh first, or check out the tag manually."
+    exit 1
+fi
+
+# ── Assemble Maven flags ─────────────────────────────────────────────────────
+
+DEPLOY_MVN_FLAGS=(
+    "-DskipTests"
+    "-Dfull"
+    # Activate the apache-release profile so the source/javadoc and GPG-signing
+    # goals defined there are executed (required for Apache releases).
+    "-Papache-release"
+    "-DaltDeploymentRepository=${SERVER_ID}::default::${STAGING_URL}"
+)
+
+if [[ -n "${MAVEN_SETTINGS:-}" ]]; then
+    DEPLOY_MVN_FLAGS+=("-s" "${MAVEN_SETTINGS}")
+fi
+
+# NOTE: MAVEN_GPG_PASSPHRASE is intentionally NOT added to the Maven command
+# line — exposing it there would print it in captured console logs.
+# The standard GPG Maven plugin (version 3+) reads the passphrase from the
+# environment variable MAVEN_GPG_PASSPHRASE via pinentry-loopback 
automatically.
+# For older plugin versions, configure loopback in ~/.gnupg/gpg-agent.conf:
+#   allow-loopback-pinentry
+# and pass: -Dgpg.useagent=false -Dgpg.loopback=true in MAVEN_SETTINGS.
+
+echo ""
+if [[ "${DEPLOY}" == "false" ]]; then
+    echo "[DRY RUN] Would run from tag ${TAG_NAME}:"
+    echo ""
+    echo "  git checkout ${TAG_NAME}"
+    echo "  mvn deploy ${DEPLOY_MVN_FLAGS[*]}"
+    echo "  git checkout -"
+    echo ""
+    echo "Pass --deploy to actually upload artifacts."
+    exit 0
+fi
+
+# ── Checkout the exact tag commit before deploying ───────────────────────────
+# Save the original ref as the commit SHA when in detached HEAD state (e.g.,
+# Jenkins SCM checkout).  `git rev-parse --abbrev-ref HEAD` returns "HEAD" when
+# detached, which cannot be used to restore the checkout afterwards.
+if git symbolic-ref --quiet HEAD &>/dev/null; then
+    ORIG_REF="$(git rev-parse --abbrev-ref HEAD)"
+else
+    ORIG_REF="$(git rev-parse HEAD)"
+fi
+
+echo "--- Checking out tag ${TAG_NAME} ---"
+git checkout "${TAG_NAME}"
+
+echo ""
+echo "--- Deploying to Nexus staging (${STAGING_URL}) ---"
+mvn deploy "${DEPLOY_MVN_FLAGS[@]}"
+
+echo ""
+echo "--- Returning to ${ORIG_REF} ---"
+git checkout "${ORIG_REF}"
+
+echo ""
+echo "========================================"
+echo "Deploy to staging complete."
+echo ""
+echo "Next steps:"
+echo "  1. Log in to https://repository.apache.org and close the staging repo."
+echo "  2. Share the staging repo URL with the release vote thread."
+echo "  3. Once the vote passes, run:"
+echo "     ./script/release/05-tag-release.sh --rc-tag ${TAG_NAME}"
+echo "========================================"
diff --git a/script/release/05-tag-release.sh b/script/release/05-tag-release.sh
new file mode 100755
index 00000000000..cc0debdbec6
--- /dev/null
+++ b/script/release/05-tag-release.sh
@@ -0,0 +1,134 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Promotes an approved RC tag to the final release tag (AUTOMATION F).
+#
+# After the community vote passes on an RC (e.g. 10.3.0-rc2), this script
+# creates the final release tag pointing to the same commit:
+#
+#   git tag 10.3.0 10.3.0-rc2
+#
+# By default the tag is created locally only.  Pass --push to publish it.
+#
+# Usage:
+#   ./script/release/05-tag-release.sh --rc-tag <rc-tag> [--push] [--dry-run]
+#
+# Examples:
+#   ./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2
+#   ./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2 --push
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+RC_TAG=""
+PUSH=false
+DRY_RUN=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --rc-tag)
+            RC_TAG="${2:-}"
+            shift 2
+            ;;
+        --push)
+            PUSH=true
+            shift
+            ;;
+        --dry-run)
+            DRY_RUN=true
+            shift
+            ;;
+        *)
+            echo "Unknown option: $1"
+            echo "Usage: $0 --rc-tag <rc-tag> [--push] [--dry-run]"
+            exit 1
+            ;;
+    esac
+done
+
+if [[ -z "${RC_TAG}" ]]; then
+    echo "ERROR: --rc-tag is required."
+    echo "Usage: $0 --rc-tag 10.3.0-rc2 [--push]"
+    exit 1
+fi
+
+if [[ ! "${RC_TAG}" =~ -rc[0-9]+$ ]]; then
+    echo "ERROR: --rc-tag must be an RC tag ending in -rc<N> (e.g. 
10.3.0-rc2), got '${RC_TAG}'."
+    exit 1
+fi
+
+# Derive the final release tag (strip -rcN suffix).
+RELEASE_TAG="$(echo "${RC_TAG}" | sed 's/-rc[0-9]*$//')"
+
+cd "${REPO_ROOT}"
+
+# Verify the RC tag exists.
+if ! git rev-parse "${RC_TAG}" &>/dev/null; then
+    echo "ERROR: RC tag '${RC_TAG}' not found."
+    echo "       Make sure you have fetched the tag: git fetch origin 
${RC_TAG}"
+    exit 1
+fi
+
+RC_COMMIT="$(git rev-parse "${RC_TAG}")"
+
+echo "========================================"
+echo "Apache KIE repo — tag release"
+echo "RC tag       : ${RC_TAG} (${RC_COMMIT:0:12})"
+echo "Release tag  : ${RELEASE_TAG}"
+echo "Push         : ${PUSH}"
+echo "Dry run      : ${DRY_RUN}"
+echo "========================================"
+
+if [[ "${DRY_RUN}" == "true" ]]; then
+    echo ""
+    echo "[DRY RUN] Would execute:"
+    echo "  git tag -a ${RELEASE_TAG} ${RC_TAG} -m \"Release ${RELEASE_TAG}\""
+    if [[ "${PUSH}" == "true" ]]; then
+        echo "  git push origin ${RELEASE_TAG}"
+    fi
+    exit 0
+fi
+
+echo ""
+echo "--- Creating release tag ${RELEASE_TAG} at ${RC_TAG} ---"
+git tag -a "${RELEASE_TAG}" "${RC_TAG}" -m "Release ${RELEASE_TAG}"
+
+if [[ "${PUSH}" == "true" ]]; then
+    echo ""
+    echo "--- Pushing release tag ${RELEASE_TAG} to origin ---"
+    git push origin "${RELEASE_TAG}"
+fi
+
+echo ""
+echo "========================================"
+echo "Release tag complete: ${RELEASE_TAG}"
+if [[ "${PUSH}" == "false" ]]; then
+    echo ""
+    echo "Tag is LOCAL only. To push when ready:"
+    echo "  git push origin ${RELEASE_TAG}"
+fi
+echo ""
+echo "Next steps (AUTOMATION G — promote artifacts):"
+echo "  1. Promote the Nexus staging repository to release."
+echo "  2. Run svn mv to move dist artifacts from dev/ to release/."
+echo "  3. Announce the release on [email protected]."
+echo "========================================"
diff --git a/script/release/README.md b/script/release/README.md
new file mode 100644
index 00000000000..e3e71ce6e00
--- /dev/null
+++ b/script/release/README.md
@@ -0,0 +1,270 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  -->
+
+# Release scripts
+
+Local-first release tooling for the Apache KIE repository.
+Since the 10.3.x consolidation, **drools, optaplanner, kogito-runtimes, and
+kogito-apps are all modules of the same root POM** — so the release process is
+now a single-repo, single-command workflow.
+
+These scripts implement the "local-first" approach: every step runs on your
+machine without needing Jenkins.  Jenkins can still call the same scripts, but
+you no longer depend on it for the mechanics.
+
+---
+
+## Terminology & Lifecycle Stages
+
+To avoid confusion between different release phases:
+
+| Term | Stage | Meaning & Scope |
+|---|---|---|
+| **`update-version`** | Automation A / D | Updates Maven POM versions across 
all modules in the reactor to stream snapshots (`10.3.999-SNAPSHOT`) or exact 
release versions (`10.3.0`). |
+| **`rc-commit` / `rc-tag`** | Automation D.1 | Creates a temporary branch, 
bumps to the release version, creates the R-commit, tags the Git RC 
(`10.3.0-rc1`), and cleans up the temporary branch. |
+| **`build`** | Automation D.2 | Compiles the full Maven reactor and installs 
artifacts to the local `~/.m2/repository`. |
+| **`deploy` / `staging`** | Automation D.3 | GPG signs artifacts and deploys 
them to the Apache Nexus Staging repository for release vote inspection. |
+| **`tag-release`** | Automation F | Promotes an approved RC tag (e.g., 
`10.3.0-rc2`) to the final release tag (`10.3.0`) once the release vote passes. 
|
+| **`promote` / `publish`** | Automation G | Releases the staging repository 
in Nexus to Maven Central and publishes release distribution files. |
+
+---
+
+## Numbered Scripts
+
+Scripts are numbered to clearly communicate their intended execution order:
+
+| Number / Script | Automation Stage | Purpose |
+|---|---|---|
+| **[`01-update-version.sh`](#01-update-versionsh)** *(alias: 
`update-version.sh`)* | Automation A / D | Update Maven POM versions + 
data-index image tag property |
+| **[`02-rc-commit.sh`](#02-rc-commitsh)** *(alias: `rc-commit.sh`)* | 
Automation D.1 | Create temporary branch, R commit, and RC git tag |
+| **[`03-build.sh`](#03-buildsh)** *(alias: `build.sh`)* | Automation D.2 | 
Build the full reactor to local `~/.m2/repository` |
+| **[`04-deploy-to-staging.sh`](#04-deploy-to-stagingsh)** *(alias: 
`deploy-to-staging.sh`)* | Automation D.3 | Deploy signed JARs to Apache Nexus 
staging repository |
+| **[`05-tag-release.sh`](#05-tag-releasesh)** *(alias: `tag-release.sh`)* | 
Automation F | Promote an approved RC tag → final release tag |
+| **[`release-all.sh`](#release-allsh)** | Orchestrator (D) | Orchestrate 
steps 02 → 03 → 04 in one single command |
+
+Make the scripts executable once:
+
+```bash
+chmod +x script/release/*.sh
+```
+
+---
+
+## Quick start — full RC candidate locally
+
+```bash
+# 1. Be on the development stream branch
+git checkout 10.3.x
+
+# 2. Run the full RC flow (no remote side-effects — dry run first)
+./script/release/release-all.sh \
+    --version 10.3.0 --tag 10.3.0-rc1 \
+    --skip-tests \
+    --dry-run
+
+# 3. If the dry run looks right, remove --dry-run
+./script/release/release-all.sh \
+    --version 10.3.0 --tag 10.3.0-rc1 \
+    --skip-tests
+```
+
+This leaves the RC tag locally (`git tag -l 10.3.0-rc1`).  Nothing has been
+pushed or published.  When you are ready to share the RC:
+
+```bash
+# Push the RC tag and deploy to Apache Nexus staging
+./script/release/release-all.sh \
+    --version 10.3.0 --tag 10.3.0-rc1 \
+    --skip-tests --push-tag --deploy
+```
+
+---
+
+## Scripts in detail
+
+### `01-update-version.sh`
+
+Updates every Maven module's `<version>` in the reactor and the
+`data-index-ephemeral.image.tagVersion` property in
+`kogito-quarkus-workflow-common-deployment`.
+
+```bash
+# Switch to a dev (SNAPSHOT) version — D commit (Automation A)
+./script/release/01-update-version.sh 10.3.999-SNAPSHOT
+
+# Switch to the exact release version — R commit (usually automated by 
02-rc-commit.sh)
+./script/release/01-update-version.sh 10.3.0
+```
+
+> **Stream vs release version.** Development branches keep
+> `major.minor.999-SNAPSHOT` as their version. The version is only set to
+> the exact release version (`10.3.0`) in the short-lived R commit created by
+> `02-rc-commit.sh`.
+
+---
+
+### `02-rc-commit.sh`
+
+Creates the **R commit** (version bump to the exact release version) on a
+short-lived *local-only* release branch, then tags that commit. The release
+branch is deleted afterwards — only the tag survives.
+
+```bash
+# Local tag only (safe — nothing is pushed)
+./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc1
+
+# Push the tag to origin when you're ready
+./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc1 --push
+
+# See what would happen without making changes
+./script/release/02-rc-commit.sh --version 10.3.0 --tag 10.3.0-rc1 --dry-run
+```
+
+The script refuses to run if there are any uncommitted changes in the working
+tree.
+
+---
+
+### `03-build.sh`
+
+Builds the full reactor. JARs are installed to your local `~/.m2` repository.
+
+```bash
+# Standard release build (tests skipped)
+./script/release/03-build.sh --skip-tests
+
+# With extra Maven options (e.g. parallelism)
+./script/release/03-build.sh --skip-tests --maven-opts "-T 4"
+```
+
+---
+
+### `04-deploy-to-staging.sh`
+
+Deploys the locally-built JARs to Apache Nexus staging. **Dry run by
+default** — the `--deploy` flag is required to actually upload anything.
+
+```bash
+# Dry run — prints the Maven command without executing it
+./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1
+
+# Actually deploy to Apache Nexus staging
+MAVEN_SETTINGS=/path/to/settings.xml \
+MAVEN_GPG_PASSPHRASE=secret \
+./script/release/04-deploy-to-staging.sh --tag 10.3.0-rc1 --deploy
+```
+
+After deployment, log in to <https://repository.apache.org>, close the
+staging repository, and share the URL with the release vote thread on
+`[email protected]`.
+
+**Required environment variables when `--deploy` is active:**
+
+| Variable | Description |
+|---|---|
+| `MAVEN_SETTINGS` | Path to a `settings.xml` with Nexus deployer credentials |
+| `MAVEN_GPG_PASSPHRASE` | GPG key passphrase for signing release artifacts |
+
+---
+
+### `05-tag-release.sh`
+
+Promotes a winning RC tag to the final release tag (AUTOMATION F).
+
+```bash
+# Local tag only
+./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2
+
+# Push the release tag to origin
+./script/release/05-tag-release.sh --rc-tag 10.3.0-rc2 --push
+```
+
+---
+
+### `release-all.sh`
+
+Orchestrates the full RC cycle in one command.
+
+```bash
+./script/release/release-all.sh \
+    --version 10.3.0 \
+    --tag 10.3.0-rc1 \
+    [--skip-tests] \
+    [--maven-opts "<opts>"] \
+    [--deploy] \
+    [--staging-url <url>] \
+    [--push-tag] \
+    [--dry-run]
+```
+
+Steps executed in order:
+
+1. `02-rc-commit.sh` — R commit + tag
+2. `03-build.sh` — full build at the tag commit
+3. `04-deploy-to-staging.sh` — Nexus staging upload *(only if `--deploy`)*
+
+---
+
+## Development stream vs release version
+
+| Situation | Version | Command |
+|---|---|---|
+| Daily development on `main` | `999-SNAPSHOT` | `update-version.sh 
999-SNAPSHOT` |
+| Daily development on `10.3.x` | `10.3.999-SNAPSHOT` | `update-version.sh 
10.3.999-SNAPSHOT` |
+| Creating RC / release | `10.3.0` | Handled automatically by `rc-commit.sh` |
+
+---
+
+## Jenkins integration
+
+Jenkins calls these exact same scripts.  The only difference is that Jenkins
+supplies credentials via environment variables:
+
+```groovy
+withCredentials([
+    file(credentialsId: 'kie-release-settings', variable: 'MAVEN_SETTINGS'),
+    string(credentialsId: 'GPG_KEY_PASSPHRASE', variable: 
'MAVEN_GPG_PASSPHRASE')
+]) {
+    sh """
+        ./script/release/release-all.sh \
+            --version \${RELEASE_VERSION} \
+            --tag \${GIT_TAG_NAME} \
+            --skip-tests --deploy --push-tag
+    """
+}
+```
+
+---
+
+## Relationship to the 10.2.x multi-repo flow
+
+In 10.2.x the release involved five separate repositories
+(drools → optaplanner → kogito-runtimes → kogito-apps → kie-tools), each with
+its own Jenkins job.  Now that the first four repos are merged, the steps map
+as follows:
+
+| Old step | New equivalent |
+|---|---|
+| Trigger Drools release job | `./script/release/release-all.sh` |
+| Trigger OptaPlanner release job | *(same command — same reactor)* |
+| Trigger Kogito Runtimes release job | *(same command — same reactor)* |
+| Trigger Kogito Apps release job | *(same command — same reactor)* |
+| `git tag 10.x.0 10.x.0-rcN` in all repos | `./script/release/tag-release.sh` 
|
+
+`kie-tools` remains a separate repository and is unaffected by these scripts.
diff --git a/script/release/release-all.sh b/script/release/release-all.sh
new file mode 100755
index 00000000000..8464c4579aa
--- /dev/null
+++ b/script/release/release-all.sh
@@ -0,0 +1,201 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Master orchestrator for the local-first release workflow.
+#
+# Runs the individual release scripts in the correct order for a full release
+# candidate cycle (02-rc-commit.sh -> 03-build.sh -> 04-deploy-to-staging.sh).
+# Each step can also be run independently — see script/release/README.md.
+#
+# Usage:
+#   ./script/release/release-all.sh <version> [--tag <tag>] [OPTIONS]
+#   ./script/release/release-all.sh --version <version> [--tag <tag>] [OPTIONS]
+#
+# Required (one of):
+#   <version> or --version <ver>   Exact release version, e.g. 10.3.0
+#
+# Optional tags:
+#   --tag <tag> | --rc-tag <tag>   RC tag name, e.g. 10.3.0-rc1 (defaults to 
<version>-rc1)
+#   --rc                           Explicit flag indicating RC mode
+#
+# Optional build flags:
+#   --skip-tests | --skip-build    Skip tests during the build
+#   --maven-opts <opts>            Extra Maven options forwarded to build.sh
+#
+# Optional deploy/publish flags:
+#   --deploy | --publish           Deploy JARs to Nexus staging after the build
+#   --staging-url <url>            Nexus staging URL (default: Apache Nexus)
+#
+# Optional git flags:
+#   --push | --push-tag            Push the RC tag to origin after creating it
+#
+# Other:
+#   --dry-run                      Print what would happen without executing 
anything
+#
+# Examples:
+#   # Positional or flag version
+#   ./script/release/release-all.sh 10.3.0 --rc --skip-tests
+#   ./script/release/release-all.sh --version 10.3.0 --tag 10.3.0-rc1 
--skip-tests
+#
+#   # Full RC with deploy to Apache Nexus staging and push tag to origin
+#   ./script/release/release-all.sh 10.3.0 --tag 10.3.0-rc1 --skip-tests 
--deploy --push-tag
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+RELEASE_VERSION=""
+TAG_NAME=""
+SKIP_TESTS=false
+EXTRA_MVN_OPTS=""
+DEPLOY=false
+STAGING_URL=""
+PUSH_TAG=false
+DRY_RUN=false
+
+while [[ $# -gt 0 ]]; do
+    case $1 in
+        --version)       RELEASE_VERSION="${2:-}"; shift 2 ;;
+        --tag|--rc-tag)  TAG_NAME="${2:-}"; shift 2 ;;
+        --rc)            shift ;; # Accepted for parity with tools repo
+        --skip-tests|--skip-build) SKIP_TESTS=true; shift ;;
+        --maven-opts)    EXTRA_MVN_OPTS="${2:-}"; shift 2 ;;
+        --deploy|--publish) DEPLOY=true; shift ;;
+        --staging-url)   STAGING_URL="${2:-}"; shift 2 ;;
+        --push|--push-tag) PUSH_TAG=true; shift ;;
+        --dry-run)       DRY_RUN=true; shift ;;
+        *)
+            if [[ -z "${RELEASE_VERSION}" && ! "$1" =~ ^- ]]; then
+                RELEASE_VERSION="$1"
+                shift
+            else
+                echo "Unknown option: $1"
+                echo "Usage: $0 [<version> | --version <version>] [--tag 
<tag>] [OPTIONS]"
+                exit 1
+            fi
+            ;;
+    esac
+done
+
+if [[ -z "${RELEASE_VERSION}" ]]; then
+    echo "ERROR: Version is required."
+    echo "Usage: $0 10.3.0 [--tag 10.3.0-rc1] [OPTIONS]"
+    exit 1
+fi
+
+if [[ -z "${TAG_NAME}" ]]; then
+    TAG_NAME="${RELEASE_VERSION}-rc1"
+fi
+
+cd "${REPO_ROOT}"
+
+echo ""
+echo "=========================================="
+echo "Apache KIE repo — release-all"
+echo "Version         : ${RELEASE_VERSION}"
+echo "RC tag          : ${TAG_NAME}"
+echo "Skip tests      : ${SKIP_TESTS}"
+echo "Deploy staging  : ${DEPLOY}"
+echo "Push tag        : ${PUSH_TAG}"
+echo "Dry run         : ${DRY_RUN}"
+echo "=========================================="
+echo ""
+
+# ── Helper ───────────────────────────────────────────────────────────────────
+
+run_step() {
+    local step_name="$1"
+    shift
+    echo ""
+    echo "────────────────────────────────────────"
+    echo "STEP: ${step_name}"
+    echo "────────────────────────────────────────"
+    if [[ "${DRY_RUN}" == "true" ]]; then
+        echo "[DRY RUN] $*"
+    else
+        "$@"
+    fi
+    echo "✅  ${step_name} — done"
+}
+
+# ── STEP 1 (Automation D.1): R commit + RC tag ───────────────────────────────
+
+RC_COMMIT_SCRIPT="${SCRIPT_DIR}/02-rc-commit.sh"
+
+RC_COMMIT_ARGS=("${RC_COMMIT_SCRIPT}" "--version" "${RELEASE_VERSION}" "--tag" 
"${TAG_NAME}")
+[[ "${PUSH_TAG}" == "true" ]] && RC_COMMIT_ARGS+=("--push")
+[[ "${DRY_RUN}" == "true" ]]  && RC_COMMIT_ARGS+=("--dry-run")
+
+run_step "R commit + RC tag (02-rc-commit.sh)" "${RC_COMMIT_ARGS[@]}"
+
+# ── STEP 2 (Automation D.2): Build ───────────────────────────────────────────
+
+BUILD_SCRIPT="${SCRIPT_DIR}/03-build.sh"
+
+BUILD_ARGS=("${BUILD_SCRIPT}")
+[[ "${SKIP_TESTS}" == "true" ]]       && BUILD_ARGS+=("--skip-tests")
+[[ -n "${EXTRA_MVN_OPTS}" ]]          && BUILD_ARGS+=("--maven-opts" 
"${EXTRA_MVN_OPTS}")
+
+# The build must run at the RC tag commit. 02-rc-commit.sh leaves HEAD on the
+# development branch, so we check out the tag, build, then return.
+if [[ "${DRY_RUN}" == "true" ]]; then
+    run_step "Build @ ${TAG_NAME} (03-build.sh)" echo "[DRY RUN] git checkout 
${TAG_NAME} && ${BUILD_ARGS[*]} && git checkout -"
+else
+    echo ""
+    echo "────────────────────────────────────────"
+    echo "STEP: Build @ ${TAG_NAME} (03-build.sh)"
+    echo "────────────────────────────────────────"
+    git checkout "${TAG_NAME}"
+    "${BUILD_ARGS[@]}"
+    git checkout -
+    echo "✅  Build — done"
+fi
+
+# ── STEP 3 (Automation D.3): Deploy to Nexus staging (optional) ──────────────
+
+if [[ "${DEPLOY}" == "true" ]]; then
+    DEPLOY_SCRIPT="${SCRIPT_DIR}/04-deploy-to-staging.sh"
+
+    DEPLOY_ARGS=("${DEPLOY_SCRIPT}" "--tag" "${TAG_NAME}" "--deploy")
+    [[ -n "${STAGING_URL}" ]] && DEPLOY_ARGS+=("--staging-url" 
"${STAGING_URL}")
+    run_step "Deploy to Nexus staging (04-deploy-to-staging.sh)" 
"${DEPLOY_ARGS[@]}"
+fi
+
+# ── Summary 
───────────────────────────────────────────────────────────────────
+
+echo ""
+echo "=========================================="
+echo "release-all complete ✅"
+echo ""
+echo "  RC tag  : ${TAG_NAME}"
+echo "  Version : ${RELEASE_VERSION}"
+echo ""
+if [[ "${DEPLOY}" == "true" ]]; then
+    echo "Artifacts are in Nexus staging."
+    echo "Close the staging repo, then start the vote on [email protected]."
+else
+    echo "Artifacts are in your local ~/.m2 repository only."
+    echo "Run 04-deploy-to-staging.sh --tag ${TAG_NAME} --deploy when ready."
+fi
+echo ""
+echo "When the vote passes, promote the RC to a final release tag:"
+echo "  ./script/release/05-tag-release.sh --rc-tag ${TAG_NAME} --push"
+echo "=========================================="
diff --git a/script/release/upload_filemgmt.sh 
b/script/release/upload_filemgmt.sh
new file mode 100755
index 00000000000..50fe339e254
--- /dev/null
+++ b/script/release/upload_filemgmt.sh
@@ -0,0 +1,127 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+set -euo pipefail
+
+# Uploads Drools release binaries and documentation to the Apache file
+# management server (people.apache.org) via SSH/rsync.
+#
+# Called by Jenkinsfile.103xplus.promote (and the original Jenkinsfile.promote)
+# inside an sshagent('drools-filemgmt') block, so SSH authentication is already
+# in the agent — no credential parameters needed here.
+#
+# Usage:
+#   ./script/release/upload_filemgmt.sh <release-version>
+#
+# Examples:
+#   ./script/release/upload_filemgmt.sh 10.3.0
+#
+# Environment variables (all have defaults):
+#   FILEMGMT_HOST        Remote host (default: people.apache.org)
+#   FILEMGMT_BASE_PATH   Remote base path (default: /home/drools/filemgmt)
+#   DRY_RUN              Set to 'true' to print commands without running them
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
+
+RELEASE_VERSION="${1:-}"
+
+if [[ -z "${RELEASE_VERSION}" ]]; then
+    echo "ERROR: release version argument is required."
+    echo "Usage: $0 <release-version>"
+    echo "  e.g. $0 10.3.0"
+    exit 1
+fi
+
+FILEMGMT_HOST="${FILEMGMT_HOST:-people.apache.org}"
+FILEMGMT_BASE_PATH="${FILEMGMT_BASE_PATH:-/home/drools/filemgmt}"
+DRY_RUN="${DRY_RUN:-false}"
+
+# Derive major.minor stream (e.g. 10.3.0 → 10.3)
+STREAM="$(echo "${RELEASE_VERSION}" | sed 's/^\([0-9]*\.[0-9]*\)\..*/\1/')"
+
+REMOTE_BINARIES_DIR="${FILEMGMT_BASE_PATH}/downloads_htdocs/drools/release/${STREAM}.x"
+REMOTE_DOCS_DIR="${FILEMGMT_BASE_PATH}/docs_htdocs/drools/release/${STREAM}.x"
+
+echo "========================================"
+echo "Apache KIE — upload binaries & docs"
+echo "Release version    : ${RELEASE_VERSION}"
+echo "Stream             : ${STREAM}"
+echo "Remote host        : ${FILEMGMT_HOST}"
+echo "Remote binaries    : ${REMOTE_BINARIES_DIR}"
+echo "Remote docs        : ${REMOTE_DOCS_DIR}"
+echo "Dry run            : ${DRY_RUN}"
+echo "========================================"
+
+cd "${REPO_ROOT}"
+
+# ── Locate locally-built distribution zips ───────────────────────────────────
+# Maven -Dfull produces zip artifacts under each module's target/ directory.
+# We upload everything matching the release version.
+BINARY_ZIPS=()
+while IFS= read -r -d '' f; do
+    BINARY_ZIPS+=("${f}")
+done < <(find . -path "*/target/*-${RELEASE_VERSION}*.zip" ! -path 
"*/target/checkout/*" -print0 2>/dev/null)
+
+if [[ ${#BINARY_ZIPS[@]} -eq 0 ]]; then
+    echo "WARNING: No distribution zips found for version ${RELEASE_VERSION}."
+    echo "         Run 03-build.sh with -Dfull before uploading."
+fi
+
+# ── Helper: run or echo 
───────────────────────────────────────────────────────
+run() {
+    if [[ "${DRY_RUN}" == "true" ]]; then
+        echo "[DRY RUN] $*"
+    else
+        "$@"
+    fi
+}
+
+# ── Create remote directories 
─────────────────────────────────────────────────
+run ssh "${FILEMGMT_HOST}" "mkdir -p 
'${REMOTE_BINARIES_DIR}/${RELEASE_VERSION}' 
'${REMOTE_DOCS_DIR}/${RELEASE_VERSION}'"
+
+# ── Upload distribution zips 
──────────────────────────────────────────────────
+if [[ ${#BINARY_ZIPS[@]} -gt 0 ]]; then
+    echo ""
+    echo "--- Uploading ${#BINARY_ZIPS[@]} distribution zip(s) ---"
+    for zip in "${BINARY_ZIPS[@]}"; do
+        echo "  ${zip}"
+        run rsync -avz --progress \
+            "${zip}" \
+            "${FILEMGMT_HOST}:${REMOTE_BINARIES_DIR}/${RELEASE_VERSION}/"
+    done
+fi
+
+# ── Upload HTML documentation (if generated) ─────────────────────────────────
+DOCS_DIR="target/docs"
+if [[ -d "${DOCS_DIR}" ]]; then
+    echo ""
+    echo "--- Uploading docs from ${DOCS_DIR} ---"
+    run rsync -avz --progress --delete \
+        "${DOCS_DIR}/" \
+        "${FILEMGMT_HOST}:${REMOTE_DOCS_DIR}/${RELEASE_VERSION}/"
+else
+    echo "(skipping docs upload — ${DOCS_DIR} not found)"
+fi
+
+echo ""
+echo "========================================"
+echo "Upload complete for version ${RELEASE_VERSION}."
+echo "========================================"


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to