diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b44df88ad9..e5115432d8c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -455,7 +455,9 @@ jobs: runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }} timeout-minutes: 20 outputs: - gateway-watch-result: ${{ steps.gateway_watch.outcome }} + channels-result: ${{ steps.built_artifact_checks.outputs['channels-result'] }} + core-support-boundary-result: ${{ steps.built_artifact_checks.outputs['core-support-boundary-result'] }} + gateway-watch-result: ${{ steps.built_artifact_checks.outputs['gateway-watch-result'] }} steps: - name: Checkout shell: bash @@ -568,11 +570,75 @@ jobs: - name: Check CLI startup memory run: pnpm test:startup:memory - - name: Run gateway watch regression - id: gateway_watch - if: needs.preflight.outputs.run_check_additional == 'true' - continue-on-error: true - run: node scripts/check-gateway-watch-regression.mjs --skip-build + - name: Run built artifact checks + id: built_artifact_checks + if: needs.preflight.outputs.run_checks == 'true' || needs.preflight.outputs.run_checks_node_core_dist == 'true' || needs.preflight.outputs.run_check_additional == 'true' + env: + RUN_CHANNELS: ${{ needs.preflight.outputs.run_checks }} + RUN_CORE_SUPPORT_BOUNDARY: ${{ needs.preflight.outputs.run_checks_node_core_dist }} + RUN_GATEWAY_WATCH: ${{ needs.preflight.outputs.run_check_additional }} + shell: bash + run: | + set -uo pipefail + + names=() + pids=() + logs=() + declare -A results=( + ["channels"]="skipped" + ["core-support-boundary"]="skipped" + ["gateway-watch"]="skipped" + ) + + start_check() { + local name="$1" + shift + local log="${RUNNER_TEMP}/${name}.log" + names+=("$name") + logs+=("$log") + echo "starting ${name}: $*" + "$@" >"$log" 2>&1 & + pids+=("$!") + } + + if [ "$RUN_CHANNELS" = "true" ]; then + start_check "channels" env \ + NODE_OPTIONS=--max-old-space-size=6144 \ + OPENCLAW_VITEST_MAX_WORKERS=1 \ + pnpm test:channels + fi + + if [ "$RUN_CORE_SUPPORT_BOUNDARY" = "true" ]; then + start_check "core-support-boundary" env \ + NODE_OPTIONS=--max-old-space-size=6144 \ + OPENCLAW_VITEST_MAX_WORKERS=2 \ + node scripts/run-vitest.mjs run --config test/vitest/vitest.full-core-support-boundary.config.ts + fi + + if [ "$RUN_GATEWAY_WATCH" = "true" ]; then + start_check "gateway-watch" node scripts/check-gateway-watch-regression.mjs --skip-build + fi + + for index in "${!pids[@]}"; do + name="${names[$index]}" + log="${logs[$index]}" + pid="${pids[$index]}" + + if wait "$pid"; then + result="success" + else + result="failure" + fi + + echo "::group::${name} log" + cat "$log" + echo "::endgroup::" + results["$name"]="$result" + done + + for name in channels core-support-boundary gateway-watch; do + echo "${name}-result=${results[$name]}" >> "$GITHUB_OUTPUT" + done - name: Upload gateway watch regression artifacts if: always() && needs.preflight.outputs.run_check_additional == 'true' @@ -936,117 +1002,25 @@ jobs: name: ${{ matrix.check_name }} needs: [preflight, build-artifacts] if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks == 'true' && needs.build-artifacts.result == 'success' }} - runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }} - timeout-minutes: 60 + runs-on: ubuntu-24.04 + timeout-minutes: 5 strategy: fail-fast: false matrix: ${{ fromJson(needs.preflight.outputs.checks_matrix) }} steps: - - name: Checkout - shell: bash - env: - CHECKOUT_REPO: ${{ github.repository }} - CHECKOUT_SHA: ${{ github.sha }} - CHECKOUT_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - workdir="$GITHUB_WORKSPACE" - auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')" - - reset_checkout_dir() { - mkdir -p "$workdir" - find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} + - } - - checkout_attempt() { - local attempt="$1" - - reset_checkout_dir - git init "$workdir" >/dev/null - git config --global --add safe.directory "$workdir" - git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}" - git -C "$workdir" config gc.auto 0 - - timeout --signal=TERM 30s git -C "$workdir" \ - -c protocol.version=2 \ - -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ - "+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1 - - git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1 - test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1 - echo "checkout attempt ${attempt}/2 succeeded" - } - - for attempt in 1 2; do - if checkout_attempt "$attempt"; then - exit 0 - fi - echo "checkout attempt ${attempt}/2 failed" - sleep $((attempt * 5)) - done - - echo "checkout failed after 2 attempts" >&2 - exit 1 - - - name: Setup Node environment - uses: ./.github/actions/setup-node-env - with: - node-version: "${{ matrix.node_version || '24.x' }}" - cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24' }}" - install-bun: "false" - - - name: Configure Node test resources - if: matrix.runtime == 'node' && (matrix.task == 'test' || matrix.task == 'channels') + - name: Verify ${{ matrix.task }} (${{ matrix.runtime }}) env: TASK: ${{ matrix.task }} - run: | - echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV" - if [ "$TASK" = "test" ]; then - echo "OPENCLAW_TEST_PROJECTS_LEAF_SHARDS=1" >> "$GITHUB_ENV" - echo "OPENCLAW_TEST_SKIP_FULL_EXTENSIONS_SHARD=1" >> "$GITHUB_ENV" - fi - if [ "$TASK" = "channels" ]; then - echo "OPENCLAW_VITEST_MAX_WORKERS=1" >> "$GITHUB_ENV" - fi - - - name: Restore dist cache - if: matrix.task == 'test' - id: checks-dist-cache - uses: actions/cache@v5 - with: - path: | - dist/ - dist-runtime/ - key: ${{ runner.os }}-dist-build-${{ github.sha }} - - - name: Verify dist cache - if: matrix.task == 'test' && steps.checks-dist-cache.outputs.cache-hit != 'true' - run: | - echo "Missing same-run dist cache for ${RUNNER_OS}-dist-build-${GITHUB_SHA}" >&2 - exit 1 - - - name: Download A2UI bundle artifact - if: matrix.task == 'test' || matrix.task == 'channels' - uses: actions/download-artifact@v8 - with: - name: canvas-a2ui-bundle - path: src/canvas-host/a2ui/ - - - name: Run ${{ matrix.task }} (${{ matrix.runtime }}) - env: - TASK: ${{ matrix.task }} - NODE_OPTIONS: --max-old-space-size=6144 + CHANNELS_RESULT: ${{ needs.build-artifacts.outputs['channels-result'] }} shell: bash run: | set -euo pipefail case "$TASK" in - test) - pnpm test - ;; channels) - pnpm test:channels + if [ "$CHANNELS_RESULT" != "success" ]; then + echo "Channel tests failed in build-artifacts: $CHANNELS_RESULT" >&2 + exit 1 + fi ;; *) echo "Unsupported checks task: $TASK" >&2 @@ -1261,144 +1235,31 @@ jobs: name: ${{ matrix.check_name }} needs: [preflight, build-artifacts] if: ${{ !cancelled() && always() && needs.preflight.outputs.run_checks_node_core_dist == 'true' && needs.build-artifacts.result == 'success' }} - runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04' }} - timeout-minutes: 60 + runs-on: ubuntu-24.04 + timeout-minutes: 5 strategy: fail-fast: false matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_dist_matrix) }} steps: - - name: Checkout - shell: bash + - name: Verify Node test shard env: - CHECKOUT_REPO: ${{ github.repository }} - CHECKOUT_SHA: ${{ github.sha }} - CHECKOUT_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - - workdir="$GITHUB_WORKSPACE" - auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')" - - reset_checkout_dir() { - mkdir -p "$workdir" - find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} + - } - - checkout_attempt() { - local attempt="$1" - - reset_checkout_dir - git init "$workdir" >/dev/null - git config --global --add safe.directory "$workdir" - git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}" - git -C "$workdir" config gc.auto 0 - - timeout --signal=TERM 30s git -C "$workdir" \ - -c protocol.version=2 \ - -c "http.https://github.com/.extraheader=AUTHORIZATION: basic ${auth_header}" \ - fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ - "+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1 - - git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1 - test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1 - echo "checkout attempt ${attempt}/2 succeeded" - } - - for attempt in 1 2; do - if checkout_attempt "$attempt"; then - exit 0 - fi - echo "checkout attempt ${attempt}/2 failed" - sleep $((attempt * 5)) - done - - echo "checkout failed after 2 attempts" >&2 - exit 1 - - - name: Setup Node environment - uses: ./.github/actions/setup-node-env - with: - node-version: "${{ matrix.node_version || '24.x' }}" - cache-key-suffix: "${{ matrix.cache_key_suffix || 'node24' }}" - install-bun: "false" - - - name: Configure Node test resources - run: echo "OPENCLAW_VITEST_MAX_WORKERS=2" >> "$GITHUB_ENV" - - - name: Restore dist cache - id: dist-cache - uses: actions/cache@v5 - with: - path: | - dist/ - dist-runtime/ - key: ${{ runner.os }}-dist-build-${{ github.sha }} - - - name: Verify dist cache - if: steps.dist-cache.outputs.cache-hit != 'true' - run: | - echo "Missing same-run dist cache for ${RUNNER_OS}-dist-build-${GITHUB_SHA}" >&2 - exit 1 - - - name: Download A2UI bundle artifact - uses: actions/download-artifact@v8 - with: - name: canvas-a2ui-bundle - path: src/canvas-host/a2ui/ - - - name: Run Node test shard - env: - NODE_OPTIONS: --max-old-space-size=6144 - OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }} - OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }} + CORE_SUPPORT_BOUNDARY_RESULT: ${{ needs.build-artifacts.outputs['core-support-boundary-result'] }} + SHARD_NAME: ${{ matrix.shard_name }} shell: bash run: | set -euo pipefail - node --input-type=module <<'EOF' - import { spawnSync } from "node:child_process"; - import { writeFileSync } from "node:fs"; - import { join } from "node:path"; - import { resolveVitestCliEntry, resolveVitestNodeArgs } from "./scripts/run-vitest.mjs"; - - const configs = JSON.parse(process.env.OPENCLAW_NODE_TEST_CONFIGS_JSON ?? "[]"); - if (!Array.isArray(configs) || configs.length === 0) { - console.error("Missing node test shard configs"); - process.exit(1); - } - const includePatterns = JSON.parse(process.env.OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON ?? "null"); - const childEnv = { ...process.env }; - if (Array.isArray(includePatterns) && includePatterns.length > 0) { - const includeFile = join( - process.env.RUNNER_TEMP ?? ".", - `node-test-include-${process.env.GITHUB_JOB ?? "local"}-${Date.now()}.json`, - ); - writeFileSync(includeFile, JSON.stringify(includePatterns), "utf8"); - childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile; - } - - for (const config of configs) { - console.error(`[test] starting ${config}`); - const result = spawnSync( - "pnpm", - [ - "exec", - "node", - ...resolveVitestNodeArgs(process.env), - resolveVitestCliEntry(), - "run", - "--config", - config, - ], - { - env: childEnv, - stdio: "inherit", - }, - ); - if ((result.status ?? 1) !== 0) { - process.exit(result.status ?? 1); - } - } - EOF + case "$SHARD_NAME" in + core-support-boundary) + if [ "$CORE_SUPPORT_BOUNDARY_RESULT" != "success" ]; then + echo "Core support boundary shard failed in build-artifacts: $CORE_SUPPORT_BOUNDARY_RESULT" >&2 + exit 1 + fi + ;; + *) + echo "Unsupported built-artifact shard: $SHARD_NAME" >&2 + exit 1 + ;; + esac checks-node-core-test: permissions: diff --git a/docs/ci.md b/docs/ci.md index 765b1f7f853..3b76b4dc1c0 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -18,7 +18,7 @@ The CI runs on every push to `main` and every pull request. It uses smart scopin | `security-scm-fast` | Private key detection and workflow audit via `zizmor` | Always on non-draft pushes and PRs | | `security-dependency-audit` | Dependency-free production lockfile audit against npm advisories | Always on non-draft pushes and PRs | | `security-fast` | Required aggregate for the fast security jobs | Always on non-draft pushes and PRs | -| `build-artifacts` | Build `dist/`, Control UI, gateway watch, and reusable downstream artifacts | Node-relevant changes | +| `build-artifacts` | Build `dist/`, Control UI, built-artifact checks, and reusable downstream artifacts | Node-relevant changes | | `checks-fast-core` | Fast Linux correctness lanes such as bundled/plugin-contract/protocol checks | Node-relevant changes | | `checks-fast-contracts-channels` | Sharded channel contract checks with a stable aggregate check result | Node-relevant changes | | `checks-node-extensions` | Full bundled-plugin test shards across the extension suite | Node-relevant changes | @@ -27,7 +27,7 @@ The CI runs on every push to `main` and every pull request. It uses smart scopin | `check` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes | | `check-additional` | Architecture, boundary, extension-surface guards, package-boundary, and gateway-watch shards | Node-relevant changes | | `build-smoke` | Built-CLI smoke tests and startup-memory smoke | Node-relevant changes | -| `checks` | Remaining Linux Node lanes: channel tests and push-only Node 22 compatibility | Node-relevant changes | +| `checks` | Verifier for built-artifact channel tests plus push-only Node 22 compatibility | Node-relevant changes | | `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed | | `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes | | `checks-windows` | Windows-specific test lanes | Windows-relevant changes | @@ -53,7 +53,7 @@ Local changed-lane logic lives in `scripts/changed-lanes.mjs` and is executed by On pushes, the `checks` matrix adds the push-only `compat-node22` lane. On pull requests, that lane is skipped and the matrix stays focused on the normal test/channel lanes. -The slowest Node test families are split or balanced so each job stays small: channel contracts split registry and core coverage into six weighted shards total, bundled plugin tests balance across six extension workers, auto-reply runs as three balanced workers instead of six tiny workers, and agentic gateway/plugin configs are spread across the existing source-only agentic Node jobs instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. The broad agents lane uses the shared Vitest file-parallel scheduler because it is import/scheduling dominated rather than owned by a single slow test file. `runtime-config` runs with the infra core-runtime shard to keep the shared runtime shard from owning the tail. `check-additional` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard shard runs its small independent guards concurrently inside one job, and the gateway watch regression runs inside `build-artifacts` after `dist/` and `dist-runtime/` are already built so it measures watch stability without reserving another runner or rebuilding runtime artifacts. +The slowest Node test families are split or balanced so each job stays small: channel contracts split registry and core coverage into six weighted shards total, bundled plugin tests balance across six extension workers, auto-reply runs as three balanced workers instead of six tiny workers, and agentic gateway/plugin configs are spread across the existing source-only agentic Node jobs instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. The broad agents lane uses the shared Vitest file-parallel scheduler because it is import/scheduling dominated rather than owned by a single slow test file. `runtime-config` runs with the infra core-runtime shard to keep the shared runtime shard from owning the tail. `check-additional` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard shard runs its small independent guards concurrently inside one job. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built, keeping their old check names as lightweight verifier jobs while avoiding two extra Blacksmith workers and a second artifact-consumer queue. Android CI runs both `testPlayDebugUnitTest` and `testThirdPartyDebugUnitTest`, then builds the Play debug APK. The third-party flavor has no separate source set or manifest; its unit-test lane still compiles that flavor with the SMS/call-log BuildConfig flags, while avoiding a duplicate debug APK packaging job on every Android-relevant push. `extension-fast` is PR-only because push runs already execute the full bundled plugin shards. That keeps changed-plugin feedback for reviews without reserving an extra Blacksmith worker on `main` for coverage already present in `checks-node-extensions`. @@ -65,7 +65,7 @@ The CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an ol | Runner | Jobs | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ubuntu-24.04` | `preflight`, fast security jobs and aggregates (`security-scm-fast`, `security-dependency-audit`, `security-fast`), fast protocol/contract/bundled checks, sharded channel contract checks, `check` shards except lint, `check-additional` shards and aggregates, Node test aggregate verifiers, docs checks, Python skills, workflow-sanity, labeler, auto-response; install-smoke preflight also uses GitHub-hosted Ubuntu so the Blacksmith matrix can queue earlier | -| `blacksmith-8vcpu-ubuntu-2404` | `build-artifacts`, build-smoke, Linux Node test shards, bundled plugin test shards, remaining built-artifact consumers, `android` | +| `blacksmith-8vcpu-ubuntu-2404` | `build-artifacts`, build-smoke, Linux Node test shards, bundled plugin test shards, `android` | | `blacksmith-16vcpu-ubuntu-2404` | `check-lint`, which remains CPU-sensitive enough that 8 vCPU cost more than it saved; install-smoke Docker builds, where 32-vCPU queue time cost more than it saved | | `blacksmith-16vcpu-windows-2025` | `checks-windows` | | `blacksmith-6vcpu-macos-latest` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-latest` |