fix(release): restore frozen validation startup (#110210)

* fix(release): restore frozen validation startup

Adapt the no-write validation portion of b597a8d364 (#103834) for the 6.x extended-stable workflow set. Remove validation-owned image publisher jobs instead of granting write permissions; Docker publication remains out of scope.

* fix(release): allow canonical extended-stable checks

Restore the openclaw-release-checks.yml portion of c7810fc697 (#99352) that authorizes the canonical extended-stable/YYYY.M.33 workflow branch.
This commit is contained in:
Dallin Romney
2026-07-17 15:41:41 -07:00
committed by GitHub
parent 343a93ded3
commit 3341ade2bf
7 changed files with 44 additions and 333 deletions
+3 -59
View File
@@ -18,10 +18,9 @@ on:
root_image_transport:
description: Root Dockerfile image transport
required: false
default: registry
default: no-push-artifact
type: choice
options:
- registry
- no-push-artifact
workflow_call:
inputs:
@@ -42,7 +41,7 @@ on:
root_image_transport:
description: Root Dockerfile image transport
required: false
default: registry
default: no-push-artifact
type: string
permissions:
@@ -409,51 +408,8 @@ jobs:
fi
} >> "$GITHUB_STEP_SUMMARY"
push_root_dockerfile_image:
needs: [preflight, root_dockerfile_image]
if: needs.preflight.outputs.root_image_transport == 'registry' && needs.root_dockerfile_image.outputs.image_exists != 'true'
runs-on: ubuntu-24.04
permissions:
contents: read
packages: write
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
steps:
- name: Checkout CLI
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.preflight.outputs.target_sha }}
persist-credentials: false
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Set up Blacksmith Docker Builder
uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1
with:
max-cache-size-mb: 800000
# The registry path publishes one matrix-extension image by target SHA.
# A direct buildx command keeps progress visible and fails on timeout.
- name: Build and push root Dockerfile smoke image
env:
IMAGE_REF: ${{ needs.root_dockerfile_image.outputs.image_ref }}
run: |
timeout --kill-after=30s 45m docker buildx build \
--progress=plain \
--push \
--build-arg OPENCLAW_EXTENSIONS=matrix \
-t "$IMAGE_REF" \
-f ./Dockerfile \
.
root_dockerfile_image_ready:
needs: [preflight, root_dockerfile_image, push_root_dockerfile_image]
needs: [preflight, root_dockerfile_image]
if: always() && needs.preflight.result == 'success' && needs.preflight.outputs.run_full_install_smoke == 'true'
runs-on: ubuntu-24.04
permissions:
@@ -461,25 +417,13 @@ jobs:
steps:
- name: Verify root Dockerfile image preparation
env:
IMAGE_EXISTS: ${{ needs.root_dockerfile_image.outputs.image_exists }}
PREPARE_RESULT: ${{ needs.root_dockerfile_image.result }}
PUSH_RESULT: ${{ needs.push_root_dockerfile_image.result }}
ROOT_IMAGE_TRANSPORT: ${{ needs.preflight.outputs.root_image_transport }}
run: |
set -euo pipefail
if [[ "$PREPARE_RESULT" != "success" ]]; then
echo "Root Dockerfile image preparation ended with ${PREPARE_RESULT}." >&2
exit 1
fi
if [[ "$ROOT_IMAGE_TRANSPORT" == "registry" && "$IMAGE_EXISTS" != "true" ]]; then
if [[ "$PUSH_RESULT" != "success" ]]; then
echo "Root Dockerfile registry image publication ended with ${PUSH_RESULT}." >&2
exit 1
fi
elif [[ "$PUSH_RESULT" != "skipped" ]]; then
echo "Unexpected root Dockerfile registry publication result: ${PUSH_RESULT}." >&2
exit 1
fi
qr_package_install_smoke:
needs: [preflight]
@@ -51,10 +51,9 @@ on:
shared_image_policy:
description: Shared Docker image transport
required: true
default: allow-push
default: no-push-artifact
type: choice
options:
- allow-push
- existing-only
- no-push-artifact
shared_image_artifact_namespace:
@@ -213,9 +212,9 @@ on:
default: ""
type: string
shared_image_policy:
description: "Shared Docker image transport: allow-push, existing-only, or no-push-artifact"
description: "Shared Docker image transport: existing-only or no-push-artifact"
required: false
default: allow-push
default: no-push-artifact
type: string
shared_image_artifact_namespace:
description: Safe unique artifact namespace when shared_image_policy=no-push-artifact
@@ -496,8 +495,6 @@ jobs:
fi
case "$SHARED_IMAGE_POLICY" in
allow-push)
;;
existing-only)
if [[ -z "${PROVIDED_BARE_IMAGE// }" && -z "${PROVIDED_FUNCTIONAL_IMAGE// }" ]]; then
echo "shared_image_policy=existing-only requires explicit shared image refs." >&2
@@ -519,7 +516,7 @@ jobs:
}
;;
*)
echo "shared_image_policy must be allow-push, existing-only, or no-push-artifact." >&2
echo "shared_image_policy must be existing-only or no-push-artifact." >&2
exit 1
;;
esac
@@ -1964,7 +1961,7 @@ jobs:
echo "Shared Docker E2E functional image: \`$functional_image\`" >> "$GITHUB_STEP_SUMMARY"
- name: Log in to GHCR
if: steps.plan.outputs.needs_e2e_image == '1' && (inputs.shared_image_policy == 'allow-push' || inputs.shared_image_policy == 'existing-only')
if: steps.plan.outputs.needs_e2e_image == '1' && inputs.shared_image_policy == 'existing-only'
run: bash .release-harness/scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
@@ -1972,7 +1969,7 @@ jobs:
- name: Check existing shared Docker E2E images
id: image_exists
if: steps.plan.outputs.needs_e2e_image == '1' && (inputs.shared_image_policy == 'allow-push' || inputs.shared_image_policy == 'existing-only')
if: steps.plan.outputs.needs_e2e_image == '1' && inputs.shared_image_policy == 'existing-only'
shell: bash
env:
PROVIDED_BARE_IMAGE: ${{ inputs.docker_e2e_bare_image }}
@@ -2104,126 +2101,8 @@ jobs:
compression-level: 0
retention-days: 7
push_docker_e2e_images:
needs: [validate_selected_ref, prepare_docker_e2e_image]
if: inputs.shared_image_policy == 'allow-push' && needs.prepare_docker_e2e_image.outputs.needs_registry_build == '1'
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
permissions:
actions: read
contents: read
packages: write
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
steps:
- name: Checkout selected ref
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
persist-credentials: false
- name: Download OpenClaw Docker E2E package
if: needs.prepare_docker_e2e_image.outputs.needs_functional_image == '1' && needs.prepare_docker_e2e_image.outputs.functional_exists != '1'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
artifact-ids: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_id }}
path: .artifacts/docker-e2e-package
run-id: ${{ needs.prepare_docker_e2e_image.outputs.package_artifact_run_id }}
github-token: ${{ github.token }}
- name: Normalize OpenClaw Docker E2E package
if: needs.prepare_docker_e2e_image.outputs.needs_functional_image == '1' && needs.prepare_docker_e2e_image.outputs.functional_exists != '1'
shell: bash
run: |
set -euo pipefail
target=".artifacts/docker-e2e-package/openclaw-current.tgz"
if [[ ! -f "$target" ]]; then
mapfile -t tgzs < <(find .artifacts/docker-e2e-package -type f -name '*.tgz' | sort)
if [[ "${#tgzs[@]}" -ne 1 ]]; then
echo "Expected exactly one package tarball for the registry image build; found ${#tgzs[@]}." >&2
exit 1
fi
cp "${tgzs[0]}" "$target"
fi
- name: Log in to GHCR
run: bash scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Setup Docker builder
uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1
with:
max-cache-size-mb: 800000
- name: Build and push bare Docker E2E image
if: needs.prepare_docker_e2e_image.outputs.needs_bare_image == '1' && needs.prepare_docker_e2e_image.outputs.bare_exists != '1'
shell: bash
env:
IMAGE_REF: ${{ needs.prepare_docker_e2e_image.outputs.bare_image }}
run: |
set -euo pipefail
build_cmd=(
docker buildx build
--file ./scripts/e2e/Dockerfile
--target bare
--platform linux/amd64
--tag "$IMAGE_REF"
--sbom=true
--provenance=mode=max
--push
.
)
for attempt in 1 2 3 4; do
if "${build_cmd[@]}"; then
exit 0
fi
if [[ "$attempt" == "4" ]]; then
echo "::error::Failed to build Docker E2E bare image after ${attempt} attempts"
exit 1
fi
sleep_seconds=$((attempt * 20))
echo "Docker E2E bare image build failed; retrying in ${sleep_seconds}s (${attempt}/4)."
sleep "$sleep_seconds"
done
- name: Build and push functional Docker E2E image
if: needs.prepare_docker_e2e_image.outputs.needs_functional_image == '1' && needs.prepare_docker_e2e_image.outputs.functional_exists != '1'
shell: bash
env:
IMAGE_REF: ${{ needs.prepare_docker_e2e_image.outputs.functional_image }}
run: |
set -euo pipefail
build_cmd=(
docker buildx build
--file ./scripts/e2e/Dockerfile
--target functional
--build-context openclaw_package=.artifacts/docker-e2e-package
--platform linux/amd64
--tag "$IMAGE_REF"
--sbom=true
--provenance=mode=max
--push
.
)
for attempt in 1 2 3 4; do
if "${build_cmd[@]}"; then
exit 0
fi
if [[ "$attempt" == "4" ]]; then
echo "::error::Failed to build Docker E2E functional image after ${attempt} attempts"
exit 1
fi
sleep_seconds=$((attempt * 20))
echo "Docker E2E functional image build failed; retrying in ${sleep_seconds}s (${attempt}/4)."
sleep "$sleep_seconds"
done
docker_e2e_image_ready:
needs: [prepare_docker_e2e_image, push_docker_e2e_images]
needs: prepare_docker_e2e_image
if: always() && needs.prepare_docker_e2e_image.result != 'skipped'
runs-on: ubuntu-24.04
timeout-minutes: 5
@@ -2233,9 +2112,6 @@ jobs:
- name: Verify Docker E2E image preparation
env:
PREPARE_RESULT: ${{ needs.prepare_docker_e2e_image.result }}
PUSH_RESULT: ${{ needs.push_docker_e2e_images.result }}
NEEDS_REGISTRY_BUILD: ${{ needs.prepare_docker_e2e_image.outputs.needs_registry_build }}
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
shell: bash
run: |
set -euo pipefail
@@ -2243,15 +2119,6 @@ jobs:
echo "Docker E2E image preparation ended with ${PREPARE_RESULT}." >&2
exit 1
fi
if [[ "$SHARED_IMAGE_POLICY" == "allow-push" && "$NEEDS_REGISTRY_BUILD" == "1" ]]; then
if [[ "$PUSH_RESULT" != "success" ]]; then
echo "Docker E2E registry image publication ended with ${PUSH_RESULT}." >&2
exit 1
fi
elif [[ "$PUSH_RESULT" != "skipped" ]]; then
echo "Unexpected Docker E2E registry publication result: ${PUSH_RESULT}." >&2
exit 1
fi
prepare_live_test_image:
needs: validate_selected_ref
@@ -2393,53 +2260,8 @@ jobs:
compression-level: 0
retention-days: 7
push_live_test_image:
needs: [validate_selected_ref, prepare_live_test_image]
if: inputs.shared_image_policy == 'allow-push' && needs.prepare_live_test_image.outputs.image_exists != '1'
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: 60
permissions:
contents: read
packages: write
env:
DOCKER_BUILD_SUMMARY: "false"
DOCKER_BUILD_RECORD_UPLOAD: "false"
steps:
- name: Checkout selected ref
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: ${{ needs.validate_selected_ref.outputs.selected_sha }}
fetch-depth: 1
persist-credentials: false
- name: Log in to GHCR
run: bash scripts/ci-docker-login-ghcr.sh
env:
GHCR_USERNAME: ${{ github.actor }}
GITHUB_TOKEN: ${{ github.token }}
- name: Setup Docker builder
uses: useblacksmith/setup-docker-builder@ab5c1da94f53f5cd75c1038092aa276dddfccbba # v1
with:
max-cache-size-mb: 800000
- name: Build and push shared live-test image
uses: useblacksmith/build-push-action@fb9e3e6a9299c78462bfadd0d93352c316adc9b8 # v2
with:
context: .
file: ./Dockerfile
target: build
build-args: |
OPENCLAW_EXTENSIONS=matrix,acpx
platforms: linux/amd64
tags: ${{ needs.prepare_live_test_image.outputs.live_image }}
sbom: true
provenance: mode=max
load: false
push: true
live_test_image_ready:
needs: [prepare_live_test_image, push_live_test_image]
needs: prepare_live_test_image
if: always() && needs.prepare_live_test_image.result != 'skipped'
runs-on: ubuntu-24.04
timeout-minutes: 5
@@ -2448,10 +2270,7 @@ jobs:
steps:
- name: Verify live-test image preparation
env:
IMAGE_EXISTS: ${{ needs.prepare_live_test_image.outputs.image_exists }}
PREPARE_RESULT: ${{ needs.prepare_live_test_image.result }}
PUSH_RESULT: ${{ needs.push_live_test_image.result }}
SHARED_IMAGE_POLICY: ${{ inputs.shared_image_policy }}
shell: bash
run: |
set -euo pipefail
@@ -2459,15 +2278,6 @@ jobs:
echo "Live-test image preparation ended with ${PREPARE_RESULT}." >&2
exit 1
fi
if [[ "$SHARED_IMAGE_POLICY" == "allow-push" && "$IMAGE_EXISTS" != "1" ]]; then
if [[ "$PUSH_RESULT" != "success" ]]; then
echo "Live-test registry image publication ended with ${PUSH_RESULT}." >&2
exit 1
fi
elif [[ "$PUSH_RESULT" != "skipped" ]]; then
echo "Unexpected live-test registry publication result: ${PUSH_RESULT}." >&2
exit 1
fi
validate_live_models_docker:
name: Docker live models (${{ matrix.provider_label }})
@@ -144,8 +144,8 @@ jobs:
tideclaw_alpha_check=true
fi
fi
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release-ci/[0-9a-f]{12}-[0-9]+$ ]] && [[ "${tideclaw_alpha_check}" != "true" ]]; then
echo "Release checks must be dispatched from main, release/YYYY.M.PATCH, a Full Release Validation release-ci/<sha>-<timestamp> ref, or a Tideclaw alpha branch for alpha prereleases." >&2
if [[ "${WORKFLOW_REF}" != "refs/heads/main" ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release/[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/release-ci/[0-9a-f]{12}-[0-9]+$ ]] && [[ ! "${WORKFLOW_REF}" =~ ^refs/heads/extended-stable/[0-9]{4}\.([1-9]|1[0-2])\.33$ ]] && [[ "${tideclaw_alpha_check}" != "true" ]]; then
echo "Release checks must be dispatched from main, release/YYYY.M.PATCH, extended-stable/YYYY.M.33, a Full Release Validation release-ci/<sha>-<timestamp> ref, or a Tideclaw alpha branch for alpha prereleases." >&2
exit 1
fi
+6 -7
View File
@@ -87,10 +87,9 @@ on:
shared_image_policy:
description: Shared Docker image transport for package acceptance
required: true
default: allow-push
default: no-push-artifact
type: choice
options:
- allow-push
- existing-only
- no-push-artifact
shared_image_artifact_namespace:
@@ -230,9 +229,9 @@ on:
default: ""
type: string
shared_image_policy:
description: "Shared Docker image transport: allow-push, existing-only, or no-push-artifact"
description: "Shared Docker image transport: existing-only or no-push-artifact"
required: false
default: allow-push
default: no-push-artifact
type: string
shared_image_artifact_namespace:
description: Unique artifact namespace when shared_image_policy=no-push-artifact
@@ -864,13 +863,13 @@ jobs:
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
docker_acceptance_registry:
name: Docker product acceptance (registry)
name: Docker product acceptance (existing registry images)
needs: [resolve_package, package_integrity]
if: inputs.shared_image_policy != 'no-push-artifact'
if: inputs.shared_image_policy == 'existing-only'
permissions:
actions: read
contents: read
packages: write
packages: read
pull-requests: read
uses: ./.github/workflows/openclaw-live-and-e2e-checks-reusable.yml
with: *docker_acceptance_inputs
@@ -52,17 +52,17 @@ function step(workflowJob: WorkflowJob, name: string): WorkflowStep {
}
describe("install smoke no-push root image transport", () => {
it("keeps registry transport as the default and validates the selected mode", () => {
it("defaults every caller to artifact-only transport", () => {
const workflow = readWorkflow(INSTALL_SMOKE);
const dispatchInput = workflow.on?.workflow_dispatch?.inputs?.root_image_transport;
const callInput = workflow.on?.workflow_call?.inputs?.root_image_transport;
expect(dispatchInput).toMatchObject({
default: "registry",
options: ["registry", "no-push-artifact"],
default: "no-push-artifact",
options: ["no-push-artifact"],
type: "choice",
});
expect(callInput).toMatchObject({
default: "registry",
default: "no-push-artifact",
type: "string",
});
expect(workflow.permissions).toMatchObject({
@@ -205,41 +205,18 @@ describe("install smoke no-push root image transport", () => {
path: "${{ steps.image_artifact.outputs.artifact_path }}",
});
const registryPublisher = job(workflow, "push_root_dockerfile_image");
expect(registryPublisher.permissions).toEqual({
contents: "read",
packages: "write",
});
expect(registryPublisher.if).toBe(
"needs.preflight.outputs.root_image_transport == 'registry' && needs.root_dockerfile_image.outputs.image_exists != 'true'",
);
expect(step(registryPublisher, "Checkout CLI").with).toMatchObject({
ref: "${{ needs.preflight.outputs.target_sha }}",
"persist-credentials": false,
});
expect(step(registryPublisher, "Log in to GHCR").if).toBeUndefined();
const registryBuild = step(registryPublisher, "Build and push root Dockerfile smoke image");
expect(registryBuild.run).toContain("--push");
expect(registryBuild.run).not.toContain("--load");
const writeScopedJobs = Object.entries(workflow.jobs)
.filter(([, candidate]) => candidate.permissions?.packages === "write")
.map(([name]) => name);
expect(writeScopedJobs).toEqual(["push_root_dockerfile_image"]);
expect(writeScopedJobs).toEqual([]);
expect(workflow.jobs.push_root_dockerfile_image).toBeUndefined();
const ready = job(workflow, "root_dockerfile_image_ready");
expect(ready.needs).toEqual([
"preflight",
"root_dockerfile_image",
"push_root_dockerfile_image",
]);
expect(ready.needs).toEqual(["preflight", "root_dockerfile_image"]);
expect(ready.if).toContain("always()");
const verify = step(ready, "Verify root Dockerfile image preparation");
expect(verify.run).toContain('if [[ "$PREPARE_RESULT" != "success" ]]');
expect(verify.run).toContain(
'if [[ "$ROOT_IMAGE_TRANSPORT" == "registry" && "$IMAGE_EXISTS" != "true" ]]',
);
expect(verify.run).toContain('elif [[ "$PUSH_RESULT" != "skipped" ]]');
expect(verify.run).not.toContain("PUSH_RESULT");
});
it("verifies and loads the artifact in every consumer without registry fallback", () => {
@@ -650,6 +650,9 @@ describe("package acceptance workflow", () => {
);
expect(workflow).toContain("| Child | Result | Minutes | Head SHA | Run |");
expect(releaseChecksWorkflow).toContain("refs/heads/release-ci/[0-9a-f]{12}-[0-9]+");
expect(releaseChecksWorkflow).toContain(
"refs/heads/extended-stable/[0-9]{4}\\.([1-9]|1[0-2])\\.33",
);
expect(releaseChecksWorkflow).toContain(
"source: ${{ (needs.resolve_target.outputs.package_acceptance_package_spec != '' || needs.resolve_target.outputs.release_package_spec != '') && 'npm' || 'artifact' }}",
);
+14 -36
View File
@@ -273,12 +273,12 @@ describe("release validation no-push transport", () => {
const registryAcceptance = job(packageAcceptance, "docker_acceptance_registry");
expect(packageAcceptance.permissions?.packages).toBe("read");
expect(packageAcceptance.on?.workflow_dispatch?.inputs?.shared_image_policy).toMatchObject({
default: "allow-push",
options: ["allow-push", "existing-only", "no-push-artifact"],
default: "no-push-artifact",
options: ["existing-only", "no-push-artifact"],
type: "choice",
});
expect(packageAcceptance.on?.workflow_call?.inputs?.shared_image_policy).toMatchObject({
default: "allow-push",
default: "no-push-artifact",
type: "string",
});
expect(standardAcceptance.with?.shared_image_policy).toBe("${{ inputs.shared_image_policy }}");
@@ -298,8 +298,8 @@ describe("release validation no-push transport", () => {
});
expect(standardAcceptance.if).toContain("shared_image_policy == 'no-push-artifact'");
expectReadOnlyPackagePermission(standardAcceptance);
expect(registryAcceptance.if).toContain("shared_image_policy != 'no-push-artifact'");
expect(registryAcceptance.permissions?.packages).toBe("write");
expect(registryAcceptance.if).toContain("shared_image_policy == 'existing-only'");
expectReadOnlyPackagePermission(registryAcceptance);
const pluginDocker = job(pluginPrerelease, "plugin-prerelease-docker-suite");
expectReadOnlyPackagePermission(pluginDocker);
@@ -322,10 +322,10 @@ describe("release validation no-push transport", () => {
const dispatchPolicy = workflow.on?.workflow_dispatch?.inputs?.shared_image_policy;
const callPolicy = workflow.on?.workflow_call?.inputs?.shared_image_policy;
expect(dispatchPolicy).toMatchObject({
default: "allow-push",
options: ["allow-push", "existing-only", "no-push-artifact"],
default: "no-push-artifact",
options: ["existing-only", "no-push-artifact"],
});
expect(callPolicy).toMatchObject({ default: "allow-push", type: "string" });
expect(callPolicy).toMatchObject({ default: "no-push-artifact", type: "string" });
const validation = job(workflow, "validate_selected_ref");
expect(validation.outputs?.workflow_repository).toBe(
@@ -354,28 +354,18 @@ describe("release validation no-push transport", () => {
const dockerProducer = job(workflow, "prepare_docker_e2e_image");
const liveProducer = job(workflow, "prepare_live_test_image");
const dockerPublisher = job(workflow, "push_docker_e2e_images");
const livePublisher = job(workflow, "push_live_test_image");
expect(workflow.permissions?.actions).toBe("read");
expect(workflow.permissions?.packages).toBe("read");
expectReadOnlyPackagePermission(dockerProducer);
expectReadOnlyPackagePermission(liveProducer);
expect(dockerPublisher.permissions?.packages).toBe("write");
expect(livePublisher.permissions?.packages).toBe("write");
expect(dockerPublisher.if).toContain("shared_image_policy == 'allow-push'");
expect(livePublisher.if).toContain("shared_image_policy == 'allow-push'");
expect(workflow.jobs?.push_docker_e2e_images).toBeUndefined();
expect(workflow.jobs?.push_live_test_image).toBeUndefined();
expect(job(workflow, "docker_e2e_image_ready").permissions?.packages).toBeUndefined();
expect(job(workflow, "live_test_image_ready").permissions?.packages).toBeUndefined();
const packageWriters = Object.entries(workflow.jobs ?? {}).filter(
([, workflowJob]) => workflowJob.permissions?.packages === "write",
);
expect(packageWriters.map(([name]) => name).sort()).toEqual([
"push_docker_e2e_images",
"push_live_test_image",
]);
for (const [, workflowJob] of packageWriters) {
expect(workflowJob.if).toContain("shared_image_policy == 'allow-push'");
}
expect(packageWriters).toEqual([]);
const validateSelectedRef = step(
job(workflow, "validate_selected_ref"),
"Validate selected ref",
@@ -557,13 +547,13 @@ describe("release validation no-push transport", () => {
push: false,
});
const dockerLoginCondition = step(dockerProducer, "Log in to GHCR").if;
expect(dockerLoginCondition).toContain("shared_image_policy == 'allow-push'");
expect(dockerLoginCondition).toContain("shared_image_policy == 'existing-only'");
expect(dockerLoginCondition).not.toContain("allow-push");
expect(step(liveProducer, "Log in to GHCR").if).toContain(
"shared_image_policy != 'no-push-artifact'",
);
expect(step(dockerProducer, "Check existing shared Docker E2E images").if).toContain(
"shared_image_policy == 'allow-push'",
"shared_image_policy == 'existing-only'",
);
expect(step(liveProducer, "Check existing shared live-test image").if).toContain(
"shared_image_policy != 'no-push-artifact'",
@@ -574,19 +564,7 @@ describe("release validation no-push transport", () => {
.filter((candidate) => candidate.run?.includes("--push"))
.map((candidate) => ({ candidate, jobName })),
);
expect(shellPushSteps.map(({ candidate }) => candidate.name).sort()).toEqual([
"Build and push bare Docker E2E image",
"Build and push functional Docker E2E image",
]);
for (const { jobName } of shellPushSteps) {
expect(jobName).toBe("push_docker_e2e_images");
}
expect(step(livePublisher, "Build and push shared live-test image").with?.push).toBe(true);
expect(step(dockerPublisher, "Download OpenClaw Docker E2E package").with).toMatchObject({
"artifact-ids": "${{ needs.prepare_docker_e2e_image.outputs.package_artifact_id }}",
"github-token": "${{ github.token }}",
"run-id": "${{ needs.prepare_docker_e2e_image.outputs.package_artifact_run_id }}",
});
expect(shellPushSteps).toEqual([]);
for (const name of [
"validate_docker_e2e",