mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Summary Redesign the permanent Desktop release flow so unrelated merges to `main` cannot invalidate an already reviewed, green release candidate. - Tag the immutable, API-confirmed release PR head instead of its later squash commit. - Treat the merged PR—including an authorized owner/admin bypass—as publication authorization, while requiring trusted check evidence that was complete at merge time. - Make tag creation idempotent and collision-safe: an existing tag succeeds only at the exact candidate SHA, and create races refetch before accepting equality. - Replace ancestry-based previous-release discovery with a validated metadata ledger for side-history candidate tags. - Compute the next release from the prior frozen base to the new frozen base, excluding only the prior release squash SHA so unrelated commits remain in the changelog. - Preserve schema-1 production-tag migration and reject malformed metadata or equal/decreasing versions. - Update operator documentation for the normal squash-merge workflow. This is the reusable release process for `0.5.6` onward, not the retired one-shot `0.5.5` recovery path. ### Invariants covered - Candidate creation → unrelated `main` merge → authorized squash merge → immutable candidate tag. - Trusted producer IDs and merge-time completion timestamps; DCO's bounded post-merge exception remains isolated. - Missing/spoofed checks, tampered candidates, ambiguous PR associations, conflicting tags, and equal/decreasing versions fail closed. - Same-SHA retries succeed; different-SHA collisions fail. - Legacy schema-1 tag-on-main migration and schema-2 side-history accounting both preserve the correct next-release changelog. ### Related issue N/A — follows the Desktop release failures in #4788 and #4800 and the recovery revert in #4808. ### Testing At clean commit `6a91fbed8147a48cf174997de0c3e4cb2fb26474`: - `scripts/test-desktop-release-candidate.sh` - `scripts/test-release-ref-contract.sh` Both focused suites passed with HEAD unchanged. Princess Donut cleared the security/provenance surface, including the hostile merge-time timestamp cases. Mongo cleared the side-history ledger, migration, version-order, documentation, and contract-test surface. --------- Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
87 lines
3.6 KiB
Bash
Executable File
87 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
: "${PR_HEAD_SHA:?}"
|
|
: "${MERGE_SHA:?}"
|
|
: "${MERGED_AT:?}"
|
|
: "${VERSION:?}"
|
|
: "${PR_NUMBER:?}"
|
|
: "${GH_TOKEN:?}"
|
|
|
|
# Keep this list aligned with the main ruleset. Producer IDs prevent a check
|
|
# with a copied display name from authorizing a release. Every current required
|
|
# gate is a check run; add explicit legacy-status verification before introducing
|
|
# any required context that reports only through the commit-status API.
|
|
required_checks=(
|
|
"Desktop E2E Integration:15368"
|
|
"Desktop:15368"
|
|
"Rust Lint:15368"
|
|
"Security:15368"
|
|
"Unit Tests:15368"
|
|
"Windows Rust (x86_64-pc-windows-msvc):15368"
|
|
"Mobile:15368"
|
|
"Web:15368"
|
|
"Backend Integration (relay e2e):15368"
|
|
"Desktop E2E Relay:15368"
|
|
"Relay E2E:15368"
|
|
"Desktop Build (macOS):15368"
|
|
"DCO Check:1455659"
|
|
"Desktop Release Candidate:15368"
|
|
)
|
|
|
|
expected_branch="version-bump/$VERSION"
|
|
[[ "${PR_HEAD_REF:-}" == "$expected_branch" ]] || { echo "unexpected release branch" >&2; exit 1; }
|
|
[[ "${PR_BASE_REF:-}" == main ]] || { echo "desktop release must target main" >&2; exit 1; }
|
|
[[ "${PR_HEAD_REPO:-}" == "$GITHUB_REPOSITORY" ]] || { echo "desktop release must be internal" >&2; exit 1; }
|
|
|
|
# The API identity must match the closed event. Branch names are mutable and are
|
|
# never used to resolve the artifact.
|
|
pr="$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER")"
|
|
jq -e \
|
|
--arg head "$PR_HEAD_SHA" --arg head_ref "$PR_HEAD_REF" --arg head_repo "$PR_HEAD_REPO" \
|
|
--arg base "$PR_BASE_REF" --arg merge "$MERGE_SHA" --arg merged_at "$MERGED_AT" \
|
|
'.merged == true and .head.sha == $head and .head.ref == $head_ref and
|
|
.head.repo.full_name == $head_repo and .base.ref == $base and
|
|
.merge_commit_sha == $merge and .merged_at == $merged_at' <<<"$pr" >/dev/null || {
|
|
echo "pull request API identity does not match the closed merge event" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Pin trusted verifier code from the candidate's frozen base, not from the
|
|
# candidate or its squash. A release PR cannot alter the code that validates it.
|
|
git fetch origin main --no-tags
|
|
git fetch origin "$PR_HEAD_SHA" --no-tags
|
|
candidate_parents="$(git show -s --format=%P "$PR_HEAD_SHA")"
|
|
[[ "$candidate_parents" =~ ^[0-9a-f]{40}$ ]] || {
|
|
echo "desktop candidate must have exactly one parent before validation" >&2
|
|
exit 1
|
|
}
|
|
git merge-base --is-ancestor "$candidate_parents" origin/main || {
|
|
echo "desktop candidate base is not protected main history" >&2
|
|
exit 1
|
|
}
|
|
verifier_dir="$(mktemp -d)"
|
|
trap 'rm -rf "$verifier_dir"' EXIT
|
|
git show "$candidate_parents:scripts/desktop_release.py" > "$verifier_dir/desktop_release.py"
|
|
git show "$candidate_parents:scripts/required-check-succeeded.jq" > "$verifier_dir/required-check-succeeded.jq"
|
|
|
|
git checkout --detach "$PR_HEAD_SHA"
|
|
DESKTOP_RELEASE_ROOT="$PWD" python3 "$verifier_dir/desktop_release.py" \
|
|
validate --candidate "$PR_HEAD_SHA" --version "$VERSION" --repo "$GITHUB_REPOSITORY"
|
|
|
|
# `filter=latest` is deliberate: GitHub exposes no per-rerun creation time. A
|
|
# post-merge rerun replaces the visible attempt and fails closed below.
|
|
checks="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/commits/$PR_HEAD_SHA/check-runs?filter=latest&per_page=100")"
|
|
for entry in "${required_checks[@]}"; do
|
|
required="${entry%:*}"
|
|
integration_id="${entry##*:}"
|
|
jq -e --arg name "$required" --argjson integration_id "$integration_id" \
|
|
--arg merged_at "$MERGED_AT" \
|
|
-f "$verifier_dir/required-check-succeeded.jq" <<<"$checks" >/dev/null || {
|
|
echo "trusted required check was not successful at merge: $required" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
|
|
echo "verified immutable desktop candidate $PR_HEAD_SHA authorized by merged PR $PR_NUMBER"
|