xworkmate-bridge/.github/workflows/pipeline.yml
Haitao Pan 57ab5711e1 fix(ci): add openclaw session contract check to validate stage
The OpenClaw session contract smoke and SSE long-task stream checks used to
live in the Ansible validate role and ran during the Deploy stage. They depend
on the public OpenClaw gateway producing a 'pong' reply, which the bridge
itself cannot guarantee end-to-end. When the gateway returned an empty
completion envelope (CI run 27010307958), the entire Deploy job failed even
though the bridge binary had been installed and was healthy.

Move the lightweight session contract check into the GitHub Actions validate
stage as a new script. Deploy now only asserts the bridge's own state
(binary, ports, /api/ping, /acp/rpc capabilities, routing.resolve), and the
OpenClaw contract check runs in validate where the release-blocking failure
belongs.
2026-06-05 19:29:53 +08:00

330 lines
12 KiB
YAML

name: Pipeline
on:
pull_request:
branches: [main]
push:
branches: ['**']
workflow_dispatch:
inputs:
target_host:
description: "Ansible inventory host or alias"
required: false
default: "jp-xhttp-contabo.svc.plus"
type: string
run_apply:
description: "Apply deployment (false = dry-run)"
required: true
default: true
type: boolean
internal_service_token:
description: "Optional ACP auth token for deploy"
required: false
default: ""
type: string
permissions:
contents: read
packages: write
concurrency:
group: pipeline-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
env:
DEFAULT_TARGET_HOST: jp-xhttp-contabo.svc.plus
jobs:
production_state:
name: Production State
runs-on: ubuntu-latest
outputs:
production_image: ${{ steps.production_state.outputs.production_image }}
production_tag: ${{ steps.production_state.outputs.production_tag }}
production_commit: ${{ steps.production_state.outputs.production_commit }}
production_version: ${{ steps.production_state.outputs.production_version }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Probe current production bridge
id: production_state
env:
BRIDGE_SERVER_URL: https://xworkmate-bridge.svc.plus
BRIDGE_AUTH_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
run: |
while IFS='=' read -r key value; do
echo "${key}=${value}" >> "$GITHUB_OUTPUT"
done < <(bash ./scripts/github-actions/report-production-state.sh "${BRIDGE_SERVER_URL}")
- name: Explain branch push vs production
if: ${{ github.event_name == 'push' }}
run: |
echo "::notice title=Production state::Current production bridge before this run is ${{ steps.production_state.outputs.production_commit }}."
prep:
name: Prep
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
go-version-file: go.mod
cache: true
- name: Install golangci-lint
run: |
GOBIN="$(go env GOPATH)/bin"
go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.64.8
echo "${GOBIN}" >> "$GITHUB_PATH"
- name: Run lint and tests
run: bash ./scripts/github-actions/prep.sh
build:
name: Build
needs:
- production_state
- prep
runs-on: ubuntu-latest
env:
SERVICE_REGISTRY: ghcr.io
SERVICE_IMAGE_REPO_OWNER: ${{ vars.IMAGE_REPO_OWNER || github.repository_owner }}
SERVICE_IMAGE_NAME: xworkmate-bridge
outputs:
artifact_name: ${{ steps.artifact_meta.outputs.artifact_name }}
binary_artifact_name: ${{ steps.binary_artifact_meta.outputs.binary_artifact_name }}
service_image_ref: ${{ steps.service_ref.outputs.image_ref }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Log in to GHCR
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
with:
registry: ghcr.io
username: ${{ vars.GHCR_USERNAME || github.repository_owner }}
password: ${{ secrets.GHCR_TOKEN || github.token }}
- name: Resolve service image ref
id: service_ref
run: bash ./scripts/github-actions/resolve-service-image-ref.sh
- name: Build and optionally push service image
uses: docker/build-push-action@471d1dc4e07e5cdedd4c2171150001c434f0b7a4 # v6.15.0
with:
context: .
file: Dockerfile
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.service_ref.outputs.image_ref }}
build-args: |
BUILD_COMMIT=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.repository.pushed_at }}
BUILD_VERSION=v1.0-beta2
- name: Build native linux service binary
run: bash ./scripts/github-actions/build-artifact.sh dist/native-binary
- name: Write image ref artifact
run: |
set -euo pipefail
mkdir -p dist
printf '%s\n' "${{ steps.service_ref.outputs.image_ref }}" > dist/service-image-ref.txt
- name: Record artifact metadata
id: artifact_meta
run: bash ./scripts/github-actions/resolve-artifact-name.sh
- name: Record native binary artifact metadata
id: binary_artifact_meta
run: printf 'binary_artifact_name=xworkmate-bridge-linux-amd64-%s\n' "${GITHUB_SHA}" >> "$GITHUB_OUTPUT"
- name: Upload image ref artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ steps.artifact_meta.outputs.artifact_name }}
path: dist/service-image-ref.txt
- name: Upload native linux service binary
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ steps.binary_artifact_meta.outputs.binary_artifact_name }}
path: dist/native-binary/xworkmate-bridge
deploy:
name: Deploy
needs: build
if: ${{ github.event_name != 'pull_request' }}
runs-on: ubuntu-latest
outputs:
run_apply: ${{ steps.deploy_meta.outputs.run_apply }}
env:
INTERNAL_SERVICE_TOKEN: ${{ github.event_name == 'workflow_dispatch' && inputs.internal_service_token || secrets.INTERNAL_SERVICE_TOKEN }}
GHCR_USERNAME: ${{ vars.GHCR_USERNAME || github.repository_owner }}
GHCR_PASSWORD: ${{ secrets.GHCR_TOKEN || github.token }}
steps:
- name: Checkout service repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: xworkmate-bridge
- name: Checkout playbooks repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: x-evor/playbooks
token: ${{ secrets.WORKSPACE_REPO_TOKEN || github.token }}
path: playbooks
- name: Download build image ref artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: ${{ needs.build.outputs.artifact_name }}
path: xworkmate-bridge/dist/image-artifact
- name: Download native linux service binary
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: ${{ needs.build.outputs.binary_artifact_name }}
path: xworkmate-bridge/dist/native-binary
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- name: Install Ansible runtime
run: |
python -m pip install --upgrade pip
python -m pip install "ansible-core==2.18.3"
- name: Resolve deployment settings
id: deploy_meta
run: |
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
target_host="${{ inputs.target_host }}"
run_apply="${{ inputs.run_apply }}"
else
target_host="${DEFAULT_TARGET_HOST}"
run_apply="true"
fi
if [[ -z "${target_host}" ]]; then
target_host="${DEFAULT_TARGET_HOST}"
fi
echo "target_host=${target_host}" >> "$GITHUB_OUTPUT"
echo "run_apply=${run_apply}" >> "$GITHUB_OUTPUT"
- name: Prepare runner SSH access
working-directory: xworkmate-bridge
env:
SINGLE_NODE_VPS_SSH_PRIVATE_KEY: ${{ secrets.SINGLE_NODE_VPS_SSH_PRIVATE_KEY }}
SSH_KNOWN_HOSTS: ${{ secrets.SSH_KNOWN_HOSTS }}
run: bash ./scripts/github-actions/prepare-ssh.sh "${{ steps.deploy_meta.outputs.target_host }}" "${SSH_KNOWN_HOSTS}"
- name: Install native bridge binary on target
if: ${{ steps.deploy_meta.outputs.run_apply == 'true' }}
working-directory: xworkmate-bridge
run: bash ./scripts/github-actions/deploy-native-binary.sh "${{ steps.deploy_meta.outputs.target_host }}" "dist/native-binary/xworkmate-bridge" "$(git rev-parse --short HEAD)"
- name: Run Ansible deploy playbook
working-directory: playbooks
env:
ANSIBLE_CONFIG: ./ansible.cfg
BRIDGE_AUTH_TOKEN: ${{ env.INTERNAL_SERVICE_TOKEN }}
run: |
CHECK_MODE_FLAG=""
if [[ "${{ steps.deploy_meta.outputs.run_apply }}" != "true" ]]; then
CHECK_MODE_FLAG="-C"
fi
ansible-playbook -i inventory.ini deploy_xworkmate_bridge_vhosts.yml \
-D ${CHECK_MODE_FLAG} \
--tags xworkmate_bridge \
-l "${{ steps.deploy_meta.outputs.target_host }}"
publish_release:
name: Publish GitHub Release
needs:
- build
- deploy
- validate
if: ${{ github.event_name != 'pull_request' && needs.deploy.result == 'success' && needs.validate.result == 'success' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download image ref artifact
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
with:
name: ${{ needs.build.outputs.artifact_name }}
path: dist
- name: Resolve release metadata
id: release_meta
run: bash ./scripts/github-actions/resolve-release-metadata.sh
- name: Publish GitHub release
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
tag="${{ steps.release_meta.outputs.tag }}"
asset="dist/service-image-ref.txt#xworkmate-bridge-service-image-ref.txt"
if gh release view "${tag}" --repo "${{ github.repository }}" >/dev/null 2>&1; then
gh release upload "${tag}" "${asset}" \
--repo "${{ github.repository }}" \
--clobber
else
gh release create "${tag}" "${asset}" \
--repo "${{ github.repository }}" \
--target "${{ github.sha }}" \
--title "${{ steps.release_meta.outputs.title }}" \
--notes "Automated release for ${GITHUB_SHA}."
fi
validate:
name: Validate
needs:
- build
- deploy
if: ${{ needs.deploy.result == 'success' && needs.deploy.outputs.run_apply == 'true' }}
runs-on: ubuntu-latest
env:
BRIDGE_SERVER_URL: https://xworkmate-bridge.svc.plus
INTERNAL_SERVICE_TOKEN: ${{ secrets.INTERNAL_SERVICE_TOKEN }}
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Validate deployed endpoints
env:
BRIDGE_AUTH_TOKEN: ${{ env.INTERNAL_SERVICE_TOKEN }}
run: bash ./scripts/github-actions/validate-deploy.sh "$(git rev-parse --short HEAD)" "${BRIDGE_SERVER_URL}"
- name: Validate public ACP contract
env:
BRIDGE_AUTH_TOKEN: ${{ env.INTERNAL_SERVICE_TOKEN }}
run: bash ./scripts/github-actions/verify-public-rpc-contract.sh
- name: Validate OpenClaw session contract
env:
BRIDGE_AUTH_TOKEN: ${{ env.INTERNAL_SERVICE_TOKEN }}
run: bash ./scripts/github-actions/validate-openclaw-session.sh