mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
fix(release): tag immutable desktop candidates (#4811)
## 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>
This commit is contained in:
@@ -64,69 +64,85 @@ grep -q 'permission-contents: write' "$auto_tag"
|
||||
grep -q 'GH_TOKEN:.*steps\.release-tagger\.outputs\.token' "$auto_tag"
|
||||
grep -Fq 'git/refs' "$auto_tag"
|
||||
grep -Fq 'TAG_PREFIX="desktop-v"' "$auto_tag"
|
||||
grep -Fq 'target_sha=${{ github.event.pull_request.merge_commit_sha }}' "$auto_tag"
|
||||
grep -Fq 'target_sha=${{ github.event.pull_request.head.sha }}' "$auto_tag"
|
||||
grep -Fq 'scripts/verify-desktop-release-merge.sh' "$auto_tag"
|
||||
grep -Fq 'current \`main\`' "$repo_root/scripts/prepare-desktop-release.sh"
|
||||
candidate_workflow="$repo_root/.github/workflows/desktop-release-candidate.yml"
|
||||
grep -Eq '^ pull-requests: read$' "$candidate_workflow" || {
|
||||
echo "desktop candidate token cannot read pull requests for prior-release lookup" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -Fq 'GH_TOKEN: ${{ github.token }}' "$candidate_workflow" || {
|
||||
echo "desktop candidate validation has no GitHub token for prior-release lookup" >&2
|
||||
exit 1
|
||||
}
|
||||
grep -Fq 'reviewed candidate' "$repo_root/scripts/prepare-desktop-release.sh"
|
||||
if grep -Fq 'current `main`' "$repo_root/scripts/prepare-desktop-release.sh"; then
|
||||
echo "desktop release PR body contains executable command substitution" >&2
|
||||
exit 1
|
||||
fi
|
||||
"$repo_root/scripts/test-desktop-release-authorization.sh"
|
||||
if rg -q 'rule-suites|desktop-release-bypass-authorized|MERGED_BY' \
|
||||
"$repo_root/scripts/verify-desktop-release-merge.sh" \
|
||||
"$repo_root/scripts/verify-desktop-release-authorization.sh" \
|
||||
"$auto_tag"; then
|
||||
echo "desktop auto-tag still depends on unavailable rule-suite authorization" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
review_filter="$repo_root/scripts/review-decision-approved.jq"
|
||||
for fixture in \
|
||||
'{"reviewDecision":"CHANGES_REQUESTED"}' \
|
||||
'{"reviewDecision":"REVIEW_REQUIRED"}' \
|
||||
'{"reviewDecision":null}' \
|
||||
'{}'; do
|
||||
if jq -e -f "$review_filter" <<<"$fixture" >/dev/null; then
|
||||
echo "review-decision filter accepted non-approved fixture: $fixture" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
jq -e -f "$review_filter" >/dev/null <<'JSON' || {
|
||||
{"reviewDecision":"APPROVED"}
|
||||
JSON
|
||||
echo "review-decision filter rejected approved GraphQL response" >&2
|
||||
exit 1
|
||||
}
|
||||
required_check_filter="$repo_root/scripts/required-check-succeeded.jq"
|
||||
check_fixture() {
|
||||
local expected="$1" conclusion="$2" status="${3:-completed}"
|
||||
local payload
|
||||
payload=$(jq -n --arg status "$status" --arg conclusion "$conclusion" '{check_runs: [{name: "Web", status: $status, conclusion: $conclusion, started_at: "2026-01-01T00:00:00Z"}]}')
|
||||
if jq -e --arg name Web -f "$required_check_filter" <<<"[$payload]" >/dev/null; then
|
||||
actual=pass
|
||||
else
|
||||
actual=fail
|
||||
fi
|
||||
[[ "$actual" == "$expected" ]] || {
|
||||
echo "required-check filter: expected $conclusion/$status to $expected" >&2
|
||||
exit 1
|
||||
}
|
||||
local expected="$1" conclusion="$2" app="${3:-15368}" completed="${4:-2026-01-01T00:00:00Z}"
|
||||
local payload actual
|
||||
# Production-shaped REST check run: notably, there is no created_at field.
|
||||
payload=$(jq -n --arg conclusion "$conclusion" --argjson app "$app" --arg completed "$completed" \
|
||||
'{check_runs: [{id: 100, check_suite: {id: 10}, name: "Web", app: {id: $app}, status: "completed", conclusion: $conclusion, started_at: "2026-01-01T00:00:00Z", completed_at: $completed}]}')
|
||||
if jq -e --arg name Web --argjson integration_id 15368 \
|
||||
--arg merged_at 2026-01-02T00:00:00Z \
|
||||
-f "$required_check_filter" <<<"[$payload]" >/dev/null; then actual=pass; else actual=fail; fi
|
||||
[[ "$actual" == "$expected" ]] || { echo "required-check fixture expected $expected, got $actual" >&2; exit 1; }
|
||||
}
|
||||
check_fixture pass success
|
||||
check_fixture pass skipped
|
||||
check_fixture pass neutral
|
||||
check_fixture fail failure
|
||||
check_fixture fail success in_progress
|
||||
# A newer failure must not be hidden by an older successful run of the same check.
|
||||
jq -e --arg name Web -f "$required_check_filter" >/dev/null <<'JSON' && {
|
||||
check_fixture fail success 999
|
||||
check_fixture fail success 15368 2026-01-03T00:00:00Z
|
||||
|
||||
# filter=latest may still return multiple same-name runs from distinct workflows.
|
||||
# Highest immutable run ID is authoritative and must not reveal stale green.
|
||||
jq -e --arg name Web --argjson integration_id 15368 --arg merged_at 2026-01-02T00:00:00Z \
|
||||
-f "$required_check_filter" >/dev/null <<'JSON' && {
|
||||
[{"check_runs":[
|
||||
{"name":"Web","status":"completed","conclusion":"success","started_at":"2026-01-01T00:00:00Z"},
|
||||
{"name":"Web","status":"completed","conclusion":"failure","started_at":"2026-01-02T00:00:00Z"}
|
||||
{"id":100,"check_suite":{"id":10},"name":"Web","app":{"id":15368},"status":"completed","conclusion":"success","started_at":"2026-01-01T00:00:00Z","completed_at":"2026-01-01T01:00:00Z"},
|
||||
{"id":101,"check_suite":{"id":11},"name":"Web","app":{"id":15368},"status":"in_progress","conclusion":null,"started_at":"2026-01-01T23:59:00Z","completed_at":null}
|
||||
]}]
|
||||
JSON
|
||||
echo "required-check filter accepted a stale pass over a newer failure" >&2
|
||||
exit 1
|
||||
echo "required-check filter hid the highest-ID pending attempt" >&2; exit 1;
|
||||
}
|
||||
# A post-merge rerun is indistinguishable from other latest attempts and fails closed.
|
||||
jq -e --arg name Web --argjson integration_id 15368 --arg merged_at 2026-01-02T00:00:00Z \
|
||||
-f "$required_check_filter" >/dev/null <<'JSON' && {
|
||||
[{"check_runs":[
|
||||
{"id":100,"check_suite":{"id":10},"name":"Web","app":{"id":15368},"status":"completed","conclusion":"success","started_at":"2026-01-01T00:00:00Z","completed_at":"2026-01-01T01:00:00Z"},
|
||||
{"id":101,"check_suite":{"id":10},"name":"Web","app":{"id":15368},"status":"completed","conclusion":"failure","started_at":"2026-01-02T00:01:00Z","completed_at":"2026-01-02T00:10:00Z"}
|
||||
]}]
|
||||
JSON
|
||||
echo "required-check filter accepted stale success after post-merge rerun" >&2; exit 1;
|
||||
}
|
||||
# DCO alone may complete just after merge, inside its explicit five-minute bound.
|
||||
dco_fixture() {
|
||||
local expected="$1" completed="$2" actual
|
||||
if jq -e --arg name "DCO Check" --argjson integration_id 1455659 --arg merged_at 2026-01-02T00:00:00Z \
|
||||
-f "$required_check_filter" >/dev/null <<JSON
|
||||
[{"check_runs":[{"id":200,"check_suite":{"id":20},"name":"DCO Check","app":{"id":1455659},"status":"completed","conclusion":"success","started_at":"$completed","completed_at":"$completed"}]}]
|
||||
JSON
|
||||
then actual=pass; else actual=fail; fi
|
||||
[[ "$actual" == "$expected" ]] || { echo "DCO completion $completed expected $expected" >&2; exit 1; }
|
||||
}
|
||||
dco_fixture pass 2026-01-02T00:04:59Z
|
||||
dco_fixture fail 2026-01-02T00:05:01Z
|
||||
|
||||
# The verifier must request production endpoint semantics and pin helpers before checkout.
|
||||
verify_merge="$repo_root/scripts/verify-desktop-release-merge.sh"
|
||||
grep -Fq 'check-runs?filter=latest&per_page=100' "$verify_merge"
|
||||
grep -Fq 'git fetch origin main --no-tags' "$verify_merge"
|
||||
grep -Fq 'git merge-base --is-ancestor "$candidate_parents" origin/main' "$verify_merge"
|
||||
grep -Fq 'git show "$candidate_parents:scripts/desktop_release.py"' "$verify_merge"
|
||||
grep -Fq 'git show "$candidate_parents:scripts/required-check-succeeded.jq"' "$verify_merge"
|
||||
grep -Fq 'DESKTOP_RELEASE_ROOT="$PWD" python3 "$verifier_dir/desktop_release.py"' "$verify_merge"
|
||||
grep -Fq -- '-f "$verifier_dir/required-check-succeeded.jq"' "$verify_merge"
|
||||
|
||||
release_workflow="$repo_root/.github/workflows/release.yml"
|
||||
[[ "$(grep -c 'contents: write' "$release_workflow")" -eq 1 ]] || {
|
||||
echo "desktop release must have exactly one GitHub contents writer" >&2; exit 1;
|
||||
|
||||
Reference in New Issue
Block a user