name: Package Acceptance on: workflow_dispatch: inputs: workflow_ref: description: Trusted repo ref for workflow scripts and Docker E2E harness required: true default: main type: string source: description: Package candidate source required: true default: npm type: choice options: - npm - ref - url - trusted-url - artifact package_ref: description: Trusted package source ref when source=ref required: true default: main type: string package_spec: description: Published package spec when source=npm required: false default: openclaw@beta type: string package_url: description: HTTPS .tgz URL when source=url or source=trusted-url required: false default: "" type: string package_sha256: description: Expected package SHA-256; required for source=url, source=trusted-url, or source=artifact required: false default: "" type: string trusted_source_id: description: Named trusted source policy when source=trusted-url required: false default: "" type: string artifact_run_id: description: GitHub Actions run id when source=artifact required: false default: "" type: string artifact_name: description: Artifact name containing one .tgz when source=artifact required: false default: package-under-test type: string artifact_id: description: Immutable GitHub artifact id when source=artifact required: false default: "" type: string artifact_digest: description: GitHub artifact service SHA-256 digest without the sha256 prefix required: false default: "" type: string artifact_run_attempt: description: Producer run attempt when source=artifact required: false default: "" type: string package_file_name: description: Exact package tarball filename when source=artifact required: false default: "" type: string package_source_sha: description: Exact source commit recorded in the package when source=artifact required: false default: "" type: string package_version: description: Exact package version when source=artifact required: false default: "" type: string shared_image_policy: description: Shared Docker image transport for package acceptance required: true default: no-push-artifact type: choice options: - existing-only - no-push-artifact shared_image_artifact_namespace: description: Unique artifact namespace when shared_image_policy=no-push-artifact required: false default: package-acceptance type: string suite_profile: description: Acceptance profile required: true default: package type: choice options: - smoke - package - product - full - custom docker_lanes: description: Comma/space separated Docker lanes when suite_profile=custom required: false default: "" type: string published_upgrade_survivor_baseline: description: Published OpenClaw package baseline for the published-upgrade-survivor Docker lane required: false default: openclaw@latest type: string published_upgrade_survivor_baselines: description: Optional baseline list for published-upgrade-survivor/update-migration; use last-stable-4, all-since-2026.4.23, release-history, or exact versions required: false default: "" type: string published_upgrade_survivor_scenarios: description: Optional scenario list for published-upgrade-survivor/update-migration; use reported-issues for known upgrade failure shapes required: false default: "" type: string telegram_mode: description: Optional Telegram QA lane for the resolved package candidate required: true default: none type: choice options: - none - mock-openai - live-frontier telegram_scenarios: description: Optional comma-separated Telegram scenario ids required: false default: "" type: string advisory: description: Treat acceptance failures as advisory for the caller required: false default: false type: boolean workflow_call: inputs: advisory: description: Treat acceptance failures as advisory for the caller required: false default: false type: boolean workflow_ref: description: Trusted repo ref for workflow scripts and Docker E2E harness required: false default: main type: string source: description: "Package candidate source: npm, ref, url, trusted-url, or artifact" required: true type: string package_ref: description: Trusted package source ref when source=ref required: false default: main type: string package_spec: description: Published package spec when source=npm required: false default: openclaw@beta type: string package_url: description: HTTPS .tgz URL when source=url or source=trusted-url required: false default: "" type: string package_sha256: description: Expected package SHA-256; required for source=url, source=trusted-url, or source=artifact required: false default: "" type: string trusted_source_id: description: Named trusted source policy when source=trusted-url required: false default: "" type: string artifact_run_id: description: GitHub Actions run id when source=artifact required: false default: "" type: string artifact_name: description: Artifact name containing one .tgz when source=artifact required: false default: package-under-test type: string artifact_id: description: Immutable GitHub artifact id when source=artifact required: false default: "" type: string artifact_digest: description: GitHub artifact service SHA-256 digest without the sha256 prefix required: false default: "" type: string artifact_run_attempt: description: Producer run attempt when source=artifact required: false default: "" type: string package_file_name: description: Exact package tarball filename when source=artifact required: false default: "" type: string package_source_sha: description: Exact source commit recorded in the package when source=artifact required: false default: "" type: string package_version: description: Exact package version when source=artifact required: false default: "" type: string shared_image_policy: description: "Shared Docker image transport: existing-only or no-push-artifact" required: false default: no-push-artifact type: string shared_image_artifact_namespace: description: Unique artifact namespace when shared_image_policy=no-push-artifact required: false default: package-acceptance type: string suite_profile: description: "Acceptance profile: smoke, package, product, full, or custom" required: false default: package type: string docker_lanes: description: Comma/space separated Docker lanes when suite_profile=custom required: false default: "" type: string published_upgrade_survivor_baseline: description: Published OpenClaw package baseline for the published-upgrade-survivor Docker lane required: false default: openclaw@latest type: string published_upgrade_survivor_baselines: description: Optional baseline list for published-upgrade-survivor/update-migration; use last-stable-4, all-since-2026.4.23, release-history, or exact versions required: false default: "" type: string published_upgrade_survivor_scenarios: description: Optional scenario list for published-upgrade-survivor/update-migration; use reported-issues for known upgrade failure shapes required: false default: "" type: string telegram_mode: description: Optional Telegram QA lane for the resolved package candidate required: false default: none type: string telegram_scenarios: description: Optional comma-separated Telegram scenario ids required: false default: "" type: string outputs: package_artifact_digest: description: GitHub artifact service digest for the canonical package value: ${{ jobs.resolve_package.outputs.package_artifact_digest }} package_artifact_id: description: Immutable GitHub artifact id for the canonical package value: ${{ jobs.resolve_package.outputs.package_artifact_id }} package_artifact_run_attempt: description: Producer run attempt for the canonical package artifact value: ${{ jobs.resolve_package.outputs.package_artifact_run_attempt }} package_artifact_run_id: description: Producer run id for the canonical package artifact value: ${{ jobs.resolve_package.outputs.package_artifact_run_id }} package_file_name: description: Canonical package tarball filename value: ${{ jobs.resolve_package.outputs.package_file_name }} package_source_sha: description: Source commit recorded in the canonical package value: ${{ jobs.resolve_package.outputs.package_source_sha }} package_sha256: description: Canonical OpenClaw package SHA-256 value: ${{ jobs.resolve_package.outputs.package_sha256 }} package_version: description: Canonical OpenClaw package version value: ${{ jobs.resolve_package.outputs.package_version }} secrets: OPENCLAW_TRUSTED_PACKAGE_TOKEN: required: false OPENAI_API_KEY: required: false OPENAI_BASE_URL: required: false ANTHROPIC_API_KEY: required: false ANTHROPIC_API_KEY_OLD: required: false ANTHROPIC_API_TOKEN: required: false FACTORY_API_KEY: required: false BYTEPLUS_API_KEY: required: false CEREBRAS_API_KEY: required: false DEEPINFRA_API_KEY: required: false DASHSCOPE_API_KEY: required: false GROQ_API_KEY: required: false KIMI_API_KEY: required: false MODELSTUDIO_API_KEY: required: false MOONSHOT_API_KEY: required: false MISTRAL_API_KEY: required: false MINIMAX_API_KEY: required: false OPENCODE_API_KEY: required: false OPENCODE_ZEN_API_KEY: required: false OPENCLAW_LIVE_BROWSER_CDP_URL: required: false OPENCLAW_LIVE_SETUP_TOKEN: required: false OPENCLAW_LIVE_SETUP_TOKEN_MODEL: required: false OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: required: false OPENCLAW_LIVE_SETUP_TOKEN_VALUE: required: false GEMINI_API_KEY: required: false GOOGLE_API_KEY: required: false OPENROUTER_API_KEY: required: false QWEN_API_KEY: required: false FAL_KEY: required: false RUNWAY_API_KEY: required: false DEEPGRAM_API_KEY: required: false TOGETHER_API_KEY: required: false VYDRA_API_KEY: required: false XAI_API_KEY: required: false ZAI_API_KEY: required: false Z_AI_API_KEY: required: false BYTEPLUS_ACCESS_KEY_ID: required: false BYTEPLUS_SECRET_ACCESS_KEY: required: false CLAUDE_CODE_OAUTH_TOKEN: required: false OPENCLAW_CODEX_AUTH_JSON: required: false OPENCLAW_CODEX_CONFIG_TOML: required: false OPENCLAW_CLAUDE_JSON: required: false OPENCLAW_CLAUDE_CREDENTIALS_JSON: required: false OPENCLAW_CLAUDE_SETTINGS_JSON: required: false OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: required: false OPENCLAW_GEMINI_SETTINGS_JSON: required: false FIREWORKS_API_KEY: required: false OPENCLAW_QA_CONVEX_SITE_URL: required: false OPENCLAW_QA_CONVEX_SECRET_CI: required: false permissions: actions: read contents: read packages: read pull-requests: read concurrency: group: package-acceptance-${{ github.run_id }} cancel-in-progress: false env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" NODE_VERSION: "24.15.0" PACKAGE_ARTIFACT_NAME: package-under-test-${{ github.run_id }}-${{ github.run_attempt }} jobs: resolve_package: name: Resolve package candidate runs-on: ubuntu-24.04 timeout-minutes: 60 outputs: docker_lanes: ${{ steps.profile.outputs.docker_lanes }} include_live_suites: ${{ steps.profile.outputs.include_live_suites }} include_openwebui: ${{ steps.profile.outputs.include_openwebui }} include_release_path_suites: ${{ steps.profile.outputs.include_release_path_suites }} package_artifact_name: ${{ steps.profile.outputs.package_artifact_name }} package_artifact_digest: ${{ steps.upload_package.outputs.artifact-digest }} package_artifact_id: ${{ steps.upload_package.outputs.artifact-id }} package_artifact_run_attempt: ${{ github.run_attempt }} package_artifact_run_id: ${{ github.run_id }} package_file_name: ${{ steps.resolve.outputs.package_file_name }} package_source_sha: ${{ steps.resolve.outputs.package_source_sha }} package_sha256: ${{ steps.resolve.outputs.sha256 }} package_version: ${{ steps.resolve.outputs.package_version }} published_upgrade_survivor_baselines: ${{ steps.upgrade_survivor_baselines.outputs.baselines }} published_upgrade_survivor_scenarios: ${{ inputs.published_upgrade_survivor_scenarios }} telegram_enabled: ${{ steps.profile.outputs.telegram_enabled }} telegram_mode: ${{ steps.profile.outputs.telegram_mode }} steps: - name: Checkout package workflow ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ inputs.workflow_ref }} fetch-depth: 0 persist-credentials: false - name: Setup Node environment uses: ./.github/actions/setup-node-env with: node-version: ${{ env.NODE_VERSION }} install-bun: ${{ inputs.source == 'ref' && 'true' || 'false' }} install-deps: "false" - name: Validate package artifact input identity id: input_artifact if: inputs.source == 'artifact' env: ARTIFACT_DIGEST: ${{ inputs.artifact_digest }} ARTIFACT_ID: ${{ inputs.artifact_id }} GH_TOKEN: ${{ github.token }} ARTIFACT_NAME: ${{ inputs.artifact_name }} ARTIFACT_RUN_ATTEMPT: ${{ inputs.artifact_run_attempt }} ARTIFACT_RUN_ID: ${{ inputs.artifact_run_id }} EXPECTED_PACKAGE_SHA256: ${{ inputs.package_sha256 }} EXPECTED_PACKAGE_FILE_NAME: ${{ inputs.package_file_name }} EXPECTED_PACKAGE_SOURCE_SHA: ${{ inputs.package_source_sha }} EXPECTED_PACKAGE_VERSION: ${{ inputs.package_version }} shell: bash run: | set -euo pipefail if [[ ! "$ARTIFACT_DIGEST" =~ ^[0-9a-f]{64}$ || ! "$ARTIFACT_ID" =~ ^[1-9][0-9]*$ || -z "${ARTIFACT_NAME// }" || ! "$ARTIFACT_RUN_ATTEMPT" =~ ^[1-9][0-9]*$ || ! "$ARTIFACT_RUN_ID" =~ ^[1-9][0-9]*$ || ! "$EXPECTED_PACKAGE_FILE_NAME" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*\.tgz$ || ! "$EXPECTED_PACKAGE_SHA256" =~ ^[0-9a-f]{64}$ || ! "$EXPECTED_PACKAGE_SOURCE_SHA" =~ ^[0-9a-f]{40}$ || -z "${EXPECTED_PACKAGE_VERSION// }" ]]; then echo "source=artifact requires the complete immutable artifact and package identity tuple." >&2 exit 1 fi [[ "$ARTIFACT_NAME" == *"-${ARTIFACT_RUN_ID}-${ARTIFACT_RUN_ATTEMPT}" ]] || { echo "Package artifact name does not bind the declared producer run attempt." >&2 exit 1 } artifact_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/artifacts/${ARTIFACT_ID}")" jq -e \ --arg digest "sha256:${ARTIFACT_DIGEST}" \ --arg id "$ARTIFACT_ID" \ --arg name "$ARTIFACT_NAME" \ --arg run_id "$ARTIFACT_RUN_ID" \ ' (.id | tostring) == $id and .name == $name and .expired == false and .digest == $digest and (.workflow_run.id | tostring) == $run_id ' <<< "$artifact_json" >/dev/null || { echo "Package artifact identity does not match the requested immutable tuple." >&2 exit 1 } attempt_json="$( gh api \ "repos/${GITHUB_REPOSITORY}/actions/runs/${ARTIFACT_RUN_ID}/attempts/${ARTIFACT_RUN_ATTEMPT}" )" jq -e \ --arg attempt "$ARTIFACT_RUN_ATTEMPT" \ --arg run_id "$ARTIFACT_RUN_ID" \ '(.id | tostring) == $run_id and (.run_attempt | tostring) == $attempt' \ <<< "$attempt_json" >/dev/null || { echo "Package artifact producer run attempt does not match the requested tuple." >&2 exit 1 } { echo "artifact_digest=$ARTIFACT_DIGEST" echo "run_attempt=$ARTIFACT_RUN_ATTEMPT" echo "run_id=$ARTIFACT_RUN_ID" } >> "$GITHUB_OUTPUT" - name: Download package artifact input if: inputs.source == 'artifact' uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: artifact-ids: ${{ inputs.artifact_id }} path: .artifacts/package-candidate-input run-id: ${{ inputs.artifact_run_id }} github-token: ${{ github.token }} - name: Resolve package candidate id: resolve env: SOURCE: ${{ inputs.source }} PACKAGE_REF: ${{ inputs.package_ref }} PACKAGE_SPEC: ${{ inputs.package_spec }} PACKAGE_URL: ${{ inputs.package_url }} PACKAGE_SHA256: ${{ inputs.package_sha256 }} PACKAGE_FILE_NAME: ${{ inputs.package_file_name }} PACKAGE_SOURCE_SHA: ${{ inputs.package_source_sha }} PACKAGE_VERSION: ${{ inputs.package_version }} TRUSTED_SOURCE_ID: ${{ inputs.trusted_source_id }} OPENCLAW_TRUSTED_PACKAGE_TOKEN: ${{ secrets.OPENCLAW_TRUSTED_PACKAGE_TOKEN }} shell: bash run: | set -euo pipefail artifact_dir="" if [[ "$SOURCE" == "artifact" ]]; then artifact_dir=".artifacts/package-candidate-input" artifact_tarball="${artifact_dir}/${PACKAGE_FILE_NAME}" [[ -f "$artifact_tarball" ]] || { echo "Declared package tarball is missing from the selected artifact." >&2 exit 1 } artifact_sha256="$(sha256sum "$artifact_tarball" | awk '{print $1}')" [[ "$artifact_sha256" == "$PACKAGE_SHA256" ]] || { echo "Selected artifact package SHA-256 differs from package_sha256." >&2 exit 1 } fi node scripts/resolve-openclaw-package-candidate.mjs \ --source "$SOURCE" \ --package-ref "$PACKAGE_REF" \ --package-spec "$PACKAGE_SPEC" \ --package-url "$PACKAGE_URL" \ --package-sha256 "$PACKAGE_SHA256" \ --trusted-source-id "$TRUSTED_SOURCE_ID" \ --artifact-dir "${artifact_dir:-.}" \ --output-dir .artifacts/docker-e2e-package \ --output-name openclaw-current.tgz \ --metadata .artifacts/docker-e2e-package/package-candidate.json \ --github-output "$GITHUB_OUTPUT" echo "package_file_name=openclaw-current.tgz" >> "$GITHUB_OUTPUT" if [[ "$SOURCE" == "artifact" ]]; then jq -e \ --arg digest "$PACKAGE_SHA256" \ --arg source_sha "$PACKAGE_SOURCE_SHA" \ --arg version "$PACKAGE_VERSION" \ ' .name == "openclaw" and .sha256 == $digest and .packageSourceSha == $source_sha and .version == $version ' .artifacts/docker-e2e-package/package-candidate.json >/dev/null || { echo "Resolved package identity differs from the declared immutable tuple." >&2 exit 1 } fi - name: Select acceptance profile id: profile env: SOURCE: ${{ inputs.source }} SUITE_PROFILE: ${{ inputs.suite_profile }} CUSTOM_DOCKER_LANES: ${{ inputs.docker_lanes }} TELEGRAM_MODE: ${{ inputs.telegram_mode }} shell: bash run: | set -euo pipefail include_release_path_suites=false include_openwebui=false include_live_suites=false docker_lanes="" case "$SUITE_PROFILE" in smoke) docker_lanes="npm-onboard-channel-agent gateway-network config-reload" ;; package) docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update" ;; product) docker_lanes="npm-onboard-channel-agent doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins plugin-update mcp-channels cron-mcp-cleanup openai-web-search-minimal openwebui" include_openwebui=true ;; full) include_release_path_suites=true include_openwebui=true ;; custom) docker_lanes="$CUSTOM_DOCKER_LANES" if [[ -z "${docker_lanes// }" ]]; then echo "docker_lanes is required when suite_profile=custom." >&2 exit 1 fi if [[ "$docker_lanes" == *"openwebui"* ]]; then include_openwebui=true fi ;; *) echo "Unknown suite_profile: $SUITE_PROFILE" >&2 exit 1 ;; esac telegram_enabled=false if [[ "$TELEGRAM_MODE" != "none" ]]; then telegram_enabled=true fi { echo "docker_lanes=$docker_lanes" echo "include_release_path_suites=$include_release_path_suites" echo "include_openwebui=$include_openwebui" echo "include_live_suites=$include_live_suites" echo "telegram_enabled=$telegram_enabled" echo "telegram_mode=$TELEGRAM_MODE" echo "package_artifact_name=${PACKAGE_ARTIFACT_NAME}" } >> "$GITHUB_OUTPUT" - name: Resolve published upgrade survivor baselines id: upgrade_survivor_baselines env: FALLBACK_BASELINE: ${{ inputs.published_upgrade_survivor_baseline }} REQUESTED_BASELINES: ${{ inputs.published_upgrade_survivor_baselines }} GH_TOKEN: ${{ github.token }} shell: bash run: | set -euo pipefail if [[ -z "${REQUESTED_BASELINES// }" ]]; then echo "baselines=" >> "$GITHUB_OUTPUT" exit 0 fi releases_json="" npm_versions_json="" if [[ "$REQUESTED_BASELINES" == *"release-history"* || "$REQUESTED_BASELINES" == *"all-since-"* || "$REQUESTED_BASELINES" == *"last-stable-"* ]]; then releases_json=".artifacts/package-candidate-input/openclaw-releases.json" npm_versions_json=".artifacts/package-candidate-input/openclaw-npm-versions.json" mkdir -p "$(dirname "$releases_json")" gh release list --repo "$GITHUB_REPOSITORY" --limit 100 --json tagName,publishedAt,isPrerelease > "$releases_json" npm view openclaw versions --json > "$npm_versions_json" fi args=( --requested "$REQUESTED_BASELINES" --fallback "$FALLBACK_BASELINE" --github-output "$GITHUB_OUTPUT" ) if [[ -n "$releases_json" ]]; then args+=( --releases-json "$releases_json" --npm-versions-json "$npm_versions_json" --history-count 6 --include-version 2026.4.23 --pre-date 2026-03-15T00:00:00Z ) fi node scripts/resolve-upgrade-survivor-baselines.mjs "${args[@]}" >/dev/null - name: Upload package-under-test artifact id: upload_package uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 with: name: ${{ env.PACKAGE_ARTIFACT_NAME }} path: | .artifacts/docker-e2e-package/openclaw-current.tgz .artifacts/docker-e2e-package/package-candidate.json retention-days: 14 if-no-files-found: error - name: Summarize package candidate env: INPUT_ARTIFACT_DIGEST: ${{ steps.input_artifact.outputs.artifact_digest }} INPUT_ARTIFACT_ID: ${{ inputs.artifact_id }} INPUT_ARTIFACT_RUN_ATTEMPT: ${{ steps.input_artifact.outputs.run_attempt }} INPUT_ARTIFACT_RUN_ID: ${{ steps.input_artifact.outputs.run_id }} OUTPUT_ARTIFACT_DIGEST: ${{ steps.upload_package.outputs.artifact-digest }} OUTPUT_ARTIFACT_ID: ${{ steps.upload_package.outputs.artifact-id }} PACKAGE_SHA256: ${{ steps.resolve.outputs.sha256 }} PACKAGE_VERSION: ${{ steps.resolve.outputs.package_version }} PACKAGE_REF: ${{ inputs.package_ref }} TRUSTED_SOURCE_ID: ${{ inputs.trusted_source_id }} SOURCE: ${{ inputs.source }} SUITE_PROFILE: ${{ inputs.suite_profile }} WORKFLOW_REF: ${{ inputs.workflow_ref }} PUBLISHED_UPGRADE_SURVIVOR_BASELINE: ${{ inputs.published_upgrade_survivor_baseline }} PUBLISHED_UPGRADE_SURVIVOR_BASELINES: ${{ steps.upgrade_survivor_baselines.outputs.baselines }} PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS: ${{ inputs.published_upgrade_survivor_scenarios }} shell: bash run: | { echo "## Package acceptance" echo echo "- Source: \`${SOURCE}\`" echo "- Workflow ref: \`${WORKFLOW_REF}\`" if [[ "${SOURCE}" == "ref" ]]; then echo "- Package ref: \`${PACKAGE_REF}\`" fi if [[ "${SOURCE}" == "trusted-url" ]]; then echo "- Trusted source: \`${TRUSTED_SOURCE_ID}\`" fi echo "- Version: \`${PACKAGE_VERSION}\`" echo "- SHA-256: \`${PACKAGE_SHA256}\`" echo "- Artifact id: \`${OUTPUT_ARTIFACT_ID}\`" echo "- Artifact digest: \`${OUTPUT_ARTIFACT_DIGEST}\`" echo "- Artifact producer: run \`${GITHUB_RUN_ID}\`, attempt \`${GITHUB_RUN_ATTEMPT}\`" if [[ "$SOURCE" == "artifact" ]]; then echo "- Input artifact id: \`${INPUT_ARTIFACT_ID}\`" echo "- Input artifact digest: \`${INPUT_ARTIFACT_DIGEST}\`" echo "- Input artifact producer: run \`${INPUT_ARTIFACT_RUN_ID}\`, attempt \`${INPUT_ARTIFACT_RUN_ATTEMPT}\`" fi echo "- Profile: \`${SUITE_PROFILE}\`" echo "- Published upgrade survivor baseline: \`${PUBLISHED_UPGRADE_SURVIVOR_BASELINE}\`" echo "- Published upgrade survivor baselines: \`${PUBLISHED_UPGRADE_SURVIVOR_BASELINES}\`" echo "- Published upgrade survivor scenarios: \`${PUBLISHED_UPGRADE_SURVIVOR_SCENARIOS}\`" } >> "$GITHUB_STEP_SUMMARY" package_integrity: name: Package integrity needs: resolve_package runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - name: Checkout package workflow ref uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 with: ref: ${{ inputs.workflow_ref }} fetch-depth: 1 persist-credentials: false - name: Download package-under-test artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: artifact-ids: ${{ needs.resolve_package.outputs.package_artifact_id }} path: .artifacts/docker-e2e-package run-id: ${{ needs.resolve_package.outputs.package_artifact_run_id }} github-token: ${{ github.token }} - name: Enforce public package integrity env: EXPECTED_PACKAGE_SHA256: ${{ needs.resolve_package.outputs.package_sha256 }} OPENCLAW_PACKAGE_TARBALL_CHECK_TIMINGS: "0" shell: bash run: | set -euo pipefail package=".artifacts/docker-e2e-package/openclaw-current.tgz" actual_sha256="$(sha256sum "$package" | awk '{print $1}')" [[ "$actual_sha256" == "$EXPECTED_PACKAGE_SHA256" ]] || { echo "Canonical package artifact SHA-256 differs from the resolver output." >&2 exit 1 } node scripts/check-openclaw-package-tarball.mjs "$package" docker_acceptance: name: Docker product acceptance (artifact-only) needs: [resolve_package, package_integrity] if: inputs.shared_image_policy == 'no-push-artifact' permissions: actions: read contents: read packages: read pull-requests: read uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml with: &docker_acceptance_inputs advisory: ${{ inputs.advisory }} ref: ${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }} include_repo_e2e: false include_release_path_suites: ${{ needs.resolve_package.outputs.include_release_path_suites == 'true' }} include_openwebui: ${{ needs.resolve_package.outputs.include_openwebui == 'true' }} docker_lanes: ${{ needs.resolve_package.outputs.docker_lanes }} published_upgrade_survivor_baseline: ${{ inputs.published_upgrade_survivor_baseline }} published_upgrade_survivor_baselines: ${{ needs.resolve_package.outputs.published_upgrade_survivor_baselines }} published_upgrade_survivor_scenarios: ${{ needs.resolve_package.outputs.published_upgrade_survivor_scenarios }} package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }} package_artifact_digest: ${{ needs.resolve_package.outputs.package_artifact_digest }} package_artifact_id: ${{ needs.resolve_package.outputs.package_artifact_id }} package_artifact_run_attempt: ${{ needs.resolve_package.outputs.package_artifact_run_attempt }} package_artifact_run_id: ${{ needs.resolve_package.outputs.package_artifact_run_id }} package_file_name: ${{ needs.resolve_package.outputs.package_file_name }} package_sha256: ${{ needs.resolve_package.outputs.package_sha256 }} package_source_sha: ${{ needs.resolve_package.outputs.package_source_sha }} package_version: ${{ needs.resolve_package.outputs.package_version }} include_live_suites: ${{ needs.resolve_package.outputs.include_live_suites == 'true' }} live_models_only: false shared_image_artifact_namespace: ${{ inputs.shared_image_artifact_namespace }} shared_image_policy: ${{ inputs.shared_image_policy }} secrets: &docker_acceptance_secrets OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }} ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }} ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }} FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }} BYTEPLUS_API_KEY: ${{ secrets.BYTEPLUS_API_KEY }} CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }} DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }} DASHSCOPE_API_KEY: ${{ secrets.DASHSCOPE_API_KEY }} GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }} KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }} MODELSTUDIO_API_KEY: ${{ secrets.MODELSTUDIO_API_KEY }} MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }} MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }} MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }} OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} OPENCODE_ZEN_API_KEY: ${{ secrets.OPENCODE_ZEN_API_KEY }} OPENCLAW_LIVE_BROWSER_CDP_URL: ${{ secrets.OPENCLAW_LIVE_BROWSER_CDP_URL }} OPENCLAW_LIVE_SETUP_TOKEN: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN }} OPENCLAW_LIVE_SETUP_TOKEN_MODEL: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_MODEL }} OPENCLAW_LIVE_SETUP_TOKEN_PROFILE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE }} OPENCLAW_LIVE_SETUP_TOKEN_VALUE: ${{ secrets.OPENCLAW_LIVE_SETUP_TOKEN_VALUE }} GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }} GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }} OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }} QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }} FAL_KEY: ${{ secrets.FAL_KEY }} RUNWAY_API_KEY: ${{ secrets.RUNWAY_API_KEY }} DEEPGRAM_API_KEY: ${{ secrets.DEEPGRAM_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} VYDRA_API_KEY: ${{ secrets.VYDRA_API_KEY }} XAI_API_KEY: ${{ secrets.XAI_API_KEY }} ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }} Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }} BYTEPLUS_ACCESS_KEY_ID: ${{ secrets.BYTEPLUS_ACCESS_KEY_ID }} BYTEPLUS_SECRET_ACCESS_KEY: ${{ secrets.BYTEPLUS_SECRET_ACCESS_KEY }} CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} OPENCLAW_CODEX_AUTH_JSON: ${{ secrets.OPENCLAW_CODEX_AUTH_JSON }} OPENCLAW_CODEX_CONFIG_TOML: ${{ secrets.OPENCLAW_CODEX_CONFIG_TOML }} OPENCLAW_CLAUDE_JSON: ${{ secrets.OPENCLAW_CLAUDE_JSON }} OPENCLAW_CLAUDE_CREDENTIALS_JSON: ${{ secrets.OPENCLAW_CLAUDE_CREDENTIALS_JSON }} OPENCLAW_CLAUDE_SETTINGS_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_JSON }} OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON: ${{ secrets.OPENCLAW_CLAUDE_SETTINGS_LOCAL_JSON }} OPENCLAW_GEMINI_SETTINGS_JSON: ${{ secrets.OPENCLAW_GEMINI_SETTINGS_JSON }} FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }} docker_acceptance_registry: name: Docker product acceptance (existing registry images) needs: [resolve_package, package_integrity] if: inputs.shared_image_policy == 'existing-only' permissions: actions: read contents: read packages: read pull-requests: read uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml with: *docker_acceptance_inputs secrets: *docker_acceptance_secrets package_telegram: name: Telegram package acceptance needs: [resolve_package, package_integrity] if: needs.resolve_package.outputs.telegram_enabled == 'true' uses: ./.github/workflows/npm-telegram-beta-e2e.yml with: advisory: ${{ inputs.advisory }} package_spec: ${{ inputs.package_spec }} package_artifact_name: ${{ needs.resolve_package.outputs.package_artifact_name }} package_artifact_digest: ${{ needs.resolve_package.outputs.package_artifact_digest }} package_artifact_id: ${{ needs.resolve_package.outputs.package_artifact_id }} package_artifact_run_attempt: ${{ needs.resolve_package.outputs.package_artifact_run_attempt }} package_artifact_run_id: ${{ needs.resolve_package.outputs.package_artifact_run_id }} package_file_name: ${{ needs.resolve_package.outputs.package_file_name }} package_sha256: ${{ needs.resolve_package.outputs.package_sha256 }} package_source_sha: ${{ needs.resolve_package.outputs.package_source_sha }} package_version: ${{ needs.resolve_package.outputs.package_version }} package_label: openclaw@${{ needs.resolve_package.outputs.package_version }} harness_ref: ${{ needs.resolve_package.outputs.package_source_sha || inputs.workflow_ref }} provider_mode: ${{ needs.resolve_package.outputs.telegram_mode }} scenario: ${{ inputs.telegram_scenarios }} secrets: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }} OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }} summary: name: Verify package acceptance needs: [ resolve_package, package_integrity, docker_acceptance, docker_acceptance_registry, package_telegram, ] if: always() runs-on: ubuntu-24.04 timeout-minutes: 5 steps: - name: Verify package acceptance results env: ADVISORY: ${{ inputs.advisory }} DOCKER_ARTIFACT_RESULT: ${{ needs.docker_acceptance.result }} DOCKER_REGISTRY_RESULT: ${{ needs.docker_acceptance_registry.result }} PACKAGE_INTEGRITY_RESULT: ${{ needs.package_integrity.result }} PACKAGE_TELEGRAM_RESULT: ${{ needs.package_telegram.result }} RESOLVE_RESULT: ${{ needs.resolve_package.result }} TELEGRAM_ENABLED: ${{ needs.resolve_package.outputs.telegram_enabled }} shell: bash run: | set -euo pipefail docker_result="$DOCKER_ARTIFACT_RESULT" if [[ "$docker_result" == "skipped" ]]; then docker_result="$DOCKER_REGISTRY_RESULT" fi if [[ "$DOCKER_ARTIFACT_RESULT" != "skipped" && "$DOCKER_REGISTRY_RESULT" != "skipped" ]]; then echo "::error::Both Docker acceptance transports ran; expected exactly one." exit 1 fi if [[ "$DOCKER_ARTIFACT_RESULT" == "skipped" && "$DOCKER_REGISTRY_RESULT" == "skipped" ]]; then echo "::error::No Docker acceptance transport ran; expected exactly one." exit 1 fi failed=0 for item in \ "resolve_package=${RESOLVE_RESULT}" \ "package_integrity=${PACKAGE_INTEGRITY_RESULT}" \ "docker_acceptance=${docker_result}" \ "package_telegram=${PACKAGE_TELEGRAM_RESULT}" do name="${item%%=*}" result="${item#*=}" result_failed=false if [[ "$result" != "success" && "$result" != "skipped" ]]; then result_failed=true fi if [[ "$name" == "package_telegram" && "$TELEGRAM_ENABLED" == "true" && "$result" != "success" ]]; then result_failed=true fi if [[ "$result_failed" == "true" ]]; then if [[ "$ADVISORY" == "true" && "$name" != "resolve_package" ]]; then echo "::warning::${name} ended with ${result}; package acceptance is advisory for this caller." continue fi echo "::error::${name} ended with ${result}" failed=1 fi done exit "$failed"