2026-08-03 20:58:56 +00:00
|
|
|
#!/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.
|
|
|
|
|
|
merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198)
`git merge` of upstream `8c3a4d1` onto `dad99dd`, common ancestor `84bb1a4`.
18 hunks in 10 files; `lib/forge.sh`, `lib/forge-github.sh` and
`lib/forge-forgejo.sh` conflict in none and come out byte-identical.
The resolutions the issue decided: VERSION and both CEREMONY_SELF_REF
carriers take upstream's numbers; `.github/labels.conf` and `drills/0.4.1.md`
keep this forge's; CHANGELOG keeps both sides and names the upstream commit
this tree carries.
The part the hunks did not contain. Upstream's 0.5.0/0.6.0 work added whole
functions to files this tree already owned, so `git merge` took its side
without raising a conflict — and with them, EIGHT runtime `gh` call sites
that #188 had removed. Seven are ported onto the shim: two reads and four
comment writes in issueflow-reconcile, and labels-reconcile's HEAD_COMMIT_AT
read. The eighth is `gh workflow run` in labels.yml, which a workflow cannot
declare a client for and whose Forgejo equivalent this instance answers with
500 rather than a 4xx — named with its reason rather than ported on a guess.
test/no-runtime-gh.test.sh makes the rule mechanical, because reviewing the
diff could not: four reviewers reading it each found a different subset, and
the contract suite stubs `gh`, so a reintroduced call site passes it.
Three seams the resolution decides are silent when resolved wrongly, and each
now has a case that fails on the wrong one: the merged record's `merged_at`
third column (without it every sort key ties and the highest PR number comes
back), the open gather's one-BODY-row-per-line feed (a whole decoded body as
one record loses every declaration including the first), and the whole-board
read whose COLLISION_FLAGS/WINDOW_FLAGS consumers auto-merged.
The open gather carries CLOSING rows as well as BODY rows. `Refs` alone would
drop every `Closes #N` link on the open side and reclaim a claim the PR was
holding — the existing base64 round-trip case is red without it.
actions/refs-not-closing declares CEREMONY_FORGE_CLIENT=gh: its only gather
is GraphQL, which Forgejo does not serve at all. #199 ports it.
test/run.sh: 28 test files, 0 failed. shellcheck and actionlint clean.
Refs #198
2026-08-05 11:56:23 +00:00
|
|
|
# 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
|
fix(198): the action fails closed, the caller decides scheduling, the guard decides the forge (#198)
@codex-reviewer-andresmgsl's second review, both points taken.
The refs action goes back to `forge_preflight || exit 1`. 97e63ac had it exit 0
with a notice so the PR check would not be red, and that conflated two
different questions: "this action cannot produce a verdict" is the ACTION's
contract and must stay a refusal, while "this check should not block the
board" is the CALLER's decision. The caller now carries it —
refs-guard.yml skips unless github.server_url is github.com, mirroring
forge_detect positively. A skipped check is a green head; an action that
reports success it did not earn is not. The leaked preflight_err temp file
goes with the revert.
The workflow guard asked the wrong question. `command -v gh` alone passes the
moment a Forgejo runner image happens to ship gh, and then dispatches against
a forge that cannot serve it — the client/forge mismatch forge_preflight
exists to prevent. It decides the FORGE first now, mirroring forge_detect
positively, and the binary second. The source guard splits to match: a
declaration guarded only by binary presence is reported, with a fixture that
fails on exactly that shape.
The warning text was also wrong on the facts, as noted: issue-event sweeps ARE
this caller's event-driven wakes, so they are precisely what is lost. It now
says the hourly scheduled sweep survives and every event-driven wake through
this caller does not, until #205.
Point 1 of that review — jq 1.6 accepting an empty payload — was already fixed
in 728102a, pushed before the review landed.
Verified under the runner's jq 1.6 as well as 1.7: 28 test files, 0 failed
both ways. shellcheck 0.10.0 (CI's pin), actionlint, self-ref, marker,
vendored, changelog-armed all clean with every file tracked.
Refs #198
2026-08-05 12:30:28 +00:00
|
|
|
# Fail CLOSED, at the action boundary. An earlier head here exited 0 with a
|
|
|
|
|
# notice so the PR check would not be red; @codex-reviewer-andresmgsl was
|
|
|
|
|
# right that this conflates two different questions. "This action cannot
|
|
|
|
|
# produce a verdict" is the ACTION's contract and must stay a refusal; "this
|
|
|
|
|
# check should not block the board" is the CALLER's decision, and it belongs
|
|
|
|
|
# in .github/workflows/refs-guard.yml, which skips on a backend this action
|
|
|
|
|
# cannot speak until #199 ports it.
|
|
|
|
|
forge_preflight || exit 1
|
merge upstream 0.6.0 onto the forge tree, and port every gh call site it brought (#198)
`git merge` of upstream `8c3a4d1` onto `dad99dd`, common ancestor `84bb1a4`.
18 hunks in 10 files; `lib/forge.sh`, `lib/forge-github.sh` and
`lib/forge-forgejo.sh` conflict in none and come out byte-identical.
The resolutions the issue decided: VERSION and both CEREMONY_SELF_REF
carriers take upstream's numbers; `.github/labels.conf` and `drills/0.4.1.md`
keep this forge's; CHANGELOG keeps both sides and names the upstream commit
this tree carries.
The part the hunks did not contain. Upstream's 0.5.0/0.6.0 work added whole
functions to files this tree already owned, so `git merge` took its side
without raising a conflict — and with them, EIGHT runtime `gh` call sites
that #188 had removed. Seven are ported onto the shim: two reads and four
comment writes in issueflow-reconcile, and labels-reconcile's HEAD_COMMIT_AT
read. The eighth is `gh workflow run` in labels.yml, which a workflow cannot
declare a client for and whose Forgejo equivalent this instance answers with
500 rather than a 4xx — named with its reason rather than ported on a guess.
test/no-runtime-gh.test.sh makes the rule mechanical, because reviewing the
diff could not: four reviewers reading it each found a different subset, and
the contract suite stubs `gh`, so a reintroduced call site passes it.
Three seams the resolution decides are silent when resolved wrongly, and each
now has a case that fails on the wrong one: the merged record's `merged_at`
third column (without it every sort key ties and the highest PR number comes
back), the open gather's one-BODY-row-per-line feed (a whole decoded body as
one record loses every declaration including the first), and the whole-board
read whose COLLISION_FLAGS/WINDOW_FLAGS consumers auto-merged.
The open gather carries CLOSING rows as well as BODY rows. `Refs` alone would
drop every `Closes #N` link on the open side and reclaim a claim the PR was
holding — the existing base64 round-trip case is red without it.
actions/refs-not-closing declares CEREMONY_FORGE_CLIENT=gh: its only gather
is GraphQL, which Forgejo does not serve at all. #199 ports it.
test/run.sh: 28 test files, 0 failed. shellcheck and actionlint clean.
Refs #198
2026-08-05 11:56:23 +00:00
|
|
|
|
2026-08-03 20:58:56 +00:00
|
|
|
owner="${GITHUB_REPOSITORY%%/*}"
|
|
|
|
|
name="${GITHUB_REPOSITORY#*/}"
|
|
|
|
|
[ -n "${PR_NUMBER:-}" ] || {
|
|
|
|
|
echo "refs-not-closing: pull request number is unavailable" >&2
|
|
|
|
|
exit 1
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-03 21:00:40 +00:00
|
|
|
# GraphQL variables are literal API syntax; the shell must not expand them.
|
|
|
|
|
# shellcheck disable=SC2016
|
2026-08-03 20:58:56 +00:00
|
|
|
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[@]}"
|