@norskvideo/ctl-dev-kit 0.2.100 → 0.2.102
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.
package/conventions/bot-prs.yml
CHANGED
|
@@ -35,7 +35,15 @@
|
|
|
35
35
|
# actually read;
|
|
36
36
|
# - checks or a tier still running -> leave it for the next trigger; a tier
|
|
37
37
|
# that has not started an hour after checks finished never will (its
|
|
38
|
-
# workflow is broken, or does not run on PRs), so that is red too
|
|
38
|
+
# workflow is broken, or does not run on PRs), so that is red too;
|
|
39
|
+
# - a tier integration.yml cancelled because the host was too contended to
|
|
40
|
+
# judge it (its job carries a `ctl-contention-uncertain` annotation) -> not
|
|
41
|
+
# yet judged, so neither red nor merged: re-run once a backoff has passed
|
|
42
|
+
# (vars.BOT_PRS_CONTENDED_BACKOFF_MINUTES, default 30, doubling per
|
|
43
|
+
# attempt), up to vars.BOT_PRS_CONTENDED_ATTEMPTS attempts (default 4);
|
|
44
|
+
# after that it is red as "still contended after N attempts: needs a human
|
|
45
|
+
# or a quieter runner", never as a failure. Any other cancelled check is
|
|
46
|
+
# red, and a cancelled tier is re-run up to its third attempt, as before.
|
|
39
47
|
# The cell goes green again once nothing is red.
|
|
40
48
|
#
|
|
41
49
|
# Why the conflict rebuild: when the three bots fired within minutes of each
|
|
@@ -111,6 +119,14 @@ jobs:
|
|
|
111
119
|
WRITE_TOKEN: ${{ secrets.CI_DISPATCH_TOKEN }}
|
|
112
120
|
REPO: ${{ github.repository }}
|
|
113
121
|
BASE: ${{ inputs.base || 'main' }}
|
|
122
|
+
# A run integration.yml cancelled because the host was too contended
|
|
123
|
+
# to judge it carries this annotation title on its job. Such a run is
|
|
124
|
+
# not yet judged: re-run with backoff, up to the attempt budget.
|
|
125
|
+
# Repo variables, so the verbatim copy needs no edit; unset or not a
|
|
126
|
+
# number takes the default (4 attempts, 30 minutes doubling).
|
|
127
|
+
CONTENDED_MARK: ctl-contention-uncertain
|
|
128
|
+
CONTENDED_ATTEMPTS: ${{ vars.BOT_PRS_CONTENDED_ATTEMPTS }}
|
|
129
|
+
CONTENDED_BACKOFF_MINUTES: ${{ vars.BOT_PRS_CONTENDED_BACKOFF_MINUTES }}
|
|
114
130
|
run: |
|
|
115
131
|
set -euo pipefail
|
|
116
132
|
api="https://api.github.com/repos/$REPO"
|
|
@@ -181,9 +197,17 @@ jobs:
|
|
|
181
197
|
# guides queue on one concurrency group per repo and GitHub keeps one
|
|
182
198
|
# pending run per group, cancelling the older, so three bot PRs opening
|
|
183
199
|
# within the hour can cancel a guides run with nothing wrong in it.
|
|
200
|
+
# CONTENDED (optional): the run ids contended_runs found. A cancelled
|
|
201
|
+
# tier among them was never judged: it is re-run once the backoff
|
|
202
|
+
# since its cancel has passed (CONTENDED_BACKOFF_MINUTES, doubling per
|
|
203
|
+
# attempt), up to CONTENDED_ATTEMPTS attempts, then reported as still
|
|
204
|
+
# contended. It never reads ok.
|
|
184
205
|
tier_verdict() {
|
|
185
|
-
local required="$1" runs="$2" now="$3" f run status conclusion checks_age
|
|
206
|
+
local required="$1" runs="$2" now="$3" contended="${4:-}" f run id status conclusion checks_age
|
|
207
|
+
local attempt max="${CONTENDED_ATTEMPTS:-}" base="${CONTENDED_BACKOFF_MINUTES:-}" backoff age
|
|
186
208
|
local -a waiting=() failed=() rerun=()
|
|
209
|
+
[[ "$max" =~ ^[1-9][0-9]*$ ]] || max=4
|
|
210
|
+
[[ "$base" =~ ^[0-9]+$ ]] || base=30
|
|
187
211
|
checks_age="$(jq -r --argjson now "$now" '
|
|
188
212
|
[.workflow_runs[] | select((.path | split("@")[0]) == ".github/workflows/checks.yml"
|
|
189
213
|
and .event == "pull_request" and .status == "completed")] | max_by(.id)
|
|
@@ -202,8 +226,22 @@ jobs:
|
|
|
202
226
|
fi
|
|
203
227
|
status="$(jq -r .status <<<"$run")"
|
|
204
228
|
conclusion="$(jq -r '.conclusion // ""' <<<"$run")"
|
|
229
|
+
id="$(jq -r .id <<<"$run")"
|
|
230
|
+
attempt="$(jq -r '.run_attempt // 1' <<<"$run")"
|
|
205
231
|
if [ "$status" != "completed" ]; then
|
|
206
232
|
waiting+=("${f%.yml} $status")
|
|
233
|
+
elif [ "$conclusion" = "cancelled" ] && grep -qxF "$id" <<<"$contended"; then
|
|
234
|
+
if [ "$attempt" -ge "$max" ]; then
|
|
235
|
+
failed+=("${f%.yml} still contended after $attempt attempts: needs a human or a quieter runner")
|
|
236
|
+
continue
|
|
237
|
+
fi
|
|
238
|
+
backoff=$((base * 60 * (1 << (attempt - 1))))
|
|
239
|
+
age=$((now - $(jq -r '.updated_at | fromdateiso8601' <<<"$run")))
|
|
240
|
+
if [ "$age" -ge "$backoff" ]; then
|
|
241
|
+
rerun+=("$id")
|
|
242
|
+
else
|
|
243
|
+
waiting+=("${f%.yml} not yet judged, the host was contended (attempt $attempt of $max); re-running in $(((backoff - age + 59) / 60))m")
|
|
244
|
+
fi
|
|
207
245
|
elif [ "$conclusion" = "cancelled" ] && [ "$(jq -r '.run_attempt // 1' <<<"$run")" -lt 3 ]; then
|
|
208
246
|
rerun+=("$(jq -r .id <<<"$run")")
|
|
209
247
|
elif [ "$conclusion" != "success" ]; then
|
|
@@ -221,6 +259,41 @@ jobs:
|
|
|
221
259
|
fi
|
|
222
260
|
}
|
|
223
261
|
|
|
262
|
+
# contended_runs CHECK_RUNS -> the workflow run ids (one per line) that
|
|
263
|
+
# integration.yml cancelled because the host was too contended to
|
|
264
|
+
# judge them: a CANCELLED job of the run carries the CONTENDED_MARK
|
|
265
|
+
# annotation. The marker on a job that failed (its cancel never
|
|
266
|
+
# landed) is not one: that run is red. Annotations it cannot read
|
|
267
|
+
# leave the run red, as every cancel was before.
|
|
268
|
+
contended_runs() {
|
|
269
|
+
local checks="$1" id notes
|
|
270
|
+
while IFS= read -r id; do
|
|
271
|
+
[ -n "$id" ] || continue
|
|
272
|
+
notes="$(gh_api "$api/check-runs/$id/annotations?per_page=100")"
|
|
273
|
+
if ! jq -e 'type == "array"' >/dev/null 2>&1 <<<"$notes"; then
|
|
274
|
+
echo "::warning::could not read the annotations of check run $id; a cancel there counts as red" >&2
|
|
275
|
+
continue
|
|
276
|
+
fi
|
|
277
|
+
jq -e --arg m "$CONTENDED_MARK" 'any(.[]; .title == $m)' >/dev/null <<<"$notes" || continue
|
|
278
|
+
jq -r --argjson id "$id" '.check_runs[] | select(.id == $id)
|
|
279
|
+
| (.details_url // "") | (capture("/actions/runs/(?<r>[0-9]+)").r // empty)' <<<"$checks"
|
|
280
|
+
done < <(jq -r '.check_runs[] | select(.conclusion == "cancelled" and (.output.annotations_count // 0) > 0) | .id' <<<"$checks") | sort -u
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
# failed_checks CHECK_RUNS CONTENDED -> the names of the checks that
|
|
284
|
+
# failed, comma separated. A cancelled job of a contended run (every
|
|
285
|
+
# one of them: GitHub cancels the run's other jobs with it) is left to
|
|
286
|
+
# tier_verdict, which re-runs it; any other cancel is red, as before.
|
|
287
|
+
failed_checks() {
|
|
288
|
+
jq -r --arg c "$2" '($c | split("\n") | map(select(. != ""))) as $contended
|
|
289
|
+
| [.check_runs[]
|
|
290
|
+
| select(.conclusion | IN("failure","cancelled","timed_out","action_required","startup_failure"))
|
|
291
|
+
| select(.conclusion != "cancelled"
|
|
292
|
+
or (((.details_url // "") | (capture("/actions/runs/(?<r>[0-9]+)").r // "")) as $r
|
|
293
|
+
| any($contended[]; . == $r) | not))
|
|
294
|
+
| .name] | unique | join(", ")' <<<"$1"
|
|
295
|
+
}
|
|
296
|
+
|
|
224
297
|
# The workflow that rebuilds each bot branch: bump.yml, for the retired
|
|
225
298
|
# bots' branches too (their workflows went when bump.yml arrived, and
|
|
226
299
|
# bump.yml supersedes their PRs for every component it moves).
|
|
@@ -276,7 +349,8 @@ jobs:
|
|
|
276
349
|
fi
|
|
277
350
|
runs="$(gh_api "$api/commits/$sha/check-runs?per_page=100")"
|
|
278
351
|
expect "reading checks for #$n" '.check_runs | type == "array"' "$runs"
|
|
279
|
-
|
|
352
|
+
contended="$(contended_runs "$runs")"
|
|
353
|
+
failed="$(failed_checks "$runs" "$contended")"
|
|
280
354
|
pending="$(jq -r '[.check_runs[] | select(.status != "completed")] | length' <<<"$runs")"
|
|
281
355
|
passed="$(jq -r '[.check_runs[] | select(.conclusion == "success")] | length' <<<"$runs")"
|
|
282
356
|
if [ -n "$failed" ]; then red+=("#$n $ref: failing $failed"); continue; fi
|
|
@@ -286,14 +360,18 @@ jobs:
|
|
|
286
360
|
required="$(tiers_for "$sha")"
|
|
287
361
|
wf_runs="$(gh_api "$api/actions/runs?head_sha=$sha&per_page=100")"
|
|
288
362
|
expect "reading workflow runs for #$n" '.workflow_runs | type == "array"' "$wf_runs"
|
|
289
|
-
verdict="$(tier_verdict "$required" "$wf_runs" "$(date +%s)")"
|
|
363
|
+
verdict="$(tier_verdict "$required" "$wf_runs" "$(date +%s)" "$contended")"
|
|
290
364
|
case "$verdict" in
|
|
291
365
|
ok) ;;
|
|
292
366
|
red\ *) red+=("#$n $ref: ${verdict#red }"); continue ;;
|
|
293
367
|
rerun\ *)
|
|
294
368
|
for id in ${verdict#rerun }; do
|
|
295
369
|
code="$(gh_actions -o /dev/null -w '%{http_code}' -X POST "$api/actions/runs/$id/rerun")"
|
|
296
|
-
|
|
370
|
+
if grep -qxF "$id" <<<"$contended"; then
|
|
371
|
+
echo "#$n $ref: a gating tier (run $id) was cancelled as uncertain, the host was contended; re-running it (HTTP $code)"
|
|
372
|
+
else
|
|
373
|
+
echo "#$n $ref: a gating tier (run $id) was cancelled; re-running it (HTTP $code)"
|
|
374
|
+
fi
|
|
297
375
|
done
|
|
298
376
|
continue ;;
|
|
299
377
|
*) echo "#$n $ref: ${verdict#wait }; next time"; continue ;;
|
|
@@ -140,9 +140,18 @@ the file present, the canonical workflow runs `bun run test:integration` through
|
|
|
140
140
|
reporter flag.
|
|
141
141
|
- When the suite fails and **every** failure overlapped contention, the run is
|
|
142
142
|
**uncertain**: the job writes a `::warning::` and a job summary (the verdict,
|
|
143
|
-
each failure with its numbers, pressure per minute),
|
|
143
|
+
each failure with its numbers, pressure per minute), plus a
|
|
144
|
+
`::notice title=ctl-contention-uncertain::` marker, then cancels its own run.
|
|
144
145
|
A cancelled run is not reported to the CI board, and bot-prs merges only a
|
|
145
146
|
success. One failure on a quiet stretch keeps the run red, as before.
|
|
147
|
+
- On a bot PR, bot-prs reads that marker off the cancelled job's annotations
|
|
148
|
+
and treats the run as **not yet judged**: it re-runs it once a backoff has
|
|
149
|
+
passed since the cancel (30 minutes, doubling per attempt), up to 4 attempts,
|
|
150
|
+
then reports it as "still contended after N attempts: needs a human or a
|
|
151
|
+
quieter runner", distinct from a failure. Repo variables
|
|
152
|
+
`BOT_PRS_CONTENDED_ATTEMPTS` and `BOT_PRS_CONTENDED_BACKOFF_MINUTES` override
|
|
153
|
+
the budget. A run cancelled for any other reason (a person, a concurrency
|
|
154
|
+
group) is treated exactly as before.
|
|
146
155
|
- If the host is starved early in the suite, or for a long stretch anywhere, the
|
|
147
156
|
suite is stopped there rather than run for 40 minutes to an uncertain end.
|
|
148
157
|
- An uncertain attempt is rerun in the same job (`rerun.max`, default 1), after
|
|
@@ -186,10 +195,9 @@ real failure; 75 (EX_TEMPFAIL) when uncertain, which the workflow believes only
|
|
|
186
195
|
alongside a `verdict.json` that says so.
|
|
187
196
|
|
|
188
197
|
**Caveats.** A `ctl-candidate` dispatch that is cancelled is still scored red by
|
|
189
|
-
release-collect (the gate is advisory). bot-prs
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
in-job rerun is what retries.
|
|
198
|
+
release-collect (the gate is advisory). bot-prs re-runs an uncertain cancel
|
|
199
|
+
only while the annotation is readable with the job token (`checks: read`); if
|
|
200
|
+
it cannot read it, the cancel counts as red, as every cancel did before.
|
|
193
201
|
|
|
194
202
|
## Reaching an image product's control plane
|
|
195
203
|
|
|
@@ -518,12 +518,17 @@ jobs:
|
|
|
518
518
|
# host was starved. Believed only with the verdict file that says so,
|
|
519
519
|
# since a suite could exit 75 for its own reasons. Not a failure:
|
|
520
520
|
# the last step of this job cancels the run instead, so it reads
|
|
521
|
-
# cancelled (not red) on the board and bot-prs
|
|
521
|
+
# cancelled (not red) on the board, and bot-prs re-runs it later
|
|
522
|
+
# rather than merging it or reporting it red.
|
|
522
523
|
# Every other status, a real failure on a quiet box included, exits
|
|
523
|
-
# exactly as it did.
|
|
524
|
+
# exactly as it did. The %2C is a comma: workflow-command properties
|
|
525
|
+
# are comma separated, so a bare one cut the title short.
|
|
524
526
|
verdict="$NORSK_CONTENTION_DIR/verdict.json"
|
|
525
527
|
if [ "$rc" -eq 75 ] && [ "$(jq -r .status "$verdict" 2>/dev/null)" = uncertain ]; then
|
|
526
|
-
echo "::warning title=integration uncertain
|
|
528
|
+
echo "::warning title=integration uncertain%2C the host was contended::$(jq -r .reason "$verdict"). This run will be cancelled rather than failed; the pressure timeline and per-test classification are in the job summary and the integration artifact."
|
|
529
|
+
# The marker bot-prs reads off this job's annotations to tell this
|
|
530
|
+
# cancel from any other (bot-prs-contended.test.ts).
|
|
531
|
+
echo "::notice title=ctl-contention-uncertain::cancelled as uncertain, not failed: bot-prs re-runs it once the host may be quieter"
|
|
527
532
|
echo "NORSK_CONTENTION_UNCERTAIN=1" >> "$GITHUB_ENV"
|
|
528
533
|
exit 0
|
|
529
534
|
fi
|
|
@@ -586,7 +591,8 @@ jobs:
|
|
|
586
591
|
# step flagged this run uncertain: its failures were the starved host's,
|
|
587
592
|
# not the product's, so it is cancelled rather than failed. A cancelled
|
|
588
593
|
# run is not reported to the CI board (notify is !cancelled()), and
|
|
589
|
-
# bot-prs merges only a success, so a bump cannot land on it
|
|
594
|
+
# bot-prs merges only a success, so a bump cannot land on it (it re-runs
|
|
595
|
+
# a run whose job carries the suite step's ctl-contention-uncertain notice).
|
|
590
596
|
# Cancellation is asynchronous: the step holds the job until it lands,
|
|
591
597
|
# and if it is refused or never lands the job FAILS, because an uncertain
|
|
592
598
|
# run must never finish reading green. contention-wired.test.ts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@norskvideo/ctl-dev-kit",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.102",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./create-product": "./create-product/create-product.ts",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@norskvideo/ctl-oas-to-ts": "^0.1.0",
|
|
42
|
-
"@norskvideo/norsk-sdk": "1.3.1-2026-10-
|
|
42
|
+
"@norskvideo/norsk-sdk": "1.3.1-2026-10-22-aeac91a4",
|
|
43
43
|
"@norskvideo/norsk-studio": "1.30.0-2026-10-20-6fb182ff",
|
|
44
44
|
"@norskvideo/norsk-studio-builder": "1.30.0-2026-10-20-6fb182ff",
|
|
45
45
|
"@norskvideo/norsk-studio-built-ins": "1.30.0-2026-10-20-6fb182ff",
|