Some checks failed
CI / test (pull_request) Failing after 3m2s
CI / release-exercise (pull_request) Successful in 11s
CI / self-guards (pull_request) Successful in 7s
CI / action-exercise (pull_request) Successful in 6s
CI / docs-sync-exercise (pull_request) Successful in 6s
Refs guard / refs-not-closing (pull_request) Successful in 5s
labels / labels (pull_request) Successful in 45s
The first head's `Refs guard` failed on this PR, correctly: spec 4's CEREMONY_FORGE_CLIENT=gh declaration made forge_preflight refuse by name on this forge. But that workflow runs on every pull request here, so the declaration as first written turns every future PR red until #199 lands — blocking the board for a gap that already has its own issue. Refusing and scheduling are different questions. This action must never produce a verdict from a graph it did not read, and it does not: on a forge it cannot speak it now says so by name, cites #199, states that no verdict was produced, and reaches the forge zero times. A preflight failure for any other reason stays fatal, and on a forge it CAN speak nothing changes. Also: five SC2016 findings in test/no-runtime-gh.test.sh. They were invisible locally because shellcheck-all.sh lints TRACKED files and the guard was still untracked when I ran it — a new file is exactly the case that check cannot see. Verified this time against CI's pinned shellcheck 0.10.0 with the file committed. test/run.sh: 28 test files, 0 failed, under CI's CEREMONY_REQUIRE_* env. shellcheck, actionlint, self-ref, marker and vendored guards all clean. Refs #198
89 lines
3.6 KiB
Bash
Executable file
89 lines
3.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# The composite action's executable boundary (#218). Keeping the GraphQL
|
|
# gather here lets the offline contract test replace `gh` and prove that
|
|
# failed and partial reads cannot accidentally produce a green verdict.
|
|
|
|
# THIS ACTION IS STILL gh-ONLY, AND SAYS SO (#198 spec 4, #199 ports it).
|
|
# Its entire gather is a single GraphQL query issued through `gh`, and
|
|
# Forgejo serves no
|
|
# GraphQL surface at all — `/api/graphql` 404s on this instance, and a real
|
|
# forgejo-runner job arrives with GITHUB_GRAPHQL_URL set to the empty string
|
|
# (lib/forge.sh's header). There is no endpoint to translate this to, so
|
|
# unlike every other call site the merge touched it cannot be ported here;
|
|
# it has to be re-expressed over REST, which is #199.
|
|
#
|
|
# Until then the declaration is the honest move: CEREMONY_FORGE_CLIENT names
|
|
# the client this file actually speaks, and forge_preflight refuses loudly on
|
|
# a forge that cannot serve it — rather than reading nothing and reporting a
|
|
# verdict. That is lib/forge.sh's own rule, "Never 'probably github'",
|
|
# applied to the one action that has not caught up yet.
|
|
# shellcheck source=lib/forge.sh
|
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/../../lib/forge.sh"
|
|
export CEREMONY_FORGE_CLIENT=gh
|
|
# The refusal and the SCHEDULING are two different questions, and #198's first
|
|
# head conflated them: preflight refused correctly on this forge and turned
|
|
# every PR's `Refs guard` red, which blocks the board rather than protecting
|
|
# it. A guard that cannot run here must not claim a verdict — but it also must
|
|
# not stand permanently red for a port that has its own issue.
|
|
#
|
|
# So: not-runnable-here is reported and skipped, loudly and by name, and only
|
|
# a preflight failure for any OTHER reason is fatal. The distinction is the
|
|
# forge, not the exit code — on a forge this action CAN speak, a preflight
|
|
# failure is still a hard refusal, which is the case the tests drive.
|
|
preflight_err="$(mktemp)"
|
|
trap 'rm -f "$preflight_err"' EXIT
|
|
if ! forge_preflight 2>"$preflight_err"; then
|
|
cat "$preflight_err" >&2
|
|
if [ "$(forge_detect 2>/dev/null)" != github ]; then
|
|
printf '::notice::refs-not-closing: not run — this action is still gh-only (its gather is GraphQL, which this forge does not serve) and #199 ports it. No verdict was produced.\n'
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
fi
|
|
|
|
owner="${GITHUB_REPOSITORY%%/*}"
|
|
name="${GITHUB_REPOSITORY#*/}"
|
|
[ -n "${PR_NUMBER:-}" ] || {
|
|
echo "refs-not-closing: pull request number is unavailable" >&2
|
|
exit 1
|
|
}
|
|
|
|
# GraphQL variables are literal API syntax; the shell must not expand them.
|
|
# shellcheck disable=SC2016
|
|
facts="$(gh api graphql \
|
|
-f query='query($owner: String!, $name: String!, $number: Int!) {
|
|
repository(owner: $owner, name: $name) {
|
|
pullRequest(number: $number) {
|
|
body
|
|
closingIssuesReferences(first: 100) {
|
|
nodes { number }
|
|
pageInfo { hasNextPage }
|
|
}
|
|
}
|
|
}
|
|
}' \
|
|
-F owner="$owner" -F name="$name" -F number="$PR_NUMBER")"
|
|
|
|
body_file="$(mktemp)"
|
|
closing_file="$(mktemp)"
|
|
trap 'rm -f "$body_file" "$closing_file"' EXIT
|
|
jq -er '
|
|
.data.repository.pullRequest
|
|
| if . == null then error("pull request was not returned") else .body // "" end
|
|
' <<<"$facts" >"$body_file"
|
|
jq -r '
|
|
.data.repository.pullRequest.closingIssuesReferences
|
|
| if . == null then
|
|
error("closing issue references were not returned")
|
|
elif .pageInfo.hasNextPage then
|
|
error("more than 100 closing issue references; refusing a partial verdict")
|
|
else
|
|
.nodes[].number
|
|
end
|
|
' <<<"$facts" >"$closing_file"
|
|
|
|
mapfile -t closing_issues <"$closing_file"
|
|
bash "$GITHUB_ACTION_PATH/refs-not-closing.sh" \
|
|
"$body_file" "${closing_issues[@]}"
|