name: 'SDK Java' on: pull_request: branches: - 'main' - 'release/**' paths: - 'packages/sdk-java/**' - '**/.mvn/**' - 'packages/core/src/config/approval-modes.json' - 'packages/core/src/managed-runtime/**' - 'packages/cli/src/config/**' - 'packages/core/src/config/**' - 'packages/core/src/core/**' - 'packages/cli/src/commands/serve.ts' - 'packages/cli/src/serve/**' - 'packages/cli/src/acp-integration/**' - 'packages/acp-bridge/**' - 'integration-tests/fake-openai-server.ts' - 'integration-tests/helpers/hosted-*' - 'integration-tests/cli/hosted-latency-baseline.test.ts' - 'integration-tests/baselines/hosted-latency.json' - 'package.json' - 'pnpm-lock.yaml' - 'docs/developers/sdk-java.md' - 'docs/design/java-daemon-sdk-alpha.md' - 'scripts/run-java-daemon-sdk-e2e.ts' - 'scripts/run-managed-agent-server-e2e.ts' - 'scripts/check-failsafe-reports.js' - 'scripts/check-flyway-migrations.js' - '.github/workflows/sdk-java.yml' - '.github/workflows/release-sdk-java.yml' push: branches: - 'main' - 'release/**' paths: - 'packages/sdk-java/**' - '**/.mvn/**' - 'packages/core/src/config/approval-modes.json' - 'packages/core/src/managed-runtime/**' - 'packages/cli/src/config/**' - 'packages/core/src/config/**' - 'packages/core/src/core/**' - 'packages/cli/src/commands/serve.ts' - 'packages/cli/src/serve/**' - 'packages/cli/src/acp-integration/**' - 'packages/acp-bridge/**' - 'integration-tests/fake-openai-server.ts' - 'integration-tests/helpers/hosted-*' - 'integration-tests/cli/hosted-latency-baseline.test.ts' - 'integration-tests/baselines/hosted-latency.json' - 'package.json' - 'pnpm-lock.yaml' - 'docs/developers/sdk-java.md' - 'docs/design/java-daemon-sdk-alpha.md' - 'scripts/run-java-daemon-sdk-e2e.ts' - 'scripts/run-managed-agent-server-e2e.ts' - 'scripts/check-failsafe-reports.js' - 'scripts/check-flyway-migrations.js' - '.github/workflows/sdk-java.yml' - '.github/workflows/release-sdk-java.yml' permissions: contents: 'read' env: MAVEN_VERSION: '3.9.11' MAVEN_SHA512: 'bcfe4fe305c962ace56ac7b5fc7a08b87d5abd8b7e89027ab251069faebee516b0ded8961445d6d91ec1985dfe30f8153268843c89aa392733d1a3ec956c9978' concurrency: group: 'sdk-java-${{ github.event.pull_request.number || github.run_id }}' cancel-in-progress: true jobs: test: name: '${{ matrix.os }} / Java ${{ matrix.java }}' runs-on: '${{ (matrix.os == ''ubuntu-latest'' && github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name == ''push'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || matrix.os }}' timeout-minutes: 30 strategy: fail-fast: false matrix: include: - os: 'ubuntu-latest' java: '11' - os: 'ubuntu-latest' java: '17' - os: 'ubuntu-latest' java: '21' - os: 'macos-latest' java: '21' - os: 'windows-latest' java: '21' steps: - name: 'Restore workspace ownership' if: "${{ runner.environment == 'self-hosted' }}" run: |- set -uo pipefail RUNNER_UID="$(id -u)" RUNNER_GID="$(id -g)" if [ "$RUNNER_UID" != "0" ]; then chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files" fi chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files" - name: 'Checkout' uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 with: ref: "${{ (runner.environment == 'self-hosted' && github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }}" - name: 'Verify checkout includes expected head commit' if: "${{ runner.environment == 'self-hosted' && github.event_name == 'pull_request' }}" env: EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}' run: |- if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}." git log --oneline --decorate -5 exit 1 fi # setup-java updates Maven files non-atomically. Keep them job-local # because runner instances on one ECS host share HOME. - name: 'Set up Java' uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0 with: distribution: 'temurin' java-version: '${{ matrix.java }}' cache: 'maven' cache-dependency-path: |- packages/sdk-java/qwencode/pom.xml packages/sdk-java/runtime-broker/pom.xml settings-path: '${{ runner.temp }}/setup-java-m2' # The runtime-broker process tests spawn the Node fixture worker. # Self-hosted keeps relying on the runner's own Node, matching the # daemon-e2e job's stance. - name: 'Set up Node.js 22 (hosted)' if: "${{ runner.environment == 'github-hosted' && matrix.java == '21' }}" uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0 with: node-version: '22' package-manager-cache: false - name: 'Set up Maven (self-hosted)' if: "${{ runner.environment == 'self-hosted' }}" run: |- archive="${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o "${archive}" echo "${MAVEN_SHA512} ${archive}" | sha512sum --check --status tar -xzf "${archive}" -C "${RUNNER_TEMP}" echo "${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}/bin" >> "${GITHUB_PATH}" - name: 'Run Java SDK tests (self-hosted)' if: "${{ runner.environment == 'self-hosted' }}" working-directory: 'packages/sdk-java/qwencode' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' MATRIX_JAVA: '${{ matrix.java }}' run: |- mkdir -p "${HOME}/.cache/qwen-code-ci" exec 9>"${HOME}/.cache/qwen-code-ci/sdk-java-tests.lock" if ! flock --wait 1200 9; then echo "::error::sdk-java host lock not acquired within 20 minutes" exit 1 fi mvn --batch-mode --no-transfer-progress clean test if [ "${MATRIX_JAVA}" = "21" ]; then cd ../runtime-broker mvn --batch-mode --no-transfer-progress clean test fi - name: 'Run Java SDK tests (hosted)' if: "${{ runner.environment == 'github-hosted' }}" working-directory: 'packages/sdk-java/qwencode' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: 'mvn --batch-mode --no-transfer-progress clean test' - name: 'Run Runtime Broker state tests' if: "${{ runner.environment == 'github-hosted' && matrix.java == '21' }}" working-directory: 'packages/sdk-java/runtime-broker' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: 'mvn --batch-mode --no-transfer-progress clean test' - name: 'Run Java SDK Checkstyle' if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '11' }}" working-directory: 'packages/sdk-java/qwencode' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: 'mvn --batch-mode --no-transfer-progress checkstyle:check' - name: 'Run Runtime Broker state Checkstyle' if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '21' }}" working-directory: 'packages/sdk-java/runtime-broker' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: 'mvn --batch-mode --no-transfer-progress checkstyle:check' - name: 'Build release artifacts' if: "${{ matrix.os == 'ubuntu-latest' && matrix.java == '11' }}" working-directory: 'packages/sdk-java/qwencode' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: 'mvn --batch-mode --no-transfer-progress -DskipTests -Dgpg.skip=true package' # The Flyway duplicate-version guard of #12940. managed-agent-server # allocates migration versions by hand across two locations that share one # sequence, so two green PRs can collide once both merge; the test that # catches it boots a database and runs only in the slow lanes below. This # job is the fast lane: a filename scan, seconds in, on every pull request — # whose checkout is the prospective merge result — and on every push. # runtime-broker and qwencode carry no migrations today but share the # namespace: managed-agent-server depends on both at compile scope, so # Flyway resolves classpath:db/migration across the jars and a version # claimed in any of them collides with managed-agent-server's exactly like # an in-module duplicate. flyway-migrations: name: 'Flyway migration version uniqueness' runs-on: 'ubuntu-latest' timeout-minutes: 5 steps: - name: 'Checkout' uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 - name: 'Check Flyway migration versions are unique' run: 'node scripts/check-flyway-migrations.js packages/sdk-java/managed-agent-server packages/sdk-java/runtime-broker packages/sdk-java/qwencode' mysql-integration: name: 'Runtime Broker and Managed Agent MariaDB / Java 21' runs-on: 'ubuntu-latest' timeout-minutes: 15 services: mariadb: image: 'mariadb:10.11.18' env: MARIADB_DATABASE: 'runtime_broker_test' MARIADB_ROOT_PASSWORD: 'runtime-broker' ports: - '3306:3306' options: >- --health-cmd "healthcheck.sh --connect --innodb_initialized" --health-interval 5s --health-timeout 5s --health-retries 20 steps: - name: 'Checkout' uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 - name: 'Set up Java' uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0 with: distribution: 'temurin' java-version: '21' cache: 'maven' cache-dependency-path: |- packages/sdk-java/qwencode/pom.xml packages/sdk-java/runtime-broker/pom.xml packages/sdk-java/managed-agent-server/pom.xml settings-path: '${{ runner.temp }}/setup-java-m2' - name: 'Run Runtime Broker MySQL integration tests' working-directory: 'packages/sdk-java/runtime-broker' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: >- mvn --batch-mode --no-transfer-progress -Pmysql-integration -Dmysql.url='jdbc:mysql://127.0.0.1:3306/runtime_broker_test?allowPublicKeyRetrieval=true&useSSL=false' -Dmysql.user=root -Dmysql.password=runtime-broker clean verify - name: 'Install Managed Agent dependencies' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: |- mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -DskipTests install - name: 'Run Managed Agent tests, Checkstyle, and MySQL integration' working-directory: 'packages/sdk-java/managed-agent-server' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: >- mvn --batch-mode --no-transfer-progress -Pmysql-integration -Dmysql.url='jdbc:mysql://127.0.0.1:3306/managed_agent_test?createDatabaseIfNotExist=true&allowPublicKeyRetrieval=true&useSSL=false' -Dmysql.user=root -Dmysql.password=runtime-broker clean verify checkstyle:check # Maven passes a selection narrowed by a profile, MAVEN_ARGS or # .mvn/maven.config, so compare what ran with the source tree. - name: 'Check that every non-Hosted integration test class ran' run: 'node scripts/check-failsafe-reports.js non-hosted packages/sdk-java/runtime-broker packages/sdk-java/managed-agent-server' hosted-harness-mysql: name: 'Hosted process fault gates / MySQL 8.4 / Java 21' runs-on: 'ubuntu-latest' # The step ceilings (12+10+20) leave room for the uncapped setup steps; # a healthy run measured ~20 min end to end. timeout-minutes: 60 services: mysql: image: 'mysql:8.4.6' env: MYSQL_DATABASE: 'hosted_harness_test' MYSQL_ROOT_PASSWORD: 'hosted-fixture' ports: - '3306/tcp' options: >- --health-cmd "mysqladmin ping -h 127.0.0.1 -phosted-fixture" --health-interval 5s --health-timeout 5s --health-retries 30 steps: - name: 'Checkout' uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 - name: 'Set up Java' uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0 with: distribution: 'temurin' java-version: '21' cache: 'maven' cache-dependency-path: 'packages/sdk-java/**/pom.xml' settings-path: '${{ runner.temp }}/setup-java-m2' - name: 'Set up Node.js' uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0 with: node-version: '22' package-manager-cache: false - name: 'Enable Corepack' run: 'corepack enable' - name: 'Install, build and bundle CLI' run: |- corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only - name: 'Install Managed Agent dependencies' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: |- mvn --batch-mode --no-transfer-progress -f packages/sdk-java/qwencode/pom.xml -DskipTests -Dgpg.skip=true -Dmaven.javadoc.skip=true install mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -DskipTests install - name: 'Verify Hosted Java, Spring and MySQL processes' timeout-minutes: 12 env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' MYSQL_PORT: "${{ job.services.mysql.ports['3306'] }}" run: >- mvn --batch-mode --no-transfer-progress -f packages/sdk-java/managed-agent-server/pom.xml -Phosted-harness-mysql -Dnode.executable="$(command -v node)" -Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js" -Dmysql.url="jdbc:mysql://127.0.0.1:${MYSQL_PORT}/hosted_harness_test?allowPublicKeyRetrieval=true&useSSL=false" -Dmysql.user=root -Dmysql.password=hosted-fixture clean verify checkstyle:check - name: 'Check that every Hosted integration test class ran' run: 'node scripts/check-failsafe-reports.js hosted packages/sdk-java/managed-agent-server' - name: 'Check Hosted latency measurements' run: |- test -s packages/sdk-java/managed-agent-server/target/hosted-latency-baseline.json cd integration-tests npx vitest run cli/hosted-latency-baseline.test.ts - name: 'Run Runtime Broker fault gates' timeout-minutes: 10 env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: >- mvn --batch-mode --no-transfer-progress -f packages/sdk-java/runtime-broker/pom.xml -Pfault-gates -Dqwen.cli.entry="${GITHUB_WORKSPACE}/dist/cli.js" test - name: 'Install MySQL binaries for the failover E2E' run: |- if ! command -v mysqld >/dev/null 2>&1; then sudo apt-get update sudo apt-get install --yes --no-install-recommends mysql-server mysql-client fi - name: 'Run in-flight owner failover E2E' timeout-minutes: 10 run: >- npm run test:e2e:managed-inflight-failover - name: 'Run continuation owner failover E2E' timeout-minutes: 10 run: >- npm run test:e2e:managed-continuation-failover - name: 'Upload Hosted process reports' if: 'always()' uses: 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a' # v7.0.1 with: name: 'hosted-harness-mysql-reports' path: | packages/sdk-java/managed-agent-server/target/failsafe-reports/ packages/sdk-java/managed-agent-server/target/hosted-latency-baseline.json retention-days: 7 daemon-e2e: name: 'Real daemon E2E / Java 11' runs-on: '${{ (github.repository == ''QwenLM/qwen-code'' && vars.MAINTAINER_ECS_RUNNER_DISABLED != ''true'' && (github.event_name == ''push'' || github.event.pull_request.head.repo.full_name == github.repository || contains(fromJSON(''["OWNER","MEMBER","COLLABORATOR"]''), github.event.pull_request.author_association))) && fromJSON(''["self-hosted", "linux", "x64", "ecs-qwen"]'') || ''ubuntu-latest'' }}' # 45, not 30: this job is ~90% setup. A quiet run is 12.2 min end to end -- # npm ci 5.9 + build 5.2 -- and the Java daemon E2E it exists for costs # about a minute. On the shared pool that setup is what stretches: npm ci # measured 4.0-5.9 min across 11 green runs and 19.9/22.6 min on the two # that died, which is the whole difference between them. At that factor the # setup alone needs ~44 min, so 30 cancelled the job before a single # assertion ran. Matches the release.yml quality_build tier # (QWEN_RELEASE_BUILD_TIMEOUT_MINUTES, default 45). timeout-minutes: 45 steps: - name: 'Restore workspace ownership' if: "${{ runner.environment == 'self-hosted' }}" run: |- set -uo pipefail RUNNER_UID="$(id -u)" RUNNER_GID="$(id -g)" if [ "$RUNNER_UID" != "0" ]; then chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chown -R "$RUNNER_UID:$RUNNER_GID" "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace ownership; checkout may fail on leftover root-owned files" fi chmod -R u+rwX "$GITHUB_WORKSPACE" 2>/dev/null || sudo -n chmod -R u+rwX "$GITHUB_WORKSPACE" || echo "::warning::could not restore workspace write permissions; checkout may fail on leftover read-only files" - name: 'Checkout' uses: 'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10' # v6.0.3 with: ref: "${{ (runner.environment == 'self-hosted' && github.event_name == 'pull_request' && format('refs/pull/{0}/head', github.event.pull_request.number)) || github.ref }}" - name: 'Verify checkout includes expected head commit' if: "${{ runner.environment == 'self-hosted' && github.event_name == 'pull_request' }}" env: EXPECTED_SHA: '${{ github.event.pull_request.head.sha }}' run: |- if ! git merge-base --is-ancestor "${EXPECTED_SHA}" HEAD; then echo "::error::Checked out ref does not contain expected head ${EXPECTED_SHA}." git log --oneline --decorate -5 exit 1 fi # Self-hosted can't reach nodejs.org reliably; reuse the machine's Node. - name: 'Set up Node.js 22 (hosted)' if: "${{ runner.environment == 'github-hosted' }}" uses: 'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e' # v6.4.0 with: node-version: '22' package-manager-cache: false - name: 'Use pre-installed Node.js (self-hosted)' if: "${{ runner.environment == 'self-hosted' }}" run: |- if ! command -v node >/dev/null 2>&1; then echo "::error::Node.js is not on PATH for this self-hosted runner. Provision Node 22.x or set MAINTAINER_ECS_RUNNER_DISABLED=true." exit 1 fi echo "Using pre-installed Node $(node -v) / npm $(npm -v)" if [[ "$(node -p 'process.versions.node.split(".")[0]')" != "22" ]]; then echo "::warning::Expected Node 22.x but found $(node -v); daemon E2E will run against the runner's Node." fi - name: 'Set up Java' uses: 'actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654' # v5.2.0 with: distribution: 'temurin' java-version: '11' cache: 'maven' cache-dependency-path: 'packages/sdk-java/qwencode/pom.xml' settings-path: '${{ runner.temp }}/setup-java-m2' - name: 'Set up Maven (self-hosted)' if: "${{ runner.environment == 'self-hosted' }}" run: |- archive="${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" curl -fsSL "https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/${MAVEN_VERSION}/apache-maven-${MAVEN_VERSION}-bin.tar.gz" -o "${archive}" echo "${MAVEN_SHA512} ${archive}" | sha512sum --check --status tar -xzf "${archive}" -C "${RUNNER_TEMP}" echo "${RUNNER_TEMP}/apache-maven-${MAVEN_VERSION}/bin" >> "${GITHUB_PATH}" - name: 'Install Node.js dependencies' run: 'corepack pnpm install --frozen-lockfile --prefer-offline --reporter=append-only' - name: 'Build Qwen Code' run: 'npm run build' - name: 'Bundle Qwen Code' run: 'npm run bundle' - name: 'Run Java daemon E2E' env: MAVEN_ARGS: '--settings ${{ runner.temp }}/setup-java-m2/settings.xml --toolchains ${{ runner.temp }}/setup-java-m2/toolchains.xml' run: 'npx tsx scripts/run-java-daemon-sdk-e2e.ts'