fix(release): keep protected tooling trusted after main moves (#126881)

* fix(release): keep protected tooling trusted after main moves

* fix(release): cover protected tooling recovery paths

* fix(release): honor live tooling contracts

* fix(release): revalidate tooling at npm publish

* fix(release): bind npm publishers to live tooling

* fix(release): preserve trusted dispatch identity

* fix(release): revalidate parent authorization

* fix(release): bind ClawHub to release parent

* docs(release): define frozen tooling identity

* test(release): align ClawHub protected dispatch ref

* fix(release): trust protected plugin npm preflight tooling

* docs(release): scope protected writer guarantees

* fix(release): keep protected tooling foundation npm-only

* test(release): cover trusted npm preflight tooling
This commit is contained in:
Vincent Koc
2026-08-21 00:24:31 -07:00
committed by GitHub
parent b470422371
commit fa86caf94f
28 changed files with 3119 additions and 248 deletions
@@ -12,6 +12,9 @@ WORKFLOW_FILE="full-release-validation.yml"
TARGET_SHA=""
VERIFIER_WORKFLOW_SHA=""
WORKFLOW_REF=""
TRUSTED_WORKFLOW_REF=""
TRUSTED_WORKFLOW_FULL_REF=""
TRUSTED_WORKFLOW_SHA=""
RELEASE_PROFILE=""
RUN_RELEASE_SOAK="false"
INPUTS_JSON=""
@@ -27,6 +30,9 @@ usage() {
cat >&2 <<'EOF'
Usage: find-reusable-release-validation.sh --target-sha <sha> --workflow-sha <sha> \
--workflow-ref <main|release-ci/sha12-timestamp> \
[--trusted-workflow-ref <main|release-publish/sha12-run>] \
[--trusted-workflow-full-ref <refs/heads/main|refs/tags/release-publish/sha12-run>] \
[--trusted-workflow-sha <sha>] \
--release-profile <beta|stable|full> --inputs-json <json> \
[--run-release-soak <true|false>] [--repo <owner/repo>] [--repo-dir <path>] \
[--workflow <file>] [--max-candidates <n>] [--github-output <file>]
@@ -55,6 +61,18 @@ while [[ $# -gt 0 ]]; do
WORKFLOW_REF="${2:-}"
shift 2
;;
--trusted-workflow-ref)
TRUSTED_WORKFLOW_REF="${2:-}"
shift 2
;;
--trusted-workflow-full-ref)
TRUSTED_WORKFLOW_FULL_REF="${2:-}"
shift 2
;;
--trusted-workflow-sha)
TRUSTED_WORKFLOW_SHA="${2:-}"
shift 2
;;
--release-profile)
RELEASE_PROFILE="${2:-}"
shift 2
@@ -124,6 +142,16 @@ if [[ ! "$VERIFIER_WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "Expected --workflow-sha to be a full lowercase commit SHA; got: ${VERIFIER_WORKFLOW_SHA}" >&2
exit 2
fi
TRUSTED_WORKFLOW_REF="${TRUSTED_WORKFLOW_REF:-main}"
TRUSTED_WORKFLOW_FULL_REF="${TRUSTED_WORKFLOW_FULL_REF:-refs/heads/main}"
TRUSTED_WORKFLOW_SHA="${TRUSTED_WORKFLOW_SHA:-${VERIFIER_WORKFLOW_SHA}}"
if [[ ! "$TRUSTED_WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "Expected --trusted-workflow-sha to be a full lowercase commit SHA; got: ${TRUSTED_WORKFLOW_SHA}" >&2
exit 2
fi
if [[ "$TRUSTED_WORKFLOW_SHA" != "$VERIFIER_WORKFLOW_SHA" ]]; then
no_reuse "trusted workflow SHA does not match verifier source SHA"
fi
if [[ "$WORKFLOW_REF" != "main" ]]; then
expected_release_ref="release-ci/${VERIFIER_WORKFLOW_SHA:0:12}-"
if [[ ! "$WORKFLOW_REF" =~ ^release-ci/[0-9a-f]{12}-[1-9][0-9]*$ ]] ||
@@ -149,18 +177,44 @@ if ! expected_inputs="$(jq -Sc 'if type == "object" then . else error("expected
exit 2
fi
workflow_lineage=""
if ! workflow_lineage="$(
gh api "repos/${REPO}/compare/${VERIFIER_WORKFLOW_SHA}...main"
)"; then
no_reuse "could not verify workflow SHA against trusted main"
fi
if ! jq -e \
--arg workflow_sha "$VERIFIER_WORKFLOW_SHA" '
(.status == "ahead" or .status == "identical")
and .merge_base_commit.sha == $workflow_sha
' <<< "$workflow_lineage" >/dev/null; then
no_reuse "workflow SHA is not on trusted main lineage"
trusted_workflow_route=""
if [[ "$TRUSTED_WORKFLOW_REF" == "main" ]]; then
if [[ "$TRUSTED_WORKFLOW_FULL_REF" != "refs/heads/main" ]]; then
no_reuse "trusted main workflow full ref is invalid"
fi
workflow_lineage=""
if ! workflow_lineage="$(
gh api "repos/${REPO}/compare/${TRUSTED_WORKFLOW_SHA}...main"
)"; then
no_reuse "could not verify workflow SHA against trusted main"
fi
if ! jq -e \
--arg workflow_sha "$TRUSTED_WORKFLOW_SHA" '
(.status == "ahead" or .status == "identical")
and .merge_base_commit.sha == $workflow_sha
' <<< "$workflow_lineage" >/dev/null; then
no_reuse "workflow SHA is not on trusted main lineage"
fi
trusted_workflow_route="main"
elif [[ "$TRUSTED_WORKFLOW_REF" =~ ^release-publish/([0-9a-f]{12})-[1-9][0-9]*$ ]] &&
[[ "$TRUSTED_WORKFLOW_FULL_REF" == "refs/tags/${TRUSTED_WORKFLOW_REF}" ]] &&
[[ "$TRUSTED_WORKFLOW_REF" == "release-publish/${TRUSTED_WORKFLOW_SHA:0:12}-"* ]]; then
trusted_tag_json=""
if ! trusted_tag_json="$(
gh api "repos/${REPO}/git/ref/tags/${TRUSTED_WORKFLOW_REF}"
)"; then
no_reuse "could not verify protected trusted workflow tag"
fi
if ! jq -e \
--arg workflow_sha "$TRUSTED_WORKFLOW_SHA" '
.object.type == "commit"
and .object.sha == $workflow_sha
' <<< "$trusted_tag_json" >/dev/null; then
no_reuse "protected trusted workflow tag moved or is not lightweight"
fi
trusted_workflow_route="protected-tag"
else
no_reuse "trusted workflow identity is not main or an exact protected tag"
fi
# Exact-target reuse still requires internally consistent version stamps
@@ -190,7 +244,9 @@ for ((index = 0; index < run_count; index += 1)); do
node "$VALIDATOR" \
--validate-run "$run_id" \
--repo "$REPO" \
--trusted-workflow-ref main \
--trusted-workflow-ref "$TRUSTED_WORKFLOW_REF" \
--trusted-workflow-full-ref "$TRUSTED_WORKFLOW_FULL_REF" \
--trusted-workflow-sha "$TRUSTED_WORKFLOW_SHA" \
--verifier-source-sha "$VERIFIER_WORKFLOW_SHA" \
--verifier-source-file "$VALIDATOR" \
--json
@@ -217,14 +273,17 @@ for ((index = 0; index < run_count; index += 1)); do
if ! jq -e \
--arg repo "$REPO" \
--arg run_id "$run_id" \
--arg trusted_workflow_full_ref "$TRUSTED_WORKFLOW_FULL_REF" \
--arg trusted_workflow_ref "$TRUSTED_WORKFLOW_REF" \
--arg trusted_workflow_route "$trusted_workflow_route" \
--arg verifier_sha "$VERIFIER_WORKFLOW_SHA" '
. as $record
| .schema == "openclaw.release-validation-evidence/v3"
and .valid == true
and .repository == $repo
and .producerOnTrustedMainLineage == true
and .trustedWorkflowRef == "main"
and .trustedWorkflowFullRef == "refs/heads/main"
and .producerOnTrustedMainLineage == ($trusted_workflow_route == "main")
and .trustedWorkflowRef == $trusted_workflow_ref
and .trustedWorkflowFullRef == $trusted_workflow_full_ref
and .directRoot == true
and .evidenceReuse == null
and .rerunGroup == "all"
@@ -239,7 +298,7 @@ for ((index = 0; index < run_count; index += 1)); do
and (.root.artifact.digest | type == "string" and test("^sha256:[0-9a-f]{64}$"))
and all($record.current, $record.root;
. as $parent
| .producerOnTrustedMainLineage == true
| .producerOnTrustedMainLineage == ($trusted_workflow_route == "main")
and .workflowRefType == "branch"
and .workflowPath == ".github/workflows/full-release-validation.yml"
and .workflowFullRef == ("refs/heads/" + .workflowRef)
@@ -249,24 +308,31 @@ for ((index = 0; index < run_count; index += 1)); do
.workflowRunPath == ".github/workflows/full-release-validation.yml"
or .workflowRunPath == .workflowQualifiedPath
)
and (
and if $trusted_workflow_route == "main" then
(
.workflowRef == "main"
and (
(.manifestVersion == 3 and .workflowRefProof == "manifest-v3-branch")
or (
.manifestVersion == 2
and .workflowRefProof == "legacy-v2-main-ancestry"
(
.workflowRef == "main"
and (
(.manifestVersion == 3 and .workflowRefProof == "manifest-v3-branch")
or (
.manifestVersion == 2
and .workflowRefProof == "legacy-v2-main-ancestry"
)
)
)
or (
.manifestVersion == 3
and .workflowRefProof == "manifest-v3-sha-pinned-main-ancestry"
and (.workflowRef | test("^release-ci/[0-9a-f]{12}-[1-9][0-9]*$"))
and (.workflowRef | startswith("release-ci/\($parent.workflowSha[0:12])-"))
)
)
or (
.manifestVersion == 3
and .workflowRefProof == "manifest-v3-sha-pinned-main-ancestry"
and (.workflowRef | test("^release-ci/[0-9a-f]{12}-[1-9][0-9]*$"))
and (.workflowRef | startswith("release-ci/\($parent.workflowSha[0:12])-"))
)
)
else
.manifestVersion == 3
and .workflowRefProof == "manifest-v3-protected-tag-exact-sha"
and (.workflowRef | test("^release-ci/[0-9a-f]{12}-[1-9][0-9]*$"))
and (.workflowRef | startswith("release-ci/\($parent.workflowSha[0:12])-"))
end
)
and (.verifier.schemaVersion == 3)
and (.verifier.sourceSha == $verifier_sha)