From e79e97b3591c4ccd86b74fb97b93fb341d659c8c Mon Sep 17 00:00:00 2001 From: ANIRUDDHA ADAK Date: Mon, 21 Sep 2026 07:03:31 +0530 Subject: [PATCH] fix(release): require exact per-manager version identity in Docker package acceptance (#138085) * fix(release): require exact per-manager version identity in Docker package acceptance Replaces substring CLI checks with parsed exact semver equality and reads each manager's own installed manifest (including Bun via the smoke proof JSON) instead of copying npm's version. Adds a focused predicate regression test. Closes #127415 * fix(release): bind package identity to artifact * test(release): distinguish pnpm identity fixture * test(release): cover Bun identity rejection * test(docker): provision native build tools for musl package installs --------- Co-authored-by: Aniruddha Adak Co-authored-by: Vincent Koc Co-authored-by: Jason (Json) <263060202+fuller-stack-dev@users.noreply.github.com> --- .../docker/install-sh-common/version-parse.sh | 2 +- scripts/e2e/Dockerfile | 3 +- scripts/e2e/bun-global-install-smoke.sh | 10 +- scripts/e2e/docker-package-install.sh | 62 +---- scripts/e2e/lib/docker-package-identity.sh | 121 +++++++++ test/scripts/docker-build-helper.test.ts | 21 +- test/scripts/docker-package-identity.test.ts | 250 ++++++++++++++++++ 7 files changed, 396 insertions(+), 73 deletions(-) create mode 100644 scripts/e2e/lib/docker-package-identity.sh create mode 100644 test/scripts/docker-package-identity.test.ts diff --git a/scripts/docker/install-sh-common/version-parse.sh b/scripts/docker/install-sh-common/version-parse.sh index 1c693310988a..e8da5bb3974e 100644 --- a/scripts/docker/install-sh-common/version-parse.sh +++ b/scripts/docker/install-sh-common/version-parse.sh @@ -3,7 +3,7 @@ extract_openclaw_semver() { local raw="${1:-}" raw="${raw//$'\r'/}" - if [[ "$raw" =~ v?([0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+(\.[0-9A-Za-z]+)*)?(\+[0-9A-Za-z.-]+)?) ]]; then + if [[ "$raw" =~ v?([0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z.-]+)?) ]]; then printf '%s' "${BASH_REMATCH[1]}" fi } diff --git a/scripts/e2e/Dockerfile b/scripts/e2e/Dockerfile index 46305b0fdeca..f70b4b35874d 100644 --- a/scripts/e2e/Dockerfile +++ b/scripts/e2e/Dockerfile @@ -51,7 +51,8 @@ CMD ["bash"] FROM ${OPENCLAW_NODE_ALPINE_IMAGE} AS musl -RUN apk add --no-cache bash +# Native dependencies without musl prebuilds must compile during real npm installs. +RUN apk add --no-cache bash g++ make python3 COPY --from=e2e-runner /opt/openclaw-e2e /opt/openclaw-e2e COPY scripts/docker/verify-fs-safe-native.mjs /tmp/verify-fs-safe-native.mjs diff --git a/scripts/e2e/bun-global-install-smoke.sh b/scripts/e2e/bun-global-install-smoke.sh index e9f429c7a3af..9e0430bfd379 100755 --- a/scripts/e2e/bun-global-install-smoke.sh +++ b/scripts/e2e/bun-global-install-smoke.sh @@ -449,15 +449,19 @@ NODE "$bun_path" \ "$openclaw_bin" \ "$openclaw_version" \ - "$runtime_label" <<'NODE' + "$runtime_label" \ + "$package_root" <<'NODE' import fs from "node:fs"; import path from "node:path"; -const [, , proofPath, bunPath, openclawPath, openclawVersion, runtime] = process.argv; +const [, , proofPath, bunPath, openclawPath, openclawVersion, runtime, installedPackageRoot] = process.argv; +const installedPackageVersion = JSON.parse( + fs.readFileSync(path.join(installedPackageRoot, "package.json"), "utf8"), +).version; fs.mkdirSync(path.dirname(proofPath), { recursive: true }); fs.writeFileSync( proofPath, - `${JSON.stringify({ bunPath, openclawPath, openclawVersion, runtime }, null, 2)}\n`, + `${JSON.stringify({ bunPath, openclawPath, openclawVersion, runtime, installedPackageRoot, installedPackageVersion }, null, 2)}\n`, ); NODE fi diff --git a/scripts/e2e/docker-package-install.sh b/scripts/e2e/docker-package-install.sh index 883a5bcf7cef..8ff52afc1619 100755 --- a/scripts/e2e/docker-package-install.sh +++ b/scripts/e2e/docker-package-install.sh @@ -184,59 +184,13 @@ for container_name in "$NPM_PROOF_CONTAINER" "$PNPM_PROOF_CONTAINER" "$BUN_PROOF wait_for_proof "$container_name" done -NPM_PACKAGE_ROOT="/usr/local/lib/node_modules/openclaw" -NPM_INSTALLED_VERSION="$(docker exec "$NPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')" -PNPM_PACKAGE_ROOT="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-package-root | tr -d '\r\n')" -PNPM_PACKAGE_VERSION="$(docker exec "$PNPM_PROOF_CONTAINER" node -p "require('$PNPM_PACKAGE_ROOT/package.json').version")" -PNPM_INSTALLED_VERSION="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')" -BUN_OPENCLAW_PATH="$( - docker exec "$BUN_PROOF_CONTAINER" \ - node -p 'JSON.parse(require("node:fs").readFileSync("/tmp/openclaw-bun-proof.json", "utf8")).openclawPath' -)" -BUN_INSTALLED_VERSION="$( - docker exec "$BUN_PROOF_CONTAINER" \ - node -p 'JSON.parse(require("node:fs").readFileSync("/tmp/openclaw-bun-proof.json", "utf8")).openclawVersion' -)" -PACKAGE_VERSION="$(docker exec "$NPM_PROOF_CONTAINER" node -p "require('$NPM_PACKAGE_ROOT/package.json').version")" -test "$PNPM_PACKAGE_VERSION" = "$PACKAGE_VERSION" -for installed_version in "$NPM_INSTALLED_VERSION" "$PNPM_INSTALLED_VERSION" "$BUN_INSTALLED_VERSION"; do - if [[ "$installed_version" != *"$PACKAGE_VERSION"* ]]; then - echo "installed CLI output $installed_version does not contain package version $PACKAGE_VERSION" >&2 - exit 1 - fi -done - -# The legacy contract intentionally skips native verification; evidence must not -# present that omission as an executed native proof. -MUSL_FS_SAFE_NATIVE_OUTCOME="passed" -if [[ "${OPENCLAW_FS_SAFE_NATIVE_CONTRACT:-required}" == "not-applicable" ]]; then - MUSL_FS_SAFE_NATIVE_OUTCOME="not-applicable" -fi - -node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts" \ - --scenario docker-package-install \ - --output "$IDENTITY_PATH" \ - --image "$IMAGE_NAME" \ - --package "$PACKAGE_TGZ" \ - --container "npm=$NPM_PROOF_CONTAINER" \ - --container "pnpm=$PNPM_PROOF_CONTAINER" \ - --container "bun=$BUN_PROOF_CONTAINER" \ - --container "musl=$MUSL_PROOF_CONTAINER" \ - --detail "npm:installedPackageRoot=$NPM_PACKAGE_ROOT" \ - --detail "npm:installedPackageVersion=$PACKAGE_VERSION" \ - --detail "npm:openclawVersion=$NPM_INSTALLED_VERSION" \ - --detail "npm:openclawPath=/usr/local/bin/openclaw" \ - --detail "npm:helpCommand=passed" \ - --detail "npm:nonRootExecution=passed" \ - --detail "musl:fsSafeNative=$MUSL_FS_SAFE_NATIVE_OUTCOME" \ - --detail "pnpm:installedPackageRoot=$PNPM_PACKAGE_ROOT" \ - --detail "pnpm:installedPackageVersion=$PNPM_PACKAGE_VERSION" \ - --detail "pnpm:openclawVersion=$PNPM_INSTALLED_VERSION" \ - --detail "pnpm:openclawPath=/tmp/pnpm-home/bin/openclaw" \ - --detail "pnpm:helpCommand=passed" \ - --detail "bun:installedPackageVersion=$PACKAGE_VERSION" \ - --detail "bun:openclawVersion=$BUN_INSTALLED_VERSION" \ - --detail "bun:openclawPath=$BUN_OPENCLAW_PATH" \ - --detail "bun:helpCommand=passed" +bash "$ROOT_DIR/scripts/e2e/lib/docker-package-identity.sh" \ + "$PACKAGE_TGZ" \ + "$IDENTITY_PATH" \ + "$IMAGE_NAME" \ + "$NPM_PROOF_CONTAINER" \ + "$PNPM_PROOF_CONTAINER" \ + "$BUN_PROOF_CONTAINER" \ + "$MUSL_PROOF_CONTAINER" echo "npm, pnpm, and Bun package artifact proofs passed." diff --git a/scripts/e2e/lib/docker-package-identity.sh b/scripts/e2e/lib/docker-package-identity.sh new file mode 100644 index 000000000000..a41697f4fc48 --- /dev/null +++ b/scripts/e2e/lib/docker-package-identity.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" +source "$ROOT_DIR/scripts/docker/install-sh-common/version-parse.sh" + +PACKAGE_TGZ="${1:?missing package tarball}" +IDENTITY_PATH="${2:?missing identity output path}" +IMAGE_NAME="${3:?missing image name}" +NPM_PROOF_CONTAINER="${4:?missing npm proof container}" +PNPM_PROOF_CONTAINER="${5:?missing pnpm proof container}" +BUN_PROOF_CONTAINER="${6:?missing Bun proof container}" +MUSL_PROOF_CONTAINER="${7:?missing musl proof container}" + +read_manifest_version() { + local container_name="$1" + local manifest_path="$2" + docker exec "$container_name" node -e ' +const fs = require("node:fs"); +const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); +if (typeof manifest.version !== "string" || manifest.version.length === 0) { + throw new Error(`missing version in ${process.argv[1]}`); +} +process.stdout.write(manifest.version); +' "$manifest_path" +} + +read_bun_proof() { + local field="$1" + docker exec "$BUN_PROOF_CONTAINER" node -e ' +const fs = require("node:fs"); +const proof = JSON.parse(fs.readFileSync("/tmp/openclaw-bun-proof.json", "utf8")); +const value = proof[process.argv[1]]; +if (typeof value !== "string" || value.length === 0) { + throw new Error(`missing Bun proof field ${process.argv[1]}`); +} +process.stdout.write(value); +' "$field" +} + +EXPECTED_PACKAGE_VERSION="$( + tar -xOf "$PACKAGE_TGZ" package/package.json | node -e ' +const fs = require("node:fs"); +const manifest = JSON.parse(fs.readFileSync(0, "utf8")); +if (typeof manifest.version !== "string" || manifest.version.length === 0) { + throw new Error("package artifact manifest is missing version"); +} +process.stdout.write(manifest.version); +' +)" + +NPM_PACKAGE_ROOT="/usr/local/lib/node_modules/openclaw" +PNPM_PACKAGE_ROOT="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-package-root | tr -d '\r\n')" +BUN_PACKAGE_ROOT="$(read_bun_proof installedPackageRoot)" + +NPM_PACKAGE_VERSION="$(read_manifest_version "$NPM_PROOF_CONTAINER" "$NPM_PACKAGE_ROOT/package.json")" +PNPM_PACKAGE_VERSION="$(read_manifest_version "$PNPM_PROOF_CONTAINER" "$PNPM_PACKAGE_ROOT/package.json")" +BUN_PACKAGE_VERSION="$(read_bun_proof installedPackageVersion)" + +NPM_INSTALLED_VERSION="$(docker exec "$NPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')" +PNPM_INSTALLED_VERSION="$(docker exec "$PNPM_PROOF_CONTAINER" cat /tmp/openclaw-version | tr -d '\r\n')" +BUN_INSTALLED_VERSION="$(read_bun_proof openclawVersion)" +BUN_OPENCLAW_PATH="$(read_bun_proof openclawPath)" + +MANAGERS=(npm pnpm bun) +MANIFEST_VERSIONS=("$NPM_PACKAGE_VERSION" "$PNPM_PACKAGE_VERSION" "$BUN_PACKAGE_VERSION") +CLI_OUTPUTS=("$NPM_INSTALLED_VERSION" "$PNPM_INSTALLED_VERSION" "$BUN_INSTALLED_VERSION") +PARSED_VERSIONS=() + +for index in "${!MANAGERS[@]}"; do + manager="${MANAGERS[$index]}" + manifest_version="${MANIFEST_VERSIONS[$index]}" + cli_output="${CLI_OUTPUTS[$index]}" + if [[ "$manifest_version" != "$EXPECTED_PACKAGE_VERSION" ]]; then + echo "[$manager] installed manifest version '$manifest_version' != artifact '$EXPECTED_PACKAGE_VERSION'" >&2 + exit 1 + fi + parsed_version="$(extract_openclaw_semver "$cli_output")" + if [[ "$parsed_version" != "$EXPECTED_PACKAGE_VERSION" ]]; then + echo "[$manager] CLI output parses to '${parsed_version:-}' (raw: '$cli_output'), expected artifact '$EXPECTED_PACKAGE_VERSION'" >&2 + exit 1 + fi + PARSED_VERSIONS+=("$parsed_version") +done + +# The legacy contract intentionally skips native verification; evidence must not +# present that omission as an executed native proof. +MUSL_FS_SAFE_NATIVE_OUTCOME="passed" +if [[ "${OPENCLAW_FS_SAFE_NATIVE_CONTRACT:-required}" == "not-applicable" ]]; then + MUSL_FS_SAFE_NATIVE_OUTCOME="not-applicable" +fi + +node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts" \ + --scenario docker-package-install \ + --output "$IDENTITY_PATH" \ + --image "$IMAGE_NAME" \ + --package "$PACKAGE_TGZ" \ + --container "npm=$NPM_PROOF_CONTAINER" \ + --container "pnpm=$PNPM_PROOF_CONTAINER" \ + --container "bun=$BUN_PROOF_CONTAINER" \ + --container "musl=$MUSL_PROOF_CONTAINER" \ + --detail "npm:installedPackageRoot=$NPM_PACKAGE_ROOT" \ + --detail "npm:installedPackageVersion=$NPM_PACKAGE_VERSION" \ + --detail "npm:openclawVersion=$NPM_INSTALLED_VERSION" \ + --detail "npm:parsedOpenclawVersion=${PARSED_VERSIONS[0]}" \ + --detail "npm:openclawPath=/usr/local/bin/openclaw" \ + --detail "npm:helpCommand=passed" \ + --detail "npm:nonRootExecution=passed" \ + --detail "musl:fsSafeNative=$MUSL_FS_SAFE_NATIVE_OUTCOME" \ + --detail "pnpm:installedPackageRoot=$PNPM_PACKAGE_ROOT" \ + --detail "pnpm:installedPackageVersion=$PNPM_PACKAGE_VERSION" \ + --detail "pnpm:openclawVersion=$PNPM_INSTALLED_VERSION" \ + --detail "pnpm:parsedOpenclawVersion=${PARSED_VERSIONS[1]}" \ + --detail "pnpm:openclawPath=/tmp/pnpm-home/bin/openclaw" \ + --detail "pnpm:helpCommand=passed" \ + --detail "bun:installedPackageRoot=$BUN_PACKAGE_ROOT" \ + --detail "bun:installedPackageVersion=$BUN_PACKAGE_VERSION" \ + --detail "bun:openclawVersion=$BUN_INSTALLED_VERSION" \ + --detail "bun:parsedOpenclawVersion=${PARSED_VERSIONS[2]}" \ + --detail "bun:openclawPath=$BUN_OPENCLAW_PATH" \ + --detail "bun:helpCommand=passed" diff --git a/test/scripts/docker-build-helper.test.ts b/test/scripts/docker-build-helper.test.ts index ee94fe92debb..dd2758bc2a60 100644 --- a/test/scripts/docker-build-helper.test.ts +++ b/test/scripts/docker-build-helper.test.ts @@ -6920,11 +6920,6 @@ source "$ROOT_DIR/scripts/lib/docker-e2e-logs.sh" expect(packageRunner.match(/verify-fs-safe-native\.mjs[^\n]+--mode require/gu)).toHaveLength(3); expect(packageRunner).toContain("bash scripts/e2e/bun-global-install-smoke.sh"); expect(packageRunner.match(/-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT/g)).toHaveLength(4); - expectTextToIncludeAll(packageRunner, [ - 'MUSL_FS_SAFE_NATIVE_OUTCOME="passed"', - 'MUSL_FS_SAFE_NATIVE_OUTCOME="not-applicable"', - '--detail "musl:fsSafeNative=$MUSL_FS_SAFE_NATIVE_OUTCOME"', - ]); expect(updateRunner).toContain('mv "$platform_package" "$platform_package.omitted"'); expect(updateRunner).toContain("--mode fallback"); expect(updateRunner).toContain("-e OPENCLAW_FS_SAFE_NATIVE_CONTRACT"); @@ -7966,11 +7961,10 @@ fs.appendFileSync(process.env.FIXTURE_DOCKER_CAPTURE, JSON.stringify({ args, sta 'DOCKER_COMMAND_TIMEOUT="$DOCKER_RUN_TIMEOUT" docker_e2e_docker_run_cmd run -d', ); expect(packageRunner).not.toMatch(/(^|\n)docker run -d/u); - for (const runner of [composeRunner, packageRunner]) { - expect(runner).toContain( - 'node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts"', - ); - } + expect(composeRunner).toContain( + 'node --import tsx "$ROOT_DIR/scripts/e2e/lib/docker-artifact-proof/write-identities.ts"', + ); + expect(packageRunner).toContain('bash "$ROOT_DIR/scripts/e2e/lib/docker-package-identity.sh"'); }); it("copies the complete bun harness closure into the package-install lane", () => { @@ -8035,7 +8029,6 @@ fs.appendFileSync(process.env.FIXTURE_DOCKER_CAPTURE, JSON.stringify({ args, sta 'corepack prepare "$1" --activate', "pnpm list --global --json", 'test -f "$package_root/package.json"', - 'test "$PNPM_PACKAGE_VERSION" = "$PACKAGE_VERSION"', "pnpm add --global openclaw@file:/tmp/openclaw-current.tgz", 'pnpm approve-builds --global "$artifact_build"', "bun@1.4.0", @@ -8045,9 +8038,9 @@ fs.appendFileSync(process.env.FIXTURE_DOCKER_CAPTURE, JSON.stringify({ args, sta 'PACKAGE_HARNESS_DIR="$(mktemp -d', "chmod -R a+rX", '-v "$PACKAGE_HARNESS_DIR:/repo:ro"', - '--container "npm=$NPM_PROOF_CONTAINER"', - '--container "pnpm=$PNPM_PROOF_CONTAINER"', - '--container "bun=$BUN_PROOF_CONTAINER"', + '"$PACKAGE_TGZ" \\', + '"$IDENTITY_PATH" \\', + '"$MUSL_PROOF_CONTAINER"', ]); expect(packageRunner).not.toContain('-v "$ROOT_DIR:/repo:ro"'); expectTextToIncludeAll(installerRunner, [ diff --git a/test/scripts/docker-package-identity.test.ts b/test/scripts/docker-package-identity.test.ts new file mode 100644 index 000000000000..f8bff9ef88ab --- /dev/null +++ b/test/scripts/docker-package-identity.test.ts @@ -0,0 +1,250 @@ +import { spawnSync } from "node:child_process"; +import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { delimiter, join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; +import { useAutoCleanupTempDirTracker } from "../helpers/temp-dir.js"; + +const tempDirs = useAutoCleanupTempDirTracker(afterEach); +const ROOT_DIR = process.cwd(); +const RUNNER_PATH = join(ROOT_DIR, "scripts/e2e/docker-package-install.sh"); + +type PackageIdentityOptions = { + artifactVersion: string; + nativeContract?: "required" | "not-applicable"; + bunCli?: string; + bunManifest?: string; + npmCli?: string; + npmManifest?: string; + pnpmCli?: string; + pnpmManifest?: string; +}; + +function runPackageIdentity(options: PackageIdentityOptions) { + const root = tempDirs.make("openclaw-docker-package-identity-"); + const binDir = join(root, "bin"); + const packageDir = join(root, "package"); + const packageTgz = join(root, "candidate.tgz"); + const identityPath = join(root, "identity.json"); + mkdirSync(binDir); + mkdirSync(packageDir); + writeFileSync( + join(packageDir, "package.json"), + JSON.stringify({ name: "openclaw", version: options.artifactVersion }), + ); + const pack = spawnSync("tar", ["-czf", packageTgz, "-C", root, "package"], { + encoding: "utf8", + }); + expect(pack.status, pack.stderr).toBe(0); + + const dockerPath = join(binDir, "docker"); + writeFileSync( + dockerPath, + `#!/usr/bin/env bash +set -euo pipefail +command="\${1:-}" +shift || true +case "$command" in + image|run|rm|logs) + exit 0 + ;; + exec) + container="\${1:?missing container}" + shift + command_line="$*" + if [[ "$command_line" == "test -f /tmp/openclaw-proof-ready" ]]; then + exit 0 + fi + if [[ "$command_line" == "cat /tmp/openclaw-package-root" ]]; then + printf "/fake/pnpm/openclaw" + exit 0 + fi + if [[ "$command_line" == "cat /tmp/openclaw-version" ]]; then + if [[ "$container" == *-npm-proof-* ]]; then + printf "%s" "$FAKE_NPM_CLI" + elif [[ "$container" == *-pnpm-proof-* ]]; then + printf "%s" "$FAKE_PNPM_CLI" + else + exit 2 + fi + exit 0 + fi + if [[ "$command_line" == *"/tmp/openclaw-bun-proof.json"* ]]; then + case "$command_line" in + *installedPackageRoot*) printf "/fake/bun/openclaw" ;; + *installedPackageVersion*) printf "%s" "$FAKE_BUN_MANIFEST" ;; + *openclawVersion*) printf "%s" "$FAKE_BUN_CLI" ;; + *openclawPath*) printf "/fake/bun/bin/openclaw" ;; + *) exit 2 ;; + esac + exit 0 + fi + if [[ "$command_line" == *"package.json"* ]]; then + if [[ "$container" == *-npm-proof-* ]]; then + printf "%s" "$FAKE_NPM_MANIFEST" + elif [[ "$container" == *-pnpm-proof-* ]]; then + printf "%s" "$FAKE_PNPM_MANIFEST" + else + exit 2 + fi + exit 0 + fi + exit 2 + ;; + inspect) + reference="\${!#}" + printf '[{"Id":"sha256:fake","Image":"sha256:image","Name":"/%s","RepoDigests":[],"State":{"Status":"running"}}]\\n' "$reference" + ;; + *) + exit 2 + ;; +esac +`, + ); + chmodSync(dockerPath, 0o755); + + const result = spawnSync("/bin/bash", [RUNNER_PATH], { + encoding: "utf8", + env: { + ...process.env, + FAKE_BUN_CLI: options.bunCli ?? `OpenClaw ${options.artifactVersion}`, + FAKE_BUN_MANIFEST: options.bunManifest ?? options.artifactVersion, + FAKE_NPM_CLI: options.npmCli ?? `OpenClaw ${options.artifactVersion}`, + FAKE_NPM_MANIFEST: options.npmManifest ?? options.artifactVersion, + FAKE_PNPM_CLI: options.pnpmCli ?? `OpenClaw ${options.artifactVersion}`, + FAKE_PNPM_MANIFEST: options.pnpmManifest ?? options.artifactVersion, + OPENCLAW_CURRENT_PACKAGE_TGZ: packageTgz, + OPENCLAW_FS_SAFE_NATIVE_CONTRACT: options.nativeContract ?? "required", + OPENCLAW_DOCKER_ARTIFACT_IDENTITY_PATH: identityPath, + OPENCLAW_DOCKER_E2E_DISABLE_RESOURCE_LIMITS: "1", + OPENCLAW_SKIP_DOCKER_BUILD: "1", + PATH: `${binDir}${delimiter}${process.env.PATH ?? ""}`, + }, + }); + return { + identity: result.status === 0 ? JSON.parse(readFileSync(identityPath, "utf8")) : undefined, + result, + }; +} + +describe.skipIf(process.platform === "win32")("Docker package identity report", () => { + it("rejects installed manifests that do not match the package artifact", () => { + const { result } = runPackageIdentity({ + artifactVersion: "1.2.3", + bunCli: "OpenClaw 11.2.30", + bunManifest: "11.2.30", + npmCli: "OpenClaw 11.2.30", + npmManifest: "11.2.30", + pnpmCli: "OpenClaw 11.2.30", + pnpmManifest: "11.2.30", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain( + "[npm] installed manifest version '11.2.30' != artifact '1.2.3'", + ); + }); + + it("rejects a stale CLI version that only contains the artifact version as a substring", () => { + const { result } = runPackageIdentity({ + artifactVersion: "1.2.3", + npmCli: "OpenClaw 11.2.30 (wrong)", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("[npm] CLI output parses to '11.2.30'"); + }); + + it("rejects a pnpm manifest version that differs from the artifact", () => { + const { result } = runPackageIdentity({ + artifactVersion: "1.2.3", + pnpmManifest: "11.2.30", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain( + "[pnpm] installed manifest version '11.2.30' != artifact '1.2.3'", + ); + }); + + it("rejects a pnpm CLI version that differs from the artifact", () => { + const { result } = runPackageIdentity({ + artifactVersion: "1.2.3", + pnpmCli: "OpenClaw 11.2.30 (wrong)", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("[pnpm] CLI output parses to '11.2.30'"); + }); + + it("rejects a Bun manifest version that differs from the artifact", () => { + const { result } = runPackageIdentity({ + artifactVersion: "1.2.3", + bunManifest: "11.2.30", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain( + "[bun] installed manifest version '11.2.30' != artifact '1.2.3'", + ); + }); + + it("rejects a Bun CLI version that differs from the artifact", () => { + const { result } = runPackageIdentity({ + artifactVersion: "1.2.3", + bunCli: "OpenClaw 11.2.30 (wrong)", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("[bun] CLI output parses to '11.2.30'"); + }); + + it.each([ + { version: "2026.6.21-beta.1+build.7", nativeContract: "required" }, + { version: "2026.6.21-beta.1+build.7", nativeContract: "not-applicable" }, + { version: "1.2.3-beta-rc.1+build.7", nativeContract: "required" }, + ] as const)( + "emits complete manager-owned identity for $version with the $nativeContract native contract", + ({ version, nativeContract }) => { + const { identity, result } = runPackageIdentity({ artifactVersion: version, nativeContract }); + + expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0); + expect(identity).toEqual( + expect.objectContaining({ + package: expect.objectContaining({ version }), + containers: expect.arrayContaining([ + expect.objectContaining({ + role: "musl", + details: expect.objectContaining({ + fsSafeNative: nativeContract === "required" ? "passed" : "not-applicable", + }), + }), + expect.objectContaining({ + role: "npm", + details: expect.objectContaining({ + installedPackageRoot: "/usr/local/lib/node_modules/openclaw", + installedPackageVersion: version, + parsedOpenclawVersion: version, + }), + }), + expect.objectContaining({ + role: "pnpm", + details: expect.objectContaining({ + installedPackageRoot: "/fake/pnpm/openclaw", + installedPackageVersion: version, + parsedOpenclawVersion: version, + }), + }), + expect.objectContaining({ + role: "bun", + details: expect.objectContaining({ + installedPackageRoot: "/fake/bun/openclaw", + installedPackageVersion: version, + parsedOpenclawVersion: version, + }), + }), + ]), + }), + ); + }, + ); +});