mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 08:40:44 +00:00
737 lines
29 KiB
YAML
737 lines
29 KiB
YAML
name: Full Release Validation
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
ref:
|
|
description: Branch, tag, or full commit SHA to validate
|
|
required: true
|
|
default: main
|
|
type: string
|
|
provider:
|
|
description: Provider lane for cross-OS onboarding and the end-to-end agent turn
|
|
required: false
|
|
default: openai
|
|
type: choice
|
|
options:
|
|
- openai
|
|
- anthropic
|
|
- minimax
|
|
mode:
|
|
description: Which cross-OS release lanes to run
|
|
required: false
|
|
default: both
|
|
type: choice
|
|
options:
|
|
- fresh
|
|
- upgrade
|
|
- both
|
|
release_profile:
|
|
description: Release coverage profile for live/Docker/provider breadth
|
|
required: false
|
|
default: full
|
|
type: choice
|
|
options:
|
|
- minimum
|
|
- stable
|
|
- full
|
|
rerun_group:
|
|
description: Validation group to run
|
|
required: false
|
|
default: all
|
|
type: choice
|
|
options:
|
|
- all
|
|
- ci
|
|
- plugin-prerelease
|
|
- release-checks
|
|
- install-smoke
|
|
- cross-os
|
|
- live-e2e
|
|
- package
|
|
- qa
|
|
- qa-parity
|
|
- qa-live
|
|
- npm-telegram
|
|
npm_telegram_package_spec:
|
|
description: Optional published package spec for the post-publish Telegram E2E lane
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
evidence_package_spec:
|
|
description: Optional published package spec to prove in the private release evidence report
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
npm_telegram_provider_mode:
|
|
description: Provider mode for the optional post-publish Telegram E2E lane
|
|
required: false
|
|
default: mock-openai
|
|
type: choice
|
|
options:
|
|
- mock-openai
|
|
- live-frontier
|
|
npm_telegram_scenario:
|
|
description: Optional comma-separated Telegram scenario ids for the post-publish lane
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
permissions:
|
|
actions: write
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: full-release-validation-${{ inputs.ref }}-${{ inputs.rerun_group }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
GH_REPO: ${{ github.repository }}
|
|
|
|
jobs:
|
|
resolve_target:
|
|
name: Resolve target ref
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
outputs:
|
|
sha: ${{ steps.resolve.outputs.sha }}
|
|
steps:
|
|
- name: Checkout trusted workflow helper
|
|
uses: actions/checkout@v6
|
|
with:
|
|
ref: ${{ github.ref_name }}
|
|
path: workflow
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Resolve target SHA
|
|
id: resolve
|
|
env:
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
run: |
|
|
bash workflow/scripts/github/resolve-openclaw-ref.sh \
|
|
--ref "$TARGET_REF" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
|
|
- name: Summarize target
|
|
env:
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ steps.resolve.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
|
EVIDENCE_PACKAGE_SPEC: ${{ inputs.evidence_package_spec }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
run: |
|
|
{
|
|
echo "## Full release validation"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
echo "- Child workflow ref: \`${CHILD_WORKFLOW_REF}\`"
|
|
echo "- Rerun group: \`${RERUN_GROUP}\`"
|
|
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "ci" ]]; then
|
|
echo "- Normal CI: \`CI\` with \`target_ref=${TARGET_SHA}\`"
|
|
else
|
|
echo "- Normal CI: skipped by rerun group"
|
|
fi
|
|
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "plugin-prerelease" ]]; then
|
|
echo "- Plugin prerelease: \`Plugin Prerelease\` with \`target_ref=${TARGET_SHA}\`"
|
|
else
|
|
echo "- Plugin prerelease: skipped by rerun group"
|
|
fi
|
|
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "release-checks" || "$RERUN_GROUP" == "install-smoke" || "$RERUN_GROUP" == "cross-os" || "$RERUN_GROUP" == "live-e2e" || "$RERUN_GROUP" == "package" || "$RERUN_GROUP" == "qa" || "$RERUN_GROUP" == "qa-parity" || "$RERUN_GROUP" == "qa-live" ]]; then
|
|
echo "- Release/live/Docker/package/QA: \`OpenClaw Release Checks\`"
|
|
else
|
|
echo "- Release/live/Docker/package/QA: skipped by rerun group"
|
|
fi
|
|
if [[ -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Post-publish Telegram E2E: \`${NPM_TELEGRAM_PACKAGE_SPEC}\`"
|
|
else
|
|
echo "- Post-publish Telegram E2E: skipped because no published package spec was provided"
|
|
fi
|
|
if [[ -n "${EVIDENCE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Private evidence package proof: \`${EVIDENCE_PACKAGE_SPEC}\`"
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
normal_ci:
|
|
name: Run normal full CI
|
|
needs: [resolve_target]
|
|
if: contains(fromJSON('["all","ci"]'), inputs.rerun_group)
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 240
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
|
steps:
|
|
- name: Dispatch and monitor CI
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
dispatch_and_wait() {
|
|
local workflow="$1"
|
|
shift
|
|
|
|
local before_json dispatch_output run_id status conclusion url
|
|
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
|
|
|
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
|
printf '%s\n' "$dispatch_output"
|
|
run_id="$(
|
|
printf '%s\n' "$dispatch_output" |
|
|
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ -z "$run_id" ]]; then
|
|
for _ in $(seq 1 60); do
|
|
run_id="$(
|
|
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
|
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
|
)"
|
|
if [[ -n "$run_id" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
if [[ -z "${run_id:-}" ]]; then
|
|
echo "Could not find dispatched run for ${workflow}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
|
|
cleanup_child_run() {
|
|
local exit_code=$?
|
|
trap - EXIT INT TERM
|
|
local child_status
|
|
child_status="$(gh run view "$run_id" --json status --jq '.status' 2>/dev/null || true)"
|
|
if [[ "$child_status" != "completed" ]]; then
|
|
echo "Cancelling child ${workflow} run ${run_id} after parent exit (${exit_code})."
|
|
gh run cancel "$run_id" || gh api -X POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/force-cancel" || true
|
|
fi
|
|
return "$exit_code"
|
|
}
|
|
trap cleanup_child_run EXIT INT TERM
|
|
|
|
while true; do
|
|
status="$(gh run view "$run_id" --json status --jq '.status')"
|
|
if [[ "$status" == "completed" ]]; then
|
|
break
|
|
fi
|
|
sleep 30
|
|
done
|
|
trap - EXIT INT TERM
|
|
|
|
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
|
url="$(gh run view "$run_id" --json url --jq '.url')"
|
|
echo "${workflow} finished with ${conclusion}: ${url}"
|
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
|
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
|
if [[ "$conclusion" != "success" ]]; then
|
|
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
|
fi
|
|
}
|
|
|
|
{
|
|
echo "### Normal CI"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
cancel_same_sha_push_ci() {
|
|
local run_ids run_id
|
|
run_ids="$(
|
|
gh run list --workflow ci.yml --limit 100 --json databaseId,event,headSha,status \
|
|
--jq 'map(select(.event == "push" and .headSha == env.TARGET_SHA and (.status == "queued" or .status == "in_progress" or .status == "waiting" or .status == "pending"))) | .[].databaseId'
|
|
)"
|
|
if [[ -z "${run_ids// }" ]]; then
|
|
return 0
|
|
fi
|
|
while IFS= read -r run_id; do
|
|
[[ -n "${run_id// }" ]] || continue
|
|
echo "Cancelling same-SHA push CI run ${run_id}; Full Release Validation dispatches the full manual CI child for ${TARGET_SHA}."
|
|
gh run cancel "$run_id" || gh api -X POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/force-cancel" || true
|
|
done <<< "$run_ids"
|
|
}
|
|
|
|
cancel_same_sha_push_ci
|
|
dispatch_and_wait ci.yml -f target_ref="$TARGET_SHA" -f include_android=true
|
|
|
|
plugin_prerelease:
|
|
name: Run plugin prerelease validation
|
|
needs: [resolve_target]
|
|
if: contains(fromJSON('["all","plugin-prerelease"]'), inputs.rerun_group)
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 300
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
|
steps:
|
|
- name: Dispatch and monitor plugin prerelease
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
dispatch_and_wait() {
|
|
local workflow="$1"
|
|
shift
|
|
|
|
local before_json dispatch_output run_id status conclusion url
|
|
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
|
|
|
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
|
printf '%s\n' "$dispatch_output"
|
|
run_id="$(
|
|
printf '%s\n' "$dispatch_output" |
|
|
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ -z "$run_id" ]]; then
|
|
for _ in $(seq 1 60); do
|
|
run_id="$(
|
|
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
|
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
|
)"
|
|
if [[ -n "$run_id" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
if [[ -z "${run_id:-}" ]]; then
|
|
echo "Could not find dispatched run for ${workflow}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
|
|
cleanup_child_run() {
|
|
local exit_code=$?
|
|
trap - EXIT INT TERM
|
|
local child_status
|
|
child_status="$(gh run view "$run_id" --json status --jq '.status' 2>/dev/null || true)"
|
|
if [[ "$child_status" != "completed" ]]; then
|
|
echo "Cancelling child ${workflow} run ${run_id} after parent exit (${exit_code})."
|
|
gh run cancel "$run_id" || gh api -X POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/force-cancel" || true
|
|
fi
|
|
return "$exit_code"
|
|
}
|
|
trap cleanup_child_run EXIT INT TERM
|
|
|
|
while true; do
|
|
status="$(gh run view "$run_id" --json status --jq '.status')"
|
|
if [[ "$status" == "completed" ]]; then
|
|
break
|
|
fi
|
|
sleep 30
|
|
done
|
|
trap - EXIT INT TERM
|
|
|
|
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
|
url="$(gh run view "$run_id" --json url --jq '.url')"
|
|
echo "${workflow} finished with ${conclusion}: ${url}"
|
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
|
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
|
if [[ "$conclusion" != "success" ]]; then
|
|
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
|
fi
|
|
}
|
|
|
|
{
|
|
echo "### Plugin prerelease"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
dispatch_and_wait plugin-prerelease.yml -f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true
|
|
|
|
release_checks:
|
|
name: Run release/live/Docker/QA validation
|
|
needs: [resolve_target]
|
|
if: contains(fromJSON('["all","release-checks","install-smoke","cross-os","live-e2e","package","qa","qa-parity","qa-live"]'), inputs.rerun_group)
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 720
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
|
steps:
|
|
- name: Dispatch and monitor release checks
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
PROVIDER: ${{ inputs.provider }}
|
|
MODE: ${{ inputs.mode }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
dispatch_and_wait() {
|
|
local workflow="$1"
|
|
shift
|
|
|
|
local before_json dispatch_output run_id status conclusion url
|
|
before_json="$(gh run list --workflow "$workflow" --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
|
|
|
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
|
printf '%s\n' "$dispatch_output"
|
|
run_id="$(
|
|
printf '%s\n' "$dispatch_output" |
|
|
sed -nE 's#.*actions/runs/([0-9]+).*#\1#p' |
|
|
tail -n 1
|
|
)"
|
|
|
|
if [[ -z "$run_id" ]]; then
|
|
for _ in $(seq 1 60); do
|
|
run_id="$(
|
|
BEFORE_IDS="$before_json" gh run list --workflow "$workflow" --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
|
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
|
)"
|
|
if [[ -n "$run_id" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
|
|
if [[ -z "${run_id:-}" ]]; then
|
|
echo "Could not find dispatched run for ${workflow}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Dispatched ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
|
|
cleanup_child_run() {
|
|
local exit_code=$?
|
|
trap - EXIT INT TERM
|
|
local child_status
|
|
child_status="$(gh run view "$run_id" --json status --jq '.status' 2>/dev/null || true)"
|
|
if [[ "$child_status" != "completed" ]]; then
|
|
echo "Cancelling child ${workflow} run ${run_id} after parent exit (${exit_code})."
|
|
gh run cancel "$run_id" || gh api -X POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/force-cancel" || true
|
|
fi
|
|
return "$exit_code"
|
|
}
|
|
trap cleanup_child_run EXIT INT TERM
|
|
|
|
while true; do
|
|
status="$(gh run view "$run_id" --json status --jq '.status')"
|
|
if [[ "$status" == "completed" ]]; then
|
|
break
|
|
fi
|
|
sleep 30
|
|
done
|
|
trap - EXIT INT TERM
|
|
|
|
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
|
url="$(gh run view "$run_id" --json url --jq '.url')"
|
|
echo "${workflow} finished with ${conclusion}: ${url}"
|
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
|
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
|
if [[ "$conclusion" != "success" ]]; then
|
|
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
|
fi
|
|
}
|
|
|
|
{
|
|
echo "### Release/live/Docker/QA validation"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
echo "- Provider: \`${PROVIDER}\`"
|
|
echo "- Cross-OS mode: \`${MODE}\`"
|
|
echo "- Release profile: \`${RELEASE_PROFILE}\`"
|
|
echo "- Rerun group: \`${RERUN_GROUP}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
child_rerun_group="$RERUN_GROUP"
|
|
if [[ "$child_rerun_group" == "release-checks" ]]; then
|
|
child_rerun_group=all
|
|
fi
|
|
|
|
dispatch_and_wait openclaw-release-checks.yml \
|
|
-f ref="$TARGET_SHA" \
|
|
-f expected_sha="$TARGET_SHA" \
|
|
-f provider="$PROVIDER" \
|
|
-f mode="$MODE" \
|
|
-f release_profile="$RELEASE_PROFILE" \
|
|
-f rerun_group="$child_rerun_group"
|
|
|
|
npm_telegram:
|
|
name: Run post-publish Telegram E2E
|
|
needs: [resolve_target]
|
|
if: inputs.npm_telegram_package_spec != '' && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group)
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 120
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
conclusion: ${{ steps.dispatch.outputs.conclusion }}
|
|
steps:
|
|
- name: Dispatch and monitor npm Telegram E2E
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
|
PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
|
SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
before_json="$(gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 100 --json databaseId --jq '[.[].databaseId]')"
|
|
|
|
args=(-f package_spec="$PACKAGE_SPEC" -f harness_ref="$TARGET_SHA" -f provider_mode="$PROVIDER_MODE")
|
|
if [[ -n "${SCENARIO// }" ]]; then
|
|
args+=(-f scenario="$SCENARIO")
|
|
fi
|
|
|
|
gh workflow run npm-telegram-beta-e2e.yml --ref "$CHILD_WORKFLOW_REF" "${args[@]}"
|
|
|
|
run_id=""
|
|
for _ in $(seq 1 60); do
|
|
run_id="$(
|
|
BEFORE_IDS="$before_json" gh run list --workflow npm-telegram-beta-e2e.yml --event workflow_dispatch --limit 50 --json databaseId,createdAt \
|
|
--jq 'map(select(.databaseId as $id | (env.BEFORE_IDS | fromjson | index($id) | not))) | sort_by(.createdAt) | reverse | .[0].databaseId // empty'
|
|
)"
|
|
if [[ -n "$run_id" ]]; then
|
|
break
|
|
fi
|
|
sleep 5
|
|
done
|
|
|
|
if [[ -z "$run_id" ]]; then
|
|
echo "Could not find dispatched run for npm-telegram-beta-e2e.yml." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Dispatched npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
|
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
|
|
cleanup_child_run() {
|
|
local exit_code=$?
|
|
trap - EXIT INT TERM
|
|
local child_status
|
|
child_status="$(gh run view "$run_id" --json status --jq '.status' 2>/dev/null || true)"
|
|
if [[ "$child_status" != "completed" ]]; then
|
|
echo "Cancelling npm-telegram-beta-e2e.yml child run ${run_id} after parent exit (${exit_code})."
|
|
gh run cancel "$run_id" || gh api -X POST "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/force-cancel" || true
|
|
fi
|
|
return "$exit_code"
|
|
}
|
|
trap cleanup_child_run EXIT INT TERM
|
|
|
|
while true; do
|
|
status="$(gh run view "$run_id" --json status --jq '.status')"
|
|
if [[ "$status" == "completed" ]]; then
|
|
break
|
|
fi
|
|
sleep 30
|
|
done
|
|
trap - EXIT INT TERM
|
|
|
|
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
|
url="$(gh run view "$run_id" --json url --jq '.url')"
|
|
echo "npm-telegram-beta-e2e.yml finished with ${conclusion}: ${url}"
|
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
|
echo "conclusion=${conclusion}" >> "$GITHUB_OUTPUT"
|
|
if [[ "$conclusion" != "success" ]]; then
|
|
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, conclusion, url}' || true
|
|
fi
|
|
|
|
summary:
|
|
name: Verify full validation
|
|
needs: [normal_ci, plugin_prerelease, release_checks, npm_telegram]
|
|
if: always()
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Request private evidence update
|
|
env:
|
|
RELEASE_PRIVATE_DISPATCH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN }}
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
PACKAGE_SPEC: ${{ inputs.evidence_package_spec || inputs.npm_telegram_package_spec }}
|
|
GITHUB_RUN_ID_VALUE: ${{ github.run_id }}
|
|
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" ]]; then
|
|
echo "Release checks were skipped by rerun group; skipping automatic private evidence update."
|
|
exit 0
|
|
fi
|
|
if [[ -z "${RELEASE_PRIVATE_DISPATCH_TOKEN// }" ]]; then
|
|
echo "OPENCLAW_RELEASES_PRIVATE_DISPATCH_TOKEN is not configured; skipping automatic private evidence update."
|
|
exit 0
|
|
fi
|
|
|
|
release_id="${TARGET_REF#refs/tags/}"
|
|
release_id="${release_id#v}"
|
|
if [[ "$PACKAGE_SPEC" =~ ^openclaw@(.+)$ ]]; then
|
|
release_id="${BASH_REMATCH[1]}"
|
|
fi
|
|
release_id="$(printf '%s' "$release_id" | tr '/:@ ' '----' | tr -cd 'A-Za-z0-9._-')"
|
|
if [[ -z "$release_id" ]]; then
|
|
echo "::error::Could not derive release evidence id from target ref '${TARGET_REF}'."
|
|
exit 1
|
|
fi
|
|
|
|
payload="$(
|
|
jq -cn \
|
|
--arg full_validation_run_id "$GITHUB_RUN_ID_VALUE" \
|
|
--arg release_id "$release_id" \
|
|
--arg release_ref "$TARGET_REF" \
|
|
--arg package_spec "$PACKAGE_SPEC" \
|
|
--arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed; the parent summary re-checks current child run conclusions." \
|
|
'{
|
|
event_type: "openclaw_full_release_validation_completed",
|
|
client_payload: {
|
|
full_validation_run_id: $full_validation_run_id,
|
|
release_id: $release_id,
|
|
release_ref: $release_ref,
|
|
package_spec: $package_spec,
|
|
notes: $notes
|
|
}
|
|
}'
|
|
)"
|
|
|
|
curl --fail-with-body \
|
|
-X POST \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "Authorization: Bearer ${RELEASE_PRIVATE_DISPATCH_TOKEN}" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
https://api.github.com/repos/openclaw/releases-private/dispatches \
|
|
-d "$payload"
|
|
|
|
- name: Verify child workflow results
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
NORMAL_CI_RUN_ID: ${{ needs.normal_ci.outputs.run_id }}
|
|
PLUGIN_PRERELEASE_RUN_ID: ${{ needs.plugin_prerelease.outputs.run_id }}
|
|
RELEASE_CHECKS_RUN_ID: ${{ needs.release_checks.outputs.run_id }}
|
|
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
|
|
NORMAL_CI_RESULT: ${{ needs.normal_ci.result }}
|
|
PLUGIN_PRERELEASE_RESULT: ${{ needs.plugin_prerelease.result }}
|
|
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
|
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
check_child() {
|
|
local label="$1"
|
|
local run_id="$2"
|
|
local required="$3"
|
|
|
|
if [[ -z "${run_id// }" ]]; then
|
|
if [[ "$required" == "0" ]]; then
|
|
echo "${label}: skipped"
|
|
return 0
|
|
fi
|
|
echo "::error::${label} did not record a child run id."
|
|
return 1
|
|
fi
|
|
|
|
local status conclusion url attempt
|
|
status="$(gh run view "$run_id" --json status --jq '.status')"
|
|
conclusion="$(gh run view "$run_id" --json conclusion --jq '.conclusion')"
|
|
url="$(gh run view "$run_id" --json url --jq '.url')"
|
|
attempt="$(gh run view "$run_id" --json attempt --jq '.attempt')"
|
|
echo "${label}: ${status}/${conclusion} attempt ${attempt}: ${url}"
|
|
|
|
if [[ "$status" != "completed" || "$conclusion" != "success" ]]; then
|
|
echo "::error::${label} child run ended with ${status}/${conclusion}: ${url}"
|
|
gh run view "$run_id" --json jobs --jq '.jobs[] | select(.conclusion != "success" and .conclusion != "skipped") | {name, status, conclusion, url}' || true
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
summarize_child_timing() {
|
|
local label="$1"
|
|
local run_id="$2"
|
|
if [[ -z "${run_id// }" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
{
|
|
echo
|
|
echo "### Slowest jobs: ${label}"
|
|
echo
|
|
gh run view "$run_id" --json jobs --jq '
|
|
def ts: fromdateiso8601;
|
|
"| Job | Result | Minutes |",
|
|
"| --- | --- | ---: |",
|
|
([.jobs[]
|
|
| select(.startedAt != "0001-01-01T00:00:00Z" and .completedAt != "0001-01-01T00:00:00Z")
|
|
| . + {durationMin: ((((.completedAt | ts) - (.startedAt | ts)) / 60) * 10 | round / 10)}
|
|
| {name, conclusion, durationMin}]
|
|
| sort_by(.durationMin)
|
|
| reverse
|
|
| .[0:10]
|
|
| map("| `" + (.name | gsub("\\|"; "\\|")) + "` | `" + ((.conclusion // "") | tostring) + "` | " + (.durationMin | tostring) + " |")
|
|
| .[])
|
|
' || echo "_Unable to summarize jobs for run ${run_id}._"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
}
|
|
|
|
failed=0
|
|
|
|
if [[ "$NORMAL_CI_RESULT" == "skipped" && -z "${NORMAL_CI_RUN_ID// }" ]]; then
|
|
check_child "normal_ci" "" 0 || failed=1
|
|
else
|
|
check_child "normal_ci" "$NORMAL_CI_RUN_ID" 1 || failed=1
|
|
fi
|
|
|
|
if [[ "$PLUGIN_PRERELEASE_RESULT" == "skipped" && -z "${PLUGIN_PRERELEASE_RUN_ID// }" ]]; then
|
|
check_child "plugin_prerelease" "" 0 || failed=1
|
|
else
|
|
check_child "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID" 1 || failed=1
|
|
fi
|
|
|
|
if [[ "$RELEASE_CHECKS_RESULT" == "skipped" && -z "${RELEASE_CHECKS_RUN_ID// }" ]]; then
|
|
check_child "release_checks" "" 0 || failed=1
|
|
else
|
|
check_child "release_checks" "$RELEASE_CHECKS_RUN_ID" 1 || failed=1
|
|
fi
|
|
|
|
if [[ "$NPM_TELEGRAM_RESULT" == "skipped" && -z "${NPM_TELEGRAM_RUN_ID// }" ]]; then
|
|
check_child "npm_telegram" "" 0 || failed=1
|
|
else
|
|
check_child "npm_telegram" "$NPM_TELEGRAM_RUN_ID" 1 || failed=1
|
|
fi
|
|
|
|
summarize_child_timing "normal_ci" "$NORMAL_CI_RUN_ID"
|
|
summarize_child_timing "plugin_prerelease" "$PLUGIN_PRERELEASE_RUN_ID"
|
|
summarize_child_timing "release_checks" "$RELEASE_CHECKS_RUN_ID"
|
|
summarize_child_timing "npm_telegram" "$NPM_TELEGRAM_RUN_ID"
|
|
|
|
exit "$failed"
|