@plainconceptsplatform/workflows 1.6.10 → 1.6.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -132,6 +132,31 @@ runs:
|
|
|
132
132
|
# both stalled on a human that way while CI was legitimately red and remediable.
|
|
133
133
|
ci_conclusion="$CONCLUSION"
|
|
134
134
|
ci_run_id="$CI_RUN_ID"
|
|
135
|
+
|
|
136
|
+
# A verdict from a CI run that judged an older head is not a verdict on this one. The
|
|
137
|
+
# router carries the run it saw, and the head can move before the gate starts: a repair
|
|
138
|
+
# pushed, a branch updated from main. Pliny-Bot #729 was blocked and sent to review on a
|
|
139
|
+
# red run of a commit that two later pushes had already fixed, and the gate's own report
|
|
140
|
+
# said so. When the run's commit is not the head, the carried verdict is dropped and read
|
|
141
|
+
# again from the head's own latest run; when that head has no finished run yet, there is
|
|
142
|
+
# nothing to judge, and the gate steps aside until CI finishes and the train hands the
|
|
143
|
+
# pull request back.
|
|
144
|
+
if [ -n "$ci_run_id" ]; then
|
|
145
|
+
run_sha="$(gh api "repos/$REPO/actions/runs/$ci_run_id" --jq '.head_sha' 2>/dev/null || true)"
|
|
146
|
+
current_sha="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')"
|
|
147
|
+
if [ -n "$run_sha" ] && [ "$run_sha" != "$current_sha" ]; then
|
|
148
|
+
echo "::notice::CI run $ci_run_id judged ${run_sha:0:7}, but the head is ${current_sha:0:7}; reading the verdict again from the head."
|
|
149
|
+
ci_conclusion=""
|
|
150
|
+
ci_run_id=""
|
|
151
|
+
fresh="$(gh api "repos/$REPO/actions/runs?head_sha=$current_sha&per_page=100" --jq '[.workflow_runs[] | select((.name | ascii_downcase) == "app: ci" or (.name | ascii_downcase) == "ci") | select(.status == "completed")] | sort_by(.created_at) | last')"
|
|
152
|
+
if [ -z "$fresh" ] || [ "$fresh" = "null" ]; then
|
|
153
|
+
none "CI has not finished on the current head of PR #$PR_NUMBER; the train hands it back when it has"
|
|
154
|
+
fi
|
|
155
|
+
ci_conclusion="$(jq -r '.conclusion // "pending"' <<<"$fresh")"
|
|
156
|
+
ci_run_id="$(jq -r '.id' <<<"$fresh")"
|
|
157
|
+
fi
|
|
158
|
+
fi
|
|
159
|
+
|
|
135
160
|
if [ -z "$ci_conclusion" ] || [ -z "$ci_run_id" ]; then
|
|
136
161
|
head_sha="$(gh pr view "$PR_NUMBER" --repo "$REPO" --json headRefOid --jq '.headRefOid')"
|
|
137
162
|
# A run keeps the name it had when it ran, so a repository whose CI was renamed still
|
|
@@ -38,6 +38,10 @@ set -euo pipefail
|
|
|
38
38
|
# CI_STATUS none, or the latest CI run's status (queued, in_progress, completed, ...)
|
|
39
39
|
# CI_CONCLUSION that run's conclusion when it completed
|
|
40
40
|
# CI_STUCK true when that run has been pending longer than the train waits
|
|
41
|
+
# SAME_AS_MAIN true when every check that failed on the head also fails on the base branch
|
|
42
|
+
# tip: the failure is the base's, and repairing the pull request spends its
|
|
43
|
+
# attempts on code that is not at fault (Pliny-Bot, 2026-10-17: a red main
|
|
44
|
+
# cost every open pull request its repairs for seven hours)
|
|
41
45
|
# BEHIND_BY commits on the base branch the head does not have
|
|
42
46
|
# TRUSTED_AUTHOR true when a trusted bot opened it (only those runs are approved)
|
|
43
47
|
train_move() {
|
|
@@ -70,7 +74,13 @@ train_move() {
|
|
|
70
74
|
move=gate; reason="green and up to date"
|
|
71
75
|
fi
|
|
72
76
|
;;
|
|
73
|
-
failure)
|
|
77
|
+
failure)
|
|
78
|
+
if [ "${SAME_AS_MAIN:-false}" = true ]; then
|
|
79
|
+
move="wait"; reason="CI failed, but only on checks that fail on the base branch too; waiting for the base to be fixed"
|
|
80
|
+
else
|
|
81
|
+
move=gate; reason="CI failed"
|
|
82
|
+
fi
|
|
83
|
+
;;
|
|
74
84
|
action_required)
|
|
75
85
|
if [ "${TRUSTED_AUTHOR:-false}" = true ]; then
|
|
76
86
|
move=approve; reason="CI is waiting for approval"
|
|
@@ -403,12 +413,28 @@ for line in "${candidates[@]}"; do
|
|
|
403
413
|
fi
|
|
404
414
|
fi
|
|
405
415
|
|
|
416
|
+
# What failed on the head, compared with what is failing on the base branch tip. Names are
|
|
417
|
+
# compared after the last " / ": a check that a reusable workflow runs is "CI / backend" on the
|
|
418
|
+
# base branch and "backend" on a pull request. Only when every failed check is also failing on
|
|
419
|
+
# the base is the pull request left alone.
|
|
420
|
+
same_as_main=false
|
|
421
|
+
if [ "$ci_status" = completed ] && [ "$ci_conclusion" = failure ] && [ "$park" = none ]; then
|
|
422
|
+
failed_here=$(gh api "repos/$REPO/commits/$head_sha/check-runs?per_page=100" \
|
|
423
|
+
--jq '[.check_runs[] | select(.conclusion == "failure") | (.name | sub("^.* / "; ""))] | unique | .[]' 2>/dev/null || true)
|
|
424
|
+
failed_base=$(gh api "repos/$REPO/commits/$base_ref/check-runs?per_page=100" \
|
|
425
|
+
--jq '[.check_runs[] | select(.conclusion == "failure") | (.name | sub("^.* / "; ""))] | unique | .[]' 2>/dev/null || true)
|
|
426
|
+
if [ -n "$failed_here" ] && [ -n "$failed_base" ] &&
|
|
427
|
+
[ -z "$(comm -23 <(sort <<<"$failed_here") <(sort <<<"$failed_base"))" ]; then
|
|
428
|
+
same_as_main=true
|
|
429
|
+
fi
|
|
430
|
+
fi
|
|
431
|
+
|
|
406
432
|
trusted_author=false
|
|
407
433
|
is_trusted_bot "$author" && trusted_author=true
|
|
408
434
|
|
|
409
435
|
decision=$(HOLD="$hold" REVIEW_DECISION="$review_decision" PARK="$park" \
|
|
410
436
|
ATTEMPTS="$attempts" MAX_ATTEMPTS="$MAX_ATTEMPTS" SILENT_GATE="$silent_gate" MERGEABLE="$mergeable" \
|
|
411
|
-
CI_STATUS="$ci_status" CI_CONCLUSION="$ci_conclusion" CI_STUCK="$ci_stuck" \
|
|
437
|
+
CI_STATUS="$ci_status" CI_CONCLUSION="$ci_conclusion" CI_STUCK="$ci_stuck" SAME_AS_MAIN="$same_as_main" \
|
|
412
438
|
BEHIND_BY="${behind_by:-0}" TRUSTED_AUTHOR="$trusted_author" train_move)
|
|
413
439
|
move=$(sed -n 's/^move=//p' <<<"$decision")
|
|
414
440
|
reason=$(sed -n 's/^reason=//p' <<<"$decision")
|
|
@@ -1979,6 +1979,11 @@ if worker_installed merge-gate; then
|
|
|
1979
1979
|
assert "running CI is waited on" wait "$(move MERGEABLE=MERGEABLE CI_STATUS=in_progress)"
|
|
1980
1980
|
assert "CI stuck past the limit is passed by" skip "$(move MERGEABLE=MERGEABLE CI_STATUS=queued CI_STUCK=true)"
|
|
1981
1981
|
assert "red CI goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure)"
|
|
1982
|
+
# A red base branch cost every open Pliny-Bot pull request its repairs for seven hours: the
|
|
1983
|
+
# failures were main's, and the gate spent attempts on code that was not at fault.
|
|
1984
|
+
assert "a failure that is main's too is waited out, not repaired" wait "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=true)"
|
|
1985
|
+
assert "a failure that is not main's still goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=false)"
|
|
1986
|
+
assert "a conflict is still the gate's even when main is red" gate "$(move MERGEABLE=CONFLICTING CI_STATUS=completed CI_CONCLUSION=failure SAME_AS_MAIN=true)"
|
|
1982
1987
|
assert "green but behind is brought up to date first" update "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=success BEHIND_BY=2)"
|
|
1983
1988
|
assert "green and current goes to the gate" gate "$(move MERGEABLE=MERGEABLE CI_STATUS=completed CI_CONCLUSION=success BEHIND_BY=0)"
|
|
1984
1989
|
assert "no CI run is passed by" skip "$(move MERGEABLE=MERGEABLE CI_STATUS=none)"
|
|
@@ -2758,6 +2763,11 @@ if worker_installed merge-gate; then
|
|
|
2758
2763
|
GATE_RUN_ID_OK=0
|
|
2759
2764
|
echo "FAIL: identify-gate-subject resolves the CI run only when the conclusion is unknown; a router that passes both gets an empty run ID" >&2
|
|
2760
2765
|
fi
|
|
2766
|
+
# A verdict from a run that judged an older head is not a verdict on this one (Pliny-Bot #729).
|
|
2767
|
+
grep -Fq '"$run_sha" != "$current_sha"' "$GATE_SUBJECT_ACTION" ||
|
|
2768
|
+
{ GATE_RUN_ID_OK=0; echo "FAIL: identify-gate-subject trusts the carried CI run even when it judged an older head" >&2; }
|
|
2769
|
+
grep -Fq 'select(.status == "completed")' "$GATE_SUBJECT_ACTION" ||
|
|
2770
|
+
{ GATE_RUN_ID_OK=0; echo "FAIL: identify-gate-subject re-reads a stale verdict without requiring a finished run on the head" >&2; }
|
|
2761
2771
|
fi
|
|
2762
2772
|
# Repeating a report the validator refused reproduces it, and every repeat is a fresh agent run
|
|
2763
2773
|
# holding the repo-wide merge-belt slot. The worker gives that failure a smaller budget than a
|
|
@@ -594,6 +594,7 @@ jobs:
|
|
|
594
594
|
body: |
|
|
595
595
|
${{ env.IMPLEMENT_MARKER }}
|
|
596
596
|
${{ env.INCOMPLETE_COMMENT }}
|
|
597
|
+
What ended: the agent job was **${{ needs.agent.result }}** after ${{ steps.decide.outputs.minutes }} minutes (-1 when that could not be read), and publishing its output was **${{ needs.safe_outputs.result }}**.
|
|
597
598
|
[View this workflow run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})
|
|
598
599
|
agent:
|
|
599
600
|
needs: [eligibility]
|