openclaw/.github/workflows/docker-release.yml
Peter Steinberger 4097fd641f
chore: refresh all dependencies with seven-day cutoff (#172883)
* chore: refresh all dependencies with seven-day cutoff

Select compatible releases published by 2026-10-08T06:25:51Z across npm, native apps, CI actions, containers and downloaded tools. Align lockfiles, hashes, license labels and consumer fixtures; preserve documented compatibility holds and runtime floors.

* test: align remaining dependency refresh fixtures

Align the Linux native-hook pressure proof with the audited Codex 0.161.0 binary. Use the existing real-Gateway fixture for Telegram conversation-binding proof instead of unrelated package-install preparation; preserve every authority and transport assertion. Original CI failures and focused proof limitations remain recorded in the PR.

* fix(whatsapp): name reply plan declaration contracts

Preserve the existing six-field reply plan and branded dispatch parameter through named public SDK type references. This avoids TS4058 while emitting the package-local declaration boundary; runtime behavior is unchanged.

* fix: align dependency fixtures with current lifecycle owners

Await automatic plugin retirement instead of polling, retain exact Codex prompt ordering, and align the newly added UI media dependency with the already eligible 1.61.3 resolution. The complete prior dependency candidate and this incremental repair have independent scoped-clean review.

* test: retire obsolete Matrix preview and simplify Telegram fixture

Keep the existing message-content privacy regression and all Telegram provider failure, continuation, and visible-settlement assertions. Use the established real-Gateway fixture instead of unrelated packaged-install preparation; do not raise test or compiler timeouts.

* fix: avoid native hook priority inversion and palette hover leakage

Keep synchronous Codex policy relays at inherited process priority instead of nice 10. The 20-way two-core pressure failure lost nineteen hook completions at the unchanged ten-second deadline; preserve the complete stress, denial, retry and output assertions for exact-head CI.

Move the browser pointer off the select before checking its idle palette. Scheduled main reports the identical hover color failure. Independent P2 review is clean; local browser replay stalls in the pre-existing menu-opening phase, and Linux stress execution remains assigned to CI.

* fix: preserve sealed worker and browser lifecycle contracts

Retain native-disabled fs-safe behavior in sealed bundles and fail closed on a native batch request instead of emitting a missing native helper URL. Preserve the nonnative permission path and reject upstream transform drift.

Use controlled native animations for the WebKit closing lease, require abort in active Talk close, and scope video failure text to the alert surface. Independent P2 review is clean. Original worker archive case passes (39.390s; 283.23s total); original Talk and video cases pass (1.876s and 1.485s; 55.96s total), excluding unrelated cases. WebKit proof belongs to CI because its matching browser is unavailable locally.

* test: wait for side-turn and favicon startup signals
2026-10-15 09:19:10 -07:00

439 lines
21 KiB
YAML

name: Docker Release
on:
workflow_call:
inputs:
runner_group:
description: Optional dedicated release runner group; empty retains the current labels
required: false
default: ""
type: string
tag:
description: Immutable stable, extended-stable, or beta release tag
required: true
type: string
release_sha:
description: Full immutable commit SHA resolved from tag
required: true
type: string
image_tag_suffix:
description: Optional dated rebuild suffix for version-identified image tags
required: false
default: ""
type: string
focused_release_evidence_run_id:
description: Optional immutable authorized beta focused evidence producer run
required: false
default: ""
type: string
focused_release_evidence_run_attempt:
description: Exact previously verified focused evidence producer attempt
required: false
default: ""
type: string
focused_release_evidence_workflow_full_ref:
description: Exact previously verified focused evidence producer tag
required: false
default: ""
type: string
focused_release_evidence_workflow_sha:
description: Exact previously verified focused evidence producer SHA
required: false
default: ""
type: string
full_release_validation_run_id:
description: Exact Full Release Validation parent authorizing prepared bytes
required: false
default: ""
type: string
full_release_validation_run_attempt:
description: Exact qualification parent attempt
required: false
default: ""
type: string
prepared_run_id:
description: Exact prepared Docker producer run; omitted to prepare in this publication
required: false
default: ""
type: string
prepared_run_attempt:
description: Exact prepared Docker producer attempt
required: false
default: ""
type: string
prepared_artifact_name:
description: Immutable prepared Docker manifest artifact name
required: false
default: ""
type: string
prepared_manifest_sha256:
description: SHA256 of the prepared Docker manifest bytes
required: false
default: ""
type: string
outputs:
version:
description: Resolved Docker release version without the v prefix
value: ${{ jobs.validate_release_identity.outputs.version }}
include_browser:
description: Whether the tagged release includes browser images
value: ${{ jobs.validate_release_identity.outputs.include_browser }}
vcr_source_digests:
description: Newline-delimited verified immutable GHCR source digests
value: ${{ jobs.publish.outputs.vcr_source_digests }}
secrets:
DOCKERHUB_USERNAME:
required: true
DOCKERHUB_TOKEN:
required: true
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
validate_release_identity:
runs-on: ${{ inputs.runner_group != '' && fromJSON(format('{{"group":{0},"labels":{1}}}', toJSON(inputs.runner_group), toJSON('ubuntu-24.04'))) || ('ubuntu-24.04') }}
outputs:
version: ${{ steps.identity.outputs.version }}
include_browser: ${{ steps.identity.outputs.include_browser }}
steps:
- name: Validate immutable tag and SHA inputs
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_SHA: ${{ inputs.release_sha }}
IMAGE_TAG_SUFFIX: ${{ inputs.image_tag_suffix }}
PREPARED_RUN_ID: ${{ inputs.prepared_run_id }}
PREPARED_RUN_ATTEMPT: ${{ inputs.prepared_run_attempt }}
PREPARED_ARTIFACT_NAME: ${{ inputs.prepared_artifact_name }}
PREPARED_MANIFEST_SHA256: ${{ inputs.prepared_manifest_sha256 }}
run: |
set -euo pipefail
if [[ -n "${IMAGE_TAG_SUFFIX}" && ! "${IMAGE_TAG_SUFFIX}" =~ ^-r[0-9]{8}$ ]]; then
echo "Invalid Docker image tag suffix: ${IMAGE_TAG_SUFFIX}" >&2
exit 1
fi
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-(beta\.)?[1-9][0-9]*)?$ || ! "${RELEASE_SHA}" =~ ^[a-f0-9]{40}$ ]]; then
echo "Docker publication requires an immutable stable, extended-stable, or beta tag and full SHA." >&2
exit 1
fi
if [[ -n "${PREPARED_RUN_ID}${PREPARED_RUN_ATTEMPT}${PREPARED_ARTIFACT_NAME}${PREPARED_MANIFEST_SHA256}" ]]; then
if [[ ! "${PREPARED_RUN_ID}" =~ ^[1-9][0-9]*$ || ! "${PREPARED_RUN_ATTEMPT}" =~ ^[1-9][0-9]*$ || ! "${PREPARED_MANIFEST_SHA256}" =~ ^[a-f0-9]{64}$ || "${PREPARED_ARTIFACT_NAME}" != "docker-release-${RELEASE_SHA}-${PREPARED_RUN_ATTEMPT}" ]]; then
echo "Prepared Docker evidence requires the complete exact producer and manifest tuple." >&2
exit 1
fi
fi
- name: Checkout immutable release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ inputs.tag }}
path: release-source
fetch-depth: 1
sparse-checkout: |
package.json
Dockerfile
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Checkout trusted Docker tooling
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
path: workflow-source
fetch-depth: 1
sparse-checkout: scripts
persist-credentials: false
- name: Setup supported Node runtime
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24.21.0"
package-manager-cache: false
- name: Verify tag, SHA, and package identity agree
id: identity
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_SHA: ${{ inputs.release_sha }}
IMAGE_TAG_SUFFIX: ${{ inputs.image_tag_suffix }}
run: |
set -euo pipefail
test "$(git -C release-source rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" = "${RELEASE_SHA}"
test "$(git -C release-source rev-parse HEAD)" = "${RELEASE_SHA}"
node --input-type=module <<'NODE'
import { appendFileSync, readFileSync } from 'node:fs';
import { validateDockerReleaseIdentity } from './workflow-source/scripts/docker-release-artifacts.mjs';
const env = process.env;
const policy = validateDockerReleaseIdentity({ tag: env.RELEASE_TAG, sourceSha: env.RELEASE_SHA, imageTagSuffix: env.IMAGE_TAG_SUFFIX, packageVersion: JSON.parse(readFileSync('release-source/package.json', 'utf8')).version });
const includeBrowser = /^ARG OPENCLAW_INSTALL_BROWSER/m.test(readFileSync('release-source/Dockerfile', 'utf8'));
appendFileSync(env.GITHUB_OUTPUT, `version=${policy.version}\ninclude_browser=${includeBrowser}\n`);
NODE
prepare:
name: Prepare immutable Docker images
needs: validate_release_identity
if: ${{ inputs.prepared_run_id == '' }}
uses: ./.github/workflows/docker-release-prepare.yml
with:
tag: ${{ inputs.tag }}
release_sha: ${{ inputs.release_sha }}
image_tag_suffix: ${{ inputs.image_tag_suffix }}
permissions:
actions: read
contents: read
approve:
name: Approve Docker images for ${{ inputs.tag }}${{ inputs.image_tag_suffix }}
needs: [validate_release_identity, prepare]
if: ${{ !cancelled() && needs.validate_release_identity.result == 'success' && (needs.prepare.result == 'success' || needs.prepare.result == 'skipped') }}
# An approval wait must not hold the global registry writer lock.
# Docker Hub credentials remain required workflow_call secrets for publish.
runs-on: ubuntu-24.04
environment: docker-release
permissions: {}
steps:
- name: Record Docker publication approval
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_SHA: ${{ inputs.release_sha }}
IMAGE_TAG_SUFFIX: ${{ inputs.image_tag_suffix }}
PREPARED_MANIFEST_SHA256: ${{ inputs.prepared_manifest_sha256 || needs.prepare.outputs.prepared_manifest_sha256 }}
run: |
echo "Approved Docker images for ${RELEASE_TAG}${IMAGE_TAG_SUFFIX}"
echo "Release SHA: ${RELEASE_SHA}"
echo "Prepared manifest SHA256: ${PREPARED_MANIFEST_SHA256}"
publish:
name: Publish prepared Docker images
needs: [validate_release_identity, prepare, approve]
if: ${{ !cancelled() && needs.validate_release_identity.result == 'success' && (needs.prepare.result == 'success' || needs.prepare.result == 'skipped') && needs.approve.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 45
# The global lock covers every registry mutation and the alias read/write
# checks. Preparation never queues behind a previous release's promotion.
concurrency:
group: docker-release-publish
cancel-in-progress: false
queue: max
permissions:
actions: read
attestations: read
contents: read
packages: write
outputs:
vcr_source_digests: ${{ steps.promote.outputs.vcr_source_digests }}
env:
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_SHA: ${{ inputs.release_sha }}
IMAGE_TAG_SUFFIX: ${{ inputs.image_tag_suffix }}
INCLUDE_BROWSER: ${{ needs.validate_release_identity.outputs.include_browser }}
PREPARED_RUN_ID: ${{ inputs.prepared_run_id || needs.prepare.outputs.prepared_run_id }}
PREPARED_RUN_ATTEMPT: ${{ inputs.prepared_run_attempt || needs.prepare.outputs.prepared_run_attempt }}
PREPARED_ARTIFACT_NAME: ${{ inputs.prepared_artifact_name || needs.prepare.outputs.prepared_artifact_name }}
PREPARED_MANIFEST_SHA256: ${{ inputs.prepared_manifest_sha256 || needs.prepare.outputs.prepared_manifest_sha256 }}
steps:
- name: Checkout immutable release source after approval
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: refs/tags/${{ inputs.tag }}
path: release-source
fetch-depth: 1
filter: blob:none
sparse-checkout: |
package.json
Dockerfile
sparse-checkout-cone-mode: false
persist-credentials: false
- name: Fetch focused release history
if: ${{ inputs.focused_release_evidence_run_id != '' }}
run: |
set -euo pipefail
# Historical focused evidence reconstructs candidate trees and ancestry.
git -C release-source fetch --no-tags --filter=blob:none --unshallow origin \
"refs/tags/${RELEASE_TAG}:refs/tags/${RELEASE_TAG}"
- name: Checkout trusted Docker tooling
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
path: workflow-source
fetch-depth: 1
sparse-checkout: scripts
persist-credentials: false
- name: Revalidate immutable source after approval
run: |
set -euo pipefail
test "$(git -C release-source rev-parse "refs/tags/${RELEASE_TAG}^{commit}")" = "${RELEASE_SHA}"
test "$(git -C release-source rev-parse HEAD)" = "${RELEASE_SHA}"
- name: Revalidate focused evidence producer after Docker approval
if: ${{ inputs.focused_release_evidence_run_id != '' }}
env:
GH_TOKEN: ${{ github.token }}
FOCUSED_RELEASE_EVIDENCE_RUN_ID: ${{ inputs.focused_release_evidence_run_id }}
FOCUSED_RELEASE_EVIDENCE_RUN_ATTEMPT: ${{ inputs.focused_release_evidence_run_attempt }}
PRODUCER_WORKFLOW_FULL_REF: ${{ inputs.focused_release_evidence_workflow_full_ref }}
PRODUCER_WORKFLOW_SHA: ${{ inputs.focused_release_evidence_workflow_sha }}
run: |
set -euo pipefail
if [[ ! "${FOCUSED_RELEASE_EVIDENCE_RUN_ID}" =~ ^[1-9][0-9]*$ ||
! "${FOCUSED_RELEASE_EVIDENCE_RUN_ATTEMPT}" =~ ^[1-9][0-9]*$ ||
! "${PRODUCER_WORKFLOW_SHA}" =~ ^[a-f0-9]{40}$ ||
! "${PRODUCER_WORKFLOW_FULL_REF}" =~ ^refs/tags/release-publish/([a-f0-9]{12})-[1-9][0-9]*$ ||
"${PRODUCER_WORKFLOW_SHA:0:12}" != "${BASH_REMATCH[1]}" ]]; then
echo "Focused evidence producer identity is missing or invalid after Docker approval." >&2
exit 1
fi
producer_ref="${PRODUCER_WORKFLOW_FULL_REF#refs/tags/}"
run_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${FOCUSED_RELEASE_EVIDENCE_RUN_ID}")"
if ! jq -e \
--arg run_id "${FOCUSED_RELEASE_EVIDENCE_RUN_ID}" \
--arg attempt "${FOCUSED_RELEASE_EVIDENCE_RUN_ATTEMPT}" \
--arg producer_ref "${producer_ref}" \
--arg producer_sha "${PRODUCER_WORKFLOW_SHA}" \
'.id == ($run_id | tonumber) and .run_attempt == ($attempt | tonumber) and
.name == "Authorized Beta Focused Validation" and
.path == ".github/workflows/authorized-beta-focused-validation.yml" and
.event == "workflow_dispatch" and .status == "completed" and .conclusion == "success" and
.head_branch == $producer_ref and .head_sha == $producer_sha' \
<<<"${run_json}" >/dev/null; then
echo "Focused evidence producer changed after Docker approval." >&2
exit 1
fi
remote_producer_sha="$(
gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${producer_ref}" \
--jq '.object | select(.type == "commit") | .sha'
)"
if [[ "${remote_producer_sha}" != "${PRODUCER_WORKFLOW_SHA}" ]]; then
echo "Focused evidence producer tag is missing, moved, or annotated after Docker approval." >&2
exit 1
fi
- name: Download focused release evidence after Docker approval
if: ${{ inputs.focused_release_evidence_run_id != '' }}
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: authorized-beta-focused-v1-${{ inputs.focused_release_evidence_run_id }}-${{ inputs.focused_release_evidence_run_attempt }}
path: ${{ runner.temp }}/authorized-beta-focused-evidence
repository: ${{ github.repository }}
run-id: ${{ inputs.focused_release_evidence_run_id }}
github-token: ${{ github.token }}
- name: Setup supported Node runtime
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24.21.0"
package-manager-cache: false
- name: Verify focused release evidence after Docker approval
if: ${{ inputs.focused_release_evidence_run_id != '' }}
env:
GH_TOKEN: ${{ github.token }}
FOCUSED_RELEASE_EVIDENCE_RUN_ID: ${{ inputs.focused_release_evidence_run_id }}
FOCUSED_RELEASE_EVIDENCE_RUN_ATTEMPT: ${{ inputs.focused_release_evidence_run_attempt }}
PRODUCER_WORKFLOW_FULL_REF: ${{ inputs.focused_release_evidence_workflow_full_ref }}
PRODUCER_WORKFLOW_SHA: ${{ inputs.focused_release_evidence_workflow_sha }}
run: |
set -euo pipefail
evidence="${RUNNER_TEMP}/authorized-beta-focused-evidence/evidence.json"
gh attestation verify "${evidence}" \
--repo "${GITHUB_REPOSITORY}" \
--signer-workflow "${GITHUB_REPOSITORY}/.github/workflows/authorized-beta-focused-validation.yml" \
--signer-digest "${PRODUCER_WORKFLOW_SHA}" \
--source-digest "${PRODUCER_WORKFLOW_SHA}" \
--source-ref "${PRODUCER_WORKFLOW_FULL_REF}" \
--deny-self-hosted-runners
node workflow-source/scripts/verify-authorized-beta-focused-candidate.mjs \
--repository-root release-source \
--validator workflow-source/scripts/validate-authorized-beta-focused-evidence.mts \
--policy workflow-source/scripts/authorized-beta-focused-policy.json \
--artifact "${evidence}" \
--producer-run-id "${FOCUSED_RELEASE_EVIDENCE_RUN_ID}" \
--producer-run-attempt "${FOCUSED_RELEASE_EVIDENCE_RUN_ATTEMPT}" \
--producer-workflow-full-ref "${PRODUCER_WORKFLOW_FULL_REF}" \
--producer-workflow-sha "${PRODUCER_WORKFLOW_SHA}"
- name: Download immutable full release qualification
if: ${{ inputs.full_release_validation_run_id != '' }}
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: full-release-validation-${{ inputs.full_release_validation_run_id }}-${{ inputs.full_release_validation_run_attempt }}
path: ${{ runner.temp }}/docker-qualification
repository: ${{ github.repository }}
run-id: ${{ inputs.full_release_validation_run_id }}
github-token: ${{ github.token }}
- name: Download prepared Docker manifest
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: ${{ env.PREPARED_ARTIFACT_NAME }}
path: ${{ runner.temp }}/docker-release
repository: ${{ github.repository }}
run-id: ${{ env.PREPARED_RUN_ID }}
github-token: ${{ github.token }}
- name: Verify exact prepared producer and payload identities
id: prepared
run: |
set -euo pipefail
qualification_args=()
if [[ -n "${FULL_RELEASE_VALIDATION_RUN_ID}" ]]; then
qualification_args+=(--full-release-manifest "${RUNNER_TEMP}/docker-qualification/full-release-validation-manifest.json")
fi
node workflow-source/scripts/docker-release-artifacts.mjs verify "${qualification_args[@]}" \
--manifest "${RUNNER_TEMP}/docker-release/manifest.json" \
--manifest-sha256 "${PREPARED_MANIFEST_SHA256}" \
--artifact-name "${PREPARED_ARTIFACT_NAME}" \
--run-id "${PREPARED_RUN_ID}" --run-attempt "${PREPARED_RUN_ATTEMPT}"
- name: Download immutable OCI payloads
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
artifact-ids: ${{ steps.prepared.outputs.artifact_ids }}
path: ${{ runner.temp }}/docker-release/payloads
repository: ${{ github.repository }}
run-id: ${{ env.PREPARED_RUN_ID }}
github-token: ${{ github.token }}
- name: Install reviewed OCI transport
run: |
set -euo pipefail
sudo apt-get update
sudo apt-get install --no-install-recommends -y skopeo
skopeo --version | grep -Fx 'skopeo version 1.13.3'
dpkg-query --show --showformat='${Package} ${Version}\n' skopeo
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- name: Validate Docker Hub publish credentials
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
set -euo pipefail
if [[ -z "${DOCKERHUB_USERNAME}" || -z "${DOCKERHUB_TOKEN}" ]]; then
echo "Docker Hub publishing requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets." >&2
exit 1
fi
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ github.token }}
- name: Log in to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: docker.io
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Copy exact OCI digests, verify attestations, and promote aliases
id: promote
run: |
set -euo pipefail
qualification_args=()
if [[ -n "${FULL_RELEASE_VALIDATION_RUN_ID}" ]]; then
qualification_args+=(--full-release-manifest "${RUNNER_TEMP}/docker-qualification/full-release-validation-manifest.json")
fi
node workflow-source/scripts/docker-release-artifacts.mjs publish "${qualification_args[@]}" \
--manifest "${RUNNER_TEMP}/docker-release/manifest.json" \
--manifest-sha256 "${PREPARED_MANIFEST_SHA256}" \
--artifact-name "${PREPARED_ARTIFACT_NAME}" \
--run-id "${PREPARED_RUN_ID}" --run-attempt "${PREPARED_RUN_ATTEMPT}" \
--directory "${RUNNER_TEMP}/docker-release/payloads"