Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ scripts/update-homebrew.sh vX.Y.Z

That helper redownloads and re-verifies the published release, requires the exact successful published verifier and both native jobs, and rejects any verifier run that did not begin strictly later than the current release and every current asset's last update. Immediately before its sole tap mutation, it repeats those checks and byte-compares every proof and asset. It renders `Formula/remindctl.rb` from the current tap blob using the locally verified archive SHA-256, updates that exact blob atomically, updates Homebrew, checks the canonical formula homepage/URL/version/checksum, installs or upgrades, runs `brew test`, and repeats the exact signed-binary verification against the installed binary.

The published verifier accepts GitHub's bare `.github/workflows/release.yml` run path or the same path qualified with `@main`. It independently requires `head_branch` to be `main` and `head_sha` to equal the release's exact source commit; a path alone is never branch or source proof.

## Closeout

- Confirm GitHub Release notes match the finalized changelog section and exactly three assets exist.
Expand Down
9 changes: 7 additions & 2 deletions scripts/require-published-verifier.sh
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ for page in run_pages:
else:
raise SystemExit("malformed workflow-run response")
candidates = []
expected_workflow_path = f".github/workflows/release.yml@{default_branch}"
# REST workflow runs use a bare path; also accept the default-branch-qualified
# form. The branch and exact source commit are checked independently below.
expected_workflow_paths = {
".github/workflows/release.yml",
f".github/workflows/release.yml@{default_branch}",
}
for run in runs:
started_raw = run.get("run_started_at")
if not isinstance(started_raw, str):
Expand All @@ -100,7 +105,7 @@ for run in runs:
run.get("event") == "workflow_dispatch"
and run.get("head_sha") == source_commit
and run.get("head_branch") == default_branch
and run.get("path") == expected_workflow_path
and run.get("path") in expected_workflow_paths
and run.get("display_title") == f"verify published {tag}"
and run.get("status") == "completed"
and run.get("conclusion") == "success"
Expand Down
32 changes: 23 additions & 9 deletions scripts/test-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -209,12 +209,18 @@ PY
exit 0
fi
if [[ "$*" == *"actions/workflows/release.yml/runs"* ]]; then
workflow_path=".github/workflows/release.yml@main"
if [[ "${GH_MODE:-valid}" == "bare-workflow-path" ]]; then
workflow_path=".github/workflows/release.yml"
fi
printf '[{"workflow_runs":[{"id":456,"event":"workflow_dispatch","head_branch":"main","head_sha":"%s","path":"%s","display_title":"verify published %s","status":"completed","conclusion":"success","run_started_at":"2026-07-09T12:01:00Z","html_url":"https://example.invalid/run/456"}]}]\n' \
"${MOCK_SOURCE_COMMIT:?}" "$workflow_path" "${MOCK_TAG:-v0.3.3}"
workflow_path=".github/workflows/release.yml"
workflow_branch="main"
workflow_commit="${MOCK_SOURCE_COMMIT:?}"
case "${GH_MODE:-valid}" in
qualified-workflow-path) workflow_path=".github/workflows/release.yml@main" ;;
wrong-workflow-path) workflow_path=".github/workflows/other.yml" ;;
wrong-workflow-qualifier) workflow_path=".github/workflows/release.yml@feature" ;;
wrong-workflow-branch) workflow_branch="feature" ;;
wrong-workflow-commit) workflow_commit="ffffffffffffffffffffffffffffffffffffffff" ;;
esac
printf '[{"workflow_runs":[{"id":456,"event":"workflow_dispatch","head_branch":"%s","head_sha":"%s","path":"%s","display_title":"verify published %s","status":"completed","conclusion":"success","run_started_at":"2026-07-09T12:01:00Z","html_url":"https://example.invalid/run/456"}]}]\n' \
"$workflow_branch" "$workflow_commit" "$workflow_path" "${MOCK_TAG:-v0.3.3}"
exit 0
fi
if [[ "$*" == *"actions/runs/456/jobs"* ]]; then
Expand Down Expand Up @@ -584,12 +590,20 @@ expect_failure "missing native Intel verifier" env \
MOCK_RELEASE_BODY_FILE="$mock_release_notes" MOCK_ASSET_DIR="$mock_api_assets" \
MOCK_SOURCE_COMMIT="$source_commit" MOCK_TAG_OBJECT="$tag_object" \
"$ROOT/scripts/require-published-verifier.sh" "$TAG"
expect_failure "unqualified published verifier workflow path" env \
GH_BIN="$work/bin/mock-gh" GH_MODE=bare-workflow-path MOCK_RELEASE_STATE=published \
env \
GH_BIN="$work/bin/mock-gh" GH_MODE=qualified-workflow-path MOCK_RELEASE_STATE=published \
MOCK_TAG="$TAG" MOCK_VERSION="$MARKETING_VERSION" \
MOCK_RELEASE_BODY_FILE="$mock_release_notes" MOCK_ASSET_DIR="$mock_api_assets" \
MOCK_SOURCE_COMMIT="$source_commit" MOCK_TAG_OBJECT="$tag_object" \
"$ROOT/scripts/require-published-verifier.sh" "$TAG"
"$ROOT/scripts/require-published-verifier.sh" "$TAG" >/dev/null
for invalid_mode in wrong-workflow-path wrong-workflow-qualifier wrong-workflow-branch wrong-workflow-commit; do
expect_failure "$invalid_mode published verifier" env \
GH_BIN="$work/bin/mock-gh" GH_MODE="$invalid_mode" MOCK_RELEASE_STATE=published \
MOCK_TAG="$TAG" MOCK_VERSION="$MARKETING_VERSION" \
MOCK_RELEASE_BODY_FILE="$mock_release_notes" MOCK_ASSET_DIR="$mock_api_assets" \
MOCK_SOURCE_COMMIT="$source_commit" MOCK_TAG_OBJECT="$tag_object" \
"$ROOT/scripts/require-published-verifier.sh" "$TAG"
done
expect_failure "stale published verifier after asset replacement" env \
GH_BIN="$work/bin/mock-gh" GH_MODE=stale-assets MOCK_RELEASE_STATE=published \
MOCK_TAG="$TAG" MOCK_VERSION="$MARKETING_VERSION" \
Expand Down