forked from heavy-duty/ceremony
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>
137 lines
4 KiB
Bash
137 lines
4 KiB
Bash
#!/usr/bin/env bash
|
|
# Contract tests for .github/scripts/self-ref-check.sh (issue #9; #1 D3) —
|
|
# the pin rules, driven against constructed fixture trees. The CI step runs
|
|
# the same script against the real tree. 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"
|
|
|
|
CHECK="$ROOT/.github/scripts/self-ref-check.sh"
|
|
|
|
TMP="$(mktemp -d)"
|
|
trap 'rm -rf "$TMP"' EXIT
|
|
|
|
# tree <name> <pin...> — a fixture tree whose workflows carry the pins (one
|
|
# workflow file per pin, mimicking release.yml + labels.yml).
|
|
tree() {
|
|
local name="$1"
|
|
shift
|
|
mkdir -p "$TMP/$name/.github/workflows"
|
|
local i=0 pin
|
|
for pin in "$@"; do
|
|
i=$((i + 1))
|
|
printf 'name: w%s\nenv:\n CEREMONY_SELF_REF: "%s"\n' "$i" "$pin" \
|
|
>"$TMP/$name/.github/workflows/w$i.yml"
|
|
done
|
|
}
|
|
|
|
run_check() {
|
|
bash "$CHECK" "$TMP/$1"
|
|
}
|
|
|
|
# --- the pre-dogfood window (no VERSION yet — the tree #9 lands on) ---------
|
|
|
|
tree pre-dogfood 0.1.0
|
|
check "no VERSION: pin unchecked, loud notice, green" 0 "cannot bind yet" \
|
|
run_check pre-dogfood
|
|
|
|
# --- a bare tree: pin must equal VERSION -------------------------------------
|
|
|
|
tree bare-good 0.3.0
|
|
printf '0.3.0\n' >"$TMP/bare-good/VERSION"
|
|
check "bare tree, pin == VERSION passes" 0 "agrees" run_check bare-good
|
|
|
|
tree bare-stale 0.2.0
|
|
printf '0.3.0\n' >"$TMP/bare-stale/VERSION"
|
|
check "bare tree, stale pin fails" 1 "must be '0.3.0'" run_check bare-stale
|
|
|
|
# --- a -dev tree: pin must equal the newest stamped heading ------------------
|
|
|
|
tree dev-good 0.3.0
|
|
printf '0.4.0-dev\n' >"$TMP/dev-good/VERSION"
|
|
cat >"$TMP/dev-good/CHANGELOG.md" <<'EOF'
|
|
# Changelog
|
|
|
|
## Unreleased
|
|
|
|
- Pending entry.
|
|
|
|
## 0.3.0 — 2026-07-20
|
|
|
|
- The shipped entry.
|
|
|
|
## 0.2.0 — 2026-07-01
|
|
|
|
- Older entry.
|
|
EOF
|
|
check "-dev tree, pin == newest stamped heading passes" 0 "agrees" \
|
|
run_check dev-good
|
|
|
|
tree dev-stale 0.2.0
|
|
printf '0.4.0-dev\n' >"$TMP/dev-stale/VERSION"
|
|
cp "$TMP/dev-good/CHANGELOG.md" "$TMP/dev-stale/CHANGELOG.md"
|
|
check "-dev tree, pin behind the last release fails" 1 "must be '0.3.0'" \
|
|
run_check dev-stale
|
|
|
|
# Whole-field match: an rc heading never satisfies the bare X.Y.Z shape —
|
|
# the newest BARE heading below it is the last release.
|
|
tree dev-rc 0.3.0
|
|
printf '0.4.0-dev\n' >"$TMP/dev-rc/VERSION"
|
|
cat >"$TMP/dev-rc/CHANGELOG.md" <<'EOF'
|
|
# Changelog
|
|
|
|
## Unreleased
|
|
|
|
## 0.4.0-rc1 — 2026-07-21
|
|
|
|
- The candidate's entry.
|
|
|
|
## 0.3.0 — 2026-07-20
|
|
|
|
- The shipped entry.
|
|
EOF
|
|
check "-dev tree: an rc heading is skipped, the bare one governs" 0 "agrees" \
|
|
run_check dev-rc
|
|
|
|
# --- the pre-first-release tree: pin == VERSION with -dev stripped -----------
|
|
|
|
tree first-good 0.1.0
|
|
printf '0.1.0-dev\n' >"$TMP/first-good/VERSION"
|
|
printf '# Changelog\n\n## Unreleased\n\n- Everything so far.\n' \
|
|
>"$TMP/first-good/CHANGELOG.md"
|
|
check "pre-first-release: pin == VERSION minus -dev passes" 0 "agrees" \
|
|
run_check first-good
|
|
|
|
tree first-stale 0.0.1
|
|
printf '0.1.0-dev\n' >"$TMP/first-stale/VERSION"
|
|
check "pre-first-release: any other pin fails" 1 "must be '0.1.0'" \
|
|
run_check first-stale
|
|
|
|
# --- degenerate trees --------------------------------------------------------
|
|
|
|
mkdir -p "$TMP/no-pin/.github/workflows"
|
|
printf 'name: w\non: push\n' >"$TMP/no-pin/.github/workflows/w.yml"
|
|
check "no pin anywhere fails — the guard must have something to guard" 1 \
|
|
"nothing to guard" run_check no-pin
|
|
|
|
tree split-pin 0.3.0 0.2.0
|
|
printf '0.3.0\n' >"$TMP/split-pin/VERSION"
|
|
check "disagreeing pins across workflows fail" 1 "disagree" run_check split-pin
|
|
|
|
tree empty-version 0.1.0
|
|
: >"$TMP/empty-version/VERSION"
|
|
check "an empty VERSION fails loudly" 1 "is empty" run_check empty-version
|
|
|
|
# --- the real tree -----------------------------------------------------------
|
|
|
|
# Whatever state the repo is in (pre-dogfood today, versioned after #11),
|
|
# the guard must hold on it — this is the CI step's exact invocation.
|
|
real_check() {
|
|
(cd "$ROOT" && bash "$CHECK")
|
|
}
|
|
check "the real tree passes its own guard" 0 "" real_check
|
|
|
|
summary
|