ToolkitRelease ancestry gaterelease-ancestry-gate.sh · shell Negative-path testsrelease-ancestry-gate.test.sh · shell Evidence and context
WorkflowVerify
Release ancestry gate
A tested shell gate that proves a prerequisite workflow run covers the required commit and belongs to the release.
Git ancestry · shell · release gate
Use this
Read and copy the source
Read it before copying. Replace example paths, workflow names, product dimensions, and authority boundaries with those in your own system.
#!/usr/bin/env bash
set -euo pipefail
workflow_name="${1:-}"
required_sha="${2:-}"
release_sha="${3:-}"
branch_filter="${BRANCH_FILTER:-main}"
max_attempts="${MAX_ATTEMPTS:-120}"
poll_seconds="${POLL_SECONDS:-30}"
if [[ -z "$workflow_name" || -z "$required_sha" || -z "$release_sha" ]]; then
echo "usage: release-ancestry-gate.sh <workflow> <required-sha> <release-sha>" >&2
exit 2
fi
git cat-file -e "${required_sha}^{commit}"
git cat-file -e "${release_sha}^{commit}"
for ((attempt = 1; attempt <= max_attempts; attempt += 1)); do
if ! successful_shas="$(gh run list \
--workflow "$workflow_name" \
--branch "$branch_filter" \
--status success \
--limit 100 \
--json headSha \
--jq '.[].headSha')"; then
echo "Could not read workflow runs." >&2
exit 1
fi
while IFS= read -r run_sha; do
[[ -n "$run_sha" ]] || continue
git cat-file -e "${run_sha}^{commit}" 2>/dev/null || continue
if git merge-base --is-ancestor "$required_sha" "$run_sha" \
&& git merge-base --is-ancestor "$run_sha" "$release_sha"; then
echo "$workflow_name succeeded at $run_sha and covers $required_sha."
exit 0
fi
done <<< "$successful_shas"
if [[ "$attempt" -lt "$max_attempts" ]]; then
sleep "$poll_seconds"
fi
done
echo "Timed out waiting for $workflow_name to cover $required_sha." >&2
exit 1#!/usr/bin/env bash
set -euo pipefail
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
subject="$script_dir/release-ancestry-gate.sh"
fixture_dir="$(mktemp -d)"
trap 'rm -rf -- "$fixture_dir"' EXIT
git -C "$fixture_dir" init -q
git -C "$fixture_dir" config user.email "test@example.com"
git -C "$fixture_dir" config user.name "Release Gate Test"
printf 'zero\n' > "$fixture_dir/value.txt"
git -C "$fixture_dir" add value.txt
git -C "$fixture_dir" commit -qm "before requirement"
before_sha="$(git -C "$fixture_dir" rev-parse HEAD)"
printf 'one\n' > "$fixture_dir/value.txt"
git -C "$fixture_dir" commit -qam "required"
required_sha="$(git -C "$fixture_dir" rev-parse HEAD)"
printf 'two\n' >> "$fixture_dir/value.txt"
git -C "$fixture_dir" commit -qam "pipeline"
pipeline_sha="$(git -C "$fixture_dir" rev-parse HEAD)"
printf 'three\n' >> "$fixture_dir/value.txt"
git -C "$fixture_dir" commit -qam "release"
release_sha="$(git -C "$fixture_dir" rev-parse HEAD)"
fake_bin="$fixture_dir/bin"
mkdir -p "$fake_bin"
# The generated fake reads FAKE_SUCCESS_SHA when it runs, not while this fixture is created.
# shellcheck disable=SC2016
printf '#!/usr/bin/env bash\nprintf "%%s\\n" "$FAKE_SUCCESS_SHA"\n' > "$fake_bin/gh"
chmod +x "$fake_bin/gh"
run_gate() {
(
cd "$fixture_dir"
PATH="$fake_bin:$PATH" \
FAKE_SUCCESS_SHA="$1" \
MAX_ATTEMPTS=1 \
POLL_SECONDS=0 \
bash "$subject" "Example Data Pipeline" "$required_sha" "$release_sha"
)
}
run_gate "$required_sha"
run_gate "$pipeline_sha"
if run_gate "$before_sha" 2>/dev/null; then
echo "expected a run before the requirement to fail" >&2
exit 1
fi
echo "workflow ancestry tests passed"23 July 2026Data before code is a graph problemModel a data-dependent deployment as a commit-ancestry invariant instead of a current-push checklist.Read 22 July 2026The reviewer rejected my fix twice. Both times it was right.Why passing tests and a good staging walk did not prove a data-dependent release was safe, and how an independent reviewer found two temporal bugs.Read