ceremony-ci-probe/test/release-chain.test.sh
claude-bot-andresmgsl ad8ebfe2c9 feat: the reusable release workflow — both doors, one implementation
Closes #9. .github/workflows/release.yml (workflow_call) replaces box's,
rig's and cast's release.yml: the merge door (facts -> decide -> notes ->
tag+publish+bump, every decision in a tested lib script) and the tag door
(the manual fallback and backfill, no bump). Plus lib/facts.sh (the merge
door's impure half, contract-tested against fixture repos with a stubbed
gh), the self-ref pin guard (.github/scripts/self-ref-check.sh + CI step +
tests), the release-exercise scratch caller (dry wiring), the end-to-end
script-chain rehearsal, and the caller + artifact-hook contracts in
docs/CONSUMERS.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 19:55:39 +00:00

128 lines
4.3 KiB
Bash

#!/usr/bin/env bash
# The merge door's script chain, composed end-to-end (issue #9): facts →
# decide → notes against a constructed fixture ceremony, exactly the way
# release.yml wires them (facts' $GITHUB_OUTPUT lines become decide's env;
# the notes come from the one canonical extractor). facts.test.sh proves the
# fact rows and decide's own suite proves the table; this file proves the
# HANDOFF between them. Also run by release-exercise.yml on dispatch. set
# -u, not -e: failing commands are behavior for the harness to inspect.
set -u
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# shellcheck source=test/harness.sh
. "$ROOT/test/harness.sh"
FACTS="$ROOT/lib/facts.sh"
DECIDE="$ROOT/lib/decide.sh"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
# A gh stub for the one API fact the ceremony path needs: the merged,
# release-labeled PR behind the commit.
mkdir -p "$TMP/stub"
cat >"$TMP/stub/gh" <<'EOF'
#!/usr/bin/env bash
if [ "$1" = api ]; then echo true; exit 0; fi
echo "gh stub: unexpected call: gh $*" >&2
exit 97
EOF
chmod +x "$TMP/stub/gh"
# The fixture: a base tree at 0.6.9-dev with an armed changelog, then the
# ceremony merge — VERSION bumped bare, Unreleased stamped, re-armed above.
git init -q "$TMP/repo"
git -C "$TMP/repo" config user.email fixture@example.invalid
git -C "$TMP/repo" config user.name fixture
printf '0.6.9-dev\n' >"$TMP/repo/VERSION"
cat >"$TMP/repo/CHANGELOG.md" <<'EOF'
# Changelog
## Unreleased
- The entry this release ships.
## 0.6.8 — 2026-07-01
- An older entry.
EOF
git -C "$TMP/repo" add VERSION CHANGELOG.md
git -C "$TMP/repo" commit -qm "base"
BASE_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
printf '0.7.0\n' >"$TMP/repo/VERSION"
cat >"$TMP/repo/CHANGELOG.md" <<'EOF'
# Changelog
## Unreleased
## 0.7.0 — 2026-07-21
- The entry this release ships.
## 0.6.8 — 2026-07-01
- An older entry.
EOF
git -C "$TMP/repo" add VERSION CHANGELOG.md
git -C "$TMP/repo" commit -qm "release: 0.7.0"
MERGE_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
# chain <merge_sha> <event_before> — facts, then decide fed from facts'
# output lines, then the notes extraction, printing each stage's result.
chain() {
(
cd "$TMP/repo" || exit 1
facts_out="$(env PATH="$TMP/stub:$PATH" GITHUB_REPOSITORY=fixture/fixture \
GH_TOKEN=stub VERSION_SOURCE=file MERGE_SHA="$1" EVENT_BEFORE="$2" \
bash "$FACTS")" || exit 1
printf '%s\n' "$facts_out"
ver="$(printf '%s\n' "$facts_out" | awk -F= '$1 == "ver" { print $2 }')"
base_ver="$(printf '%s\n' "$facts_out" | awk -F= '$1 == "base_ver" { print $2 }')"
released="$(printf '%s\n' "$facts_out" | awk -F= '$1 == "released" { print $2 }')"
labeled="$(printf '%s\n' "$facts_out" | awk -F= '$1 == "labeled" { print $2 }')"
decide_out="$(env VER="$ver" BASE_VER="$base_ver" RELEASED="$released" \
LABELED="$labeled" bash "$DECIDE")" || exit 1
printf '%s\n' "$decide_out"
case "$decide_out" in
*ceremony=yes*)
# shellcheck source=lib/changelog.sh
. "$ROOT/lib/changelog.sh"
notes="$(changelog_section CHANGELOG.md "$ver")"
if [ -z "$notes" ]; then
echo "chain: the changelog section for $ver is empty" >&2
exit 1
fi
printf 'notes: %s\n' "$notes"
;;
esac
)
}
check "the ceremony merge decides ceremony=yes" 0 "ceremony=yes" \
chain "$MERGE_SHA" "$BASE_SHA"
check "the notes are the stamped section's prose" 0 \
"notes: - The entry this release ships." chain "$MERGE_SHA" "$BASE_SHA"
# The same chain on an ordinary merge: -dev, unchanged — a green NOTICE
# no-op that never consults the API (the stub would refuse a release view).
printf 'ordinary work\n' >"$TMP/repo/notes.txt"
git -C "$TMP/repo" add notes.txt
git -C "$TMP/repo" commit -qm "ordinary work"
WORK_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
printf '0.7.1-dev\n' >"$TMP/repo/VERSION"
git -C "$TMP/repo" add VERSION
git -C "$TMP/repo" commit -qm "chore: bump main to 0.7.1-dev"
BUMP_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
printf 'more ordinary work\n' >"$TMP/repo/notes.txt"
git -C "$TMP/repo" add notes.txt
git -C "$TMP/repo" commit -qm "more ordinary work"
WORK2_SHA="$(git -C "$TMP/repo" rev-parse HEAD)"
check "the post-release bump decides ceremony=no" 0 "ceremony=no" \
chain "$BUMP_SHA" "$WORK_SHA"
check "an ordinary -dev merge decides ceremony=no" 0 "ceremony=no" \
chain "$WORK2_SHA" "$BUMP_SHA"
summary