diff --git a/docs/CONTRIBUTING.org b/docs/CONTRIBUTING.org index 868e9c1..a8db717 100644 --- a/docs/CONTRIBUTING.org +++ b/docs/CONTRIBUTING.org @@ -94,6 +94,17 @@ ln -sf ../../scripts/pre-commit-repl-check .git/hooks/pre-commit #+end_src Runs automatically on ~git commit~. +* Pre-Push Hook (Release Guard) + +Blocks tag pushes (~git push --tags~) without a release token: +#+begin_src bash +ln -sf ../../scripts/pre-push-release-guard .git/hooks/pre-push +#+end_src +To authorize a release: ~touch /tmp/passepartout-release-approved~ +The token is consumed on first successful push. This prevents automated +release tagging without human approval — a hard enforcement of the +AGENTS.md release-permission rule. + * Testing Tools ** TUI REPL (~/eval~) diff --git a/scripts/pre-push-release-guard b/scripts/pre-push-release-guard new file mode 100755 index 0000000..5372be2 --- /dev/null +++ b/scripts/pre-push-release-guard @@ -0,0 +1,34 @@ +#!/bin/bash +# Pre-push hook: block tag pushes without release token. +# Tag pushes are blocked unless /tmp/passepartout-release-approved exists. +# The token is consumed (deleted) on first successful push. +# +# Install: +# ln -sf ../../scripts/pre-push-release-guard .git/hooks/pre-push +# +# Returns 0 (pass) or 1 (blocked). + +set -euo pipefail + +BLOCKED=0 + +while read -r local_ref local_oid remote_ref remote_oid; do + case "$remote_ref" in + refs/tags/*) + if [ ! -f /tmp/passepartout-release-approved ]; then + echo "" >&2 + echo "============================================================" >&2 + echo " BLOCKED: tag push requires release token" >&2 + echo " Only the user may authorize a release." >&2 + echo " To grant permission: touch /tmp/passepartout-release-approved" >&2 + echo "============================================================" >&2 + echo "" >&2 + BLOCKED=1 + else + rm /tmp/passepartout-release-approved + fi + ;; + esac +done + +exit $BLOCKED