From 9a87128cea53f69c6eaa841e009ef125bf27146f Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Tue, 14 Jul 2026 07:13:01 -0700 Subject: [PATCH] fix(release): validate candidate installer evidence --- .github/workflows/full-release-validation.yml | 2 +- .github/workflows/install-smoke.yml | 5 ++- .../find-reusable-release-validation.sh | 1 + scripts/test-install-sh-docker.sh | 43 ++++++++++++------- .../find-reusable-release-validation.test.ts | 2 + test/scripts/release-no-push-workflow.test.ts | 1 + test/scripts/test-install-sh-docker.test.ts | 16 +++++-- 7 files changed, 47 insertions(+), 23 deletions(-) diff --git a/.github/workflows/full-release-validation.yml b/.github/workflows/full-release-validation.yml index c1ad0086ba9c..18ec10bf9e53 100644 --- a/.github/workflows/full-release-validation.yml +++ b/.github/workflows/full-release-validation.yml @@ -1834,7 +1834,7 @@ jobs: --arg release_id "$release_id" \ --arg release_ref "$TARGET_REF" \ --arg package_spec "$evidence_package_spec" \ - --arg notes "Automatically requested by Full Release Validation ${GITHUB_RUN_ID_VALUE} after child workflows completed; the parent summary re-checks current child run conclusions." \ + --arg notes "$notes" \ '{ event_type: "openclaw_full_release_validation_completed", client_payload: { diff --git a/.github/workflows/install-smoke.yml b/.github/workflows/install-smoke.yml index eebe4ce96c6e..c180b9ed011a 100644 --- a/.github/workflows/install-smoke.yml +++ b/.github/workflows/install-smoke.yml @@ -896,6 +896,7 @@ jobs: OPENCLAW_INSTALL_SMOKE_UPDATE_BASELINE: ${{ inputs.update_baseline_version || 'latest' }} OPENCLAW_INSTALL_SMOKE_UPDATE_DIST_IMAGE: ${{ needs.root_dockerfile_image.outputs.image_ref }} OPENCLAW_INSTALL_SMOKE_UPDATE_SKIP_LOCAL_BUILD: "1" + OPENCLAW_INSTALL_SMOKE_SOURCE_DIR: ${{ github.workspace }}/candidate run: bash scripts/test-install-sh-docker.sh - name: Run Rocky Linux installer smoke @@ -904,7 +905,7 @@ jobs: --platform linux/amd64 \ -e OPENCLAW_NO_ONBOARD=1 \ -e OPENCLAW_NO_PROMPT=1 \ - -v "$PWD/scripts/install.sh:/tmp/install.sh:ro" \ + -v "$PWD/candidate/scripts/install.sh:/tmp/install.sh:ro" \ rockylinux:9@sha256:d644d203142cd5b54ad2a83a203e1dee68af2229f8fe32f52a30c6e1d3c3a9e0 \ bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install.sh --install-method npm --version latest --no-onboard --no-prompt --verify && openclaw --version' @@ -914,7 +915,7 @@ jobs: --platform linux/amd64 \ -e OPENCLAW_NO_ONBOARD=1 \ -e OPENCLAW_NO_PROMPT=1 \ - -v "$PWD/scripts/install-cli.sh:/tmp/install-cli.sh:ro" \ + -v "$PWD/candidate/scripts/install-cli.sh:/tmp/install-cli.sh:ro" \ rockylinux:9@sha256:d644d203142cd5b54ad2a83a203e1dee68af2229f8fe32f52a30c6e1d3c3a9e0 \ bash -lc 'dnf install -y -q ca-certificates tar gzip xz findutils which sudo >/dev/null && bash /tmp/install-cli.sh --prefix /tmp/openclaw-cli --version latest --no-onboard && /tmp/openclaw-cli/bin/openclaw --version' diff --git a/scripts/github/find-reusable-release-validation.sh b/scripts/github/find-reusable-release-validation.sh index 834a47920ac1..f66dcb7c5cf1 100755 --- a/scripts/github/find-reusable-release-validation.sh +++ b/scripts/github/find-reusable-release-validation.sh @@ -299,6 +299,7 @@ for ((index = 0; index < run_count; index += 1)); do write_output evidence_root_run_id "$run_id" write_output evidence_run_url "$run_url" write_output evidence_sha "$prior_sha" + write_output evidence_policy "exact-target-full-validation-v1" write_output changed_path_count "0" write_output changed_paths "[]" write_output evidence_manifest "$(jq -c '.manifest' <<< "$validation_record")" diff --git a/scripts/test-install-sh-docker.sh b/scripts/test-install-sh-docker.sh index 1f9e6699620a..f7c8bf37d6a3 100755 --- a/scripts/test-install-sh-docker.sh +++ b/scripts/test-install-sh-docker.sh @@ -1,11 +1,13 @@ #!/usr/bin/env bash set -euo pipefail -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +HARNESS_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ROOT_DIR="${OPENCLAW_INSTALL_SMOKE_SOURCE_DIR:-$HARNESS_ROOT}" +ROOT_DIR="$(cd "$ROOT_DIR" && pwd)" # shellcheck source=./docker/install-sh-common/version-parse.sh -source "$ROOT_DIR/scripts/docker/install-sh-common/version-parse.sh" -source "$ROOT_DIR/scripts/lib/docker-build.sh" -source "$ROOT_DIR/scripts/lib/docker-e2e-container.sh" +source "$HARNESS_ROOT/scripts/docker/install-sh-common/version-parse.sh" +source "$HARNESS_ROOT/scripts/lib/docker-build.sh" +source "$HARNESS_ROOT/scripts/lib/docker-e2e-container.sh" DOCKER_COMMAND_TIMEOUT="${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_INSTALL_SMOKE_DOCKER_COMMAND_TIMEOUT:-600s}}" INSTALL_SMOKE_DOCKER_RUN_TIMEOUT="${OPENCLAW_INSTALL_SMOKE_DOCKER_RUN_TIMEOUT:-2700s}" @@ -287,11 +289,14 @@ restore_local_dist_from_image() { } ensure_local_update_dist_import_closure() { - if node scripts/check-package-dist-imports.mjs "$ROOT_DIR"; then + if node "$HARNESS_ROOT/scripts/check-package-dist-imports.mjs" "$ROOT_DIR"; then return 0 fi echo "WARN: reused Docker image dist failed import-closure check; rebuilding local release artifacts" >&2 - pnpm build + ( + cd "$ROOT_DIR" + pnpm build + ) } prepare_update_tarball() { @@ -311,18 +316,24 @@ prepare_update_tarball() { restore_local_dist_from_image "$UPDATE_DIST_IMAGE" ensure_local_update_dist_import_closure elif [[ "$UPDATE_SKIP_LOCAL_BUILD" != "1" ]]; then - pnpm build + ( + cd "$ROOT_DIR" + pnpm build + ) fi UPDATE_EXPECT_VERSION="$( - node -p 'JSON.parse(require("node:fs").readFileSync("package.json", "utf8")).version' + node -p 'JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8")).version' "$ROOT_DIR/package.json" )" - node --import tsx scripts/write-package-dist-inventory.ts - node scripts/check-package-dist-imports.mjs "$ROOT_DIR" - quiet_npm pack --ignore-scripts --json --pack-destination "$UPDATE_DIR" >"$pack_json_file" + ( + cd "$ROOT_DIR" + node --import tsx scripts/write-package-dist-inventory.ts + node "$HARNESS_ROOT/scripts/check-package-dist-imports.mjs" "$ROOT_DIR" + quiet_npm pack --ignore-scripts --json --pack-destination "$UPDATE_DIR" >"$pack_json_file" + ) fi UPDATE_TGZ_FILE="$(read_pack_tarball_filename "$pack_json_file")" if [[ -z "$UPDATE_PACKAGE_SPEC" ]]; then - node scripts/check-openclaw-package-tarball.mjs "${UPDATE_DIR}/${UPDATE_TGZ_FILE}" + node "$HARNESS_ROOT/scripts/check-openclaw-package-tarball.mjs" "${UPDATE_DIR}/${UPDATE_TGZ_FILE}" fi print_pack_audit "update" "$pack_json_file" assert_pack_unpacked_size_budget "update" "$pack_json_file" @@ -418,8 +429,8 @@ else docker_build_run install-smoke-build \ --platform "$SMOKE_PLATFORM" \ -t "$SMOKE_IMAGE" \ - -f "$ROOT_DIR/scripts/docker/install-sh-smoke/Dockerfile" \ - "$ROOT_DIR/scripts/docker" + -f "$HARNESS_ROOT/scripts/docker/install-sh-smoke/Dockerfile" \ + "$HARNESS_ROOT/scripts/docker" fi if [[ "$SKIP_UPDATE" == "1" ]]; then @@ -531,8 +542,8 @@ else docker_build_run install-nonroot-build \ --platform "$NONROOT_PLATFORM" \ -t "$NONROOT_IMAGE" \ - -f "$ROOT_DIR/scripts/docker/install-sh-nonroot/Dockerfile" \ - "$ROOT_DIR/scripts/docker" + -f "$HARNESS_ROOT/scripts/docker/install-sh-nonroot/Dockerfile" \ + "$HARNESS_ROOT/scripts/docker" fi echo "==> Run installer non-root test: $INSTALL_URL" diff --git a/test/scripts/find-reusable-release-validation.test.ts b/test/scripts/find-reusable-release-validation.test.ts index 11bb0ce55b74..cba60d3a7e99 100644 --- a/test/scripts/find-reusable-release-validation.test.ts +++ b/test/scripts/find-reusable-release-validation.test.ts @@ -554,6 +554,7 @@ describe("scripts/github/find-reusable-release-validation.sh", () => { changed_path_count: "0", evidence_root_run_id: "111", evidence_run_id: "111", + evidence_policy: "exact-target-full-validation-v1", evidence_sha: priorSha, reuse: "true", }); @@ -584,6 +585,7 @@ describe("scripts/github/find-reusable-release-validation.sh", () => { expect(parseOutput(result.stdout)).toMatchObject({ changed_path_count: "0", changed_paths: "[]", + evidence_policy: "exact-target-full-validation-v1", reuse: "true", }); }); diff --git a/test/scripts/release-no-push-workflow.test.ts b/test/scripts/release-no-push-workflow.test.ts index 2e17d94857e4..0a0889779601 100644 --- a/test/scripts/release-no-push-workflow.test.ts +++ b/test/scripts/release-no-push-workflow.test.ts @@ -164,6 +164,7 @@ describe("release validation no-push transport", () => { expect(evidenceUpdate.run).toContain( '"$RELEASE_CHECKS_RESULT" == "skipped" && "$EVIDENCE_REUSE" != "true"', ); + expect(evidenceUpdate.run).toContain('--arg notes "$notes"'); expect(evidenceUpdate.run).not.toContain('GITHUB_RUN_ID_VALUE="$EVIDENCE_ROOT_RUN_ID"'); const manifest = step(summary, "Write release validation manifest"); diff --git a/test/scripts/test-install-sh-docker.test.ts b/test/scripts/test-install-sh-docker.test.ts index 739f5d07e76f..fdb15fdffea8 100644 --- a/test/scripts/test-install-sh-docker.test.ts +++ b/test/scripts/test-install-sh-docker.test.ts @@ -5,6 +5,7 @@ import { tmpdir } from "node:os"; import path, { join } from "node:path"; import { runInNewContext } from "node:vm"; import { afterEach, describe, expect, it } from "vitest"; +import { parse } from "yaml"; import { createTempDirTracker } from "../helpers/temp-dir.js"; const SCRIPT_PATH = "scripts/test-install-sh-docker.sh"; @@ -382,9 +383,10 @@ describe("test-install-sh-docker", () => { const script = readFileSync(SCRIPT_PATH, "utf8"); const dockerfile = readFileSync("Dockerfile", "utf8"); + expect(script).toContain('ROOT_DIR="${OPENCLAW_INSTALL_SMOKE_SOURCE_DIR:-$HARNESS_ROOT}"'); expect(script).toContain('UPDATE_DIST_IMAGE="${OPENCLAW_INSTALL_SMOKE_UPDATE_DIST_IMAGE:-}"'); expect(script).toContain("restore_local_dist_from_image"); - expect(script).toContain('source "$ROOT_DIR/scripts/lib/docker-e2e-container.sh"'); + expect(script).toContain('source "$HARNESS_ROOT/scripts/lib/docker-e2e-container.sh"'); expect(script).toContain( 'DOCKER_COMMAND_TIMEOUT="${DOCKER_COMMAND_TIMEOUT:-${OPENCLAW_INSTALL_SMOKE_DOCKER_COMMAND_TIMEOUT:-600s}}"', ); @@ -397,10 +399,14 @@ describe("test-install-sh-docker", () => { expect(script).not.toContain('docker cp "${container_id}:/app/dist" "$ROOT_DIR/dist"'); expect(script).toContain('echo "==> Reuse local dist/ from Docker image: $image"'); expect(script).toContain("ensure_local_update_dist_import_closure"); - expect(script).toContain('node scripts/check-package-dist-imports.mjs "$ROOT_DIR"'); + expect(script).toContain( + 'node "$HARNESS_ROOT/scripts/check-package-dist-imports.mjs" "$ROOT_DIR"', + ); expect(script).toContain("WARN: reused Docker image dist failed import-closure check"); expect(script).toContain("pnpm build"); expect(script).not.toContain("pnpm ui:build"); + expect(script).toContain('-f "$HARNESS_ROOT/scripts/docker/install-sh-smoke/Dockerfile"'); + expect(script).toContain('-f "$HARNESS_ROOT/scripts/docker/install-sh-nonroot/Dockerfile"'); expect(dockerfile).toContain("node scripts/check-package-dist-imports.mjs /app"); }); @@ -643,9 +649,11 @@ describe("test-install-sh-docker", () => { const script = readFileSync(SCRIPT_PATH, "utf8"); expect(script).toContain("node --import tsx scripts/write-package-dist-inventory.ts"); - expect(script).toContain('node scripts/check-package-dist-imports.mjs "$ROOT_DIR"'); + expect(script).toContain( + 'node "$HARNESS_ROOT/scripts/check-package-dist-imports.mjs" "$ROOT_DIR"', + ); expect(script).toContain("quiet_npm pack --ignore-scripts"); - expect(script).toContain("node scripts/check-openclaw-package-tarball.mjs"); + expect(script).toContain('node "$HARNESS_ROOT/scripts/check-openclaw-package-tarball.mjs"'); }); it("runs candidate tarballs through the installer script instead of direct npm", () => {