diff --git a/.github/workflows/full-release-validation.yml b/.github/workflows/full-release-validation.yml index 6ce6bf2fac3c..783ce1d99b87 100644 --- a/.github/workflows/full-release-validation.yml +++ b/.github/workflows/full-release-validation.yml @@ -18,6 +18,11 @@ on: 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 @@ -175,6 +180,17 @@ jobs: --ref "$TARGET_REF" \ --github-output "$GITHUB_OUTPUT" + - name: Validate release inputs + env: + RELEASE_PROFILE: ${{ inputs.release_profile }} + SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }} + 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 + - name: Summarize target env: TARGET_REF: ${{ inputs.ref }} @@ -188,6 +204,7 @@ jobs: 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: ${{ inputs.live_suite_filter }} @@ -201,6 +218,7 @@ jobs: echo "- Child workflow ref: \`${CHILD_WORKFLOW_REF}\`" 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 @@ -239,7 +257,11 @@ jobs: 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" == "release-checks" || "$RERUN_GROUP" == "package" ]]; then - echo "- Package Telegram E2E: OpenClaw Release Checks Package Acceptance" + 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 @@ -311,6 +333,7 @@ jobs: 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')) }} run: | set -euo pipefail @@ -328,6 +351,7 @@ jobs: --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" \ '{ provider: $provider, @@ -341,6 +365,7 @@ jobs: npmTelegramPackageSpec: $npmTelegramPackageSpec, npmTelegramProviderMode: $npmTelegramProviderMode, npmTelegramScenario: $npmTelegramScenario, + skipPackageTelegramE2e: $skipPackageTelegramE2e, allowUnreleasedChangelog: $allowUnreleasedChangelog }')" bash workflow/scripts/github/find-reusable-release-validation.sh \ @@ -857,6 +882,7 @@ jobs: 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" child_rerun_group="$RERUN_GROUP" if [[ "$child_rerun_group" == "release-checks" ]]; then @@ -872,6 +898,7 @@ jobs: -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="$child_rerun_group" ) if [[ -n "${TARGET_CONTEXT_REF// }" ]]; then @@ -1011,6 +1038,7 @@ jobs: 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: @@ -1099,6 +1127,7 @@ jobs: RELEASE_PACKAGE_SPEC: ${{ inputs.release_package_spec }} NPM_TELEGRAM_PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec }} PACKAGE_ACCEPTANCE_PACKAGE_SPEC: ${{ inputs.package_acceptance_package_spec }} + SKIP_PACKAGE_TELEGRAM_E2E: ${{ inputs.skip_package_telegram_e2e }} EVIDENCE_REUSE: ${{ needs.evidence_reuse.outputs.reuse }} EVIDENCE_ROOT_RUN_ID: ${{ needs.evidence_reuse.outputs.evidence_root_run_id }} EVIDENCE_RUN_URL: ${{ needs.evidence_reuse.outputs.evidence_run_url }} @@ -1374,6 +1403,7 @@ jobs: if [[ "$RERUN_GROUP" == "npm-telegram" || ( "$RERUN_GROUP" == "all" && ( -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" || -n "${RELEASE_PACKAGE_SPEC// }" ) ) ]]; then npm_telegram_required=1 fi + echo "- Package Telegram E2E deferred: \`${SKIP_PACKAGE_TELEGRAM_E2E}\`" >> "$GITHUB_STEP_SUMMARY" if [[ -z "${RELEASE_PACKAGE_SPEC// }" && -z "${PACKAGE_ACCEPTANCE_PACKAGE_SPEC// }" ]]; then case "$RERUN_GROUP" in all|plugin-prerelease|release-checks|cross-os|live-e2e|package) @@ -1625,6 +1655,7 @@ jobs: 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')) }} run: | set -euo pipefail @@ -1700,6 +1731,7 @@ jobs: --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" \ '{ version: 3, @@ -1727,6 +1759,7 @@ jobs: npmTelegramPackageSpec: $npmTelegramPackageSpec, npmTelegramProviderMode: $npmTelegramProviderMode, npmTelegramScenario: $npmTelegramScenario, + skipPackageTelegramE2e: $skipPackageTelegramE2e, allowUnreleasedChangelog: $allowUnreleasedChangelog }, controls: { diff --git a/.github/workflows/openclaw-release-checks.yml b/.github/workflows/openclaw-release-checks.yml index 5a056ffe1f0c..d54866f716f1 100644 --- a/.github/workflows/openclaw-release-checks.yml +++ b/.github/workflows/openclaw-release-checks.yml @@ -61,6 +61,11 @@ on: required: false default: false type: boolean + skip_package_telegram_e2e: + description: Defer beta Package Acceptance Telegram E2E + required: false + default: false + type: boolean allow_frozen_target_scenario_omissions: description: Allow trusted compatibility filtering for a canonical frozen release target required: false @@ -141,6 +146,7 @@ jobs: fail_fast: ${{ steps.inputs.outputs.fail_fast }} run_maturity_scorecard: ${{ steps.inputs.outputs.run_maturity_scorecard }} allow_unreleased_changelog: ${{ steps.inputs.outputs.allow_unreleased_changelog }} + skip_package_telegram_e2e: ${{ steps.inputs.outputs.skip_package_telegram_e2e }} rerun_group: ${{ steps.inputs.outputs.rerun_group }} live_suite_filter: ${{ steps.inputs.outputs.live_suite_filter }} repo_live_suite_filter: ${{ steps.inputs.outputs.repo_live_suite_filter }} @@ -324,6 +330,7 @@ jobs: RELEASE_FAIL_FAST_INPUT: ${{ inputs.fail_fast }} RELEASE_RUN_MATURITY_SCORECARD_INPUT: ${{ inputs.run_maturity_scorecard }} RELEASE_ALLOW_UNRELEASED_CHANGELOG_INPUT: ${{ inputs.allow_unreleased_changelog }} + RELEASE_SKIP_PACKAGE_TELEGRAM_E2E_INPUT: ${{ inputs.skip_package_telegram_e2e }} RELEASE_RERUN_GROUP_INPUT: ${{ inputs.rerun_group }} RELEASE_LIVE_SUITE_FILTER_INPUT: ${{ inputs.live_suite_filter }} RELEASE_CROSS_OS_SUITE_FILTER_INPUT: ${{ inputs.cross_os_suite_filter }} @@ -408,6 +415,16 @@ jobs: else allow_unreleased_changelog=false fi + skip_package_telegram_e2e="$(printf '%s' "$RELEASE_SKIP_PACKAGE_TELEGRAM_E2E_INPUT" | tr '[:upper:]' '[:lower:]')" + if [[ "$skip_package_telegram_e2e" != "true" && "$skip_package_telegram_e2e" != "1" && "$skip_package_telegram_e2e" != "yes" ]]; then + skip_package_telegram_e2e=false + else + skip_package_telegram_e2e=true + fi + 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 codex_plugin_spec="$RELEASE_CODEX_PLUGIN_SPEC_INPUT" if [[ -z "${codex_plugin_spec// }" && "$RELEASE_PACKAGE_SPEC_INPUT" =~ ^openclaw@(.+)$ ]]; then codex_plugin_spec="npm:@openclaw/codex@${BASH_REMATCH[1]}" @@ -528,6 +545,7 @@ jobs: printf 'fail_fast=%s\n' "$fail_fast" printf 'run_maturity_scorecard=%s\n' "$run_maturity_scorecard" printf 'allow_unreleased_changelog=%s\n' "$allow_unreleased_changelog" + printf 'skip_package_telegram_e2e=%s\n' "$skip_package_telegram_e2e" printf 'rerun_group=%s\n' "$RELEASE_RERUN_GROUP_INPUT" printf 'live_suite_filter=%s\n' "$RELEASE_LIVE_SUITE_FILTER_INPUT" printf 'repo_live_suite_filter=%s\n' "$repo_live_suite_filter" @@ -556,6 +574,7 @@ jobs: RUN_RELEASE_SOAK: ${{ steps.inputs.outputs.run_release_soak }} FAIL_FAST: ${{ steps.inputs.outputs.fail_fast }} RUN_MATURITY_SCORECARD: ${{ steps.inputs.outputs.run_maturity_scorecard }} + SKIP_PACKAGE_TELEGRAM_E2E: ${{ steps.inputs.outputs.skip_package_telegram_e2e }} ALLOW_UNRELEASED_CHANGELOG: ${{ steps.inputs.outputs.allow_unreleased_changelog }} RELEASE_RERUN_GROUP: ${{ inputs.rerun_group }} RELEASE_LIVE_SUITE_FILTER: ${{ inputs.live_suite_filter }} @@ -576,6 +595,7 @@ jobs: echo "- Release soak lanes: \`${RUN_RELEASE_SOAK}\`" echo "- Matrix QA fail fast: \`${FAIL_FAST}\`" echo "- Maturity scorecard docs: \`${RUN_MATURITY_SCORECARD}\`" + echo "- Package Acceptance Telegram E2E deferred: \`${SKIP_PACKAGE_TELEGRAM_E2E}\`" echo "- Allow Unreleased changelog packaging: \`${ALLOW_UNRELEASED_CHANGELOG}\`" echo "- Rerun group: \`${RELEASE_RERUN_GROUP}\`" if [[ -n "${RELEASE_LIVE_SUITE_FILTER// }" ]]; then @@ -1117,7 +1137,7 @@ jobs: docker_lanes: release-typed-onboarding doctor-switch update-channel-switch skill-install update-corrupt-plugin upgrade-survivor published-upgrade-survivor root-managed-vps-upgrade update-restart-auth plugins-offline plugin-update plugin-binding-command-escape published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }} published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }} - telegram_mode: mock-openai + telegram_mode: ${{ needs.resolve_target.outputs.skip_package_telegram_e2e == 'true' && 'none' || 'mock-openai' }} telegram_advisory: ${{ needs.resolve_target.outputs.release_profile == 'beta' }} shared_image_artifact_namespace: release-package candidate_artifact_json: ${{ inputs.candidate_artifact_json }} diff --git a/docs/reference/full-release-validation.md b/docs/reference/full-release-validation.md index 3259529ada5e..1979375eb5e8 100644 --- a/docs/reference/full-release-validation.md +++ b/docs/reference/full-release-validation.md @@ -89,6 +89,11 @@ Codex `final`, reads randomized workspace inputs, writes their exact artifact, and sends explicit completion. This catches the v2026.7.1 regression where an ordinary progress send terminated the turn. +Use `-f skip_package_telegram_e2e=true` only when the release owner explicitly +defers the Package Acceptance Telegram E2E to a follow-up beta. The input is +rejected for `stable` and `full`, recorded in validation evidence, and does not disable the focused +`rerun_group=npm-telegram` workflow. + ## Top-level stages For `rerun_group=all`, a `Check for reusable validation evidence` job runs diff --git a/scripts/e2e/npm-telegram-live-docker.sh b/scripts/e2e/npm-telegram-live-docker.sh index 9a6f1ae7c22b..9fccc88d1dd2 100755 --- a/scripts/e2e/npm-telegram-live-docker.sh +++ b/scripts/e2e/npm-telegram-live-docker.sh @@ -417,6 +417,7 @@ run_logged_print_heartbeat "npm-telegram-live-suite" 60 docker_e2e_run_with_harn -v "$ROOT_DIR/dist:/app/.openclaw-qa-harness-dist:ro" \ -v "$ROOT_DIR/extensions/qa-lab:/app/extensions/qa-lab:ro" \ -v "$ROOT_DIR/qa/scenarios:/app/qa/scenarios:ro" \ + -v "$ROOT_DIR/taxonomy.yaml:/app/taxonomy.yaml:ro" \ -v "$npm_prefix_host:/npm-global" \ -i "$IMAGE_NAME" bash -s <<'EOF' set -euo pipefail diff --git a/test/scripts/find-reusable-release-validation.test.ts b/test/scripts/find-reusable-release-validation.test.ts index 1fa34300b748..22ef8f7d1c6c 100644 --- a/test/scripts/find-reusable-release-validation.test.ts +++ b/test/scripts/find-reusable-release-validation.test.ts @@ -29,6 +29,7 @@ const DEFAULT_INPUTS = { npmTelegramPackageSpec: "", npmTelegramProviderMode: "mock-openai", npmTelegramScenario: "", + skipPackageTelegramE2e: "false", allowUnreleasedChangelog: "false", }; @@ -906,6 +907,14 @@ describe("scripts/github/find-reusable-release-validation.sh", () => { }, resolverOptions: {}, }, + { + expected: "validation inputs differ", + label: "different package Telegram deferral", + recordOptions: { + validationInputs: { ...DEFAULT_INPUTS, skipPackageTelegramE2e: "true" }, + }, + resolverOptions: {}, + }, { expected: "validation inputs differ", label: "different unreleased changelog policy", diff --git a/test/scripts/npm-telegram-live.test.ts b/test/scripts/npm-telegram-live.test.ts index a23e8cab1c97..9700d80982a1 100644 --- a/test/scripts/npm-telegram-live.test.ts +++ b/test/scripts/npm-telegram-live.test.ts @@ -228,6 +228,13 @@ describe("package Telegram live Docker E2E", () => { }); }); + it("mounts the QA taxonomy without exposing the repository root", () => { + const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8"); + + expect(script).toContain('-v "$ROOT_DIR/taxonomy.yaml:/app/taxonomy.yaml:ro"'); + expect(script).not.toContain('-v "$ROOT_DIR:/app'); + }); + it("mounts configured output paths before entering the container", () => { const script = readFileSync(DOCKER_SCRIPT_PATH, "utf8"); const dockerEnvStart = script.indexOf("docker_env=("); diff --git a/test/scripts/package-acceptance-workflow.test.ts b/test/scripts/package-acceptance-workflow.test.ts index 0f099b65ad8a..fe53e0c151d4 100644 --- a/test/scripts/package-acceptance-workflow.test.ts +++ b/test/scripts/package-acceptance-workflow.test.ts @@ -165,6 +165,9 @@ type Workflow = { workflow_call?: { inputs?: Record; }; + workflow_dispatch?: { + inputs?: Record; + }; }; }; @@ -194,6 +197,78 @@ function workflowStep(job: WorkflowJob, stepName: string): WorkflowStep { return step; } +function runFullReleaseInputValidation(releaseProfile: string, skipTelegram: string) { + const step = workflowStep( + workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "resolve_target"), + "Validate release inputs", + ); + return spawnSync("bash", ["-c", step.run ?? ""], { + encoding: "utf8", + env: { + PATH: process.env.PATH, + RELEASE_PROFILE: releaseProfile, + SKIP_PACKAGE_TELEGRAM_E2E: skipTelegram, + }, + }); +} + +function runReleaseChecksInputValidation(releaseProfile: string, skipTelegram: string) { + const step = workflowStep( + workflowJob(RELEASE_CHECKS_WORKFLOW, "resolve_target"), + "Capture selected inputs", + ); + const workdir = tempDirs.make("release-checks-input-validation-"); + const outputPath = resolve(workdir, "github-output"); + const stepEnv = Object.fromEntries(Object.keys(step.env ?? {}).map((name) => [name, ""])); + const result = spawnSync("bash", ["-c", step.run ?? ""], { + encoding: "utf8", + env: { + ...stepEnv, + CANDIDATE_ARTIFACT_JSON_INPUT: "", + GITHUB_OUTPUT: outputPath, + PATH: process.env.PATH, + RELEASE_FAIL_FAST_INPUT: "false", + RELEASE_MODE_INPUT: "both", + RELEASE_PROFILE_INPUT: releaseProfile, + RELEASE_PROVIDER_INPUT: "openai", + RELEASE_QA_DISCORD_LIVE_CI_ENABLED: "false", + RELEASE_QA_SLACK_LIVE_CI_ENABLED: "false", + RELEASE_QA_WHATSAPP_LIVE_CI_ENABLED: "false", + RELEASE_REF_INPUT: "main", + RELEASE_RERUN_GROUP_INPUT: "all", + RELEASE_RUN_MATURITY_SCORECARD_INPUT: "false", + RELEASE_RUN_RELEASE_SOAK_INPUT: "false", + RELEASE_SKIP_PACKAGE_TELEGRAM_E2E_INPUT: skipTelegram, + }, + }); + return { outputPath, result }; +} + +function runFullReleaseTargetSummary(rerunGroup: string, skipTelegram: string) { + const step = workflowStep( + workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "resolve_target"), + "Summarize target", + ); + const workdir = tempDirs.make("full-release-target-summary-"); + const summaryPath = resolve(workdir, "github-summary"); + const stepEnv = Object.fromEntries(Object.keys(step.env ?? {}).map((name) => [name, ""])); + const result = spawnSync("bash", ["-c", step.run ?? ""], { + encoding: "utf8", + env: { + ...stepEnv, + GITHUB_STEP_SUMMARY: summaryPath, + PATH: process.env.PATH, + RELEASE_PROFILE: "beta", + RERUN_GROUP: rerunGroup, + SKIP_PACKAGE_TELEGRAM_E2E: skipTelegram, + TARGET_REF: "main", + TARGET_SHA: "a".repeat(40), + }, + }); + const summary = result.status === 0 ? readFileSync(summaryPath, "utf8") : ""; + return { result, summary }; +} + function shellFunctionSource(source: string, functionName: string): string { const startMarker = `${functionName}() {`; const endMarker = "\n}\n"; @@ -368,6 +443,7 @@ if (args[0] === "workflow" && args[1] === "run") { RERUN_GROUP: "all", RUN_RELEASE_SOAK: "false", SCENARIO: "", + SKIP_PACKAGE_TELEGRAM_E2E: "false", TARGET_CONTEXT_REF: "", TARGET_REF: "main", TARGET_SHA: "b".repeat(40), @@ -3292,6 +3368,66 @@ describe("package artifact reuse", () => { expect(workflow).toContain("run_package_telegram_e2e:"); }); + it.each(["stable", "full"])( + "rejects Package Acceptance Telegram deferral for direct %s release checks", + (releaseProfile) => { + const { result } = runReleaseChecksInputValidation(releaseProfile, "true"); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + "skip_package_telegram_e2e is allowed only for release_profile=beta.", + ); + }, + ); + + it.each(["stable", "full"])( + "rejects Package Acceptance Telegram deferral for umbrella %s validation", + (releaseProfile) => { + const result = runFullReleaseInputValidation(releaseProfile, "true"); + + expect(result.status).toBe(1); + expect(result.stderr).toContain( + "skip_package_telegram_e2e is allowed only for release_profile=beta.", + ); + }, + ); + + it("allows Package Acceptance Telegram deferral only for beta validation", () => { + const direct = runReleaseChecksInputValidation("beta", "true"); + const umbrella = runFullReleaseInputValidation("beta", "true"); + + expect(direct.result.status, direct.result.stderr).toBe(0); + expect(readFileSync(direct.outputPath, "utf8")).toContain("skip_package_telegram_e2e=true"); + expect(umbrella.status, umbrella.stderr).toBe(0); + }); + + it.each(["stable", "full"])( + "preserves normal %s validation when Telegram deferral is false", + (releaseProfile) => { + const direct = runReleaseChecksInputValidation(releaseProfile, "false"); + const umbrella = runFullReleaseInputValidation(releaseProfile, "false"); + + expect(direct.result.status, direct.result.stderr).toBe(0); + expect(readFileSync(direct.outputPath, "utf8")).toContain("skip_package_telegram_e2e=false"); + expect(umbrella.status, umbrella.stderr).toBe(0); + }, + ); + + it("summarizes Telegram deferral only when Package Acceptance is scheduled", () => { + const scheduled = runFullReleaseTargetSummary("release-checks", "true"); + const unrelated = runFullReleaseTargetSummary("ci", "true"); + + expect(scheduled.result.status, scheduled.result.stderr).toBe(0); + expect(scheduled.summary).toContain( + "Package Telegram E2E: deferred by `skip_package_telegram_e2e`", + ); + expect(unrelated.result.status, unrelated.result.stderr).toBe(0); + expect(unrelated.summary).toContain("Package Telegram E2E: skipped by rerun group"); + expect(unrelated.summary).not.toContain( + "Package Telegram E2E: deferred by `skip_package_telegram_e2e`", + ); + }); + it("includes package acceptance in release checks", () => { const workflow = readFileSync(RELEASE_CHECKS_WORKFLOW, "utf8"); const packageAcceptanceWorkflow = parse(readFileSync(PACKAGE_ACCEPTANCE_WORKFLOW, "utf8")) as { @@ -3303,6 +3439,10 @@ describe("package artifact reuse", () => { RELEASE_CHECKS_WORKFLOW, "package_acceptance_release_checks", ); + const releaseChecksTargetSummary = workflowStep( + workflowJob(RELEASE_CHECKS_WORKFLOW, "resolve_target"), + "Summarize validated ref", + ); const dockerAcceptanceJob = workflowJob(PACKAGE_ACCEPTANCE_WORKFLOW, "docker_acceptance"); expect(workflow).toContain("package_acceptance_release_checks:"); @@ -3324,6 +3464,10 @@ describe("package artifact reuse", () => { package_version: "${{ needs.prepare_release_package.outputs.package_version }}", suite_profile: "custom", }); + expect(releaseChecksTargetSummary.env).toMatchObject({ + SKIP_PACKAGE_TELEGRAM_E2E: "${{ steps.inputs.outputs.skip_package_telegram_e2e }}", + }); + expect(releaseChecksTargetSummary.run).toContain("Package Acceptance Telegram E2E deferred:"); expect(dockerAcceptanceJob.with).toMatchObject({ allow_frozen_target_scenario_omissions: "${{ inputs.allow_frozen_target_scenario_omissions || false }}", @@ -3381,7 +3525,16 @@ describe("package artifact reuse", () => { expect(workflow).toContain( "published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}", ); - expect(workflow).toContain("telegram_mode: mock-openai"); + expect(readWorkflow(RELEASE_CHECKS_WORKFLOW).on?.workflow_dispatch?.inputs).toMatchObject({ + skip_package_telegram_e2e: { + default: false, + type: "boolean", + }, + }); + expect(packageAcceptanceJob.with).toMatchObject({ + telegram_mode: + "${{ needs.resolve_target.outputs.skip_package_telegram_e2e == 'true' && 'none' || 'mock-openai' }}", + }); expect(packageAcceptanceJob.with).toMatchObject({ telegram_advisory: "${{ needs.resolve_target.outputs.release_profile == 'beta' }}", }); @@ -4007,14 +4160,30 @@ describe("package artifact reuse", () => { it("runs full release children from the trusted workflow ref", () => { const workflow = readFileSync(FULL_RELEASE_VALIDATION_WORKFLOW, "utf8"); + const workflowInputs = readWorkflow(FULL_RELEASE_VALIDATION_WORKFLOW).on?.workflow_dispatch + ?.inputs; + const resolveTargetJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "resolve_target"); const evidenceReuseJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "evidence_reuse"); + const releaseChecksJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "release_checks"); const npmTelegramJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "npm_telegram"); const performanceJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "performance"); const summaryJob = workflowJob(FULL_RELEASE_VALIDATION_WORKFLOW, "summary"); + const targetSummaryStep = workflowStep(resolveTargetJob, "Summarize target"); const evidenceReuseStep = workflowStep(evidenceReuseJob, "Find reusable validation evidence"); + const releaseChecksDispatchStep = workflowStep( + releaseChecksJob, + "Dispatch and monitor release checks", + ); const dispatchStep = workflowStep(npmTelegramJob, "Dispatch and monitor npm Telegram E2E"); + const verificationStep = workflowStep(summaryJob, "Verify child workflow results"); const manifestStep = workflowStep(summaryJob, "Write release validation manifest"); + expect(workflowInputs).toMatchObject({ + skip_package_telegram_e2e: { + default: false, + type: "boolean", + }, + }); expect(workflow).toContain("CHILD_WORKFLOW_REF: ${{ github.ref_name }}"); expect(workflow).toContain('gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1'); expect(npmTelegramJob.name).toBe("Run package Telegram E2E"); @@ -4035,13 +4204,28 @@ describe("package artifact reuse", () => { 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 }}", }); expectTextToIncludeAll(evidenceReuseStep.run, [ "npmTelegramPackageSpec: $npmTelegramPackageSpec", "npmTelegramProviderMode: $npmTelegramProviderMode", "npmTelegramScenario: $npmTelegramScenario", + "skipPackageTelegramE2e: $skipPackageTelegramE2e", "allowUnreleasedChangelog: $allowUnreleasedChangelog", ]); + expect(targetSummaryStep.env).toMatchObject({ + SKIP_PACKAGE_TELEGRAM_E2E: "${{ inputs.skip_package_telegram_e2e }}", + }); + expectTextToIncludeAll(targetSummaryStep.run, [ + "Package Acceptance Telegram E2E deferred:", + "Package Telegram E2E: deferred by \\`skip_package_telegram_e2e\\`", + ]); + expect(releaseChecksDispatchStep.env).toMatchObject({ + SKIP_PACKAGE_TELEGRAM_E2E: "${{ inputs.skip_package_telegram_e2e }}", + }); + expect(releaseChecksDispatchStep.run).toContain( + '-f skip_package_telegram_e2e="$SKIP_PACKAGE_TELEGRAM_E2E"', + ); expect(dispatchStep.env).toEqual({ CHILD_WORKFLOW_KIND: "npm-telegram", CHILD_WORKFLOW_REF: "${{ github.ref_name }}", @@ -4059,13 +4243,19 @@ describe("package artifact reuse", () => { 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 }}", }); expectTextToIncludeAll(manifestStep.run, [ "npmTelegramPackageSpec: $npmTelegramPackageSpec", "npmTelegramProviderMode: $npmTelegramProviderMode", "npmTelegramScenario: $npmTelegramScenario", + "skipPackageTelegramE2e: $skipPackageTelegramE2e", "allowUnreleasedChangelog: $allowUnreleasedChangelog", ]); + expect(verificationStep.env).toMatchObject({ + SKIP_PACKAGE_TELEGRAM_E2E: "${{ inputs.skip_package_telegram_e2e }}", + }); + expect(verificationStep.run).toContain("Package Telegram E2E deferred:"); expectTextToIncludeAll(dispatchStep.run, [ 'dispatch_id="full-release-validation-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}-npm-telegram"', 'dispatch_output="$(gh workflow run "$workflow" --ref "$CHILD_WORKFLOW_REF" "$@" 2>&1)"', @@ -4118,6 +4308,7 @@ describe("package artifact reuse", () => { expectTextToIncludeAll(workflow, [ "Published-package Telegram E2E:", + "Package Telegram E2E: deferred by \\`skip_package_telegram_e2e\\`", "Package Telegram E2E: OpenClaw Release Checks Package Acceptance", "Package Telegram E2E: focused rerun requires \\`release_package_spec\\` or \\`npm_telegram_package_spec\\`", ]); diff --git a/test/scripts/release-ci-summary.test.ts b/test/scripts/release-ci-summary.test.ts index 4fa7e33b9143..026f357b0a12 100644 --- a/test/scripts/release-ci-summary.test.ts +++ b/test/scripts/release-ci-summary.test.ts @@ -247,6 +247,7 @@ function rawManifest({ packageAcceptancePackageSpec: "", provider: "openai", releasePackageSpec: "", + skipPackageTelegramE2e: "false", targetContextRef: "", }, version, @@ -1316,6 +1317,7 @@ describe("release CI summary child correlation", () => { const raw = rawManifest({}); raw.childRuns.npmTelegram = "505"; raw.validationInputs.npmTelegramPackageSpec = "openclaw@beta"; + raw.validationInputs.skipPackageTelegramE2e = "true"; const manifest = validateParentManifest(raw, { runAttempt: 2, runId: "29090000000",