diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 29ce7b6f..382a1986 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -16,5 +16,21 @@ tagging, run `bash tools/changelog_fragments.sh cut `, whi section, bumps `VERSION` and deletes the fragments, and commit that as the cut PR (`changes/README.md`). Check `git tag` for published versions. The front-door docs do not carry a copied latest-version line. -Homebrew tap: github.com/InauguralSystems/homebrew-eigenscript (tracks the -latest release). +The Homebrew tap at github.com/InauguralSystems/homebrew-eigenscript must +track the latest release automatically. Its `bump-formula.yml` workflow runs +on a schedule and can also be started with `workflow_dispatch`. It reads the latest +EigenScript release from GitHub's public API, downloads that tag's source +tarball, computes the formula's `sha256` from the downloaded bytes, and opens a +formula-bump PR when the formula is behind. Do not copy a checksum into the +formula by hand and do not add a cross-repository credential to this release +workflow. + +After a release, look in the tap's pull requests for the automated formula +bump and its `brew test-bot` result. GitHub may require a maintainer to approve +the test-bot runs on a PR created with `GITHUB_TOKEN`; approve those runs on +the generated PR before waiting for its formula checks. A manual dispatch of +the test-bot runs only setup/syntax checks and does not replace PR formula checks. +A red PR is repaired in the tap; do not +paper over it by declaring the release complete here. If no PR appears after +the scheduled run, dispatch `bump-formula.yml` in the tap and inspect that +workflow's log. diff --git a/changes/documentation/1297-homebrew-tap-automation.md b/changes/documentation/1297-homebrew-tap-automation.md new file mode 100644 index 00000000..8a488237 --- /dev/null +++ b/changes/documentation/1297-homebrew-tap-automation.md @@ -0,0 +1,2 @@ +- Document the Homebrew tap's automatic formula-bump workflow, including its + computed checksum, test-bot gate, and manual recovery path. diff --git a/tools/release_skill_check.sh b/tools/release_skill_check.sh new file mode 100755 index 00000000..4cc0f853 --- /dev/null +++ b/tools/release_skill_check.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Keep the release runbook explicit about the independently automated tap. +set -eu +cd "$(dirname "$0")/.." + +check_file() { + file=$1 + failed=0 + for phrase in \ + 'track the latest release automatically' \ + 'bump-formula.yml' \ + 'workflow_dispatch' \ + "GitHub's public API" \ + 'computes the formula' \ + 'sha256' \ + 'brew test-bot' \ + 'dispatch `bump-formula.yml`' + do + if ! grep -Fq "$phrase" "$file"; then + echo "release_skill_check: RED: missing required guidance: $phrase" + failed=1 + fi + done + return "$failed" +} + +if [ "${1:-}" = --selftest ]; then + # Check the actual runbook before requiring an edited copy to fail. + # Otherwise a pre-existing omission makes the negative control vacuous. + check_file .claude/skills/release/SKILL.md + scratch=$(mktemp "${TMPDIR:-/tmp}/release-skill.XXXXXX") + trap 'rm -f "$scratch"' EXIT HUP INT TERM + cp .claude/skills/release/SKILL.md "$scratch" + sed 's/brew test-bot/brew verification/' "$scratch" > "$scratch.mutant" + mv "$scratch.mutant" "$scratch" + if check_file "$scratch" > /dev/null 2>&1; then + echo 'release_skill_check selftest: RED: missing test-bot guidance was accepted' + exit 1 + fi + echo 'release_skill_check selftest: PASS: missing test-bot guidance rejected' + exit 0 +fi + +check_file "${1:-.claude/skills/release/SKILL.md}" +echo 'release_skill_check: PASS' diff --git a/tools/selftests.txt b/tools/selftests.txt index 099c9f0f..eb602c2f 100644 --- a/tools/selftests.txt +++ b/tools/selftests.txt @@ -35,6 +35,7 @@ tools/obs_marker_check.sh | timeout 30 bash tools/obs_marker_check.sh --selftest tools/obs_reader_sync_check.sh tools/obs_marker_check.sh | timeout 30 bash tools/obs_reader_sync_check.sh --selftest | [{"pattern":"^SELFTEST: ([0-9]+) passed.*$","count":11,"floor":true,"value":true}] tools/performance_observer_docs.py docs/PERFORMANCE.md bench/observer_callgrind.txt bench/baseline.txt bench/observed_loop.eigs bench/unobserved_loop.eigs bench/conservative_observed_loop.eigs bench/conservative_unobserved_loop.eigs | timeout 30 python3 tools/performance_observer_docs.py --selftest tools/replay_diff.sh | timeout 30 bash tools/replay_diff.sh --selftest | [{"pattern":" selftest ok:","count":11},{"pattern":"^SELFTEST: all planted faults caught","count":1}] +tools/release_skill_check.sh .claude/skills/release/SKILL.md | timeout 30 bash tools/release_skill_check.sh --selftest | [{"pattern":"^release_skill_check selftest: PASS:","count":1}] tools/section_plan.sh tests/suite_plan.sh .github/workflows/ci.yml | timeout 1860 bash tools/section_plan.sh --selftest tools/section_fragments_check.sh tools/runner_text.sh tools/section_plan.sh tools/suite_label_check.sh tools/enrolment_check.sh tools/child_exit_check.sh tests/run_all_tests.sh tests/sections/* | timeout 60 bash tools/section_fragments_check.sh --selftest tools/stop_gate.sh | timeout 30 bash tools/stop_gate.sh --selftest | [{"pattern":"^stop_gate selftest: committed-fragment PASS; no-fragment BLOCK$","count":1}]