forked from heavy-duty/ceremony
test(labels): drive the fetch that feeds the grace, at the sweep level
The predicate's fixtures cannot see the read that sets HEAD_COMMIT_AT, so a sweep probe drives it both ways: read, and the blocker is written off a dated head; denied, and the denial is named on its own line while the state still converges — this read narrows one blocker, it does not skip the PR the way an unreadable rollup does. Renaming the assignment reds the probe. The read also moves after the mergeability/checks skip: a PR the sweep walks away from must not pay for a call whose only consumer is a blocker that pass will never decide. Refs #236
This commit is contained in:
parent
8459a9255b
commit
d4512a1e82
2 changed files with 94 additions and 22 deletions
|
|
@ -971,28 +971,6 @@ main() {
|
|||
# PENDING reviews are unsubmitted drafts in someone's browser — not a verdict
|
||||
REVIEWS_JSON="$(gh api --paginate "repos/$REPO/pulls/$n/reviews" --jq '.[]' \
|
||||
| jq -s '[.[] | select(.state != "PENDING")]')"
|
||||
# The head's own clock, for the blocker:unrequested grace (#236 D2). One
|
||||
# read, pinned to the head SHA — not `gh pr view --json commits`, which
|
||||
# asks for the FIRST hundred commits and would date a longer PR by a
|
||||
# commit that is not its head. Drafts never reach that blocker, so they
|
||||
# do not pay for the call. Empty (a failed read, or a body without the
|
||||
# field) leaves the blocker unjudged this pass, by unrequested_quiescent.
|
||||
HEAD_COMMIT_AT=""
|
||||
if [ "$DRAFT" != true ]; then
|
||||
HEAD_COMMIT_ERR_FILE="$(mktemp)"
|
||||
HEAD_COMMIT_AT="$(gh api "repos/$REPO/commits/$HEAD_SHA" \
|
||||
--jq '.commit.committer.date' 2>"$HEAD_COMMIT_ERR_FILE" || echo "")"
|
||||
HEAD_COMMIT_ERR="$(cat "$HEAD_COMMIT_ERR_FILE")"
|
||||
rm -f "$HEAD_COMMIT_ERR_FILE"
|
||||
case "$HEAD_COMMIT_AT" in
|
||||
"" | null)
|
||||
# Say why it degraded (#101 D2/D4), on its own line: this one
|
||||
# narrows a blocker rather than skipping the PR, so it must not
|
||||
# read as the wholly-blind shape the counted line above matches.
|
||||
HEAD_COMMIT_AT=""
|
||||
log "#$n: could not read the head commit's date: $(read_failure_reason "$HEAD_COMMIT_ERR") — blocker:unrequested not judged this pass" ;;
|
||||
esac
|
||||
fi
|
||||
# mergeability + the check rollup, the two facts the state machine was
|
||||
# blind to (#136). `gh pr view` rather than the REST PR object: the API's
|
||||
# `mergeable` is a tri-state boolean that GitHub computes lazily, while
|
||||
|
|
@ -1024,6 +1002,29 @@ main() {
|
|||
log "#$n: read failed: $(read_failure_reason "$GH_VIEW_ERR")"
|
||||
exit 0
|
||||
fi
|
||||
# The head's own clock, for the blocker:unrequested grace (#236 D2). One
|
||||
# read, pinned to the head SHA — not `gh pr view --json commits`, which
|
||||
# asks for the FIRST hundred commits and would date a longer PR by a
|
||||
# commit that is not its head. Last of the fetches on purpose: a PR the
|
||||
# skip above walked away from must not pay for it, and neither do drafts,
|
||||
# which never reach that blocker. Empty (a failed read, or a body without
|
||||
# the field) leaves the blocker unjudged, by unrequested_quiescent.
|
||||
HEAD_COMMIT_AT=""
|
||||
if [ "$DRAFT" != true ]; then
|
||||
HEAD_COMMIT_ERR_FILE="$(mktemp)"
|
||||
HEAD_COMMIT_AT="$(gh api "repos/$REPO/commits/$HEAD_SHA" \
|
||||
--jq '.commit.committer.date' 2>"$HEAD_COMMIT_ERR_FILE" || echo "")"
|
||||
HEAD_COMMIT_ERR="$(cat "$HEAD_COMMIT_ERR_FILE")"
|
||||
rm -f "$HEAD_COMMIT_ERR_FILE"
|
||||
case "$HEAD_COMMIT_AT" in
|
||||
"" | null)
|
||||
# Say why it degraded (#101 D2/D4), on its own line: this one
|
||||
# narrows a blocker rather than skipping the PR, so it must not
|
||||
# read as the wholly-blind shape the counted line above matches.
|
||||
HEAD_COMMIT_AT=""
|
||||
log "#$n: could not read the head commit's date: $(read_failure_reason "$HEAD_COMMIT_ERR") — blocker:unrequested not judged this pass" ;;
|
||||
esac
|
||||
fi
|
||||
reconcile_pr "$n"
|
||||
) 2>&1
|
||||
)" || status=$?
|
||||
|
|
|
|||
|
|
@ -840,6 +840,77 @@ expect "each blind PR logs its reason as its own line beside the counted one" 2
|
|||
expect "exactly the blind PRs match the counted shape whole-line — no more, no less" 2 \
|
||||
"$(grep -c '^labels: #[0-9]*: could not read mergeability/checks — left alone this pass$' <<<"$blind_main")"
|
||||
|
||||
# -- the grace's own wiring: the fixtures above prove the predicate, and only a
|
||||
# sweep can prove the fetch that feeds it (#236 D2). The fixture-only version
|
||||
# of this change would have passed with the global never assigned — the #91
|
||||
# shape, where the probes could not reach the per-PR path at all.
|
||||
unrequested_main_probe() { # $1 = read | denied, the head-commit read's outcome
|
||||
(
|
||||
GITHUB_EVENT_NAME=schedule
|
||||
REPO=owner/repo
|
||||
LABELS_CONF=.github/labels.conf
|
||||
UMODE="$1"
|
||||
gh() {
|
||||
if [ "$1" = label ] && [ "$2" = list ]; then core_label_rows | cut -d'|' -f1; return 0; fi
|
||||
if [ "$1" = pr ] && [ "$2" = list ]; then printf '303\n'; return 0; fi
|
||||
if [ "$1" = pr ] && [ "$2" = view ]; then
|
||||
# green, so the D1 gate is open and D2 is the only question left
|
||||
jq -n '{mergeable:"MERGEABLE",
|
||||
statusCheckRollup:[{__typename:"CheckRun",workflowName:"ci",
|
||||
name:"check",conclusion:"SUCCESS",
|
||||
startedAt:"2026-07-01T00:00:00Z"}]}'
|
||||
return 0
|
||||
fi
|
||||
# recorded to a file, not to stdout: reconcile_pr sends the edit call's
|
||||
# stdout to /dev/null, so a narrating stub would look like no edit at all
|
||||
if [ "$1" = issue ] && [ "$2" = edit ]; then printf '%s\n' "$*" >>"$RTMP/uedits-$UMODE"; return 0; fi
|
||||
[ "$1" = api ] || return 0
|
||||
shift
|
||||
local jqexpr="" endpoint=""
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--jq) jqexpr="$2"; shift ;;
|
||||
-*) ;;
|
||||
*) [ -n "$endpoint" ] || endpoint="$1" ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
case "$endpoint" in
|
||||
*/commits/*) # the head-commit read; ordered before the commit LIST below
|
||||
if [ "$UMODE" = denied ]; then
|
||||
printf 'gh: Not Found (HTTP 404)\n' >&2
|
||||
return 1
|
||||
fi
|
||||
jq -n '{commit:{committer:{date:"2026-07-01T00:00:00Z"}}}' | jq -r "${jqexpr:-.}" ;;
|
||||
*/pulls/303)
|
||||
jq -n '{draft:false,user:{login:"author"},head:{sha:"headsha"},
|
||||
base:{sha:"basesha"},labels:[],requested_reviewers:[],
|
||||
created_at:"2026-07-01T00:00:00Z"}' ;;
|
||||
*) printf '[]\n' | jq -r "${jqexpr:-.}" ;; # every collection empty
|
||||
esac
|
||||
}
|
||||
main
|
||||
)
|
||||
}
|
||||
|
||||
read_sweep="$(unrequested_main_probe read)"
|
||||
expect "the sweep reads the head's date and writes the stall it now dates" yes \
|
||||
"$(grep -q 'blocker:unrequested' "$RTMP/uedits-read" && echo yes || echo no)"
|
||||
expect "...saying nothing about a degraded read" no \
|
||||
"$(grep -q "could not read the head commit's date" <<<"$read_sweep" && echo yes || echo no)"
|
||||
denied_sweep="$(unrequested_main_probe denied)"
|
||||
expect "a denied head-commit read names the denial (#101's shape)" yes \
|
||||
"$(grep -q "^labels: #303: could not read the head commit's date: gh: Not Found (HTTP 404)" <<<"$denied_sweep" \
|
||||
&& echo yes || echo no)"
|
||||
expect "...and writes no blocker it could not date" no \
|
||||
"$(grep -q 'blocker:unrequested' "$RTMP/uedits-denied" && echo yes || echo no)"
|
||||
# ...while the PR is still converged: this read narrows one blocker, it does not
|
||||
# skip the PR the way an unreadable rollup does
|
||||
expect "...while the state still converges — one blocker unjudged, not a skip" yes \
|
||||
"$(grep -q 'state:addressing' "$RTMP/uedits-denied" && echo yes || echo no)"
|
||||
expect "...and the sweep does not report it as a blind pass" 0 \
|
||||
"$(grep -c 'could not read mergeability/checks' <<<"$denied_sweep" || true)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# bootstrap_labels retires the GitHub defaults (#93). LABELS.md published
|
||||
# them as deleted at bootstrap; nothing deleted them — incubator's first
|
||||
|
|
|
|||
Loading…
Reference in a new issue