fix(release): stage Codex for npm onboarding candidates (#130360)

* fix(release): stage Codex for npm onboarding candidates

* refactor(release): share candidate registry lifecycle
This commit is contained in:
Dallin Romney
2026-08-26 16:58:45 -07:00
committed by GitHub
parent c9f791e075
commit cb1d892b3d
7 changed files with 151 additions and 51 deletions
+8 -39
View File
@@ -6,12 +6,13 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-codex-on-demand-e2e" OPENCLAW_CODEX_ON_DEMAND_E2E_IMAGE)"
DOCKER_TARGET="${OPENCLAW_CODEX_ON_DEMAND_DOCKER_TARGET:-bare}"
HOST_BUILD="${OPENCLAW_CODEX_ON_DEMAND_HOST_BUILD:-1}"
PACKAGE_TGZ="${OPENCLAW_CURRENT_PACKAGE_TGZ:-}"
PREPUBLISH_PLUGIN_REGISTRY_ARGS=()
OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=()
AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT=""
run_log=""
@@ -20,33 +21,9 @@ run_log=""
# the Codex assertions instead of failing as a silent package-install timeout.
export OPENCLAW_E2E_NPM_INSTALL_TIMEOUT="${OPENCLAW_E2E_NPM_INSTALL_TIMEOUT:-1200s}"
configure_prepublish_plugin_registry() {
local registry_dir="$1"
local resolved_registry_dir
resolved_registry_dir="$(cd "$registry_dir" && pwd)"
local manifest="$resolved_registry_dir/prepublish-plugin-registry.json"
if [ ! -f "$manifest" ]; then
echo "Prepublish plugin registry manifest is missing." >&2
exit 1
fi
local source_sha="${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}"
local candidate_version="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:-}"
local manifest_sha256="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:-}"
source_sha="${source_sha:-$(node -e 'process.stdout.write(require(process.argv[1]).sourceSha)' "$manifest")}"
candidate_version="${candidate_version:-$(node -e 'process.stdout.write(require(process.argv[1]).candidateVersion)' "$manifest")}"
if [ -z "$manifest_sha256" ]; then
manifest_sha256="$(node -e 'const fs=require("node:fs"),crypto=require("node:crypto");process.stdout.write(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$manifest")"
fi
PREPUBLISH_PLUGIN_REGISTRY_ARGS=(
-e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR=/tmp/openclaw-prepublish-plugin-registry
-e OPENCLAW_DOCKER_E2E_SELECTED_SHA="$source_sha"
-e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION="$candidate_version"
-e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256="$manifest_sha256"
-v "$resolved_registry_dir:/tmp/openclaw-prepublish-plugin-registry:ro"
)
}
if [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then
configure_prepublish_plugin_registry "$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR"
openclaw_prepublish_plugin_registry_configure_docker_args \
"$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR"
fi
cleanup() {
@@ -88,7 +65,7 @@ if [ -z "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] &&
OPENCLAW_DOCKER_ALL_LOG_DIR="$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT" \
OPENCLAW_DOCKER_ALL_TIMINGS=0 \
node "$ROOT_DIR/scripts/test-docker-all.mjs" --prepare-plugin-registry >/dev/null
configure_prepublish_plugin_registry \
openclaw_prepublish_plugin_registry_configure_docker_args \
"$AUTO_PREPUBLISH_PLUGIN_REGISTRY_ROOT/prepublish-plugin-registry"
fi
@@ -100,7 +77,7 @@ echo "Running Codex on-demand Docker E2E..."
if ! docker_e2e_run_with_harness \
-e COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]+"${PREPUBLISH_PLUGIN_REGISTRY_ARGS[@]}"} \
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}" \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
-i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF'; then
set -euo pipefail
@@ -135,16 +112,8 @@ cleanup_inner() {
trap cleanup_inner EXIT
configure_plugin_registry() {
[ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] || return 0
local registry_root="/tmp/openclaw-codex-registry"
OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON='["@openclaw/codex"]' \
openclaw_prepublish_plugin_registry_start \
"$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" \
"${OPENCLAW_DOCKER_E2E_SELECTED_SHA:?missing selected SHA}" \
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:?missing candidate version}" \
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:?missing manifest SHA-256}" \
"$registry_root" \
plugin_registry_pid
openclaw_prepublish_plugin_registry_start_mounted \
/tmp/openclaw-codex-registry plugin_registry_pid '["@openclaw/codex"]'
}
mkdir -p "$NPM_CONFIG_PREFIX" "$XDG_CACHE_HOME" "$NPM_CONFIG_CACHE"
@@ -1,5 +1,33 @@
#!/usr/bin/env bash
openclaw_prepublish_plugin_registry_configure_docker_args() {
local registry_dir="$1"
local resolved_registry_dir
resolved_registry_dir="$(cd "$registry_dir" && pwd)"
local manifest="$resolved_registry_dir/prepublish-plugin-registry.json"
if [ ! -f "$manifest" ]; then
echo "Prepublish plugin registry manifest is missing." >&2
return 1
fi
local source_sha="${OPENCLAW_DOCKER_E2E_SELECTED_SHA:-}"
local candidate_version="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:-}"
local manifest_sha256="${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:-}"
source_sha="${source_sha:-$(node -e 'process.stdout.write(require(process.argv[1]).sourceSha)' "$manifest")}"
candidate_version="${candidate_version:-$(node -e 'process.stdout.write(require(process.argv[1]).candidateVersion)' "$manifest")}"
if [ -z "$manifest_sha256" ]; then
manifest_sha256="$(node -e 'const fs=require("node:fs"),crypto=require("node:crypto");process.stdout.write(crypto.createHash("sha256").update(fs.readFileSync(process.argv[1])).digest("hex"))' "$manifest")"
fi
OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=(
-e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR=/tmp/openclaw-prepublish-plugin-registry
-e OPENCLAW_DOCKER_E2E_SELECTED_SHA="$source_sha"
-e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION="$candidate_version"
-e OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256="$manifest_sha256"
-v "$resolved_registry_dir:/tmp/openclaw-prepublish-plugin-registry:ro"
)
}
openclaw_prepublish_plugin_registry_start() {
local artifact_dir="$1" source_sha="$2" candidate_version="$3"
local manifest_sha256="$4" registry_root="$5" pid_variable="$6"
@@ -90,3 +118,19 @@ NODE
export npm_config_registry="$NPM_CONFIG_REGISTRY"
printf -v "$pid_variable" "%s" "$server_pid"
}
openclaw_prepublish_plugin_registry_start_mounted() {
local registry_root="$1" pid_variable="$2" required_packages_json="$3"
shift 3
[ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ] || return 0
OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON="$required_packages_json" \
openclaw_prepublish_plugin_registry_start \
"$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR" \
"${OPENCLAW_DOCKER_E2E_SELECTED_SHA:?missing selected SHA}" \
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION:?missing candidate version}" \
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256:?missing manifest SHA-256}" \
"$registry_root" \
"$pid_variable" \
"$@"
}
@@ -7,6 +7,7 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SOURCE_ROOT="$(cd "${OPENCLAW_NPM_ONBOARD_SOURCE_ROOT:-${OPENCLAW_LIVE_DOCKER_REPO_ROOT:-$ROOT_DIR}}" && pwd)"
source "$ROOT_DIR/scripts/lib/docker-e2e-image.sh"
source "$ROOT_DIR/scripts/lib/docker-e2e-package.sh"
source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"
IMAGE_NAME="$(docker_e2e_resolve_image "openclaw-npm-onboard-channel-agent-e2e" OPENCLAW_NPM_ONBOARD_E2E_IMAGE)"
DOCKER_TARGET="${OPENCLAW_NPM_ONBOARD_DOCKER_TARGET:-bare}"
@@ -23,6 +24,7 @@ STATUS_TEXT_MAX_BYTES="$(
run_log=""
plugin_pack_dir=""
plugin_package_args=()
OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS=()
cleanup() {
if [ -n "${PACKAGE_TGZ:-}" ]; then
@@ -95,6 +97,11 @@ prepare_source_plugin_package() {
prepare_source_plugin_package
if [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then
openclaw_prepublish_plugin_registry_configure_docker_args \
"$OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR"
fi
docker_e2e_package_mount_args "$PACKAGE_TGZ"
run_log="$(docker_e2e_run_log npm-onboard-channel-agent)"
OPENCLAW_TEST_STATE_SCRIPT_B64="$(docker_e2e_test_state_shell_b64 npm-onboard-channel-agent empty)"
@@ -108,10 +115,12 @@ if ! docker_e2e_run_with_harness \
-e "OPENCLAW_TEST_STATE_SCRIPT_B64=$OPENCLAW_TEST_STATE_SCRIPT_B64" \
"${DOCKER_E2E_PACKAGE_ARGS[@]}" \
"${plugin_package_args[@]}" \
"${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}" \
-i "$IMAGE_NAME" bash -s >"$run_log" 2>&1 <<'EOF'; then
set -Eeuo pipefail
source scripts/lib/openclaw-e2e-instance.sh
source scripts/e2e/lib/prepublish-plugin-registry.sh
openclaw_e2e_eval_test_state_from_b64 "${OPENCLAW_TEST_STATE_SCRIPT_B64:?missing OPENCLAW_TEST_STATE_SCRIPT_B64}"
export NPM_CONFIG_PREFIX="$HOME/.npm-global"
export PATH="$NPM_CONFIG_PREFIX/bin:$PATH"
@@ -126,6 +135,7 @@ scenario_tmp="$(mktemp -d "${TMPDIR:-/tmp}/openclaw-npm-onboard-channel-agent.XX
MOCK_REQUEST_LOG="$scenario_tmp/mock-openai-requests.jsonl"
export SUCCESS_MARKER MOCK_REQUEST_LOG
mock_pid=""
plugin_registry_pid=""
case "$CHANNEL" in
telegram)
@@ -155,6 +165,7 @@ esac
cleanup() {
openclaw_e2e_stop_process "${mock_pid:-}"
openclaw_e2e_stop_process "${plugin_registry_pid:-}"
rm -rf "$scenario_tmp"
}
trap cleanup EXIT
@@ -180,6 +191,11 @@ dump_debug_logs() {
}
trap 'status=$?; dump_debug_logs "$status"; exit "$status"' ERR
if [ -n "${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR:-}" ]; then
openclaw_prepublish_plugin_registry_start_mounted \
/tmp/openclaw-npm-onboard-plugin-registry plugin_registry_pid '["@openclaw/codex"]'
fi
openclaw_e2e_install_package /tmp/openclaw-install.log
command -v openclaw >/dev/null
+11 -5
View File
@@ -64,6 +64,12 @@ const updateMigrationCommand = upgradeSurvivorScriptCommand(
const updateRunPackageSelfUpgradeCommand =
"OPENCLAW_QA_ALLOW_UPDATE_RUN_SELF=1 OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:update-run-package-self-upgrade";
const CODEX_HARNESS_API_KEY_ENV = "OPENCLAW_LIVE_CODEX_HARNESS_AUTH=api-key";
const npmOnboardLaneOptions = {
prepublishPluginPackages: ["@openclaw/codex"],
resources: ["service"],
stateScenario: "empty",
weight: 3,
} satisfies LaneOptions;
const LIVE_RETRY_PATTERNS = [
/529\b/i,
@@ -461,17 +467,17 @@ export const mainLanes: DockerE2eLane[] = [
npmLane(
"npm-onboard-channel-agent",
"OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:npm-onboard-channel-agent",
{ resources: ["service"], stateScenario: "empty", weight: 3 },
npmOnboardLaneOptions,
),
npmLane(
"npm-onboard-discord-channel-agent",
"OPENCLAW_NPM_ONBOARD_CHANNEL=discord OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:npm-onboard-channel-agent",
{ resources: ["service"], stateScenario: "empty", weight: 3 },
npmOnboardLaneOptions,
),
npmLane(
"npm-onboard-slack-channel-agent",
"OPENCLAW_NPM_ONBOARD_CHANNEL=slack OPENCLAW_SKIP_DOCKER_BUILD=1 pnpm test:docker:npm-onboard-channel-agent",
{ resources: ["service"], stateScenario: "empty", weight: 3 },
npmOnboardLaneOptions,
),
// Prerelease validation must pair frozen core bytes with matching target plugin bytes.
// Keep the registry-backed lanes above unchanged for published-package proof.
@@ -481,7 +487,7 @@ export const mainLanes: DockerE2eLane[] = [
"e2e/npm-onboard-channel-agent-docker.sh",
"OPENCLAW_NPM_ONBOARD_CHANNEL=discord OPENCLAW_NPM_ONBOARD_USE_SOURCE_PLUGIN_PACKAGE=1",
),
{ resources: ["service"], stateScenario: "empty", weight: 3 },
npmOnboardLaneOptions,
),
npmLane(
"npm-onboard-slack-candidate-channel-agent",
@@ -489,7 +495,7 @@ export const mainLanes: DockerE2eLane[] = [
"e2e/npm-onboard-channel-agent-docker.sh",
"OPENCLAW_NPM_ONBOARD_CHANNEL=slack OPENCLAW_NPM_ONBOARD_USE_SOURCE_PLUGIN_PACKAGE=1",
),
{ resources: ["service"], stateScenario: "empty", weight: 3 },
npmOnboardLaneOptions,
),
npmLane(
"release-user-journey",
+17 -3
View File
@@ -3882,10 +3882,10 @@ grep -Fxq preserved "$TMPDIR/caller-fd"
expectTextToIncludeAll(runner, [
"OPENCLAW_DOCKER_ALL_LANES=codex-on-demand",
"OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR=/tmp/openclaw-prepublish-plugin-registry",
"source scripts/e2e/lib/prepublish-plugin-registry.sh",
"openclaw_prepublish_plugin_registry_start",
"OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON='[\"@openclaw/codex\"]'",
"openclaw_prepublish_plugin_registry_configure_docker_args",
"openclaw_prepublish_plugin_registry_start_mounted",
"'[\"@openclaw/codex\"]'",
]);
expectTextToIncludeAll(registryHelper, [
'OPENCLAW_NPM_REGISTRY_DIST_TAGS="$dist_tags"',
@@ -3899,6 +3899,20 @@ grep -Fxq preserved "$TMPDIR/caller-fd"
);
});
it("reuses the candidate registry lifecycle for channel onboarding", () => {
const runner = readFileSync(NPM_ONBOARD_CHANNEL_AGENT_DOCKER_E2E_PATH, "utf8");
expectTextToIncludeAll(runner, [
'source "$ROOT_DIR/scripts/e2e/lib/prepublish-plugin-registry.sh"',
"openclaw_prepublish_plugin_registry_configure_docker_args",
"openclaw_prepublish_plugin_registry_start_mounted",
"'[\"@openclaw/codex\"]'",
]);
expect(runner.indexOf("openclaw_prepublish_plugin_registry_start_mounted")).toBeLessThan(
runner.indexOf("\nopenclaw_e2e_install_package"),
);
});
it("cleans package-backed onboarding and plugin Docker artifacts on every exit path", () => {
for (const path of [
CODEX_ON_DEMAND_DOCKER_E2E_PATH,
+16
View File
@@ -144,6 +144,22 @@ function bundledPluginSweepLane(index: number): ReturnType<typeof summarizeLane>
}
describe("scripts/lib/docker-e2e-plan", () => {
it("prepares the matching Codex package for candidate npm onboarding", () => {
const laneNames = [
"npm-onboard-channel-agent",
"npm-onboard-discord-channel-agent",
"npm-onboard-slack-channel-agent",
"npm-onboard-discord-candidate-channel-agent",
"npm-onboard-slack-candidate-channel-agent",
];
const lanes = laneNames.map((name) => findLaneByName(name));
expect(lanes.map((lane) => lane?.name)).toEqual(laneNames);
expect(requiredPrepublishPluginPackagesForLanes(lanes.flatMap((lane) => lane ?? []))).toEqual([
"@openclaw/codex",
]);
});
it("omits a package-script lane unavailable from the candidate", () => {
const plan = planFor({
candidatePackageRoot: writeCandidatePackage({}),
@@ -30,6 +30,39 @@ function createTarball(
}
describe("prepublish plugin registry shell helper", () => {
it("derives the immutable Docker mount contract from the registry artifact", () => {
const root = tempDirs.make("openclaw-prepublish-registry-mount-");
const manifestPath = join(root, "prepublish-plugin-registry.json");
writeFileSync(
manifestPath,
`${JSON.stringify({ candidateVersion: VERSION, packages: [], sourceSha: SOURCE_SHA })}\n`,
);
const result = spawnSync(
"bash",
[
"-c",
`
set -euo pipefail
source "$HELPER"
openclaw_prepublish_plugin_registry_configure_docker_args "$ARTIFACT_DIR"
printf '%s\n' "\${OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DOCKER_ARGS[@]}"
`,
],
{ encoding: "utf8", env: { ...process.env, ARTIFACT_DIR: root, HELPER: SCRIPT } },
);
expect(result.status, result.stderr).toBe(0);
expect(result.stdout).toContain(`OPENCLAW_DOCKER_E2E_SELECTED_SHA=${SOURCE_SHA}`);
expect(result.stdout).toContain(
`OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION=${VERSION}`,
);
expect(result.stdout).toContain(`${root}:/tmp/openclaw-prepublish-plugin-registry:ro`);
expect(result.stdout).toContain(
`OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256=${sha256(manifestPath)}`,
);
});
it("verifies and serves every artifact package plus caller-owned fixtures", () => {
const root = tempDirs.make("openclaw-prepublish-registry-shell-");
const artifactDir = join(root, "artifact");
@@ -85,10 +118,12 @@ cleanup() {
fi
}
trap cleanup EXIT
export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_REQUIRED_PACKAGES_JSON='["@openclaw/codex"]'
openclaw_prepublish_plugin_registry_start \
"$ARTIFACT_DIR" "$SOURCE_SHA" "$VERSION" "$MANIFEST_SHA256" \
"$REGISTRY_ROOT" registry_pid \
export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_DIR="$ARTIFACT_DIR"
export OPENCLAW_DOCKER_E2E_SELECTED_SHA="$SOURCE_SHA"
export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_CANDIDATE_VERSION="$VERSION"
export OPENCLAW_PREPUBLISH_PLUGIN_REGISTRY_MANIFEST_SHA256="$MANIFEST_SHA256"
openclaw_prepublish_plugin_registry_start_mounted \
"$REGISTRY_ROOT" registry_pid '["@openclaw/codex"]' \
"@openclaw/brave-plugin" "$VERSION" "$EXTRA_TARBALL"
node <<'NODE'
const packages = ["@openclaw/codex", "@openclaw/telegram", "@openclaw/brave-plugin"];