mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
a2f0b84e8a
* fix(release): separate decisions from diagnostic drain * test(release): align decision drain fixtures * fix(release): pin artifact downloads to valid v8 SHA * fix(release): default empty evidence paths in plans * fix(release): bind complete evidence reuse selection * fix(release): harden retry artifact integrity * fix(release): canonicalize reused validation evidence * fix(release): bind manifest children to execution plan * test(release): fix validation fixture types * fix(release): retry transient decision artifact reads
1724 lines
86 KiB
YAML
1724 lines
86 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
|
|
expected_sha:
|
|
description: Optional full Validation SHA that ref must resolve to
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
trusted_workflow_json:
|
|
description: Trusted release tooling identity JSON
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
target_context_ref:
|
|
description: Optional canonical release branch or tag context for an exact-SHA target
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
allow_unreleased_changelog:
|
|
description: Allow current-tree packaging to use Unreleased notes; release contexts require this explicit opt-in
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
skip_package_telegram_e2e:
|
|
description: Defer beta Package Acceptance Telegram E2E while preserving focused npm Telegram validation
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
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: stable
|
|
type: choice
|
|
options:
|
|
- beta
|
|
- stable
|
|
- full
|
|
run_release_soak:
|
|
description: Run exhaustive live/Docker and upgrade-survivor soak lanes; forced on for stable and full release profiles
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
fail_fast:
|
|
description: Cancel only an exact active child after its first blocking job; false drains all children to completion
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
rerun_group:
|
|
description: Validation group to run
|
|
required: false
|
|
default: all
|
|
type: choice
|
|
options:
|
|
- all
|
|
- ci
|
|
- plugin-prerelease
|
|
- install-smoke
|
|
- cross-os
|
|
- live-e2e
|
|
- package
|
|
- qa-parity
|
|
- qa-live
|
|
- npm-telegram
|
|
- performance
|
|
plugin_prerelease_node_exclude_patterns_json:
|
|
description: Exact Plugin Prerelease Node test paths omitted only for frozen-target validation
|
|
required: false
|
|
default: "[]"
|
|
type: string
|
|
reuse_evidence:
|
|
description: Reuse matching green product validation for the same target or a changelog-only Release SHA
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
live_suite_filter:
|
|
description: Optional exact live/E2E suite id, or comma-separated QA live lane ids (qa-live-matrix, qa-live-telegram, qa-live-discord, qa-live-whatsapp, qa-live-slack); blank runs all selected live suites
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
cross_os_suite_filter:
|
|
description: Optional focused cross-OS suite filter, e.g. windows/packaged-upgrade or packaged-fresh
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
npm_telegram_package_spec:
|
|
description: Optional published package spec for the focused package Telegram E2E rerun
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
release_package_spec:
|
|
description: Optional published package spec for release checks and package lanes; blank builds a SHA package artifact
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
evidence_package_spec:
|
|
description: Optional published package spec to prove in the release evidence report
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
dispatch_release_evidence:
|
|
description: Dispatch the validated run to openclaw/releases after child proof succeeds
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
package_acceptance_package_spec:
|
|
description: Optional published package spec for Package Acceptance; blank uses the SHA-built release artifact
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
codex_plugin_spec:
|
|
description: Optional Codex plugin install spec for live Docker package checks; blank derives from release_package_spec or packs the selected ref
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
npm_telegram_provider_mode:
|
|
description: Provider mode for the focused package Telegram E2E rerun
|
|
required: false
|
|
default: mock-openai
|
|
type: choice
|
|
options:
|
|
- mock-openai
|
|
- live-frontier
|
|
npm_telegram_scenario:
|
|
description: Optional comma-separated Telegram scenario ids for the focused package Telegram E2E rerun
|
|
required: false
|
|
default: ""
|
|
type: string
|
|
|
|
permissions:
|
|
actions: write
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: full-release-validation-${{ inputs.expected_sha || inputs.ref }}-${{ github.sha }}-${{ inputs.rerun_group }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
|
GH_REPO: ${{ github.repository }}
|
|
# Read retries and one-shot dispatch recovery share this classifier; dispatch POSTs never retry.
|
|
GH_TRANSIENT_SERVER_OR_NETWORK_PATTERN: "HTTP 5[0-9][0-9]|Server Error|invalid character .* looking for beginning of value|error connecting to|context deadline exceeded|connection reset by peer|connection refused|TLS handshake timeout|i/o timeout|network is unreachable|(^|[^A-Za-z0-9_])EOF([^A-Za-z0-9_]|$)|ETIMEDOUT|ECONNRESET|EAI_AGAIN"
|
|
NODE_VERSION: "24.16.0"
|
|
RELEASE_ISOLATION_TOOLING_CONTRACT: "2"
|
|
|
|
jobs:
|
|
resolve_target:
|
|
name: Resolve target ref
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
outputs:
|
|
sha: ${{ steps.resolve.outputs.sha }}
|
|
trusted_workflow_json: ${{ steps.tooling_identity.outputs.json }}
|
|
live_suite_filter: ${{ steps.filters.outputs.live_suite_filter }}
|
|
cross_os_suite_filter: ${{ steps.filters.outputs.cross_os_suite_filter }}
|
|
steps:
|
|
- name: Checkout trusted workflow helper
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
path: workflow
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Resolve trusted workflow identity
|
|
id: tooling_identity
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REQUESTED_IDENTITY_JSON: ${{ inputs.trusted_workflow_json }}
|
|
WORKFLOW_CONTRACT: ${{ env.RELEASE_ISOLATION_TOOLING_CONTRACT }}
|
|
WORKFLOW_FULL_REF: ${{ github.ref }}
|
|
WORKFLOW_REF: ${{ github.ref_name }}
|
|
WORKFLOW_SHA: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
identity="$(
|
|
node workflow/scripts/release-tooling-identity.mjs resolve \
|
|
--repository "$GITHUB_REPOSITORY" \
|
|
--workflow-contract "$WORKFLOW_CONTRACT" \
|
|
--workflow-ref "$WORKFLOW_REF" \
|
|
--workflow-full-ref "$WORKFLOW_FULL_REF" \
|
|
--workflow-sha "$WORKFLOW_SHA" \
|
|
--requested-identity-json "$REQUESTED_IDENTITY_JSON"
|
|
)"
|
|
echo "json=${identity}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Resolve target SHA
|
|
id: resolve
|
|
env:
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
|
|
EXPECTED_SHA: ${{ inputs.expected_sha }}
|
|
run: |
|
|
bash workflow/scripts/github/resolve-openclaw-ref.sh \
|
|
--ref "$TARGET_REF" \
|
|
--expected-sha "$EXPECTED_SHA" \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
|
|
- name: Validate suite filters
|
|
id: filters
|
|
env:
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
RAW_LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }}
|
|
RAW_CROSS_OS_SUITE_FILTER: ${{ inputs.cross_os_suite_filter }}
|
|
RELEASE_FILTER_VALIDATOR: workflow/scripts/github/validate-release-suite-filters.sh
|
|
run: |
|
|
set -euo pipefail
|
|
source "$RELEASE_FILTER_VALIDATOR"
|
|
validate_release_suite_filters \
|
|
"$RERUN_GROUP" \
|
|
"$RAW_LIVE_SUITE_FILTER" \
|
|
"$RAW_CROSS_OS_SUITE_FILTER" \
|
|
controller
|
|
{
|
|
printf 'live_suite_filter=%s\n' "$RELEASE_FILTER_LIVE_SUITE_FILTER"
|
|
printf 'cross_os_suite_filter=%s\n' "$RELEASE_FILTER_CROSS_OS_SUITE_FILTER"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Checkout target package manifest
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ steps.resolve.outputs.sha }}
|
|
path: target
|
|
sparse-checkout: package.json
|
|
sparse-checkout-cone-mode: false
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Validate release inputs
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
|
|
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ steps.resolve.outputs.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "$SKIP_PACKAGE_TELEGRAM_E2E" == "true" && "$RELEASE_PROFILE" != "beta" ]]; then
|
|
echo "skip_package_telegram_e2e is allowed only for release_profile=beta." >&2
|
|
exit 1
|
|
fi
|
|
|
|
context_ref="${TARGET_CONTEXT_REF:-$TARGET_REF}"
|
|
context_ref="${context_ref#refs/heads/}"
|
|
context_ref="${context_ref#refs/tags/}"
|
|
target_version="$(jq -er '.version | select(type == "string")' target/package.json)"
|
|
extended_stable_line=""
|
|
release_version_pattern=""
|
|
expected_version=""
|
|
identity_kind=""
|
|
if [[ "$context_ref" =~ ^release/([0-9]{4}\.([1-9]|1[0-2])\.[1-9][0-9]*)$ ]]; then
|
|
expected_version="${BASH_REMATCH[1]}"
|
|
release_version_pattern="${expected_version//./\\.}"
|
|
identity_kind="release branch"
|
|
if [[ "$target_version" != "$expected_version" &&
|
|
! "$target_version" =~ ^${release_version_pattern}-beta\.[1-9][0-9]*$ ]]; then
|
|
echo "Target package version ${target_version} does not belong to release branch ${context_ref}; expected ${expected_version} or a beta prerelease of it." >&2
|
|
exit 1
|
|
fi
|
|
elif [[ "$context_ref" =~ ^extended-stable/([0-9]{4}\.([1-9]|1[0-2])\.33)$ ]]; then
|
|
extended_stable_line="${BASH_REMATCH[1]%.33}"
|
|
identity_kind="extended-stable branch"
|
|
elif [[ "$context_ref" =~ ^v([0-9]{4}\.([1-9]|1[0-2])\.[1-9][0-9]*(-(alpha|beta)\.[1-9][0-9]*)?)$ ]]; then
|
|
expected_version="${BASH_REMATCH[1]}"
|
|
identity_kind="release tag"
|
|
elif [[ -n "$TARGET_CONTEXT_REF" ]]; then
|
|
echo "target_context_ref must be a canonical OpenClaw release branch or tag." >&2
|
|
exit 1
|
|
fi
|
|
if [[ "$identity_kind" == "extended-stable branch" ]]; then
|
|
if [[ ! "$target_version" =~ ^([0-9]{4}\.([1-9]|1[0-2]))\.([1-9][0-9]*)$ ]] ||
|
|
[[ "${BASH_REMATCH[1]}" != "$extended_stable_line" ]] ||
|
|
(( 10#${BASH_REMATCH[3]} < 33 )); then
|
|
echo "Target package version ${target_version} does not belong to extended-stable branch ${context_ref}; expected a final ${extended_stable_line}.PATCH version with PATCH >= 33." >&2
|
|
exit 1
|
|
fi
|
|
elif [[ -n "$expected_version" &&
|
|
"$identity_kind" != "release branch" &&
|
|
"$target_version" != "$expected_version" ]]; then
|
|
echo "Target package version ${target_version} does not match ${identity_kind} ${context_ref}; expected ${expected_version}." >&2
|
|
exit 1
|
|
fi
|
|
if [[ -n "$TARGET_CONTEXT_REF" ]]; then
|
|
if [[ ! "$TARGET_REF" =~ ^[a-f0-9]{40}$ || "$TARGET_REF" != "$TARGET_SHA" ]]; then
|
|
echo "target_context_ref requires ref to be the resolved full Validation SHA." >&2
|
|
exit 1
|
|
fi
|
|
repository_url="https://github.com/${GITHUB_REPOSITORY}.git"
|
|
if [[ "$identity_kind" == "release tag" ]]; then
|
|
remote_sha="$(git ls-remote --tags "$repository_url" "refs/tags/${context_ref}^{}" | awk 'NR == 1 { print $1 }')"
|
|
if [[ -z "$remote_sha" ]]; then
|
|
remote_sha="$(git ls-remote --tags "$repository_url" "refs/tags/${context_ref}" | awk 'NR == 1 { print $1 }')"
|
|
fi
|
|
if [[ "$remote_sha" != "$TARGET_SHA" ]]; then
|
|
echo "Target SHA ${TARGET_SHA} does not match release tag ${context_ref} at ${remote_sha:-missing}." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
remote_sha="$(git ls-remote --heads "$repository_url" "refs/heads/${context_ref}" | awk 'NR == 1 { print $1 }')"
|
|
if [[ -z "$remote_sha" ]]; then
|
|
echo "Release context branch ${context_ref} does not resolve." >&2
|
|
exit 1
|
|
fi
|
|
comparison_status="$(gh api "repos/${GITHUB_REPOSITORY}/compare/${TARGET_SHA}...${remote_sha}" --jq .status)"
|
|
if [[ "$comparison_status" != "ahead" && "$comparison_status" != "identical" ]]; then
|
|
echo "Target SHA ${TARGET_SHA} is not reachable from release context branch ${context_ref} at ${remote_sha}." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
- name: Summarize target
|
|
env:
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ steps.resolve.outputs.sha }}
|
|
TOOLING_SHA: ${{ github.sha }}
|
|
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
|
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
|
EVIDENCE_PACKAGE_SPEC: ${{ inputs.evidence_package_spec }}
|
|
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
|
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'stable' || inputs.release_profile == 'full' }}
|
|
FAIL_FAST: ${{ inputs.fail_fast }}
|
|
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
|
|
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
LIVE_SUITE_FILTER: ${{ steps.filters.outputs.live_suite_filter }}
|
|
CROSS_OS_SUITE_FILTER: ${{ steps.filters.outputs.cross_os_suite_filter }}
|
|
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
|
|
run: |
|
|
plugin_prerelease_node_exclusions="$(jq -c . <<< "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON")"
|
|
{
|
|
echo "## Full release validation"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Validation SHA: \`${TARGET_SHA}\`"
|
|
echo "- Tooling SHA: \`${TOOLING_SHA}\`"
|
|
echo "- Frozen tuple: \`${TARGET_SHA} / ${TOOLING_SHA} / ${RERUN_GROUP}\`"
|
|
echo "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`"
|
|
echo "- Fail fast: \`${FAIL_FAST}\`"
|
|
echo "- Package Acceptance Telegram E2E deferred: \`${SKIP_PACKAGE_TELEGRAM_E2E}\`"
|
|
echo "- Allow Unreleased changelog packaging: \`${ALLOW_UNRELEASED_CHANGELOG}\`"
|
|
echo "- Rerun group: \`${RERUN_GROUP}\`"
|
|
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
|
|
echo "- Live suite filter: \`${LIVE_SUITE_FILTER}\`"
|
|
fi
|
|
if [[ -n "${CROSS_OS_SUITE_FILTER// }" ]]; then
|
|
echo "- Cross-OS suite filter: \`${CROSS_OS_SUITE_FILTER}\`"
|
|
fi
|
|
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" == "performance" ]]; then
|
|
echo "- Product performance: \`OpenClaw Performance\` with \`target_ref=${TARGET_SHA}\`"
|
|
else
|
|
echo "- Product performance: skipped by rerun group"
|
|
fi
|
|
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "plugin-prerelease" ]]; then
|
|
echo "- Plugin prerelease: \`Plugin Prerelease\` with \`target_ref=${TARGET_SHA}\`"
|
|
echo "- Plugin prerelease Node exclusions: \`${plugin_prerelease_node_exclusions}\`"
|
|
else
|
|
echo "- Plugin prerelease: skipped by rerun group"
|
|
fi
|
|
if [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "install-smoke" || "$RERUN_GROUP" == "cross-os" || "$RERUN_GROUP" == "live-e2e" || "$RERUN_GROUP" == "package" || "$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 "${RELEASE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Published release package: \`${RELEASE_PACKAGE_SPEC}\`"
|
|
fi
|
|
if [[ "$RERUN_GROUP" == "npm-telegram" && -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Published-package Telegram E2E: \`${NPM_TELEGRAM_PACKAGE_SPEC}\`"
|
|
elif [[ "$RERUN_GROUP" == "npm-telegram" && -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Published-package Telegram E2E: \`${RELEASE_PACKAGE_SPEC}\`"
|
|
elif [[ "$RERUN_GROUP" == "npm-telegram" ]]; then
|
|
echo "- Package Telegram E2E: focused rerun requires \`release_package_spec\` or \`npm_telegram_package_spec\`"
|
|
elif [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "package" ]]; then
|
|
if [[ "$SKIP_PACKAGE_TELEGRAM_E2E" == "true" ]]; then
|
|
echo "- Package Telegram E2E: deferred by \`skip_package_telegram_e2e\`"
|
|
else
|
|
echo "- Package Telegram E2E: OpenClaw Release Checks Package Acceptance"
|
|
fi
|
|
else
|
|
echo "- Package Telegram E2E: skipped by rerun group"
|
|
fi
|
|
if [[ -n "${EVIDENCE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Private evidence package proof: \`${EVIDENCE_PACKAGE_SPEC}\`"
|
|
fi
|
|
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Package Acceptance package spec: \`${PACKAGE_ACCEPTANCE_PACKAGE_SPEC}\`"
|
|
elif [[ -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Package Acceptance package spec: \`${RELEASE_PACKAGE_SPEC}\`"
|
|
else
|
|
echo "- Package Acceptance package spec: SHA-built release artifact"
|
|
fi
|
|
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
|
|
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
evidence_reuse:
|
|
name: Check for reusable validation evidence
|
|
needs: [resolve_target]
|
|
if: inputs.rerun_group == 'all' && inputs.reuse_evidence && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release-ci/'))
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 10
|
|
outputs:
|
|
reuse: ${{ steps.find.outputs.reuse }}
|
|
evidence_run_id: ${{ steps.find.outputs.evidence_run_id }}
|
|
evidence_root_run_id: ${{ steps.find.outputs.evidence_root_run_id }}
|
|
evidence_run_url: ${{ steps.find.outputs.evidence_run_url }}
|
|
evidence_sha: ${{ steps.find.outputs.evidence_sha }}
|
|
evidence_policy: ${{ steps.find.outputs.evidence_policy }}
|
|
evidence_manifest: ${{ steps.find.outputs.evidence_manifest }}
|
|
changed_paths: ${{ steps.find.outputs.changed_paths }}
|
|
steps:
|
|
- name: Checkout trusted workflow helper
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
path: workflow
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Checkout target SHA
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ needs.resolve_target.outputs.sha }}
|
|
path: target
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
submodules: false
|
|
|
|
- name: Find reusable validation evidence
|
|
id: find
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
WORKFLOW_REF: ${{ github.ref_name }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'stable' || inputs.release_profile == 'full' }}
|
|
PROVIDER: ${{ inputs.provider }}
|
|
MODE: ${{ inputs.mode }}
|
|
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
|
|
LIVE_SUITE_FILTER: ${{ needs.resolve_target.outputs.live_suite_filter }}
|
|
CROSS_OS_SUITE_FILTER: ${{ needs.resolve_target.outputs.cross_os_suite_filter }}
|
|
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
|
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
|
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
|
|
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
|
NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
|
NPM_TELEGRAM_SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
|
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
|
|
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
|
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
|
|
TRUSTED_WORKFLOW_JSON: ${{ needs.resolve_target.outputs.trusted_workflow_json }}
|
|
run: |
|
|
set -euo pipefail
|
|
# Lane-selection inputs must match the prior run's manifest exactly;
|
|
# a default-input run must not stand in for a focused one.
|
|
inputs_json="$(jq -nc \
|
|
--arg provider "$PROVIDER" \
|
|
--arg mode "$MODE" \
|
|
--arg targetContextRef "$TARGET_CONTEXT_REF" \
|
|
--arg liveSuiteFilter "$LIVE_SUITE_FILTER" \
|
|
--arg crossOsSuiteFilter "$CROSS_OS_SUITE_FILTER" \
|
|
--arg releasePackageSpec "$RELEASE_PACKAGE_SPEC" \
|
|
--arg packageAcceptancePackageSpec "$PACKAGE_ACCEPTANCE_PACKAGE_SPEC" \
|
|
--arg codexPluginSpec "$CODEX_PLUGIN_SPEC" \
|
|
--arg npmTelegramPackageSpec "$NPM_TELEGRAM_PACKAGE_SPEC" \
|
|
--arg npmTelegramProviderMode "$NPM_TELEGRAM_PROVIDER_MODE" \
|
|
--arg npmTelegramScenario "$NPM_TELEGRAM_SCENARIO" \
|
|
--arg skipPackageTelegramE2e "$SKIP_PACKAGE_TELEGRAM_E2E" \
|
|
--arg allowUnreleasedChangelog "$ALLOW_UNRELEASED_CHANGELOG" \
|
|
--arg pluginPrereleaseNodeExcludePatternsJson "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON" \
|
|
'{
|
|
provider: $provider,
|
|
mode: $mode,
|
|
targetContextRef: $targetContextRef,
|
|
liveSuiteFilter: $liveSuiteFilter,
|
|
crossOsSuiteFilter: $crossOsSuiteFilter,
|
|
releasePackageSpec: $releasePackageSpec,
|
|
packageAcceptancePackageSpec: $packageAcceptancePackageSpec,
|
|
codexPluginSpec: $codexPluginSpec,
|
|
npmTelegramPackageSpec: $npmTelegramPackageSpec,
|
|
npmTelegramProviderMode: $npmTelegramProviderMode,
|
|
npmTelegramScenario: $npmTelegramScenario,
|
|
skipPackageTelegramE2e: $skipPackageTelegramE2e,
|
|
allowUnreleasedChangelog: $allowUnreleasedChangelog,
|
|
pluginPrereleaseNodeExcludePatternsJson: $pluginPrereleaseNodeExcludePatternsJson
|
|
}')"
|
|
trusted_workflow_json="${TRUSTED_WORKFLOW_JSON}"
|
|
trusted_workflow_ref="$(jq -er '.ref | select(type == "string" and length > 0)' <<< "$trusted_workflow_json")"
|
|
trusted_workflow_full_ref="$(jq -er '.fullRef | select(type == "string" and length > 0)' <<< "$trusted_workflow_json")"
|
|
trusted_workflow_sha="$(jq -er '.sha | select(type == "string" and test("^[0-9a-f]{40}$"))' <<< "$trusted_workflow_json")"
|
|
bash workflow/scripts/github/find-reusable-release-validation.sh \
|
|
--target-sha "$TARGET_SHA" \
|
|
--workflow-sha "$GITHUB_SHA" \
|
|
--workflow-ref "$WORKFLOW_REF" \
|
|
--trusted-workflow-ref "$trusted_workflow_ref" \
|
|
--trusted-workflow-full-ref "$trusted_workflow_full_ref" \
|
|
--trusted-workflow-sha "$trusted_workflow_sha" \
|
|
--release-profile "$RELEASE_PROFILE" \
|
|
--run-release-soak "$RUN_RELEASE_SOAK" \
|
|
--inputs-json "$inputs_json" \
|
|
--repo "$GITHUB_REPOSITORY" \
|
|
--repo-dir target \
|
|
--github-output "$GITHUB_OUTPUT"
|
|
|
|
- name: Summarize evidence reuse
|
|
env:
|
|
REUSE: ${{ steps.find.outputs.reuse }}
|
|
REUSE_REASON: ${{ steps.find.outputs.reuse_reason }}
|
|
EVIDENCE_RUN_URL: ${{ steps.find.outputs.evidence_run_url }}
|
|
EVIDENCE_SHA: ${{ steps.find.outputs.evidence_sha }}
|
|
CHANGED_PATHS: ${{ steps.find.outputs.changed_paths }}
|
|
run: |
|
|
changed_paths_summary="$(jq -r 'if length == 0 then "none" else join(", ") end' <<< "${CHANGED_PATHS:-[]}")"
|
|
{
|
|
echo "## Validation evidence reuse"
|
|
echo
|
|
if [[ "$REUSE" == "true" ]]; then
|
|
echo "- Reusing evidence: ${EVIDENCE_RUN_URL}"
|
|
echo "- Evidence SHA: \`${EVIDENCE_SHA}\`"
|
|
echo "- Reused validation changed paths: \`${changed_paths_summary}\`"
|
|
else
|
|
echo "- No reusable evidence: ${REUSE_REASON:-unknown}"
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
docker_runtime_assets_preflight:
|
|
name: Verify Docker runtime image assets
|
|
needs: [resolve_target, evidence_reuse]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && inputs.rerun_group == 'all' && needs.evidence_reuse.outputs.reuse != 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout target SHA
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ needs.resolve_target.outputs.sha }}
|
|
fetch-depth: 1
|
|
persist-credentials: false
|
|
|
|
- name: Verify Docker runtime-assets prune path
|
|
env:
|
|
DOCKER_BUILDKIT: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
timeout --kill-after=30s 15m docker build \
|
|
--target runtime-assets \
|
|
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
|
|
.
|
|
|
|
prepare_release_candidate:
|
|
name: Prepare shared release candidate
|
|
needs: [resolve_target, evidence_reuse]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && needs.evidence_reuse.outputs.reuse != 'true' && inputs.release_package_spec == '' && inputs.package_acceptance_package_spec == '' && (contains(fromJSON('["all","plugin-prerelease","cross-os","package"]'), inputs.rerun_group) || (inputs.rerun_group == 'live-e2e' && needs.resolve_target.outputs.live_suite_filter == '')) }}
|
|
permissions:
|
|
actions: read
|
|
contents: read
|
|
packages: read
|
|
pull-requests: read
|
|
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
|
|
with:
|
|
ref: ${{ needs.resolve_target.outputs.sha }}
|
|
prepare_only: true
|
|
include_repo_e2e: false
|
|
include_release_path_suites: false
|
|
include_openwebui: false
|
|
include_live_suites: false
|
|
enable_prepublish_plugin_registry: true
|
|
published_upgrade_survivor_scenarios: ${{ (inputs.run_release_soak || inputs.release_profile == 'stable' || inputs.release_profile == 'full') && 'reported-issues' || '' }}
|
|
allow_frozen_target_scenario_omissions: ${{ inputs.target_context_ref != '' }}
|
|
allow_unreleased_changelog: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
|
release_test_profile: ${{ inputs.release_profile }}
|
|
shared_image_artifact_namespace: full-release
|
|
shared_image_policy: no-push-artifact
|
|
|
|
normal_ci:
|
|
name: Run normal full CI
|
|
needs: [resolve_target, evidence_reuse]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && contains(fromJSON('["all","ci"]'), inputs.rerun_group) && needs.evidence_reuse.outputs.reuse != 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
run_attempt: ${{ steps.dispatch.outputs.run_attempt }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
steps:
|
|
- name: Dispatch CI
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CHILD_WORKFLOW_KIND: ci
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
PARENT_WORKFLOW_SHA: ${{ github.sha }}
|
|
run: &full_release_child_dispatch |
|
|
set -euo pipefail
|
|
|
|
gh_with_retry() {
|
|
local output status attempt
|
|
for attempt in 1 2 3 4 5 6; do
|
|
set +e
|
|
output="$(gh "$@" 2>&1)"
|
|
status=$?
|
|
set -e
|
|
if [[ "$status" -eq 0 ]]; then
|
|
printf '%s\n' "$output"
|
|
return 0
|
|
fi
|
|
if [[ "$output" == *"Bad credentials"* || "$output" == *"HTTP 401"* || "$output" == *"secondary rate limit"* || "$output" == *"API rate limit"* || "$output" == *"HTTP 429"* || "$output" == *"abuse detection"* || "$output" == *"Sorry. Your account was suspended"* || "$output" =~ $GH_TRANSIENT_SERVER_OR_NETWORK_PATTERN ]]; then
|
|
echo "::warning::gh $* failed on attempt ${attempt}: ${output}" >&2
|
|
sleep $((attempt * 10))
|
|
continue
|
|
fi
|
|
printf '%s\n' "$output" >&2
|
|
return "$status"
|
|
done
|
|
printf '%s\n' "$output" >&2
|
|
return "$status"
|
|
}
|
|
|
|
validate_child_run() {
|
|
local candidate_run_id="$1"
|
|
local candidate_run_json attempt
|
|
if [[ ! "$candidate_run_id" =~ ^[0-9]+$ ]]; then
|
|
echo "::error::Refusing to adopt invalid ${workflow} run ID ${candidate_run_id}." >&2
|
|
return 1
|
|
fi
|
|
# GitHub materializes a dispatched run asynchronously: the API can 404 the
|
|
# fresh run id (read-after-write lag) and can briefly report the default
|
|
# display_title before run-name evaluates. Both are retryable; a REAL run
|
|
# object with mismatched identity fields still refuses immediately.
|
|
for attempt in $(seq 1 12); do
|
|
if ! candidate_run_json="$(
|
|
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/runs/${candidate_run_id}"
|
|
)" || ! jq -e --argjson id "$candidate_run_id" '.id == $id' \
|
|
<<< "$candidate_run_json" >/dev/null 2>&1; then
|
|
echo "Waiting for ${workflow} run ${candidate_run_id} to become readable (attempt ${attempt})." >&2
|
|
sleep 5
|
|
continue
|
|
fi
|
|
if ! jq -e \
|
|
--argjson workflow_id "$expected_workflow_id" \
|
|
--arg branch "$CHILD_WORKFLOW_REF" \
|
|
'(.workflow_id == $workflow_id)
|
|
and (.head_branch == $branch)
|
|
and (.event == "workflow_dispatch")' \
|
|
<<< "$candidate_run_json" >/dev/null; then
|
|
echo "::error::Refusing to adopt unvalidated ${workflow} run ${candidate_run_id}." >&2
|
|
jq '{id, workflow_id, path, display_title, head_branch, head_sha, event, html_url}' \
|
|
<<< "$candidate_run_json" >&2
|
|
return 1
|
|
fi
|
|
if jq -e --arg title "$dispatch_run_name" '.display_title == $title' \
|
|
<<< "$candidate_run_json" >/dev/null; then
|
|
printf '%s\n' "$candidate_run_json"
|
|
return 0
|
|
fi
|
|
echo "Waiting for ${workflow} run ${candidate_run_id} display title (attempt ${attempt})." >&2
|
|
sleep 5
|
|
done
|
|
echo "::error::Refusing to adopt ${workflow} run ${candidate_run_id}: run never became readable with display title ${dispatch_run_name}." >&2
|
|
jq '{id, workflow_id, path, display_title, head_branch, head_sha, event, html_url}' \
|
|
<<< "$candidate_run_json" >&2 || printf '%s\n' "$candidate_run_json" >&2
|
|
return 1
|
|
}
|
|
|
|
dispatch_child() {
|
|
local workflow="$1"
|
|
local dispatch_run_name="$2"
|
|
shift 2
|
|
local dispatch_output dispatch_status dispatch_run_ids matches_json match_count run_id run_json child_head_sha child_run_attempt url encoded_workflow_ref current_workflow_sha expected_workflow_id
|
|
|
|
encoded_workflow_ref="$(jq -rn --arg value "$CHILD_WORKFLOW_REF" '$value | @uri')"
|
|
current_workflow_sha="$(
|
|
gh_with_retry api "repos/${GITHUB_REPOSITORY}/commits/${encoded_workflow_ref}" --jq .sha
|
|
)"
|
|
if [[ "$current_workflow_sha" != "$PARENT_WORKFLOW_SHA" ]]; then
|
|
echo "::error::Child workflow ref ${CHILD_WORKFLOW_REF} moved to ${current_workflow_sha}, expected ${PARENT_WORKFLOW_SHA}; refusing dispatch." >&2
|
|
return 1
|
|
fi
|
|
expected_workflow_id="$(
|
|
gh_with_retry api "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}" --jq .id
|
|
)"
|
|
|
|
# The dispatch POST is one-shot: adopt its returned identity when available.
|
|
# If absent or ambiguous, exact-name recovery avoids a duplicate child.
|
|
set +e
|
|
dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"
|
|
dispatch_status=$?
|
|
set -e
|
|
printf '%s\n' "$dispatch_output"
|
|
if [[ "$dispatch_status" -ne 0 && ! "$dispatch_output" =~ $GH_TRANSIENT_SERVER_OR_NETWORK_PATTERN ]]; then
|
|
echo "::error::${workflow} dispatch failed with non-ambiguous status ${dispatch_status}; refusing adoption polling." >&2
|
|
exit "$dispatch_status"
|
|
fi
|
|
|
|
run_id=""
|
|
dispatch_run_ids="$(
|
|
sed -nE "s#^https://github[.]com/${GITHUB_REPOSITORY}/actions/runs/([0-9]+)\$#\1#p" \
|
|
<<< "$dispatch_output" | sort -u
|
|
)"
|
|
if [[ "$dispatch_run_ids" == *$'\n'* ]]; then
|
|
echo "::error::${workflow} dispatch returned multiple run identities; refusing to guess." >&2
|
|
exit 1
|
|
elif [[ -n "$dispatch_run_ids" ]]; then
|
|
run_id="$dispatch_run_ids"
|
|
else
|
|
for _ in $(seq 1 60); do
|
|
if matches_json="$(
|
|
DISPATCH_RUN_NAME="$dispatch_run_name" CHILD_WORKFLOW_REF="$CHILD_WORKFLOW_REF" \
|
|
gh_with_retry api -X GET "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow}/runs" \
|
|
-F event=workflow_dispatch \
|
|
-F per_page=100 \
|
|
--jq '[.workflow_runs[] | select(.display_title == env.DISPATCH_RUN_NAME and .head_branch == env.CHILD_WORKFLOW_REF) | .id]'
|
|
)"; then
|
|
match_count="$(jq 'length' <<< "$matches_json")"
|
|
if (( match_count > 1 )); then
|
|
echo "::error::Multiple runs matched ${dispatch_run_name}; refusing to guess." >&2
|
|
exit 1
|
|
fi
|
|
if (( match_count == 1 )); then
|
|
run_id="$(jq -r '.[0]' <<< "$matches_json")"
|
|
break
|
|
fi
|
|
fi
|
|
sleep 5
|
|
done
|
|
fi
|
|
if [[ -z "$run_id" ]]; then
|
|
echo "::error::Could not find exact dispatched run ${dispatch_run_name}; dispatch status ${dispatch_status}. The dispatch was not retried to avoid creating a duplicate child." >&2
|
|
exit 1
|
|
fi
|
|
|
|
run_json="$(validate_child_run "$run_id")"
|
|
{
|
|
echo "- Adopted child: \`${workflow}\` run \`${run_id}\`"
|
|
echo "- Release Decision owns blocking policy; Diagnostic Drain owns terminal collection."
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
|
|
child_head_sha="$(jq -r '.head_sha // ""' <<< "$run_json")"
|
|
if [[ "$child_head_sha" != "$PARENT_WORKFLOW_SHA" ]]; then
|
|
echo "::error::${workflow} child run used workflow SHA ${child_head_sha}, expected parent workflow SHA ${PARENT_WORKFLOW_SHA}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ "$dispatch_status" -ne 0 ]]; then
|
|
echo "::warning::${workflow} dispatch returned status ${dispatch_status}; adopted exact run ${run_id}." >&2
|
|
fi
|
|
child_run_attempt="$(jq -r '.run_attempt // ""' <<< "$run_json")"
|
|
url="$(jq -r '.html_url // ""' <<< "$run_json")"
|
|
if [[ ! "$child_run_attempt" =~ ^[1-9][0-9]*$ || -z "${url// }" ]]; then
|
|
echo "::error::${workflow} child run omitted its attempt or URL." >&2
|
|
exit 1
|
|
fi
|
|
echo "Dispatched ${workflow}: ${url} (attempt ${child_run_attempt})"
|
|
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
echo "run_attempt=${child_run_attempt}" >> "$GITHUB_OUTPUT"
|
|
echo "url=${url}" >> "$GITHUB_OUTPUT"
|
|
}
|
|
|
|
case "$CHILD_WORKFLOW_KIND" in
|
|
ci)
|
|
{
|
|
echo "### Normal CI"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-ci"
|
|
dispatch_run_name="CI ${dispatch_id}"
|
|
args=(-f target_ref="$TARGET_SHA" -f include_android=true -f dispatch_id="$dispatch_id")
|
|
if [[ "$TARGET_REF" =~ ^v[0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?$ ]]; then
|
|
args+=(-f historical_target_tag="$TARGET_REF")
|
|
elif [[ "$TARGET_CONTEXT_REF" =~ ^v[0-9]{4}\.[0-9]+\.[0-9]+(-(alpha|beta)\.[0-9]+)?$ ]]; then
|
|
args+=(-f historical_target_tag="$TARGET_CONTEXT_REF")
|
|
elif [[ "$TARGET_CONTEXT_REF" =~ ^(release/[0-9]{4}\.[0-9]+\.[0-9]+|extended-stable/[0-9]{4}\.[0-9]+\.33)$ ]]; then
|
|
args+=(-f target_context_ref="$TARGET_CONTEXT_REF")
|
|
fi
|
|
dispatch_child ci.yml "$dispatch_run_name" "${args[@]}"
|
|
;;
|
|
plugin-prerelease)
|
|
plugin_prerelease_node_exclusions="$(
|
|
jq -c . <<< "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON"
|
|
)"
|
|
{
|
|
echo "### Plugin prerelease"
|
|
echo
|
|
echo "- Target ref: \`${TARGET_REF}\`"
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
echo "- Frozen-target Node test omissions: \`${plugin_prerelease_node_exclusions}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-plugin-prerelease"
|
|
dispatch_run_name="Plugin Prerelease ${dispatch_id}"
|
|
args=(-f target_ref="$TARGET_SHA" -f expected_sha="$TARGET_SHA" -f full_release_validation=true -f dispatch_id="$dispatch_id" -f node_test_exclude_patterns_json="$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON")
|
|
if [[ -n "${CANDIDATE_ARTIFACT_JSON// }" ]]; then
|
|
args+=(-f candidate_artifact_json="$CANDIDATE_ARTIFACT_JSON")
|
|
fi
|
|
dispatch_child plugin-prerelease.yml "$dispatch_run_name" "${args[@]}"
|
|
;;
|
|
release-checks)
|
|
{
|
|
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 "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`"
|
|
echo "- Rerun group: \`${RERUN_GROUP}\`"
|
|
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
|
|
echo "- Live suite filter: \`${LIVE_SUITE_FILTER}\`"
|
|
fi
|
|
if [[ -n "${CROSS_OS_SUITE_FILTER// }" ]]; then
|
|
echo "- Cross-OS suite filter: \`${CROSS_OS_SUITE_FILTER}\`"
|
|
fi
|
|
if [[ -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Release package spec: \`${RELEASE_PACKAGE_SPEC}\`"
|
|
fi
|
|
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
|
|
echo "- Package Acceptance package spec: \`${PACKAGE_ACCEPTANCE_PACKAGE_SPEC}\`"
|
|
fi
|
|
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
|
|
echo "- Codex plugin spec: \`${CODEX_PLUGIN_SPEC}\`"
|
|
fi
|
|
echo "- Package Telegram E2E deferred: \`${SKIP_PACKAGE_TELEGRAM_E2E}\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
args=(
|
|
-f ref="$TARGET_SHA"
|
|
-f expected_sha="$TARGET_SHA"
|
|
-f provider="$PROVIDER"
|
|
-f mode="$MODE"
|
|
-f release_profile="$RELEASE_PROFILE"
|
|
-f run_release_soak="$RUN_RELEASE_SOAK"
|
|
-f fail_fast="$FAIL_FAST"
|
|
-f allow_unreleased_changelog="$ALLOW_UNRELEASED_CHANGELOG"
|
|
-f skip_package_telegram_e2e="$SKIP_PACKAGE_TELEGRAM_E2E"
|
|
-f rerun_group="$RERUN_GROUP"
|
|
)
|
|
if [[ -n "${TARGET_CONTEXT_REF// }" ]]; then
|
|
args+=(-f target_context_ref="$TARGET_CONTEXT_REF")
|
|
args+=(-f allow_frozen_target_scenario_omissions=true)
|
|
fi
|
|
if [[ -n "${LIVE_SUITE_FILTER// }" ]]; then
|
|
args+=(-f live_suite_filter="$LIVE_SUITE_FILTER")
|
|
fi
|
|
if [[ -n "${CROSS_OS_SUITE_FILTER// }" ]]; then
|
|
args+=(-f cross_os_suite_filter="$CROSS_OS_SUITE_FILTER")
|
|
fi
|
|
if [[ -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
|
|
args+=(-f release_package_spec="$RELEASE_PACKAGE_SPEC")
|
|
fi
|
|
if [[ -n "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then
|
|
args+=(-f package_acceptance_package_spec="$PACKAGE_ACCEPTANCE_PACKAGE_SPEC")
|
|
fi
|
|
if [[ -n "${CODEX_PLUGIN_SPEC// }" ]]; then
|
|
args+=(-f codex_plugin_spec="$CODEX_PLUGIN_SPEC")
|
|
fi
|
|
if [[ -n "${CANDIDATE_ARTIFACT_JSON// }" ]]; then
|
|
args+=(-f candidate_artifact_json="$CANDIDATE_ARTIFACT_JSON")
|
|
fi
|
|
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-release-checks"
|
|
dispatch_run_name="OpenClaw Release Checks ${dispatch_id}"
|
|
args+=(-f dispatch_id="$dispatch_id")
|
|
dispatch_child openclaw-release-checks.yml "$dispatch_run_name" "${args[@]}"
|
|
;;
|
|
npm-telegram)
|
|
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
|
|
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-npm-telegram"
|
|
dispatch_run_name="NPM Telegram Beta E2E ${dispatch_id}"
|
|
args+=(-f dispatch_id="$dispatch_id")
|
|
dispatch_child npm-telegram-beta-e2e.yml "$dispatch_run_name" "${args[@]}"
|
|
;;
|
|
performance)
|
|
fail_on_regression=true
|
|
if [[ "$RELEASE_PROFILE" == "beta" ]]; then
|
|
fail_on_regression=false
|
|
fi
|
|
{
|
|
echo "### Product performance"
|
|
echo
|
|
echo "- Target SHA: \`${TARGET_SHA}\`"
|
|
echo "- Profile: \`release\`"
|
|
echo "- Repeat: \`3\`"
|
|
echo "- Deep profile: \`false\`"
|
|
echo "- Live OpenAI candidate: \`false\`"
|
|
echo "- Regression gate: \`${fail_on_regression}\`"
|
|
echo "- Report publication: disabled (artifacts only)"
|
|
if [[ "$RELEASE_PROFILE" == "beta" ]]; then
|
|
echo "- Release impact: advisory"
|
|
else
|
|
echo "- Release impact: blocking"
|
|
fi
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
|
dispatch_run_name="OpenClaw Performance ${dispatch_id}"
|
|
args=(
|
|
-f target_ref="$TARGET_SHA"
|
|
-f profile=release
|
|
-f repeat=3
|
|
-f deep_profile=false
|
|
-f live_openai_candidate=false
|
|
-f fail_on_regression="$fail_on_regression"
|
|
-f publish_reports=false
|
|
-f dispatch_id="$dispatch_id"
|
|
)
|
|
dispatch_child openclaw-performance.yml "$dispatch_run_name" "${args[@]}"
|
|
;;
|
|
*)
|
|
echo "::error::Unsupported full-release child workflow kind ${CHILD_WORKFLOW_KIND}." >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
plugin_prerelease:
|
|
name: Run plugin prerelease validation
|
|
needs: [resolve_target, evidence_reuse, prepare_release_candidate]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && (needs.prepare_release_candidate.result == 'success' || needs.prepare_release_candidate.result == 'skipped') && contains(fromJSON('["all","plugin-prerelease"]'), inputs.rerun_group) && needs.evidence_reuse.outputs.reuse != 'true' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
run_attempt: ${{ steps.dispatch.outputs.run_attempt }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
steps:
|
|
- name: Dispatch plugin prerelease
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CHILD_WORKFLOW_KIND: plugin-prerelease
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
PARENT_WORKFLOW_SHA: ${{ github.sha }}
|
|
CANDIDATE_ARTIFACT_JSON: ${{ needs.prepare_release_candidate.outputs.candidate_artifact_json }}
|
|
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
|
|
run: *full_release_child_dispatch
|
|
|
|
release_checks:
|
|
name: Run release/live/Docker/QA validation
|
|
needs: [resolve_target, evidence_reuse, prepare_release_candidate]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && (needs.prepare_release_candidate.result == 'success' || needs.prepare_release_candidate.result == 'skipped') && contains(fromJSON('["all","install-smoke","cross-os","live-e2e","package","qa-parity","qa-live"]'), inputs.rerun_group) && needs.evidence_reuse.outputs.reuse != 'true' }}
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
timeout-minutes: 15
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
run_attempt: ${{ steps.dispatch.outputs.run_attempt }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
steps:
|
|
- name: Dispatch release checks
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CHILD_WORKFLOW_KIND: release-checks
|
|
TARGET_REF: ${{ inputs.ref }}
|
|
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
PARENT_WORKFLOW_SHA: ${{ github.sha }}
|
|
PROVIDER: ${{ inputs.provider }}
|
|
MODE: ${{ inputs.mode }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'stable' || inputs.release_profile == 'full' }}
|
|
FAIL_FAST: ${{ inputs.fail_fast }}
|
|
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
LIVE_SUITE_FILTER: ${{ needs.resolve_target.outputs.live_suite_filter }}
|
|
CROSS_OS_SUITE_FILTER: ${{ needs.resolve_target.outputs.cross_os_suite_filter }}
|
|
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
|
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
|
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
|
|
CANDIDATE_ARTIFACT_JSON: ${{ needs.prepare_release_candidate.outputs.candidate_artifact_json }}
|
|
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
|
|
run: *full_release_child_dispatch
|
|
|
|
npm_telegram:
|
|
name: Run package Telegram E2E
|
|
needs: [resolve_target, evidence_reuse]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group) && (inputs.npm_telegram_package_spec != '' || inputs.release_package_spec != '') && needs.evidence_reuse.outputs.reuse != 'true' }}
|
|
continue-on-error: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 15
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
run_attempt: ${{ steps.dispatch.outputs.run_attempt }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
steps:
|
|
- name: Dispatch npm Telegram E2E
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CHILD_WORKFLOW_KIND: npm-telegram
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
PARENT_WORKFLOW_SHA: ${{ github.sha }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec || inputs.release_package_spec }}
|
|
PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
|
SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
|
run: *full_release_child_dispatch
|
|
|
|
performance:
|
|
name: Run product performance evidence
|
|
needs: [resolve_target, evidence_reuse]
|
|
if: ${{ always() && github.run_attempt == 1 && needs.resolve_target.result == 'success' && contains(fromJSON('["all","performance"]'), inputs.rerun_group) && needs.evidence_reuse.outputs.reuse != 'true' }}
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
timeout-minutes: 15
|
|
outputs:
|
|
run_id: ${{ steps.dispatch.outputs.run_id }}
|
|
run_attempt: ${{ steps.dispatch.outputs.run_attempt }}
|
|
url: ${{ steps.dispatch.outputs.url }}
|
|
steps:
|
|
- name: Dispatch OpenClaw Performance
|
|
id: dispatch
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
CHILD_WORKFLOW_KIND: performance
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
|
|
PARENT_WORKFLOW_SHA: ${{ github.sha }}
|
|
run: *full_release_child_dispatch
|
|
|
|
release_execution_plan:
|
|
name: Seal release execution plan
|
|
needs:
|
|
[
|
|
resolve_target,
|
|
evidence_reuse,
|
|
docker_runtime_assets_preflight,
|
|
prepare_release_candidate,
|
|
normal_ci,
|
|
plugin_prerelease,
|
|
release_checks,
|
|
npm_telegram,
|
|
performance,
|
|
]
|
|
if: always()
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
timeout-minutes: 15
|
|
outputs:
|
|
sha256: ${{ steps.plan.outputs.sha256 }}
|
|
source_parent_attempt: ${{ steps.plan.outputs.source_parent_attempt }}
|
|
steps:
|
|
- name: Checkout release execution plan tooling
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
sparse-checkout: |
|
|
scripts/full-release-validation-state.mjs
|
|
scripts/full-release-validation-policy.mjs
|
|
scripts/release-ci-summary.mjs
|
|
scripts/lib/plain-gh.mjs
|
|
sparse-checkout-cone-mode: false
|
|
persist-credentials: false
|
|
|
|
- name: Restore immutable release execution plan
|
|
if: ${{ github.run_attempt != 1 }}
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: full-release-execution-plan-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/full-release-execution-plan
|
|
|
|
- name: Seal immutable release execution plan
|
|
id: plan
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
EVIDENCE_REUSE: ${{ needs.evidence_reuse.outputs.reuse }}
|
|
EVIDENCE_RUN_ID: ${{ needs.evidence_reuse.outputs.evidence_run_id }}
|
|
EVIDENCE_ROOT_RUN_ID: ${{ needs.evidence_reuse.outputs.evidence_root_run_id }}
|
|
EVIDENCE_RUN_URL: ${{ needs.evidence_reuse.outputs.evidence_run_url }}
|
|
EVIDENCE_SHA: ${{ needs.evidence_reuse.outputs.evidence_sha }}
|
|
EVIDENCE_POLICY: ${{ needs.evidence_reuse.outputs.evidence_policy }}
|
|
EVIDENCE_CHANGED_PATHS: ${{ needs.evidence_reuse.outputs.changed_paths || '[]' }}
|
|
TRUSTED_WORKFLOW_JSON: ${{ needs.resolve_target.outputs.trusted_workflow_json }}
|
|
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
|
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
|
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
|
LIVE_SUITE_FILTER: ${{ needs.resolve_target.outputs.live_suite_filter }}
|
|
NORMAL_CI_RESULT: ${{ needs.normal_ci.result }}
|
|
NORMAL_CI_RUN_ID: ${{ needs.normal_ci.outputs.run_id }}
|
|
NORMAL_CI_RUN_ATTEMPT: ${{ needs.normal_ci.outputs.run_attempt }}
|
|
NORMAL_CI_URL: ${{ needs.normal_ci.outputs.url }}
|
|
PLUGIN_PRERELEASE_RESULT: ${{ needs.plugin_prerelease.result }}
|
|
PLUGIN_PRERELEASE_RUN_ID: ${{ needs.plugin_prerelease.outputs.run_id }}
|
|
PLUGIN_PRERELEASE_RUN_ATTEMPT: ${{ needs.plugin_prerelease.outputs.run_attempt }}
|
|
PLUGIN_PRERELEASE_URL: ${{ needs.plugin_prerelease.outputs.url }}
|
|
RELEASE_CHECKS_RESULT: ${{ needs.release_checks.result }}
|
|
RELEASE_CHECKS_RUN_ID: ${{ needs.release_checks.outputs.run_id }}
|
|
RELEASE_CHECKS_RUN_ATTEMPT: ${{ needs.release_checks.outputs.run_attempt }}
|
|
RELEASE_CHECKS_URL: ${{ needs.release_checks.outputs.url }}
|
|
NPM_TELEGRAM_RESULT: ${{ needs.npm_telegram.result }}
|
|
NPM_TELEGRAM_RUN_ID: ${{ needs.npm_telegram.outputs.run_id }}
|
|
NPM_TELEGRAM_RUN_ATTEMPT: ${{ needs.npm_telegram.outputs.run_attempt }}
|
|
NPM_TELEGRAM_URL: ${{ needs.npm_telegram.outputs.url }}
|
|
PERFORMANCE_RESULT: ${{ needs.performance.result }}
|
|
PERFORMANCE_RUN_ID: ${{ needs.performance.outputs.run_id }}
|
|
PERFORMANCE_RUN_ATTEMPT: ${{ needs.performance.outputs.run_attempt }}
|
|
PERFORMANCE_URL: ${{ needs.performance.outputs.url }}
|
|
RESOLVE_TARGET_RESULT: ${{ needs.resolve_target.result }}
|
|
DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT: ${{ needs.docker_runtime_assets_preflight.result }}
|
|
PREPARE_RELEASE_CANDIDATE_RESULT: ${{ needs.prepare_release_candidate.result }}
|
|
FULL_RELEASE_RESTORE_PLAN: ${{ github.run_attempt != 1 }}
|
|
FULL_RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "$FULL_RELEASE_RESTORE_PLAN" != "true" ]]; then
|
|
export FULL_RELEASE_PLAN_INPUTS_JSON="$(
|
|
jq -cn \
|
|
--arg parentRunId "$GITHUB_RUN_ID" \
|
|
--arg parentRunAttempt "$GITHUB_RUN_ATTEMPT" \
|
|
--arg workflowRef "$GITHUB_REF_NAME" \
|
|
--arg workflowSha "$GITHUB_SHA" \
|
|
--argjson trustedWorkflow "$TRUSTED_WORKFLOW_JSON" \
|
|
--arg evidenceReuse "$EVIDENCE_REUSE" \
|
|
--arg evidenceRunId "$EVIDENCE_RUN_ID" \
|
|
--arg evidenceRootRunId "$EVIDENCE_ROOT_RUN_ID" \
|
|
--arg evidenceRunUrl "$EVIDENCE_RUN_URL" \
|
|
--arg evidenceSha "$EVIDENCE_SHA" \
|
|
--arg evidencePolicy "$EVIDENCE_POLICY" \
|
|
--argjson evidenceChangedPaths "$EVIDENCE_CHANGED_PATHS" \
|
|
--arg rerunGroup "$RERUN_GROUP" \
|
|
--arg releasePackageSpec "$RELEASE_PACKAGE_SPEC" \
|
|
--arg packageAcceptancePackageSpec "$PACKAGE_ACCEPTANCE_PACKAGE_SPEC" \
|
|
--arg npmTelegramPackageSpec "$NPM_TELEGRAM_PACKAGE_SPEC" \
|
|
--arg liveSuiteFilter "$LIVE_SUITE_FILTER" \
|
|
--arg resolveTargetResult "$RESOLVE_TARGET_RESULT" \
|
|
--arg dockerPreflightResult "$DOCKER_RUNTIME_ASSETS_PREFLIGHT_RESULT" \
|
|
--arg prepareCandidateResult "$PREPARE_RELEASE_CANDIDATE_RESULT" \
|
|
--arg normalCiResult "$NORMAL_CI_RESULT" \
|
|
--arg normalCiRunId "$NORMAL_CI_RUN_ID" \
|
|
--arg normalCiRunAttempt "$NORMAL_CI_RUN_ATTEMPT" \
|
|
--arg normalCiUrl "$NORMAL_CI_URL" \
|
|
--arg pluginPrereleaseResult "$PLUGIN_PRERELEASE_RESULT" \
|
|
--arg pluginPrereleaseRunId "$PLUGIN_PRERELEASE_RUN_ID" \
|
|
--arg pluginPrereleaseRunAttempt "$PLUGIN_PRERELEASE_RUN_ATTEMPT" \
|
|
--arg pluginPrereleaseUrl "$PLUGIN_PRERELEASE_URL" \
|
|
--arg releaseChecksResult "$RELEASE_CHECKS_RESULT" \
|
|
--arg releaseChecksRunId "$RELEASE_CHECKS_RUN_ID" \
|
|
--arg releaseChecksRunAttempt "$RELEASE_CHECKS_RUN_ATTEMPT" \
|
|
--arg releaseChecksUrl "$RELEASE_CHECKS_URL" \
|
|
--arg npmTelegramResult "$NPM_TELEGRAM_RESULT" \
|
|
--arg npmTelegramRunId "$NPM_TELEGRAM_RUN_ID" \
|
|
--arg npmTelegramRunAttempt "$NPM_TELEGRAM_RUN_ATTEMPT" \
|
|
--arg npmTelegramUrl "$NPM_TELEGRAM_URL" \
|
|
--arg performanceResult "$PERFORMANCE_RESULT" \
|
|
--arg performanceRunId "$PERFORMANCE_RUN_ID" \
|
|
--arg performanceRunAttempt "$PERFORMANCE_RUN_ATTEMPT" \
|
|
--arg performanceUrl "$PERFORMANCE_URL" \
|
|
'{
|
|
parentRunId: $parentRunId,
|
|
parentRunAttempt: $parentRunAttempt,
|
|
workflowRef: $workflowRef,
|
|
workflowSha: $workflowSha,
|
|
trustedWorkflow: $trustedWorkflow,
|
|
evidenceReuse: $evidenceReuse,
|
|
evidenceRunId: $evidenceRunId,
|
|
evidenceRootRunId: $evidenceRootRunId,
|
|
evidenceRunUrl: $evidenceRunUrl,
|
|
evidenceSha: $evidenceSha,
|
|
evidencePolicy: $evidencePolicy,
|
|
evidenceChangedPaths: $evidenceChangedPaths,
|
|
rerunGroup: $rerunGroup,
|
|
releasePackageSpec: $releasePackageSpec,
|
|
packageAcceptancePackageSpec: $packageAcceptancePackageSpec,
|
|
npmTelegramPackageSpec: $npmTelegramPackageSpec,
|
|
liveSuiteFilter: $liveSuiteFilter,
|
|
resolveTargetResult: $resolveTargetResult,
|
|
dockerPreflightResult: $dockerPreflightResult,
|
|
prepareCandidateResult: $prepareCandidateResult,
|
|
children: {
|
|
normalCi: {result: $normalCiResult, runId: $normalCiRunId, runAttempt: $normalCiRunAttempt, url: $normalCiUrl},
|
|
pluginPrerelease: {result: $pluginPrereleaseResult, runId: $pluginPrereleaseRunId, runAttempt: $pluginPrereleaseRunAttempt, url: $pluginPrereleaseUrl},
|
|
releaseChecks: {result: $releaseChecksResult, runId: $releaseChecksRunId, runAttempt: $releaseChecksRunAttempt, url: $releaseChecksUrl},
|
|
npmTelegram: {result: $npmTelegramResult, runId: $npmTelegramRunId, runAttempt: $npmTelegramRunAttempt, url: $npmTelegramUrl},
|
|
productPerformance: {result: $performanceResult, runId: $performanceRunId, runAttempt: $performanceRunAttempt, url: $performanceUrl}
|
|
}
|
|
}'
|
|
)"
|
|
fi
|
|
node scripts/full-release-validation-state.mjs plan
|
|
|
|
- name: Upload immutable release execution plan
|
|
if: ${{ always() && github.run_attempt == 1 }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: full-release-execution-plan-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/full-release-execution-plan
|
|
if-no-files-found: error
|
|
|
|
release_decision:
|
|
name: Release Decision
|
|
needs: [resolve_target, release_execution_plan]
|
|
if: always()
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
timeout-minutes: 720
|
|
outputs:
|
|
state: ${{ steps.state.outputs.state }}
|
|
steps:
|
|
- name: Checkout release decision tooling
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
sparse-checkout: |
|
|
scripts/full-release-validation-state.mjs
|
|
scripts/full-release-validation-policy.mjs
|
|
sparse-checkout-cone-mode: false
|
|
persist-credentials: false
|
|
|
|
- name: Download immutable release execution plan
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: full-release-execution-plan-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/full-release-execution-plan
|
|
|
|
- name: Evaluate release decision
|
|
id: state
|
|
continue-on-error: true
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
FAIL_FAST: ${{ inputs.fail_fast }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
FULL_RELEASE_STATE_MODE: decision
|
|
FULL_RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
FULL_RELEASE_STATE_PATH: ${{ runner.temp }}/full-release-decision/full-release-decision.json
|
|
run: &full_release_state |
|
|
set -euo pipefail
|
|
node scripts/full-release-validation-state.mjs "$FULL_RELEASE_STATE_MODE"
|
|
|
|
- name: Upload release decision
|
|
if: always() && steps.state.outputs.state != ''
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: full-release-decision-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/full-release-decision
|
|
if-no-files-found: error
|
|
|
|
- name: Enforce release decision
|
|
if: always()
|
|
env:
|
|
RELEASE_DECISION_STATE: ${{ steps.state.outputs.state }}
|
|
run: |
|
|
set -euo pipefail
|
|
if [[ "$RELEASE_DECISION_STATE" == "passed" ]]; then
|
|
exit 0
|
|
fi
|
|
echo "::error::Release Decision ended in ${RELEASE_DECISION_STATE:-orchestration_error}."
|
|
exit 1
|
|
|
|
diagnostic_drain:
|
|
name: Diagnostic Drain
|
|
needs: [resolve_target, release_execution_plan]
|
|
if: always()
|
|
runs-on: blacksmith-4vcpu-ubuntu-2404
|
|
timeout-minutes: 720
|
|
outputs:
|
|
state: ${{ steps.state.outputs.state }}
|
|
normal_ci_conclusion: ${{ steps.state.outputs.normalCi_conclusion }}
|
|
plugin_prerelease_conclusion: ${{ steps.state.outputs.pluginPrerelease_conclusion }}
|
|
release_checks_conclusion: ${{ steps.state.outputs.releaseChecks_conclusion }}
|
|
npm_telegram_conclusion: ${{ steps.state.outputs.npmTelegram_conclusion }}
|
|
performance_conclusion: ${{ steps.state.outputs.productPerformance_conclusion }}
|
|
steps:
|
|
- name: Checkout diagnostic drain tooling
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
sparse-checkout: |
|
|
scripts/full-release-validation-state.mjs
|
|
scripts/full-release-validation-policy.mjs
|
|
scripts/release-ci-summary.mjs
|
|
scripts/lib/plain-gh.mjs
|
|
sparse-checkout-cone-mode: false
|
|
persist-credentials: false
|
|
|
|
- name: Download immutable release execution plan
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: full-release-execution-plan-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/full-release-execution-plan
|
|
|
|
- name: Drain child diagnostics
|
|
id: state
|
|
continue-on-error: true
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
FAIL_FAST: "false"
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
FULL_RELEASE_STATE_MODE: drain
|
|
FULL_RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
FULL_RELEASE_STATE_PATH: ${{ runner.temp }}/full-release-diagnostics/full-release-diagnostic-manifest.json
|
|
run: *full_release_state
|
|
|
|
- name: Upload diagnostic drain manifest
|
|
if: always() && steps.state.outputs.state != ''
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: full-release-diagnostics-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/full-release-diagnostics
|
|
if-no-files-found: error
|
|
|
|
- name: Enforce diagnostic drain integrity
|
|
if: always()
|
|
env:
|
|
DIAGNOSTIC_DRAIN_STATE: ${{ steps.state.outputs.state }}
|
|
run: |
|
|
set -euo pipefail
|
|
case "$DIAGNOSTIC_DRAIN_STATE" in
|
|
passed|blocked_complete)
|
|
exit 0
|
|
;;
|
|
*)
|
|
echo "::error::Diagnostic Drain ended in ${DIAGNOSTIC_DRAIN_STATE:-orchestration_error}."
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
summary:
|
|
name: Verify full validation
|
|
needs:
|
|
[
|
|
resolve_target,
|
|
evidence_reuse,
|
|
docker_runtime_assets_preflight,
|
|
prepare_release_candidate,
|
|
normal_ci,
|
|
plugin_prerelease,
|
|
release_checks,
|
|
npm_telegram,
|
|
performance,
|
|
release_execution_plan,
|
|
release_decision,
|
|
diagnostic_drain,
|
|
]
|
|
if: always()
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout release state verifier
|
|
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
with:
|
|
ref: ${{ github.sha }}
|
|
sparse-checkout: |
|
|
scripts/full-release-validation-state.mjs
|
|
scripts/full-release-validation-policy.mjs
|
|
scripts/release-ci-summary.mjs
|
|
scripts/lib/plain-gh.mjs
|
|
sparse-checkout-cone-mode: false
|
|
persist-credentials: false
|
|
|
|
- name: Download immutable release execution plan
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
name: full-release-execution-plan-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/full-release-execution-plan
|
|
|
|
- name: Download release decision attempts
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
pattern: full-release-decision-${{ github.run_id }}-*
|
|
path: ${{ runner.temp }}/full-release-decision-attempts
|
|
|
|
- name: Download diagnostic drain attempts
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
with:
|
|
pattern: full-release-diagnostics-${{ github.run_id }}-*
|
|
path: ${{ runner.temp }}/full-release-diagnostic-attempts
|
|
|
|
- name: Select newest compatible release state artifacts
|
|
id: selected_state
|
|
env:
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
RELEASE_DECISION_ATTEMPTS_PATH: ${{ runner.temp }}/full-release-decision-attempts
|
|
DIAGNOSTIC_DRAIN_ATTEMPTS_PATH: ${{ runner.temp }}/full-release-diagnostic-attempts
|
|
RELEASE_DECISION_PATH: ${{ runner.temp }}/full-release-decision/full-release-decision.json
|
|
DIAGNOSTIC_DRAIN_PATH: ${{ runner.temp }}/full-release-diagnostics/full-release-diagnostic-manifest.json
|
|
run: node scripts/full-release-validation-state.mjs select
|
|
|
|
- name: Verify exact release state artifacts
|
|
env:
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
RELEASE_DECISION_PATH: ${{ runner.temp }}/full-release-decision/full-release-decision.json
|
|
DIAGNOSTIC_DRAIN_PATH: ${{ runner.temp }}/full-release-diagnostics/full-release-diagnostic-manifest.json
|
|
run: node scripts/full-release-validation-state.mjs verify
|
|
|
|
- name: Request release evidence update
|
|
if: ${{ inputs.dispatch_release_evidence }}
|
|
env:
|
|
RELEASES_DISPATCH_TOKEN: ${{ secrets.OPENCLAW_RELEASES_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_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
run: |
|
|
set -euo pipefail
|
|
EVIDENCE_REUSE="$(jq -r '.evidenceReuse.requested' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_ROOT_RUN_ID="$(jq -r '.evidenceReuse.rootRunId // ""' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_POLICY="$(jq -r '.evidenceReuse.policy // ""' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
RELEASE_CHECKS_SELECTED="$(jq -r '.children[] | select(.key == "releaseChecks") | .selected' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
if [[ "$RELEASE_CHECKS_SELECTED" != "true" && "$EVIDENCE_REUSE" != "true" ]]; then
|
|
echo "Release checks were skipped by rerun group; skipping automatic release evidence update."
|
|
exit 0
|
|
fi
|
|
notes="Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after exact Release Decision and Diagnostic Drain artifacts passed shared policy verification."
|
|
if [[ "$EVIDENCE_REUSE" == "true" && -n "${EVIDENCE_ROOT_RUN_ID// }" ]]; then
|
|
notes="Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE}, which reused green product evidence from chain-root run ${EVIDENCE_ROOT_RUN_ID} under policy ${EVIDENCE_POLICY}."
|
|
fi
|
|
if [[ -z "${RELEASES_DISPATCH_TOKEN// }" ]]; then
|
|
echo "OPENCLAW_RELEASES_DISPATCH_TOKEN is not configured; skipping automatic release evidence update."
|
|
exit 0
|
|
fi
|
|
|
|
evidence_package_spec="$PACKAGE_SPEC"
|
|
if [[ -z "${evidence_package_spec// }" ]]; then
|
|
tag_ref="${TARGET_REF#refs/tags/}"
|
|
if [[ "$tag_ref" =~ ^v([0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?)$ ]]; then
|
|
evidence_package_spec="openclaw@${BASH_REMATCH[1]}"
|
|
fi
|
|
fi
|
|
|
|
release_id="${TARGET_REF#refs/tags/}"
|
|
release_id="${release_id#v}"
|
|
if [[ "$evidence_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 "::warning::Could not derive release evidence id from target ref '${TARGET_REF}'; skipping automatic release evidence update."
|
|
exit 0
|
|
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 "$evidence_package_spec" \
|
|
--arg notes "$notes" \
|
|
'{
|
|
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
|
|
}
|
|
}'
|
|
)"
|
|
|
|
if ! curl --fail-with-body \
|
|
--connect-timeout 10 \
|
|
--max-time 30 \
|
|
-X POST \
|
|
-H "Accept: application/vnd.github+json" \
|
|
-H "Authorization: Bearer ${RELEASES_DISPATCH_TOKEN}" \
|
|
-H "X-GitHub-Api-Version: 2022-11-28" \
|
|
https://api.github.com/repos/openclaw/releases/dispatches \
|
|
-d "$payload"; then
|
|
echo "::warning::Automatic release evidence dispatch failed; child workflow validation remains authoritative."
|
|
{
|
|
echo "### Release evidence dispatch failed"
|
|
echo
|
|
echo "Child workflow validation remains authoritative. Backfill durable evidence from \`openclaw/releases\`:"
|
|
echo
|
|
echo "\`\`\`bash"
|
|
echo "gh workflow run openclaw-release-evidence-from-full-validation.yml --repo openclaw/releases --ref main -f full_validation_run_id=${GITHUB_RUN_ID_VALUE} -f release_id=${release_id} -f release_ref=${TARGET_REF} -f package_spec=${evidence_package_spec}"
|
|
echo "\`\`\`"
|
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
fi
|
|
|
|
- name: Write release validation manifest
|
|
if: ${{ success() }}
|
|
env:
|
|
TARGET_REF: ${{ startsWith(github.ref, 'refs/heads/release-ci/') && needs.resolve_target.outputs.sha || inputs.ref }}
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
RUN_RELEASE_SOAK: ${{ inputs.run_release_soak || inputs.release_profile == 'stable' || inputs.release_profile == 'full' }}
|
|
PROVIDER: ${{ inputs.provider }}
|
|
MODE: ${{ inputs.mode }}
|
|
TARGET_CONTEXT_REF: ${{ inputs.target_context_ref }}
|
|
LIVE_SUITE_FILTER: ${{ needs.resolve_target.outputs.live_suite_filter }}
|
|
CROSS_OS_SUITE_FILTER: ${{ needs.resolve_target.outputs.cross_os_suite_filter }}
|
|
RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }}
|
|
PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }}
|
|
CODEX_PLUGIN_SPEC: ${{ inputs.codex_plugin_spec }}
|
|
NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }}
|
|
NPM_TELEGRAM_PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
|
|
NPM_TELEGRAM_SCENARIO: ${{ inputs.npm_telegram_scenario }}
|
|
SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }}
|
|
ALLOW_UNRELEASED_CHANGELOG: ${{ inputs.allow_unreleased_changelog || (inputs.target_context_ref == '' && (inputs.ref == 'main' || inputs.ref == 'refs/heads/main')) }}
|
|
PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON: ${{ inputs.plugin_prerelease_node_exclude_patterns_json }}
|
|
RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
DIAGNOSTIC_DRAIN_PATH: ${{ runner.temp }}/full-release-diagnostics/full-release-diagnostic-manifest.json
|
|
run: |
|
|
set -euo pipefail
|
|
manifest_dir="${RUNNER_TEMP}/full-release-validation"
|
|
mkdir -p "$manifest_dir"
|
|
TARGET_SHA="$(jq -r '.targetSha' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
NORMAL_CI_RUN_ID="$(jq -r '.children[] | select(.key == "normalCi") | .runId' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
PLUGIN_PRERELEASE_RUN_ID="$(jq -r '.children[] | select(.key == "pluginPrerelease") | .runId' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
RELEASE_CHECKS_RUN_ID="$(jq -r '.children[] | select(.key == "releaseChecks") | .runId' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
NPM_TELEGRAM_RUN_ID="$(jq -r '.children[] | select(.key == "npmTelegram") | .runId' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
PERFORMANCE_RUN_ID="$(jq -r '.children[] | select(.key == "productPerformance") | .runId' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EXECUTION_PLAN_SHA256="$(jq -r '.sha256' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
SOURCE_PARENT_RUN_ATTEMPT="$(jq -r '.parentRunAttempt' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_REUSE="$(jq -r '.evidenceReuse.requested' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_RUN_ID="$(jq -r '.evidenceReuse.selectedRunId // ""' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_ROOT_RUN_ID="$(jq -r '.evidenceReuse.rootRunId // ""' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_SHA="$(jq -r '.evidenceReuse.evidenceSha // ""' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_POLICY="$(jq -r '.evidenceReuse.policy // ""' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_CHANGED_PATHS="$(jq -c '.evidenceReuse.changedPaths // []' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
EVIDENCE_MANIFEST="$(jq -c '.evidenceReuse.sourceManifest // empty' "$RELEASE_EXECUTION_PLAN_PATH")"
|
|
PERFORMANCE_CONCLUSION="$(jq -r '.children.productPerformance.conclusion // ""' "$DIAGNOSTIC_DRAIN_PATH")"
|
|
if [[ "$EVIDENCE_REUSE" == "true" ]]; then
|
|
# Inherit the evidence manifest (profile, soak, child runs) so future
|
|
# reuse lookups and evidence consumers keep resolving the chain root.
|
|
jq \
|
|
--arg runId "$GITHUB_RUN_ID" \
|
|
--arg runAttempt "$GITHUB_RUN_ATTEMPT" \
|
|
--arg workflowRef "$GITHUB_REF_NAME" \
|
|
--arg workflowSha "$GITHUB_SHA" \
|
|
--arg workflowFullRef "$GITHUB_REF" \
|
|
--arg workflowRefType "$GITHUB_REF_TYPE" \
|
|
--arg targetRef "$TARGET_REF" \
|
|
--arg targetSha "$TARGET_SHA" \
|
|
--arg evidenceRunId "$EVIDENCE_RUN_ID" \
|
|
--arg evidenceRootRunId "$EVIDENCE_ROOT_RUN_ID" \
|
|
--arg evidenceSha "$EVIDENCE_SHA" \
|
|
--arg evidencePolicy "$EVIDENCE_POLICY" \
|
|
--arg executionPlanSha256 "$EXECUTION_PLAN_SHA256" \
|
|
--arg sourceParentRunAttempt "$SOURCE_PARENT_RUN_ATTEMPT" \
|
|
--argjson evidenceChangedPaths "$EVIDENCE_CHANGED_PATHS" \
|
|
'. + {
|
|
version: 3,
|
|
runId: $runId,
|
|
runAttempt: $runAttempt,
|
|
workflowRef: $workflowRef,
|
|
workflowSha: $workflowSha,
|
|
workflowFullRef: $workflowFullRef,
|
|
workflowRefType: $workflowRefType,
|
|
targetRef: $targetRef,
|
|
targetSha: $targetSha,
|
|
evidenceReuse: {
|
|
policy: $evidencePolicy,
|
|
runId: $evidenceRootRunId,
|
|
selectedRunId: $evidenceRunId,
|
|
evidenceSha: $evidenceSha,
|
|
changedPaths: $evidenceChangedPaths
|
|
},
|
|
controls: ((.controls // {}) + {
|
|
performanceReportPublication: "artifact-only"
|
|
}),
|
|
executionPlanSha256: $executionPlanSha256,
|
|
sourceParentRunAttempt: ($sourceParentRunAttempt | tonumber)
|
|
}' <<< "$EVIDENCE_MANIFEST" > "${manifest_dir}/full-release-validation-manifest.json"
|
|
exit 0
|
|
fi
|
|
jq -n \
|
|
--arg workflowName "Full Release Validation" \
|
|
--arg runId "$GITHUB_RUN_ID" \
|
|
--arg runAttempt "$GITHUB_RUN_ATTEMPT" \
|
|
--arg workflowRef "$GITHUB_REF_NAME" \
|
|
--arg workflowSha "$GITHUB_SHA" \
|
|
--arg workflowFullRef "$GITHUB_REF" \
|
|
--arg workflowRefType "$GITHUB_REF_TYPE" \
|
|
--arg targetRef "$TARGET_REF" \
|
|
--arg targetSha "$TARGET_SHA" \
|
|
--arg releaseProfile "$RELEASE_PROFILE" \
|
|
--arg rerunGroup "$RERUN_GROUP" \
|
|
--arg runReleaseSoak "$RUN_RELEASE_SOAK" \
|
|
--arg normalCiRunId "$NORMAL_CI_RUN_ID" \
|
|
--arg pluginPrereleaseRunId "$PLUGIN_PRERELEASE_RUN_ID" \
|
|
--arg releaseChecksRunId "$RELEASE_CHECKS_RUN_ID" \
|
|
--arg npmTelegramRunId "$NPM_TELEGRAM_RUN_ID" \
|
|
--arg performanceRunId "$PERFORMANCE_RUN_ID" \
|
|
--arg performanceConclusion "$PERFORMANCE_CONCLUSION" \
|
|
--arg executionPlanSha256 "$EXECUTION_PLAN_SHA256" \
|
|
--arg sourceParentRunAttempt "$SOURCE_PARENT_RUN_ATTEMPT" \
|
|
--arg provider "$PROVIDER" \
|
|
--arg mode "$MODE" \
|
|
--arg targetContextRef "$TARGET_CONTEXT_REF" \
|
|
--arg liveSuiteFilter "$LIVE_SUITE_FILTER" \
|
|
--arg crossOsSuiteFilter "$CROSS_OS_SUITE_FILTER" \
|
|
--arg releasePackageSpec "$RELEASE_PACKAGE_SPEC" \
|
|
--arg packageAcceptancePackageSpec "$PACKAGE_ACCEPTANCE_PACKAGE_SPEC" \
|
|
--arg codexPluginSpec "$CODEX_PLUGIN_SPEC" \
|
|
--arg npmTelegramPackageSpec "$NPM_TELEGRAM_PACKAGE_SPEC" \
|
|
--arg npmTelegramProviderMode "$NPM_TELEGRAM_PROVIDER_MODE" \
|
|
--arg npmTelegramScenario "$NPM_TELEGRAM_SCENARIO" \
|
|
--arg skipPackageTelegramE2e "$SKIP_PACKAGE_TELEGRAM_E2E" \
|
|
--arg allowUnreleasedChangelog "$ALLOW_UNRELEASED_CHANGELOG" \
|
|
--arg pluginPrereleaseNodeExcludePatternsJson "$PLUGIN_PRERELEASE_NODE_EXCLUDE_PATTERNS_JSON" \
|
|
'{
|
|
version: 3,
|
|
workflowName: $workflowName,
|
|
runId: $runId,
|
|
runAttempt: $runAttempt,
|
|
workflowRef: $workflowRef,
|
|
workflowSha: $workflowSha,
|
|
workflowFullRef: $workflowFullRef,
|
|
workflowRefType: $workflowRefType,
|
|
targetRef: $targetRef,
|
|
targetSha: $targetSha,
|
|
releaseProfile: $releaseProfile,
|
|
rerunGroup: $rerunGroup,
|
|
runReleaseSoak: $runReleaseSoak,
|
|
executionPlanSha256: $executionPlanSha256,
|
|
sourceParentRunAttempt: ($sourceParentRunAttempt | tonumber),
|
|
validationInputs: {
|
|
provider: $provider,
|
|
mode: $mode,
|
|
targetContextRef: $targetContextRef,
|
|
liveSuiteFilter: $liveSuiteFilter,
|
|
crossOsSuiteFilter: $crossOsSuiteFilter,
|
|
releasePackageSpec: $releasePackageSpec,
|
|
packageAcceptancePackageSpec: $packageAcceptancePackageSpec,
|
|
codexPluginSpec: $codexPluginSpec,
|
|
npmTelegramPackageSpec: $npmTelegramPackageSpec,
|
|
npmTelegramProviderMode: $npmTelegramProviderMode,
|
|
npmTelegramScenario: $npmTelegramScenario,
|
|
skipPackageTelegramE2e: $skipPackageTelegramE2e,
|
|
allowUnreleasedChangelog: $allowUnreleasedChangelog,
|
|
pluginPrereleaseNodeExcludePatternsJson: $pluginPrereleaseNodeExcludePatternsJson
|
|
},
|
|
controls: {
|
|
stableSoakRequired: ($releaseProfile == "stable" or $releaseProfile == "full"),
|
|
performanceBlocking: ($releaseProfile != "beta"),
|
|
performanceReportPublication: "artifact-only"
|
|
},
|
|
childRuns: {
|
|
normalCi: $normalCiRunId,
|
|
pluginPrerelease: $pluginPrereleaseRunId,
|
|
releaseChecks: $releaseChecksRunId,
|
|
npmTelegram: $npmTelegramRunId,
|
|
productPerformance: {
|
|
runId: $performanceRunId,
|
|
conclusion: $performanceConclusion,
|
|
blocking: ($releaseProfile != "beta")
|
|
}
|
|
}
|
|
}' > "${manifest_dir}/full-release-validation-manifest.json"
|
|
|
|
- name: Validate release validation manifest
|
|
env:
|
|
RELEASE_PROFILE: ${{ inputs.release_profile }}
|
|
RERUN_GROUP: ${{ inputs.rerun_group }}
|
|
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
|
|
RELEASE_EXECUTION_PLAN_PATH: ${{ runner.temp }}/full-release-execution-plan/full-release-execution-plan.json
|
|
RELEASE_VALIDATION_MANIFEST_PATH: ${{ runner.temp }}/full-release-validation/full-release-validation-manifest.json
|
|
run: node scripts/full-release-validation-state.mjs validate-manifest
|
|
|
|
- name: Upload release validation manifest
|
|
if: ${{ success() }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: full-release-validation-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: ${{ runner.temp }}/full-release-validation
|
|
if-no-files-found: error
|
|
|
|
- name: Upload legacy release validation manifest alias
|
|
if: ${{ success() }}
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
with:
|
|
name: full-release-validation-${{ github.run_id }}
|
|
path: ${{ runner.temp }}/full-release-validation
|
|
if-no-files-found: error
|
|
overwrite: true
|