mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-22 14:41:16 +00:00
ci: stabilize release validation tests (#108420)
* test(codex): remove turn-watch timing race * ci: pin workflow sanity shellcheck * ci: run workflow sanity on blacksmith * ci: serialize workflow sanity lint * ci: bound workflow lint stalls * ci: cap actionlint process fanout * ci: report stalled workflow shellcheck * ci: stabilize release validation checks * ci: restore release workflow shellcheck * test: preserve codex turn watch reset proof
This commit is contained in:
committed by
GitHub
parent
ff8a015981
commit
28d30e9dae
122
.github/workflows/openclaw-release-publish.yml
vendored
122
.github/workflows/openclaw-release-publish.yml
vendored
@@ -1290,7 +1290,7 @@ jobs:
|
||||
|
||||
resolve_openclaw_npm_publish_state() {
|
||||
local artifact_name manifest_dir manifest_path manifest_sha manifest_tarball_sha published_sha published_tarball_path published_tarball_url release_version
|
||||
local canonical_workflow_id resume_branch resume_compare_status resume_conclusion resume_event resume_jobs resume_json resume_path resume_path_trusted resume_sha resume_tag_commit_sha resume_tag_json resume_tag_object_sha resume_tag_verified resume_url resume_workflow_id
|
||||
local resume_state resume_url
|
||||
|
||||
openclaw_npm_already_published="false"
|
||||
if [[ "${PUBLISH_OPENCLAW_NPM}" != "true" ]]; then
|
||||
@@ -1339,59 +1339,12 @@ jobs:
|
||||
echo "openclaw@${release_version} is already published; openclaw_npm_resume_run_id is required to bind postpublish proof to the original workflow identity." >&2
|
||||
exit 1
|
||||
fi
|
||||
resume_json="$(gh api "repos/${GITHUB_REPOSITORY}/actions/runs/${OPENCLAW_NPM_RESUME_RUN_ID}")"
|
||||
canonical_workflow_id="$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/openclaw-npm-release.yml" --jq '.id')"
|
||||
resume_branch="$(printf '%s' "${resume_json}" | jq -er '.head_branch | select(type == "string" and length > 0)')"
|
||||
resume_sha="$(printf '%s' "${resume_json}" | jq -er '.head_sha | select(test("^[a-f0-9]{40}$"))')"
|
||||
resume_conclusion="$(printf '%s' "${resume_json}" | jq -er '.conclusion')"
|
||||
resume_event="$(printf '%s' "${resume_json}" | jq -er '.event')"
|
||||
resume_path="$(printf '%s' "${resume_json}" | jq -er '.path')"
|
||||
resume_workflow_id="$(printf '%s' "${resume_json}" | jq -er '.workflow_id')"
|
||||
resume_url="$(printf '%s' "${resume_json}" | jq -er '.html_url')"
|
||||
if [[ ! "${resume_branch}" =~ ^release-publish/([a-f0-9]{12})-[1-9][0-9]*$ ]]; then
|
||||
echo "OpenClaw npm resume run has an untrusted workflow ref: ${resume_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
resume_path_trusted="false"
|
||||
case "${resume_path}" in
|
||||
".github/workflows/openclaw-npm-release.yml" | \
|
||||
".github/workflows/openclaw-npm-release.yml@${resume_branch}" | \
|
||||
".github/workflows/openclaw-npm-release.yml@refs/tags/${resume_branch}")
|
||||
resume_path_trusted="true"
|
||||
;;
|
||||
esac
|
||||
if [[ "${resume_conclusion}" != "success" ||
|
||||
"${resume_event}" != "workflow_dispatch" ||
|
||||
"${resume_path_trusted}" != "true" ||
|
||||
"${resume_workflow_id}" != "${canonical_workflow_id}" ||
|
||||
"${resume_sha:0:12}" != "${BASH_REMATCH[1]}" ]]; then
|
||||
echo "OpenClaw npm resume run has an untrusted workflow identity: ${resume_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
resume_tag_json="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/tags/${resume_branch}")"
|
||||
resume_tag_object_sha="$(printf '%s' "${resume_tag_json}" | jq -er '.object.sha | select(test("^[a-f0-9]{40}$"))')"
|
||||
if [[ "$(printf '%s' "${resume_tag_json}" | jq -er '.object.type')" != "tag" ]]; then
|
||||
echo "OpenClaw npm resume run tooling ref is not a signed annotated tag: ${resume_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
resume_tag_json="$(gh api "repos/${GITHUB_REPOSITORY}/git/tags/${resume_tag_object_sha}")"
|
||||
resume_tag_commit_sha="$(printf '%s' "${resume_tag_json}" | jq -er '.object.sha | select(test("^[a-f0-9]{40}$"))')"
|
||||
resume_tag_verified="$(printf '%s' "${resume_tag_json}" | jq -er '.verification.verified')"
|
||||
resume_compare_status="$(gh api "repos/${GITHUB_REPOSITORY}/compare/${resume_sha}...main" --jq '.status')"
|
||||
if [[ "$(printf '%s' "${resume_tag_json}" | jq -er '.object.type')" != "commit" ||
|
||||
"${resume_tag_commit_sha}" != "${resume_sha}" ||
|
||||
"${resume_tag_verified}" != "true" ||
|
||||
( "${resume_compare_status}" != "ahead" && "${resume_compare_status}" != "identical" ) ]]; then
|
||||
echo "OpenClaw npm resume run is not bound to a real, main-reachable protected tooling tag: ${resume_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
resume_jobs="$(gh run view "${OPENCLAW_NPM_RESUME_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --json jobs --jq '.jobs')"
|
||||
if ! printf '%s' "${resume_jobs}" | jq -e '.[] | select(.name == "validate_publish_request" and .conclusion == "success")' >/dev/null; then
|
||||
echo "OpenClaw npm resume run lacks successful parent release approval validation: ${resume_url}" >&2
|
||||
exit 1
|
||||
fi
|
||||
openclaw_npm_expected_workflow_ref="refs/tags/${resume_branch}"
|
||||
openclaw_npm_expected_workflow_sha="${resume_sha}"
|
||||
resume_state="$(node "${GITHUB_WORKSPACE}/.release-harness/scripts/openclaw-npm-resume-run.mjs" \
|
||||
--repo "${GITHUB_REPOSITORY}" \
|
||||
--run-id "${OPENCLAW_NPM_RESUME_RUN_ID}")"
|
||||
resume_url="$(printf '%s' "${resume_state}" | jq -er '.url')"
|
||||
openclaw_npm_expected_workflow_ref="$(printf '%s' "${resume_state}" | jq -er '.workflowRef')"
|
||||
openclaw_npm_expected_workflow_sha="$(printf '%s' "${resume_state}" | jq -er '.workflowSha')"
|
||||
|
||||
openclaw_npm_already_published="true"
|
||||
echo "openclaw@${release_version} is already published on npm with this tag's preflight tarball; resuming from ${resume_url}."
|
||||
@@ -1802,65 +1755,8 @@ jobs:
|
||||
release_evidence_zip_trees_match() {
|
||||
local source_path="$1"
|
||||
local existing_path="$2"
|
||||
python3 - "${source_path}" "${existing_path}" <<'PY'
|
||||
import hashlib
|
||||
import stat
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import PurePosixPath
|
||||
|
||||
MAX_ENTRIES = 4096
|
||||
MAX_FILE_BYTES = 64 * 1024 * 1024
|
||||
MAX_TOTAL_BYTES = 256 * 1024 * 1024
|
||||
|
||||
def archive_tree(path):
|
||||
files = {}
|
||||
total_bytes = 0
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
infos = archive.infolist()
|
||||
if len(infos) > MAX_ENTRIES:
|
||||
raise ValueError("too many dependency evidence archive entries")
|
||||
seen = set()
|
||||
for info in infos:
|
||||
name = info.filename
|
||||
pure = PurePosixPath(name)
|
||||
normalized = str(pure) + ("/" if info.is_dir() else "")
|
||||
if (
|
||||
not name
|
||||
or "\\" in name
|
||||
or name != normalized
|
||||
or len(pure.parts) < 2
|
||||
or pure.parts[0] != "dependency-evidence"
|
||||
or any(part in (".", "..") for part in pure.parts)
|
||||
or name in seen
|
||||
):
|
||||
raise ValueError(f"unsafe dependency evidence archive entry: {name!r}")
|
||||
seen.add(name)
|
||||
file_type = stat.S_IFMT(info.external_attr >> 16)
|
||||
allowed_types = (0, stat.S_IFDIR) if info.is_dir() else (0, stat.S_IFREG)
|
||||
if file_type not in allowed_types or info.flag_bits & 0x1:
|
||||
raise ValueError(f"unsupported dependency evidence archive entry: {name!r}")
|
||||
if info.is_dir():
|
||||
continue
|
||||
if info.file_size > MAX_FILE_BYTES:
|
||||
raise ValueError(f"oversized dependency evidence archive entry: {name!r}")
|
||||
total_bytes += info.file_size
|
||||
if total_bytes > MAX_TOTAL_BYTES:
|
||||
raise ValueError("dependency evidence archive exceeds the size limit")
|
||||
digest = hashlib.sha256()
|
||||
with archive.open(info) as source:
|
||||
while chunk := source.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
files[name] = (info.file_size, digest.hexdigest())
|
||||
return files
|
||||
|
||||
try:
|
||||
matches = archive_tree(sys.argv[1]) == archive_tree(sys.argv[2])
|
||||
except (OSError, ValueError, zipfile.BadZipFile, RuntimeError) as error:
|
||||
print(f"dependency evidence ZIP comparison failed: {error}", file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
raise SystemExit(0 if matches else 1)
|
||||
PY
|
||||
python3 "${GITHUB_WORKSPACE}/.release-harness/scripts/compare-release-evidence-zip.py" \
|
||||
"${source_path}" "${existing_path}"
|
||||
}
|
||||
|
||||
attach_or_verify_release_asset() {
|
||||
|
||||
15
.github/workflows/workflow-sanity.yml
vendored
15
.github/workflows/workflow-sanity.yml
vendored
@@ -162,6 +162,19 @@ jobs:
|
||||
- name: Install pre-commit
|
||||
run: python -m pip install --disable-pip-version-check pre-commit==4.2.0
|
||||
|
||||
- name: Install ShellCheck
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
SHELLCHECK_VERSION="0.11.0"
|
||||
archive="shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz"
|
||||
curl --retry 5 --retry-delay 2 --retry-all-errors -sSfL \
|
||||
-o "${archive}" \
|
||||
"https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/${archive}"
|
||||
echo "8c3be12b05d5c177a04c29e3c78ce89ac86f1595681cab149b65b97c4e227198 ${archive}" | sha256sum -c -
|
||||
tar -xJf "${archive}" "shellcheck-v${SHELLCHECK_VERSION}/shellcheck"
|
||||
sudo install -m 0755 "shellcheck-v${SHELLCHECK_VERSION}/shellcheck" /usr/local/bin/shellcheck
|
||||
|
||||
- name: Install actionlint
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -179,7 +192,7 @@ jobs:
|
||||
sudo install -m 0755 actionlint /usr/local/bin/actionlint
|
||||
|
||||
- name: Lint workflows
|
||||
run: actionlint -shellcheck=
|
||||
run: actionlint
|
||||
|
||||
- name: Audit all workflows with zizmor
|
||||
shell: bash
|
||||
|
||||
Reference in New Issue
Block a user