autonomous-sdlc-harness 0.6.1 → 0.6.3
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/dist/commands/init.js +73 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/config/check.js +3 -2
- package/dist/config/check.js.map +1 -1
- package/dist/config/model.js +1 -1
- package/dist/config/model.js.map +1 -1
- package/dist/core/writer.js +3 -2
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +262 -18
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/githubWorkflows.js +60 -2
- package/dist/generators/githubWorkflows.js.map +1 -1
- package/dist/remote/githubActions.js +59 -3
- package/dist/remote/githubActions.js.map +1 -1
- package/package.json +1 -1
- package/templates/claude/context/conventions.md +1 -1
- package/templates/claude/context/layer.md +1 -1
- package/templates/github/workflows/harness-control.yml +21 -5
- package/templates/github/workflows/harness-resume.yml +6 -5
- package/templates/github/workflows/harness-run.yml +143 -7
- package/templates/github/workflows/harness-trigger.yml +4 -1
- package/templates/scripts/autonomous-watcher.sh +121 -21
- package/templates/scripts/lib/harness-run-lib.sh +140 -13
- package/templates/scripts/push-branch.sh +69 -11
- package/templates/scripts/remote-run.sh +1428 -266
- package/templates/state-dir/user_reviews/README.md +1 -1
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
# remote-run.sh discard <dir> [--repo <root>]
|
|
33
33
|
# remote-run.sh report <event> <branch> [--note <text>] [--repo <root>]
|
|
34
34
|
# (always 0, 1 only on a usage error: its paragraph)
|
|
35
|
+
# remote-run.sh open <branch> [--repo <root>]
|
|
36
|
+
# (always 0, 1 only on a usage error: its paragraph)
|
|
35
37
|
# remote-run.sh deliver <branch> <bundle_dir> [--repo <root>]
|
|
36
38
|
# (always 0, 1 only on a usage error: its paragraph)
|
|
37
39
|
# remote-run.sh collect <branch> [--pr <n>] [--repo <root>]
|
|
@@ -53,9 +55,7 @@
|
|
|
53
55
|
# 1 usage error, or the library or the configuration could not be
|
|
54
56
|
# resolved; for fetch, <out_dir> is not an existing, empty directory;
|
|
55
57
|
# for discard, <dir>'s parent does not resolve or the removal failed;
|
|
56
|
-
# for sync and restore, a local copy or write failed
|
|
57
|
-
# pause-requested, also NO such run — a caller that reads 1 as "no
|
|
58
|
-
# pause" passes arguments it has already validated
|
|
58
|
+
# for sync and restore, a local copy or write failed
|
|
59
59
|
# 2 refused, nothing sent or written: execution.target is not
|
|
60
60
|
# github-actions (sending verbs, fetch, review and list); for
|
|
61
61
|
# review, a protected branch, a review file that is not a readable
|
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
# copy (the branch checked out in another working copy included), the
|
|
92
92
|
# fast-forward, the commit or the push — and nothing was dispatched; a
|
|
93
93
|
# copy it cut was removed
|
|
94
|
+
# 5 pause-requested only: the read succeeded and found no such run
|
|
94
95
|
#
|
|
95
96
|
# `start` IS THE ADAPTERS' ONE ENTRY: every trigger (an issue event, a forge
|
|
96
97
|
# dispatch, anything later) reduces to a branch and a task text and ends here.
|
|
@@ -191,13 +192,18 @@
|
|
|
191
192
|
# a refusal can still be commented. It reads, only from the environment:
|
|
192
193
|
# GITHUB_EVENT_NAME, GITHUB_EVENT_PATH the event; each field is read by `jq`
|
|
193
194
|
# into a variable and is only ever an argument or file
|
|
194
|
-
# bytes, never shell source
|
|
195
|
+
# bytes, never shell source; `.repository.owner.login`
|
|
196
|
+
# and `.repository.owner.type` only when
|
|
197
|
+
# `HARNESS_RUN_ACTORS` is unset
|
|
195
198
|
# GITHUB_REPOSITORY, GITHUB_SERVER_URL, GITHUB_RUN_ID the `gh` target and
|
|
196
199
|
# the URLs its comments name
|
|
197
200
|
# HARNESS_REMOTE_STOP non-empty: every start is refused
|
|
198
201
|
# HARNESS_TRIGGER_LABEL the trigger label; when empty, `DEFAULT_TRIGGER_LABEL`
|
|
199
202
|
# or `LEGACY_TRIGGER_LABEL`, per the paragraph below
|
|
200
203
|
# HARNESS_TRIGGER_ALLOWED_BOTS comma-separated bot logins allowed to start
|
|
204
|
+
# HARNESS_RUN_ACTORS comma-separated people allowed to act on a run, `*`
|
|
205
|
+
# for every writer; unset admits a `User` owner alone
|
|
206
|
+
# (`run_actor_listed`)
|
|
201
207
|
# HARNESS_TRIGGER_LOOKUP_SECS seconds between run lookups; `5` when empty.
|
|
202
208
|
# A test seam
|
|
203
209
|
# RUNNER_TEMP where the prompt snapshot is written; a `mktemp -d`
|
|
@@ -226,7 +232,13 @@
|
|
|
226
232
|
# 6. a `User` whose `collaborators/<login>/permission` is not `admin` or
|
|
227
233
|
# `write` — `maintain` reads as `write` and `triage` as `read` there; a
|
|
228
234
|
# failed call is "could not confirm write access", never a pass
|
|
229
|
-
#
|
|
235
|
+
# 7. a writer `HARNESS_RUN_ACTORS` does not admit — after refusal 6, so a
|
|
236
|
+
# non-writer is still told about write access
|
|
237
|
+
# Refusals 4 to 7 are `authorise_actor`, the one actor check `control` reuses.
|
|
238
|
+
# Before them, after refusal 2, a re-run (GITHUB_RUN_ATTEMPT above 1) whose
|
|
239
|
+
# GITHUB_TRIGGERING_ACTOR is not `github-actions[bot]` and is not admitted by
|
|
240
|
+
# `HARNESS_RUN_ACTORS` is refused (`rerun_actor_listed`), on both event kinds:
|
|
241
|
+
# a re-run replays the event, so its sender is whoever first acted.
|
|
230
242
|
# Then it fetches `origin <defaultBranch>` (a failure tolerated), derives the
|
|
231
243
|
# branch with `hr_derive_branch <title> issue_<number>`, passing `gh` so a name
|
|
232
244
|
# with run-workflow history counts as taken (2 or 3 refused), writes
|
|
@@ -252,7 +264,10 @@
|
|
|
252
264
|
# and exit 0 with no `gh` call; an empty or missing `title` is refused. It has
|
|
253
265
|
# no labeller: GitHub sends one only for a fine-grained token with Contents
|
|
254
266
|
# write or a classic token with `repo`, so the token holder is the authority,
|
|
255
|
-
# and refusals 3 to 6 do not apply.
|
|
267
|
+
# and refusals 3 to 6 do not apply. After refusal 2 a `User` sender
|
|
268
|
+
# (`.sender.login`, `.sender.type`) that `HARNESS_RUN_ACTORS` does not admit is
|
|
269
|
+
# refused with no permission call, and so is a sender with no type unless the
|
|
270
|
+
# list is `*`, failing closed; any other sender type passes. The fallback name is `task_<GITHUB_RUN_ID>`
|
|
256
271
|
# and the snapshot's provenance sentence names the event type, `source` when
|
|
257
272
|
# set, and the time. There is no issue, so no comment and no label: every
|
|
258
273
|
# outcome is printed to stdout and appended as a Markdown block to
|
|
@@ -271,18 +286,38 @@
|
|
|
271
286
|
# as `trigger` is, and outside the sending-verb gate: it does nothing, with one
|
|
272
287
|
# line, unless `hr_forge` is `github` and `hr_execution_target` is
|
|
273
288
|
# `github-actions`. The comment goes to the open same-repository pull request
|
|
274
|
-
# whose head is <branch> when origin's <branch> carries
|
|
275
|
-
# `<state_dir>/flow_progress/<branch>_progress.md
|
|
276
|
-
# the last `Started from <server>/<repo>/issues/<n> by @` line of its committed
|
|
289
|
+
# whose head is <branch> when origin's <branch> carries its task prompt or
|
|
290
|
+
# `<state_dir>/flow_progress/<branch>_progress.md` (`forge_recognised`), else to
|
|
291
|
+
# the issue named by the last `Started from <server>/<repo>/issues/<n> by @` line of its committed
|
|
277
292
|
# task prompt, else nowhere (`stop`'s `--pr` and `--branch-gone` change this
|
|
278
293
|
# for its own report: its paragraph). The label `STATE_LABEL_PREFIX<state>` replaces any
|
|
279
294
|
# other state label on that issue and that pull request, each when known; the
|
|
280
295
|
# label is a view, and the run list stays the authority. The state map:
|
|
281
296
|
# `parked` and `park_loop` -> parked, `paused` -> paused, `resumed` -> running,
|
|
282
|
-
# `failed` -> failed, `stopped` -> stopped, `round` (review's) -> running
|
|
283
|
-
# `
|
|
284
|
-
# `
|
|
285
|
-
#
|
|
297
|
+
# `failed` -> failed, `stopped` -> stopped, `round` (review's) -> running,
|
|
298
|
+
# `not_started` (collect's) -> paused or failed, as its caller says. A
|
|
299
|
+
# `not_started` comment says GitHub did not start the run's job, so nothing ran
|
|
300
|
+
# and the branch is unchanged, and names its way on: `COMMAND_HANDLE resume`
|
|
301
|
+
# when the state is paused and an engine was recovered, else the **Run
|
|
302
|
+
# workflow** form with that engine, or with the three to choose from.
|
|
303
|
+
# On a pull-request target that is not a draft, `round` runs `gh pr ready
|
|
304
|
+
# --undo` before its comment, with the caller's own token, and its comment says
|
|
305
|
+
# the pull request is a draft again until the round completes; a refusal (a
|
|
306
|
+
# plan without drafts) is one `::warning::` line naming gh's error, and the
|
|
307
|
+
# comment then says turning it back was refused and it stays ready for review
|
|
308
|
+
# while the round works. A `failed` comment on a pull request reads the
|
|
309
|
+
# registry record's `engine`: for
|
|
310
|
+
# `user_review` it names a review requesting changes and says the pull request
|
|
311
|
+
# stays open; otherwise it says the draft stays open, to close to discard the
|
|
312
|
+
# run, or, when an issue is known, to re-apply the trigger label there for a
|
|
313
|
+
# new run on the next indexed branch. A plain `stopped` on a pull request says
|
|
314
|
+
# its draft stays open and closing it discards the run. No other event changes
|
|
315
|
+
# a pull request's draft state.
|
|
316
|
+
# Every job event — `parked`, `park_loop`, `paused`, `resumed`, `round`,
|
|
317
|
+
# `failed` and `not_started` — posts nothing and sets no label when `remote_branch_stopped`,
|
|
318
|
+
# asked afresh, finds the branch stopped, so a job a stop overtook never
|
|
319
|
+
# overwrites `stopped`; a failed listing reports anyway, and `stopped` is never
|
|
320
|
+
# withheld. `completed` (deliver's) and `launched` (the trigger's
|
|
286
321
|
# own comment) are one line each, as is any other event. The comment names the
|
|
287
322
|
# next GitHub action — never a slash command — then <note> byte for byte, then
|
|
288
323
|
# this run's URL when `GITHUB_RUN_ID` is set, then the marker line
|
|
@@ -303,36 +338,127 @@
|
|
|
303
338
|
# comment and its answer are public, as the artifact already is
|
|
304
339
|
# (`docs/remote-execution.md` -> `## 11. Security`, *What a reader of the
|
|
305
340
|
# repository's Actions runs can see*).
|
|
341
|
+
# `progress` (`forge_progress`) keeps ONE comment per run or round on the pull
|
|
342
|
+
# request, never on the issue, and sets no label. Gated in order, each stop one
|
|
343
|
+
# line: `forge_on`; `hr_progress_comments` (`execution.progressComments` false,
|
|
344
|
+
# or unreadable); `remote_branch_stopped`, as for the job events above; no task
|
|
345
|
+
# or user-review ledger at `$root`'s `<state_dir>/flow_progress/<branch>_progress.md`
|
|
346
|
+
# (`hr_ledger_phases` — a docs-engine ledger posts nothing); no open
|
|
347
|
+
# same-repository pull request that `forge_recognised` holds for. The body is
|
|
348
|
+
# *Progress of the harness run on `<branch>`:* or *Progress of user-review round
|
|
349
|
+
# <n> on `<branch>`:*, then `- <label>: done`, `in progress` (the first pending
|
|
350
|
+
# phase) or `not started` for `Planning`, `Implementation`, `Branch review`,
|
|
351
|
+
# `Done` (a round: `Fix plan`, `Fix implementation`, `Branch review`, `Done`) —
|
|
352
|
+
# plain list items, no timestamp, no run URL — then the marker
|
|
353
|
+
# `<!-- sdlc-harness event=progress branch=<branch> -->`, gaining ` round=<n>`
|
|
354
|
+
# for a round. One paginated comment listing picks the newest
|
|
355
|
+
# `github-actions[bot]` comment whose last non-empty line is that marker: none
|
|
356
|
+
# is one `forge_comment`; a body equal to the render (carriage returns and
|
|
357
|
+
# trailing newlines aside) is no call and one `already current` line; otherwise
|
|
358
|
+
# one `PATCH` of that comment — the only comment this file ever edits. A refused
|
|
359
|
+
# listing, create or edit is one `::warning::` line.
|
|
306
360
|
# It never fails its caller: every problem is one line and exit 0.
|
|
307
361
|
#
|
|
362
|
+
# `open` OPENS THE RUN'S DRAFT PULL REQUEST at the run's start, so its issue
|
|
363
|
+
# links it from the moment it exists. Job-side for its root and self-gated, as
|
|
364
|
+
# `deliver` is (`forge_on`, one line when off); it takes <branch> and no
|
|
365
|
+
# bundle. A pull-request lookup that fails is one `::warning::` line and opens
|
|
366
|
+
# nothing; an open same-repository pull request whose head is <branch> is one
|
|
367
|
+
# line, and none is opened. Otherwise `forge_open_pr` — the one opener, which
|
|
368
|
+
# `deliver` also calls — creates it against `defaultBranch` with `deliver`'s
|
|
369
|
+
# title, body, token and retry rules: a `--draft` create with
|
|
370
|
+
# `HARNESS_PR_TOKEN` as `GH_TOKEN` when set, no retry on `PR_CREATE_FORBIDDEN`,
|
|
371
|
+
# one retry without `--draft` on any other failure. Opened, the pull request is
|
|
372
|
+
# labelled `STATE_LABEL_PREFIX`running, and when the task prompt names an issue
|
|
373
|
+
# one comment there names it — as a draft, or as not a draft because the
|
|
374
|
+
# repository's plan may not offer drafts — says the run's questions, lifecycle
|
|
375
|
+
# comments and progress go to it from now on while `COMMAND_HANDLE` commands
|
|
376
|
+
# keep working on the issue, adds this run's URL when `GITHUB_RUN_ID` is set,
|
|
377
|
+
# and ends with the `opened` marker. With no issue nothing is posted. Not
|
|
378
|
+
# opened is one `::warning::` line naming why and saying the run goes on, its
|
|
379
|
+
# comments reach the issue and `deliver` tries again at the run's end; nothing
|
|
380
|
+
# is posted, since `deliver`'s `completed` comment is the one notification.
|
|
381
|
+
# It never fails its caller: every outcome but a usage error is exit 0.
|
|
382
|
+
#
|
|
308
383
|
# `deliver` HANDS A COMPLETED RUN TO REVIEW: the run workflow's step after the
|
|
309
384
|
# job's own `push-branch.sh`, which opens no pull request. Job-side for its
|
|
310
385
|
# root and self-gated, as `report` is (`forge_on`, one line when off). Anything
|
|
311
386
|
# but `status: completed` in <bundle_dir>/status.json, or no status.json, is
|
|
312
|
-
# one line and nothing sent.
|
|
313
|
-
#
|
|
314
|
-
#
|
|
315
|
-
#
|
|
316
|
-
#
|
|
317
|
-
#
|
|
318
|
-
#
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
322
|
-
#
|
|
323
|
-
#
|
|
324
|
-
#
|
|
325
|
-
#
|
|
326
|
-
#
|
|
327
|
-
# branch
|
|
328
|
-
#
|
|
329
|
-
#
|
|
330
|
-
#
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
387
|
+
# one line and nothing sent. The bundle's `engine` names what completed:
|
|
388
|
+
# `user_review` is a round, anything else a run. An open same-repository pull
|
|
389
|
+
# request whose head is <branch> is reused and no second one is opened. With
|
|
390
|
+
# none open and a lookup that succeeded, `forge_open_pr` is the fallback — one
|
|
391
|
+
# line says none was open at completion, because the start's attempt failed or
|
|
392
|
+
# the workflow predates `open`. It is created against `defaultBranch`, titled
|
|
393
|
+
# from the task prompt's `# ` first line (cut to `PR_TITLE_MAX_CHARS`, else
|
|
394
|
+
# <branch>), with a body naming the issue as `Started from #<n>.` — a plain
|
|
395
|
+
# mention, never a closing keyword — stating what a review requesting changes
|
|
396
|
+
# and the `COMMAND_HANDLE` commands do, and ending with the `pull-request`
|
|
397
|
+
# marker: a `--draft` create with `HARNESS_PR_TOKEN` as `GH_TOKEN` when set, so
|
|
398
|
+
# the adopter's CI runs without an approval click, no retry on
|
|
399
|
+
# `PR_CREATE_FORBIDDEN`, one retry without `--draft` on any other failure. A
|
|
400
|
+
# pull request so opened is authored by that token's owner, who therefore
|
|
401
|
+
# cannot request changes on it: use a machine account's token, another
|
|
402
|
+
# reviewer, or a local `branch-user-review` round. A lookup that fails opens
|
|
403
|
+
# nothing. Then the ready flip, with the job's token and never
|
|
404
|
+
# `HARNESS_PR_TOKEN`: a draft gets one `gh pr ready` (flipped), and a refusal
|
|
405
|
+
# is one `::warning::` line naming gh's error (refused); a pull request that is
|
|
406
|
+
# not a draft — no drafts on the plan, or a person marked it ready — gets no
|
|
407
|
+
# call (not a draft) and is left alone. A round's review threads are handled
|
|
408
|
+
# next, after the flip and before the comments, with the job's token, per THE
|
|
409
|
+
# REVIEW-COMMENT LINE below: only when the pull request is known, the
|
|
410
|
+
# highest-numbered round file on origin's tip is the highest-numbered marked
|
|
411
|
+
# one (`round_markers_read`) — a round placed by the local command consumes no
|
|
412
|
+
# pull-request item — and its marker's `comments=` is non-empty; otherwise one
|
|
413
|
+
# line. Its fix plan is `<branch>_fix_plan.md` and `<branch>_fix_plan/` for
|
|
414
|
+
# round 1, `<branch>_fix_plan_<n>.md` and `<branch>_fix_plan_<n>/` for round
|
|
415
|
+
# <n>, read from origin's tip. A verdict is kept only for an id the round
|
|
416
|
+
# collected: `fixed <sha>` for an implemented finding, `reason <text>` for an
|
|
417
|
+
# out-of-scope bullet; a collected id with none is one line, left alone. One
|
|
418
|
+
# paginated `api graphql` listing of the pull request's `reviewThreads` (`id`,
|
|
419
|
+
# `isResolved`, each comment's `databaseId`, `createdAt` and `body`); the
|
|
420
|
+
# thread holding a collected id is that id's thread. Left alone, one line
|
|
421
|
+
# each: a thread already resolved; a thread with a comment created after the
|
|
422
|
+
# round's `collected_at` that is not a collected id and carries no
|
|
423
|
+
# `COMMENT_MARKER` — the reviewer replied after the collection; and a thread
|
|
424
|
+
# already carrying this round's `thread` marker (` round=<n>` included), so a
|
|
425
|
+
# re-run of the same round replies nothing twice while a later round still
|
|
426
|
+
# handles a thread an earlier round replied to. Otherwise one reply, through
|
|
427
|
+
# `pulls/<pr>/comments/<id>/replies` to the thread's first comment, ending with
|
|
428
|
+
# `forge_marker thread <branch>` and the round's ` round=<n>` — whose
|
|
429
|
+
# `COMMENT_MARKER` keeps the next `round_collect` from collecting it: for
|
|
430
|
+
# `fixed`, ``Addressed in `<sha>`.`` and, only once that reply is posted, one
|
|
431
|
+
# `resolveReviewThread` mutation; for `reason`, `Not changed in this round:
|
|
432
|
+
# <reason>`, the thread left open. A refused listing, reply or resolve is one
|
|
433
|
+
# `::warning::` line. It never dismisses a review.
|
|
434
|
+
# THE REVIEW-COMMENT LINE is this file's contract, written by one writer,
|
|
435
|
+
# `plugin/agents/user-review-fix-plan-writer.md`, and read by `deliver` alone:
|
|
436
|
+
# - in a per-finding file `<state_dir>/user_reviews/<branch>_fix_plan[_<n>]/
|
|
437
|
+
# finding_<K>.md`, one line beginning `**Review comments:** ` followed by
|
|
438
|
+
# one or more comment ids separated by `, `, e.g.
|
|
439
|
+
# `**Review comments:** 2735551234, 2735551240`;
|
|
440
|
+
# - in the fix-plan index's `## Out of scope / verified-OK` section, a bullet
|
|
441
|
+
# (a line beginning `- `) ending with ` **Review comments:** <id>[, <id>…]`,
|
|
442
|
+
# its text before that suffix the reason the reply quotes;
|
|
443
|
+
# - the ids are the round marker's `comments=` ids, GitHub's review-comment
|
|
444
|
+
# `id`s; an observation from no inline comment carries no such line;
|
|
445
|
+
# - finding <K> is implemented when the index carries the literal
|
|
446
|
+
# `[x] **Finding <K>**`, and its fix commit is the oldest commit on
|
|
447
|
+
# `origin/<branch>` that introduced that literal into the index (`git log
|
|
448
|
+
# --reverse -S`), else origin's tip — the committer flips the entry in the
|
|
449
|
+
# fix's own commit.
|
|
450
|
+
# Then one `completed` comment
|
|
451
|
+
# on the pull request, its readiness clause by the flip's outcome, and one on
|
|
452
|
+
# the issue naming the pull request's number and URL, each when known; with no
|
|
453
|
+
# pull request, one comment on the issue alone naming why none could be opened
|
|
454
|
+
# — the Actions setting and `HARNESS_GIT_TOKEN` on `PR_CREATE_FORBIDDEN`, else
|
|
455
|
+
# gh's error — with the branch's compare URL; nowhere when neither is known.
|
|
456
|
+
# Each names reviewing and requesting changes as the next action, and with
|
|
457
|
+
# `phases.qa` true the local `branch-qa-test` still owed. Then
|
|
458
|
+
# `sdlc-harness: done` on the issue and the pull request, each when known. It
|
|
459
|
+
# writes at most one pull request, one flip, a round's thread replies and
|
|
460
|
+
# resolves, two comments and those labels, and never pushes. It never fails its caller: every problem, a refused flip
|
|
461
|
+
# included, is one line and exit 0.
|
|
336
462
|
#
|
|
337
463
|
# `collect` STARTS THE NEXT ROUND FROM THE REVIEWS COLLECTED DURING A RUN: the
|
|
338
464
|
# one step of the run workflow's `collect` job, which follows its `run` job, so
|
|
@@ -340,19 +466,27 @@
|
|
|
340
466
|
# Job-side for its root and self-gated, as `report` and `deliver` are
|
|
341
467
|
# (`forge_on`, one line when off). In order, each stop one line and exit 0:
|
|
342
468
|
# `HARNESS_REMOTE_STOP` set; `remote_branch_stopped` finding the branch stopped,
|
|
343
|
-
# or failing;
|
|
344
|
-
#
|
|
469
|
+
# or failing; `branch_settled_var`, read as `control` reads it, failing; the
|
|
470
|
+
# newest run's job never started — when that run is this one (or
|
|
471
|
+
# `GITHUB_RUN_ID` is unset), `report`'s `not_started` on its pull request or
|
|
472
|
+
# issue, with the state and engine the read derived, and one push notification,
|
|
473
|
+
# no round collected, since the reviews stay for the resumed run's own end;
|
|
474
|
+
# when it is another run, one line, since that run's own `collect` reports it;
|
|
475
|
+
# no pull request, from --pr or else `forge_pr_var` (a failed lookup
|
|
476
|
+
# included); the settledness read finding the
|
|
345
477
|
# branch in flight — a newer run listed, or this run ending `parked`,
|
|
346
478
|
# `park_loop` or `paused` (a budget chain's included), whose own end collects
|
|
347
|
-
# next
|
|
479
|
+
# next; `round_collect` finding no review requesting changes
|
|
348
480
|
# pending (inline comments alone start no round, as *Comment* starts none; they
|
|
349
481
|
# ride along in the next), or failing, a `::warning::` line. Otherwise `review
|
|
350
482
|
# <branch> --review-file <file> --allow-no-run --reviewers <logins> --source
|
|
351
483
|
# <pull request url>` runs as a child, as `control` runs it, placing,
|
|
352
484
|
# dispatching and reporting the round. A child that exits non-zero gets exactly
|
|
353
485
|
# one comment on the pull request, with the `reply` marker, naming its last
|
|
354
|
-
# stderr line and saying the reviews stay there and that
|
|
355
|
-
#
|
|
486
|
+
# stderr line and saying the reviews stay there and that, when `GITHUB_RUN_ID`
|
|
487
|
+
# is set, re-running this run's `collect` job retries with no new review, and
|
|
488
|
+
# that submitting a review requesting changes retries; there is no automatic
|
|
489
|
+
# retry. It never fails its
|
|
356
490
|
# caller: every outcome but a usage error is exit 0.
|
|
357
491
|
#
|
|
358
492
|
# `control` IS THE COMMENT AND REVIEW ADAPTER, the twin of `trigger` and the one
|
|
@@ -379,7 +513,10 @@
|
|
|
379
513
|
# 1. `HARNESS_REMOTE_STOP` is set
|
|
380
514
|
# 2. `forge_on` fails — before any authorisation, so a disabled coupling asks
|
|
381
515
|
# GitHub nothing about the commenter
|
|
382
|
-
#
|
|
516
|
+
# then a re-run whose GITHUB_TRIGGERING_ACTOR `rerun_actor_listed`
|
|
517
|
+
# refuses, before the event's own actor is checked
|
|
518
|
+
# 3. `authorise_actor` fails: `AUTH_WHY`, and who may command a run — a
|
|
519
|
+
# writer the `HARNESS_RUN_ACTORS` allow-list admits, or a listed bot
|
|
383
520
|
# 4. the verb is empty, not a `COMMAND_VERBS` word, or one no arm carries out
|
|
384
521
|
# yet: the reply lists every command and names `docs/github-run-control.md`
|
|
385
522
|
# THE BRANCH. On a pull request (`.issue.pull_request.url` set), its head, by
|
|
@@ -393,8 +530,20 @@
|
|
|
393
530
|
# marker quoted mid-body, in a comment not opening with that sentence, or by
|
|
394
531
|
# anyone else, is never trusted; none is a refusal. Both paths then pass
|
|
395
532
|
# `control_check_branch`: a branch `hr_branch_is_protected` does not answer 1
|
|
396
|
-
# for is refused, then it is fetched
|
|
397
|
-
# flow-progress ledger (`forge_recognised`)
|
|
533
|
+
# for is refused, then it is fetched. It is a harness branch when its origin
|
|
534
|
+
# tip carries its task prompt or the flow-progress ledger (`forge_recognised`)
|
|
535
|
+
# — the task prompt, `start`'s first commit, makes a pull request opened at
|
|
536
|
+
# the run's start commandable before the ledger's first push; or, for a command
|
|
537
|
+
# typed on an issue, when it is the branch that issue's genuine `started`
|
|
538
|
+
# marker names and it still exists on origin (`remote_branch_exists`) — the
|
|
539
|
+
# marker shows the harness started a run on it, so a first run GitHub never
|
|
540
|
+
# started, which committed only its task prompt, is still commandable; an
|
|
541
|
+
# issue keeps its marker after its branch is deleted, so a marker-named branch
|
|
542
|
+
# gone from origin is refused as deleted, and a failed existence check is a
|
|
543
|
+
# refusal naming the read; or when a `harness run <branch>` run is queued,
|
|
544
|
+
# waiting, requested, pending or in progress (`control_run_in_flight`), so a
|
|
545
|
+
# run is commandable from its first minute on its pull request too. Otherwise
|
|
546
|
+
# it is not a harness branch. A failed listing is a refusal naming the read.
|
|
398
547
|
# THE ARMS. `pause`: the state by a `fetch` child (`control_state_var`); only
|
|
399
548
|
# `running` sends `pause <branch>` as a child and replies that the run yields at
|
|
400
549
|
# its next clean checkpoint; any other state is a refusal naming it. `stop`:
|
|
@@ -409,9 +558,11 @@
|
|
|
409
558
|
# accepts only `park_loop`, the GitHub form of `branch-resume`'s confirmation;
|
|
410
559
|
# any other state is a refusal naming it. Each sends the local relay's dispatch,
|
|
411
560
|
# `dispatch <branch> --engine <the state's engine> --resume pause --chain 0`,
|
|
412
|
-
# `clear` with `park_loop_clear` set, and no other command sets it.
|
|
413
|
-
#
|
|
414
|
-
#
|
|
561
|
+
# `clear` with `park_loop_clear` set, and no other command sets it. A run whose
|
|
562
|
+
# job GitHub never started first takes the engine its dispatch's comment
|
|
563
|
+
# records (`forge_dispatch_engine_var`); an engine still empty after that is
|
|
564
|
+
# refused, never guessed (the `engine` input defaults to `task`), and the
|
|
565
|
+
# refusal names the Run workflow form. On 0 a reply, then `running` on the run's issue
|
|
415
566
|
# and pull request; on 2 a refusal and exit 2. `answer`: the first line is
|
|
416
567
|
# `answer <n>` and the answer every line below it, a trailing CR stripped from
|
|
417
568
|
# each line and its bytes otherwise unchanged; text after <n> on the first line
|
|
@@ -451,8 +602,12 @@
|
|
|
451
602
|
# first `- [ ] ` entry of the flow-progress ledger on origin's tip with the
|
|
452
603
|
# `## ` heading above it, and replies once — the state by `CS_WORD` with its
|
|
453
604
|
# pause reason, the state underneath a stopped one, an expired bundle's detail,
|
|
454
|
-
# the next ledger entry (or
|
|
455
|
-
#
|
|
605
|
+
# the next ledger entry (or, on a fully ticked ledger, that a user-review round
|
|
606
|
+
# has started and its ledger is not written yet, when origin's tip carries the
|
|
607
|
+
# round's `chore: add user review for <branch>` commit after the ledger's last
|
|
608
|
+
# change; else, for a `running` run, that it is still finishing or starting a
|
|
609
|
+
# stage, never that every entry is ticked; else that every entry is ticked; or
|
|
610
|
+
# that the ledger could not be read), each open question with its `answer <n>` form, and the
|
|
456
611
|
# latest run's URL. It sets no label and runs no child verb but `fetch`; a
|
|
457
612
|
# failed state read is a refusal and exit 3.
|
|
458
613
|
# THE REVIEW. A `pull_request_review` event reads `.action`, `.review.state`,
|
|
@@ -496,9 +651,10 @@
|
|
|
496
651
|
# inline comment, by any author and whatever its review's state, whose id is
|
|
497
652
|
# unrecorded, whose body carries no `COMMENT_MARKER`, and whose `created_at` is
|
|
498
653
|
# at or after the boundary or whose review is pending. Every distinct author
|
|
499
|
-
# of a pending item passes `authorise_actor
|
|
500
|
-
#
|
|
501
|
-
#
|
|
654
|
+
# of a pending item passes `authorise_actor`, the `HARNESS_RUN_ACTORS`
|
|
655
|
+
# allow-list included; a refused author's items — a writer the list refuses
|
|
656
|
+
# among them — are dropped with one line naming the login, `AUTH_WHY` and the
|
|
657
|
+
# count, and a failed permission call (status 4) fails the collection. A round is placed only when at
|
|
502
658
|
# least one kept review has state `REVIEW_ROUND_STATE`; a *Comment* or
|
|
503
659
|
# *Approve* review rides along in the next round one requesting changes starts.
|
|
504
660
|
# The file: one `## Review by @<login>` section per pending review, oldest
|
|
@@ -531,8 +687,10 @@
|
|
|
531
687
|
# submitting a review requesting changes retries now.
|
|
532
688
|
# A child's failure is a reply
|
|
533
689
|
# naming its last stderr line, and exit 3. Every reply goes to the item the comment was
|
|
534
|
-
# typed on, opens `@<login>`, and carries the `reply` marker; a
|
|
535
|
-
#
|
|
690
|
+
# typed on, opens `@<login>`, and carries the `reply` marker; a reply posted
|
|
691
|
+
# after a successful dispatch adds ` engine=<engine>` to that marker, the engine
|
|
692
|
+
# the dispatch named, so a run whose job never ran can still be resumed with
|
|
693
|
+
# it. A refusal reads `@<login>: `<verb>` was not run: <reason>. <way on>`.
|
|
536
694
|
# THE CLOSE. Whatever the run's phase, it is stopped when its issue is closed,
|
|
537
695
|
# its pull request closed or merged, or its branch deleted; the routing is by
|
|
538
696
|
# event name, never by verb, so a comment naming `close` is not one. `issues`
|
|
@@ -548,9 +706,11 @@
|
|
|
548
706
|
# 1. `HARNESS_REMOTE_STOP` is set
|
|
549
707
|
# 2. `forge_on` fails
|
|
550
708
|
# 3. the actor: on `issues` and `pull_request`, `authorise_actor` refused
|
|
551
|
-
# (statuses 1–3
|
|
552
|
-
#
|
|
553
|
-
#
|
|
709
|
+
# (statuses 1–3 and 5, so a writer the `HARNESS_RUN_ACTORS` allow-list
|
|
710
|
+
# refuses is ignored too), the line naming the login and `AUTH_WHY`; on
|
|
711
|
+
# `delete`, only a `Bot` sender `trigger_bot_listed` does not list, and
|
|
712
|
+
# never the allow-list: deleting a branch already needs write access, the
|
|
713
|
+
# run's branch is gone, and a stop spends no credential
|
|
554
714
|
# 4. no branch: an issue's from `control_issue_branch_var` (no genuine start
|
|
555
715
|
# comment), a pull request's head ref, a deletion's `.ref`; then not a
|
|
556
716
|
# valid branch name
|
|
@@ -559,8 +719,8 @@
|
|
|
559
719
|
# answers 1): GitHub closed the pull request because the branch was
|
|
560
720
|
# deleted, and the `delete` event's job stops the run; an `ls-remote` that
|
|
561
721
|
# cannot answer is one line and proceeds
|
|
562
|
-
# There is no
|
|
563
|
-
# branch may no longer carry
|
|
722
|
+
# There is no harness-branch check (`forge_recognised`): a merged or deleted
|
|
723
|
+
# branch may no longer carry its task prompt or ledger, and a listed `harness run <b>` run is the
|
|
564
724
|
# proof. The state is read by `control_state_var`: only `running`, `parked`,
|
|
565
725
|
# `park_loop` and `paused` are acted on; `completed`, `failed` and `none` are
|
|
566
726
|
# `left alone: the run on <b> is <state>`, and one `remote_branch_stopped`
|
|
@@ -657,7 +817,9 @@
|
|
|
657
817
|
# HARNESS_MAX_CHAIN the automatic-dispatch limit; `24` when empty. Not a
|
|
658
818
|
# non-negative integer: nothing is dispatched
|
|
659
819
|
# HARNESS_POLL_MAX_DISPATCH_FAILURES `poll` only: failed re-dispatches of
|
|
660
|
-
# one paused run
|
|
820
|
+
# one paused run, and consecutive failed downloads of
|
|
821
|
+
# one run's listed bundle, each counted apart, before it
|
|
822
|
+
# gives up; `3` when empty
|
|
661
823
|
# HARNESS_POLL_GIVE_UP_AFTER_MINUTES `poll` only: minutes after a run's
|
|
662
824
|
# `usage_resume_at` past which a failed re-dispatch gives
|
|
663
825
|
# up; `360` when empty. Either one not a non-negative
|
|
@@ -668,7 +830,9 @@
|
|
|
668
830
|
# GITHUB_RUN_ID, GITHUB_SERVER_URL, GITHUB_REPOSITORY the run URL
|
|
669
831
|
# Notifications go through the sibling `autonomous-notify.sh`, as `paused` or
|
|
670
832
|
# `failed`, and each is then reported as `report` reports that event, with a
|
|
671
|
-
# note of its own that names no slash command and no shell command.
|
|
833
|
+
# note of its own that names no slash command and no shell command. The one
|
|
834
|
+
# exception is `poll`'s push-only `bundle_unreadable`, which posts no comment
|
|
835
|
+
# and sets no label. A re-dispatch is `dispatch <branch> --engine <status.json engine>
|
|
672
836
|
# --resume pause --chain <chain + 1>`, composed by `dispatch` itself.
|
|
673
837
|
#
|
|
674
838
|
# `chain` HAS ONE SOURCE: the bundle's `status.json`, whose `chain` is the
|
|
@@ -715,7 +879,8 @@
|
|
|
715
879
|
# than `GITHUB_RUN_ID` (bounded) carrying an unexpired `harness-poll-state`
|
|
716
880
|
# artifact is downloaded to `<state_dir>/autonomous_logs/poll_state/previous/`;
|
|
717
881
|
# any failure to find or read it is one line and an empty state. Its
|
|
718
|
-
# `poll_state.json` is {"<branch>": {"run_id", "failures", "notified"
|
|
882
|
+
# `poll_state.json` is {"<branch>": {"run_id", "failures", "notified",
|
|
883
|
+
# "download_failures"}}, the last absent until a download fails; an
|
|
719
884
|
# entry whose `run_id` is not the branch's newest `harness run` run is dropped,
|
|
720
885
|
# so a new run restarts the count. `poll` writes the state to `poll_state/
|
|
721
886
|
# current/` on every exit, `HARNESS_REMOTE_STOP` included, and the poller
|
|
@@ -730,9 +895,16 @@
|
|
|
730
895
|
# download that fails for it is one line and waiting. For a `completed` run,
|
|
731
896
|
# skipped, not waiting: a stopped branch, a branch absent on origin (its state
|
|
732
897
|
# entry dropped, so a later branch of the name starts clean; an `ls-remote`
|
|
733
|
-
# that cannot answer is one line and proceeds), a
|
|
734
|
-
#
|
|
735
|
-
#
|
|
898
|
+
# that cannot answer is one line and proceeds), a run whose `run` job GitHub
|
|
899
|
+
# never started (one line; its `collect` job reports it), and anything but
|
|
900
|
+
# `status: paused` / `pause_reason: usage` with an integer `usage_resume_at`,
|
|
901
|
+
# and a run whose state entry says `notified`. A failed artifact lookup on a
|
|
902
|
+
# `completed` run counts as a failed download of its bundle, below. A listed
|
|
903
|
+
# bundle that cannot be downloaded is waiting, counted per run in `download_failures` and reset by a
|
|
904
|
+
# later successful download; at `HARNESS_POLL_MAX_DISPATCH_FAILURES`
|
|
905
|
+
# consecutive failures it sends exactly one `bundle_unreadable` push
|
|
906
|
+
# notification, with no comment and no label, the entry marked `notified`, and
|
|
907
|
+
# is no longer waiting. Due
|
|
736
908
|
# (reset passed): re-dispatched under the same chain limit — a refusal is one
|
|
737
909
|
# `failed` and not waiting; a success drops the branch's state entry. A
|
|
738
910
|
# dispatch that fails counts one more failure for that run and is still
|
|
@@ -757,9 +929,12 @@
|
|
|
757
929
|
# `pause-requested` lists the branch's runs and exits 0 when one whose
|
|
758
930
|
# `displayTitle` is exactly `harness pause <branch>` has a `createdAt` at or
|
|
759
931
|
# after <since_epoch> — AT, because `createdAt` has one-second resolution and
|
|
760
|
-
# the caller
|
|
761
|
-
#
|
|
762
|
-
#
|
|
932
|
+
# the caller passes a bound that starts at the job's own starting bound and
|
|
933
|
+
# afterwards lags each query by an overlap (`CONTROL_POLL_OVERLAP_SECS` in
|
|
934
|
+
# `autonomous-watcher.sh`), floored at that starting bound, so a pause created
|
|
935
|
+
# later in a second already queried is still seen; seeing one twice is
|
|
936
|
+
# harmless, since the caller drops PAUSE once. Exit 5 is "no pause"; exit 1 is
|
|
937
|
+
# a refused call and never one. `run-created-at` prints
|
|
763
938
|
# `gh run view <run_id> --json createdAt` as an epoch second.
|
|
764
939
|
#
|
|
765
940
|
# `status` AND `sync` READ THE RECORD, NOT THE KEY. A run keeps the execution
|
|
@@ -814,12 +989,17 @@
|
|
|
814
989
|
# `engine` when the bundle names `task`, `user_review` or `docs`. A
|
|
815
990
|
# `mirror` restore places no planning draft
|
|
816
991
|
# 4. no artifact, while some bundle exists (`remote_run_id` is set, or an
|
|
817
|
-
# older finished run carries one): a job that died before its upload
|
|
992
|
+
# older finished run carries one): a job that died before its upload,
|
|
993
|
+
# or that GitHub never started (the detail says so).
|
|
818
994
|
# `paused` / `killed`, `remote_run_id` / `remote_run_url` re-pointed at
|
|
819
|
-
# THIS run,
|
|
995
|
+
# THIS run, and, for a run GitHub never started whose dispatch's comment
|
|
996
|
+
# records its engine, `engine` set to that engine; nothing restored — so
|
|
997
|
+
# a later sync with no newer run is case 1
|
|
820
998
|
# 5. no bundle in any run and an empty `remote_run_id`: `failed`. Not
|
|
821
999
|
# `paused`: with no bundle anywhere a pause resume has nothing to restore,
|
|
822
|
-
# and re-dropping the artifact is the recovery
|
|
1000
|
+
# and re-dropping the artifact is the recovery, except a run whose job
|
|
1001
|
+
# GitHub never started and whose engine its dispatch's comment records,
|
|
1002
|
+
# which `sync` records as case 4 does, `paused (killed)`
|
|
823
1003
|
#
|
|
824
1004
|
# THE `killed` AND `expired` MAPPINGS. A finished run whose bundle still says
|
|
825
1005
|
# `running` (a kill, a timeout with no chain left) syncs as `status: paused`,
|
|
@@ -828,7 +1008,16 @@
|
|
|
828
1008
|
# never carries either, because `sync` derives them from the run and its
|
|
829
1009
|
# artifact list, never from a bundle. Both are `paused` rather than `failed`
|
|
830
1010
|
# because a `failed` record has no resume path, while the ledger on the branch
|
|
831
|
-
# is intact and a resume continues from it.
|
|
1011
|
+
# is intact and a resume continues from it. A finished run with no bundle
|
|
1012
|
+
# whose `run` job GitHub never started (it ended `cancelled` or `failure` with
|
|
1013
|
+
# no step run) maps to the same states as any other run with no bundle:
|
|
1014
|
+
# `paused` / `killed` when an older run carries a bundle, else `failed`. Its
|
|
1015
|
+
# detail names GitHub's reason instead of "killed, cancelled or replaced".
|
|
1016
|
+
# A never-started run with no bundle anywhere whose engine its dispatch's
|
|
1017
|
+
# comment records maps to `paused` / `killed` too, so `resume` accepts it.
|
|
1018
|
+
# When that run was the branch's first, the branch has no ledger yet:
|
|
1019
|
+
# `restore` finds no previous bundle and the resumed job starts as the
|
|
1020
|
+
# branch's first. With no such comment it stays `failed`.
|
|
832
1021
|
#
|
|
833
1022
|
# THE WORKFLOW INPUT CONTRACT (the workflow template declares the same inputs):
|
|
834
1023
|
#
|
|
@@ -893,37 +1082,55 @@
|
|
|
893
1082
|
# listing that `trigger` and `review` make. Only `start` and `review` push, and
|
|
894
1083
|
# only through `create-worktree.sh` and `push-branch.sh`; `start`'s writes are
|
|
895
1084
|
# the prompt committed on `origin/<branch>`, through a working copy and a local
|
|
896
|
-
# branch it removes before it returns
|
|
897
|
-
#
|
|
898
|
-
#
|
|
899
|
-
#
|
|
1085
|
+
# branch it removes before it returns, and, after a push that did not land, a
|
|
1086
|
+
# fetch that force-writes that copy's `refs/remotes/origin/<branch>`
|
|
1087
|
+
# (`hr_push_landed`'s, to name the commit the remote moved to); `review`'s are
|
|
1088
|
+
# the round committed on `origin/<branch>`, through the record's mirror or a
|
|
1089
|
+
# copy and a local branch it removes, and, after a push that did not land, the
|
|
1090
|
+
# same `hr_push_landed` fetch force-writing `refs/remotes/origin/<branch>` in
|
|
1091
|
+
# that mirror or copy, the record's `status` / `engine`, the bundle download
|
|
1092
|
+
# directory `sync` uses, and `report`'s writes for the round. Its settledness
|
|
1093
|
+
# read, when the newest run's job GitHub never started, also fetches origin's
|
|
1094
|
+
# <branch>, force-writing `refs/remotes/origin/<branch>` in the root checkout:
|
|
1095
|
+
# for a local `/autonomous-sdlc-harness:branch-user-review`, the main checkout. A user's chain-0 `dispatch --resume answer|pause`
|
|
900
1096
|
# writes the record's `status`, `resumed_at` and `resume_kind` in one write
|
|
901
1097
|
# when the main checkout's registry file exists and holds a record with
|
|
902
1098
|
# `execution: github-actions`; any other `dispatch` writes nothing. `trigger` writes its snapshot and comment
|
|
903
1099
|
# files under `RUNNER_TEMP`, one comment on the issue and the label removal, or
|
|
904
1100
|
# for a dispatch event a block in the step summary. `report` writes its comment
|
|
905
1101
|
# file under `RUNNER_TEMP` (removed), one comment and the state labels on the
|
|
906
|
-
# issue and the pull request, and nothing local. `
|
|
1102
|
+
# issue and the pull request, and nothing local. `open` writes its body and
|
|
1103
|
+
# comment files under `RUNNER_TEMP` (removed), at most one pull request, its
|
|
1104
|
+
# `running` label and one comment on the issue. `deliver` writes its body and
|
|
907
1105
|
# comment files under `RUNNER_TEMP` (removed), at most one pull request, one
|
|
908
1106
|
# comment and the state labels. `control` writes its reply file and its
|
|
909
1107
|
# `fetch` directory under `RUNNER_TEMP` (removed) and one reply comment, plus
|
|
910
|
-
# what the child verb it runs writes
|
|
911
|
-
#
|
|
912
|
-
#
|
|
1108
|
+
# what the child verb it runs writes, and the fetch of origin's <branch> that
|
|
1109
|
+
# force-writes `refs/remotes/origin/<branch>` in its checkout. `collect` writes its round file and its
|
|
1110
|
+
# settledness directory under `RUNNER_TEMP` (removed), at most one comment, on
|
|
1111
|
+
# the pull request, or on the issue for a run whose job never started, and for
|
|
1112
|
+
# that event `report`'s state labels on both items and one push notification,
|
|
1113
|
+
# the fetch of origin's <branch> its settledness read makes for such a run,
|
|
1114
|
+
# force-writing `refs/remotes/origin/<branch>`, plus what its `review` child
|
|
1115
|
+
# writes. `stop`, `continue` and `poll` also make
|
|
913
1116
|
# `report`'s writes for each event they report. Every other verb's only writes are the
|
|
914
1117
|
# registry record (`stop`, `sync`) and, for `sync`, the download directory
|
|
915
1118
|
# `<state_dir>/autonomous_logs/remote_download/<branch>/<id>/` and
|
|
916
1119
|
# `<branch>.remote.log` in the main checkout, plus the mirror restore
|
|
917
|
-
# `hr_remote_bundle_restore` performs in the record's `worktree
|
|
1120
|
+
# `hr_remote_bundle_restore` performs in the record's `worktree`, and, when
|
|
1121
|
+
# the newest run's job GitHub never started, a fetch force-writing
|
|
1122
|
+
# `refs/remotes/origin/<branch>` in the main checkout; for
|
|
918
1123
|
# `restore`, that download directory, the job restore (the planning drafts
|
|
919
1124
|
# among it), `answer_<n>.md` and the
|
|
920
1125
|
# `park_loop_cycles` rewrite of `remote_status.json`, all in the job's
|
|
921
1126
|
# checkout; for `save`, <out_dir> and the step summary; for `poll`, its
|
|
922
1127
|
# download directories and `<state_dir>/autonomous_logs/poll_state/previous/`
|
|
923
|
-
# and `current/`. For `fetch`, <out_dir>
|
|
924
|
-
#
|
|
925
|
-
#
|
|
926
|
-
#
|
|
1128
|
+
# and `current/`. For `fetch`, <out_dir> and, when the newest run's job GitHub
|
|
1129
|
+
# never started, a fetch force-writing `refs/remotes/origin/<branch>` in the
|
|
1130
|
+
# root checkout. For `discard`, the removal of <dir> only. `pause-requested`,
|
|
1131
|
+
# `run-created-at` and `list` write nothing, and `status` writes nothing but,
|
|
1132
|
+
# with no record, that same fetch; a no-record `status` downloads into a
|
|
1133
|
+
# temporary directory it removes on exit.
|
|
927
1134
|
#
|
|
928
1135
|
# MIRRORS OF `cli/src/remote/githubActions.ts`, which owns these names; a
|
|
929
1136
|
# rename there is an edit here, byte for byte:
|
|
@@ -936,6 +1143,7 @@
|
|
|
936
1143
|
# DEFAULT_TRIGGER_LABEL mirrors DEFAULT_TRIGGER_LABEL
|
|
937
1144
|
# LEGACY_TRIGGER_LABEL mirrors LEGACY_TRIGGER_LABEL
|
|
938
1145
|
# HARNESS_TRIGGER_ALLOWED_BOTS mirrors TRIGGER_ALLOWED_BOTS_VARIABLE
|
|
1146
|
+
# HARNESS_RUN_ACTORS mirrors RUN_ACTORS_VARIABLE
|
|
939
1147
|
# TRIGGER_DISPATCH_EVENT_TYPE mirrors TRIGGER_DISPATCH_EVENT_TYPE ('harness-task')
|
|
940
1148
|
# WORKFLOW_CONTROL_FILE mirrors WORKFLOW_CONTROL_FILE
|
|
941
1149
|
# COMMAND_HANDLE mirrors COMMAND_HANDLE
|
|
@@ -1112,7 +1320,9 @@
|
|
|
1112
1320
|
# the job's reads: a `run list` answer whose run has `displayTitle` `harness
|
|
1113
1321
|
# pause feat_x` and `createdAt` `2026-01-01T00:00:10Z` (epoch 1767225610):
|
|
1114
1322
|
# pause-requested bash scripts/remote-run.sh pause-requested feat_x 1767225600
|
|
1115
|
-
# -> 0; with 1767225620 ->
|
|
1323
|
+
# -> 0; with 1767225620 -> 5; a stub failing `run list` -> 3;
|
|
1324
|
+
# bash scripts/remote-run.sh pause-requested feat_x (no
|
|
1325
|
+
# <since_epoch>) -> 1
|
|
1116
1326
|
# run-created-at a stub answering `run view 42 --json createdAt` with
|
|
1117
1327
|
# {"createdAt":"2026-01-01T00:00:10Z"}: bash scripts/remote-run.sh
|
|
1118
1328
|
# run-created-at 42 -> prints 1767225610, 0; a failing stub -> 3
|
|
@@ -1123,13 +1333,18 @@
|
|
|
1123
1333
|
# "html_url":"https://github.com/o/r/issues/7","state":"open"}}, and a stub
|
|
1124
1334
|
# answering `api repos/o/r/collaborators/alice/permission` with
|
|
1125
1335
|
# {"permission":"write"}; export GITHUB_EVENT_NAME=issues GITHUB_EVENT_PATH=e.json
|
|
1126
|
-
# GITHUB_REPOSITORY=o/r HARNESS_TRIGGER_LOOKUP_SECS=0:
|
|
1336
|
+
# GITHUB_REPOSITORY=o/r HARNESS_TRIGGER_LOOKUP_SECS=0 HARNESS_RUN_ACTORS='*':
|
|
1127
1337
|
# trigger bash scripts/remote-run.sh trigger -> 0; origin/add_comments gains
|
|
1128
1338
|
# the prompt commit, "$s.log" gains `workflow run harness-run.yml
|
|
1129
1339
|
# --ref add_comments ...`, `issue comment 7 ...` naming the branch,
|
|
1130
1340
|
# then `issue edit 7 ... --remove-label sdlc-harness`
|
|
1131
1341
|
# read the permission answer {"permission":"read"} -> 2, no `workflow
|
|
1132
1342
|
# run`, one comment naming write access, the label removed
|
|
1343
|
+
# not listed HARNESS_RUN_ACTORS=bob -> 2, no `workflow run`, one comment
|
|
1344
|
+
# naming HARNESS_RUN_ACTORS, the label removed
|
|
1345
|
+
# unset HARNESS_RUN_ACTORS= and e.json gaining
|
|
1346
|
+
# "repository":{"owner":{"login":"alice","type":"User"}} -> 0, as
|
|
1347
|
+
# `trigger`; the owner `bob` instead -> 2, the comment naming @bob
|
|
1133
1348
|
# ignored e.json's label name `bug` -> 0, one line, "$s.log" unchanged
|
|
1134
1349
|
# dispatch GITHUB_EVENT_NAME=repository_dispatch GITHUB_RUN_ID=9
|
|
1135
1350
|
# GITHUB_STEP_SUMMARY=/tmp/s, e.json {"action":<TRIGGER_DISPATCH_EVENT_TYPE>,
|
|
@@ -1146,15 +1361,45 @@
|
|
|
1146
1361
|
# naming `@sdlc-harness resume`, then `api --method POST
|
|
1147
1362
|
# repos/o/r/issues/7/labels -f labels[]=sdlc-harness: paused`
|
|
1148
1363
|
# forge off `"forge": "none"`, then the same -> 0, one line, "$s.log" unchanged
|
|
1364
|
+
# progress a task ledger at sdlc-harness/flow_progress/feat_x_progress.md in
|
|
1365
|
+
# the checkout and the stub answering `pr list` with [{"number":12,
|
|
1366
|
+
# "isCrossRepository":false}]: bash scripts/remote-run.sh report
|
|
1367
|
+
# progress feat_x -> 0; "$s.log" gains `api --paginate
|
|
1368
|
+
# repos/o/r/issues/12/comments ...`, then one POST on 12 ending in
|
|
1369
|
+
# `event=progress branch=feat_x -->`; with that comment listed, no
|
|
1370
|
+
# write; with the ledger changed, one `PATCH repos/o/r/issues/comments/<id>`
|
|
1149
1371
|
#
|
|
1150
1372
|
# deliver needs report's setup, a stub answering the create with
|
|
1151
1373
|
# https://github.com/o/r/pull/12, and <b> a bundle directory whose status.json
|
|
1152
1374
|
# carries schema "1" and status completed:
|
|
1153
1375
|
# deliver bash scripts/remote-run.sh deliver feat_x <b> -> 0; "$s.log" gains
|
|
1154
|
-
# a `--draft` create with `--base main --head feat_x`, then
|
|
1155
|
-
#
|
|
1156
|
-
# 7
|
|
1376
|
+
# a `--draft` create with `--base main --head feat_x`, then `pr
|
|
1377
|
+
# ready 12` with the job's token, then one comment on 12 saying
|
|
1378
|
+
# it is now marked ready, then one on issue 7 naming #12 and
|
|
1379
|
+
# /pull/12, then `sdlc-harness: done` on 7 and 12
|
|
1380
|
+
# reused the stub answering `pr list` with [{"number":12,
|
|
1381
|
+
# "isCrossRepository":false,"isDraft":false}] -> 0, no create, no
|
|
1382
|
+
# `pr ready`, the pull request's comment saying it is not a draft
|
|
1383
|
+
# refused the stub failing `pr ready` -> 0, one `::warning::` line, both
|
|
1384
|
+
# comments posted, the pull request's saying the flip was refused
|
|
1157
1385
|
# not done status parked, or an empty <b> -> 0, one line, "$s.log" unchanged
|
|
1386
|
+
# threads <b>'s engine user_review; on feat_x a `feat_x_review.md` ending in
|
|
1387
|
+
# the marker `comments=101,103`, a `feat_x_fix_plan.md` whose
|
|
1388
|
+
# `1. [x] **Finding 1**` lands in its own commit and whose `## Out
|
|
1389
|
+
# of scope / verified-OK` bullet ends ` **Review comments:** 103`,
|
|
1390
|
+
# and `feat_x_fix_plan/finding_1.md` carrying `**Review comments:**
|
|
1391
|
+
# 101`; a stub answering `api graphql` with one open thread per id
|
|
1392
|
+
# -> 0; "$s.log" gains a reply on comment 101 naming that commit,
|
|
1393
|
+
# then one `resolveReviewThread`, then a reply on 103 quoting the
|
|
1394
|
+
# bullet and no resolve; no dismissal call
|
|
1395
|
+
#
|
|
1396
|
+
# open needs deliver's setup without the bundle:
|
|
1397
|
+
# open bash scripts/remote-run.sh open feat_x -> 0; "$s.log" gains a
|
|
1398
|
+
# `--draft` create with `--base main --head feat_x`, then
|
|
1399
|
+
# `sdlc-harness: running` on 12, then one comment on issue 7
|
|
1400
|
+
# naming /pull/12 and ending in `event=opened branch=feat_x`
|
|
1401
|
+
# reused the stub answering `pr list` with [{"number":12,
|
|
1402
|
+
# "isCrossRepository":false}] -> 0, one line, no create, no comment
|
|
1158
1403
|
#
|
|
1159
1404
|
# collect needs deliver's setup, a story index on feat_x, a stub answering
|
|
1160
1405
|
# `pr list` with [{"number":12,"isCrossRepository":false}], `run list` with a
|
|
@@ -1242,6 +1487,11 @@ RUN_JOB_NAME='run'
|
|
|
1242
1487
|
# How far before the previous round's `collected_at` `round_collect` lists
|
|
1243
1488
|
# from, absorbing runner-clock skew; the id check drops what it re-lists.
|
|
1244
1489
|
ROUND_OVERLAP_SECS=300
|
|
1490
|
+
# How far before a run's `createdAt` `forge_dispatch_engine_var` still trusts
|
|
1491
|
+
# a dispatcher's comment: the comment is posted after the dispatch request and
|
|
1492
|
+
# GitHub creates the run asynchronously, so either may carry the earlier
|
|
1493
|
+
# timestamp. Small enough that an older dispatch's comment falls outside it.
|
|
1494
|
+
DISPATCH_MARKER_SLACK_SECS=120
|
|
1245
1495
|
|
|
1246
1496
|
# GitHub's documented limit on a `workflow_dispatch` inputs payload: "The
|
|
1247
1497
|
# maximum payload for inputs is 65,535 characters."
|
|
@@ -1253,8 +1503,9 @@ EXIT_USAGE=1
|
|
|
1253
1503
|
EXIT_REFUSED=2
|
|
1254
1504
|
EXIT_GH=3
|
|
1255
1505
|
EXIT_PLACEMENT=4
|
|
1256
|
-
# pause-requested only: the read succeeded and found no pause
|
|
1257
|
-
|
|
1506
|
+
# pause-requested only: the read succeeded and found no pause; distinct from
|
|
1507
|
+
# EXIT_USAGE so a caller never reads a refused call as no pause.
|
|
1508
|
+
EXIT_NO_PAUSE=5
|
|
1258
1509
|
|
|
1259
1510
|
usage() {
|
|
1260
1511
|
echo "remote-run.sh: $1" >&2
|
|
@@ -1277,6 +1528,7 @@ usage() {
|
|
|
1277
1528
|
echo " remote-run.sh list [--repo <root>]" >&2
|
|
1278
1529
|
echo " remote-run.sh discard <dir> [--repo <root>]" >&2
|
|
1279
1530
|
echo " remote-run.sh report <event> <branch> [--note <text>] [--repo <root>]" >&2
|
|
1531
|
+
echo " remote-run.sh open <branch> [--repo <root>]" >&2
|
|
1280
1532
|
echo " remote-run.sh deliver <branch> <bundle_dir> [--repo <root>]" >&2
|
|
1281
1533
|
echo " remote-run.sh collect <branch> [--pr <n>] [--repo <root>]" >&2
|
|
1282
1534
|
echo " remote-run.sh control [--repo <root>]" >&2
|
|
@@ -1349,7 +1601,7 @@ verb=""
|
|
|
1349
1601
|
verb="$1"
|
|
1350
1602
|
shift
|
|
1351
1603
|
case "$verb" in
|
|
1352
|
-
dispatch|pause|warm|stop|status|sync|fetch|restore|save|continue|poll|pause-requested|run-created-at|start|review|trigger|list|discard|report|deliver|collect|control) ;;
|
|
1604
|
+
dispatch|pause|warm|stop|status|sync|fetch|restore|save|continue|poll|pause-requested|run-created-at|start|review|trigger|list|discard|report|open|deliver|collect|control) ;;
|
|
1353
1605
|
*) usage "unknown verb '$verb'" ;;
|
|
1354
1606
|
esac
|
|
1355
1607
|
|
|
@@ -1574,10 +1826,11 @@ fi
|
|
|
1574
1826
|
# ---------------------------------------------------------------------------
|
|
1575
1827
|
|
|
1576
1828
|
# setup_fail <message> — a configuration problem: exit 1, except for save,
|
|
1577
|
-
# report, deliver and collect, which never fail the step that calls them.
|
|
1829
|
+
# report, open, deliver and collect, which never fail the step that calls them.
|
|
1578
1830
|
setup_fail() {
|
|
1579
1831
|
echo "remote-run.sh: $1" >&2
|
|
1580
|
-
[ "$verb" != save ] && [ "$verb" != report ] && [ "$verb" !=
|
|
1832
|
+
[ "$verb" != save ] && [ "$verb" != report ] && [ "$verb" != open ] && [ "$verb" != deliver ] \
|
|
1833
|
+
&& [ "$verb" != collect ] || exit "$EXIT_OK"
|
|
1581
1834
|
exit "$EXIT_USAGE"
|
|
1582
1835
|
}
|
|
1583
1836
|
|
|
@@ -1585,8 +1838,8 @@ if [ -n "$repo_arg" ]; then
|
|
|
1585
1838
|
root=$(hr_repo_root "$repo_arg") || setup_fail "'$repo_arg' is not a git repository"
|
|
1586
1839
|
elif [ "$verb" = restore ] || [ "$verb" = save ] || [ "$verb" = continue ] || [ "$verb" = poll ] \
|
|
1587
1840
|
|| [ "$verb" = pause-requested ] || [ "$verb" = run-created-at ] || [ "$verb" = trigger ] \
|
|
1588
|
-
|| [ "$verb" = discard ] || [ "$verb" = report ] || [ "$verb" =
|
|
1589
|
-
|| [ "$verb" = control ]; then
|
|
1841
|
+
|| [ "$verb" = discard ] || [ "$verb" = report ] || [ "$verb" = open ] || [ "$verb" = deliver ] \
|
|
1842
|
+
|| [ "$verb" = collect ] || [ "$verb" = control ]; then
|
|
1590
1843
|
root=$(hr_repo_root "${PWD-.}") || setup_fail "'${PWD-.}' is not inside a git repository"
|
|
1591
1844
|
else
|
|
1592
1845
|
root=$(hr_main_repo "${PWD-.}") || setup_fail "'${PWD-.}' is not inside a git repository"
|
|
@@ -1605,7 +1858,7 @@ case "$verb" in
|
|
|
1605
1858
|
trigger|control)
|
|
1606
1859
|
# Gates itself, after reading the event, so a refusal can still be commented.
|
|
1607
1860
|
;;
|
|
1608
|
-
report|deliver|collect)
|
|
1861
|
+
report|open|deliver|collect)
|
|
1609
1862
|
# Gates itself (`forge_on`) and exits 0 on every outcome.
|
|
1610
1863
|
;;
|
|
1611
1864
|
status|sync)
|
|
@@ -1885,6 +2138,54 @@ expired_line() {
|
|
|
1885
2138
|
printf '%s' "the state bundle of run $1 expired on $BUNDLE_EXPIRES_AT: resume from the committed ledger with $RESUME_HINT $branch, or re-drop the task"
|
|
1886
2139
|
}
|
|
1887
2140
|
|
|
2141
|
+
# run_not_started_var <run_id> — whether GitHub never started the run's
|
|
2142
|
+
# RUN_JOB_NAME job: 0 when that job's `conclusion` is `cancelled` or `failure`
|
|
2143
|
+
# and its `steps` array is absent or empty, with NOT_STARTED_REASON set; 1 when
|
|
2144
|
+
# the job started or none of that name is listed; 2 with GH_ERR set when the
|
|
2145
|
+
# jobs lookup failed or was not the expected JSON. Never exits. The reason is
|
|
2146
|
+
# the first message of the job's check-run annotations (a job's `id` is its
|
|
2147
|
+
# check run's id); that lookup is best-effort, and a failed or empty one falls
|
|
2148
|
+
# back to a line naming the conclusion. Replaces GH_OUT.
|
|
2149
|
+
NOT_STARTED_REASON=""
|
|
2150
|
+
run_not_started_var() {
|
|
2151
|
+
local answer job_id conclusion message
|
|
2152
|
+
NOT_STARTED_REASON=""
|
|
2153
|
+
gh_call api "repos/{owner}/{repo}/actions/runs/$1/jobs" || return 2
|
|
2154
|
+
answer=$(printf '%s' "$GH_OUT" | jq -r --arg n "$RUN_JOB_NAME" '
|
|
2155
|
+
if type == "object" and (.jobs | type) == "array" then
|
|
2156
|
+
([.jobs[] | select(type == "object" and .name == $n)] | first) as $j
|
|
2157
|
+
| if $j == null then "absent"
|
|
2158
|
+
elif ($j.conclusion == "cancelled" or $j.conclusion == "failure")
|
|
2159
|
+
and (($j.steps // []) | if type == "array" then length else 1 end) == 0
|
|
2160
|
+
then "not_started\t" + ($j.id | tostring) + "\t" + $j.conclusion
|
|
2161
|
+
else "started" end
|
|
2162
|
+
else "invalid" end' 2>/dev/null)
|
|
2163
|
+
case "$answer" in
|
|
2164
|
+
absent|started) return 1 ;;
|
|
2165
|
+
not_started$'\t'*) ;;
|
|
2166
|
+
*) GH_ERR="its job list is not the expected JSON"; return 2 ;;
|
|
2167
|
+
esac
|
|
2168
|
+
answer="${answer#*$'\t'}"
|
|
2169
|
+
job_id="${answer%%$'\t'*}"
|
|
2170
|
+
conclusion="${answer#*$'\t'}"
|
|
2171
|
+
message=""
|
|
2172
|
+
case "$job_id" in
|
|
2173
|
+
''|*[!0-9]*) ;;
|
|
2174
|
+
*)
|
|
2175
|
+
if gh_call api "repos/{owner}/{repo}/check-runs/$job_id/annotations"; then
|
|
2176
|
+
message=$(printf '%s' "$GH_OUT" | jq -r '
|
|
2177
|
+
[.[]? | .message? | select(type == "string" and length > 0) | split("\n")[0]] | first // ""' 2>/dev/null) || message=""
|
|
2178
|
+
fi
|
|
2179
|
+
;;
|
|
2180
|
+
esac
|
|
2181
|
+
if [ -n "$message" ]; then
|
|
2182
|
+
NOT_STARTED_REASON="$message"
|
|
2183
|
+
else
|
|
2184
|
+
NOT_STARTED_REASON="its \`$RUN_JOB_NAME\` job ended \`$conclusion\` with no step run"
|
|
2185
|
+
fi
|
|
2186
|
+
return 0
|
|
2187
|
+
}
|
|
2188
|
+
|
|
1888
2189
|
set_or_fail() {
|
|
1889
2190
|
hr_registry_set "$registry" "$branch" "$1" "$2" || {
|
|
1890
2191
|
echo "remote-run.sh: writing $1 of $branch to '$registry' failed" >&2
|
|
@@ -2003,10 +2304,17 @@ sync_expired() {
|
|
|
2003
2304
|
# RS_BUNDLE is then 1. <applied_run_id>, when set, also counts as a bundle
|
|
2004
2305
|
# existing for case 4. <finished> 1 reads the newest run as finished whatever
|
|
2005
2306
|
# its `status`: `branch_settled_var` passes it once that run's `run` job has
|
|
2006
|
-
# completed.
|
|
2307
|
+
# completed. RS_RUN_CREATED_AT is the newest run's `createdAt` (ISO 8601).
|
|
2308
|
+
# RS_NOT_STARTED is 1 only in cases 4 and 5, when `run_not_started_var` finds
|
|
2309
|
+
# GitHub never started that run's RUN_JOB_NAME job; RS_ENGINE is then
|
|
2310
|
+
# `forge_dispatch_engine_var`'s answer, and case 5 with one is `paused` /
|
|
2311
|
+
# `killed`. Replaces GH_OUT in cases 4 and 5. Exits 3 when gh fails, 2
|
|
2312
|
+
# for an unrecognised bundle.
|
|
2007
2313
|
RS_RUNS=""
|
|
2008
2314
|
RS_RUN_ID=""
|
|
2009
2315
|
RS_RUN_URL=""
|
|
2316
|
+
RS_RUN_CREATED_AT=""
|
|
2317
|
+
RS_NOT_STARTED=0
|
|
2010
2318
|
RS_GH_STATUS=""
|
|
2011
2319
|
RS_STATE=""
|
|
2012
2320
|
RS_PAUSE_REASON=""
|
|
@@ -2020,7 +2328,8 @@ remote_state() {
|
|
|
2020
2328
|
local download="${1-}" applied="${2-}" finished="${3-0}" newest status_file older bundle_exists=0
|
|
2021
2329
|
RS_RUNS=""; RS_RUN_ID=""; RS_RUN_URL=""; RS_GH_STATUS=""; RS_STATE=""
|
|
2022
2330
|
RS_PAUSE_REASON=""; RS_DETAIL=""; RS_ENGINE=""; RS_USAGE_RESUME_AT=""
|
|
2023
|
-
RS_PARK_LOOP_CYCLES=""; RS_BUNDLE=0; RS_DOWNLOAD=""
|
|
2331
|
+
RS_PARK_LOOP_CYCLES=""; RS_BUNDLE=0; RS_DOWNLOAD=""; RS_RUN_CREATED_AT=""
|
|
2332
|
+
RS_NOT_STARTED=0
|
|
2024
2333
|
RS_RUNS=$(titled_runs "harness run $branch") || RS_RUNS='[]'
|
|
2025
2334
|
newest=$(printf '%s' "$RS_RUNS" | jq -c '.[0] // empty')
|
|
2026
2335
|
if [ -z "$newest" ]; then
|
|
@@ -2030,6 +2339,7 @@ remote_state() {
|
|
|
2030
2339
|
RS_RUN_ID=$(printf '%s' "$newest" | jq -r '.databaseId | tostring')
|
|
2031
2340
|
RS_GH_STATUS=$(printf '%s' "$newest" | jq -r '.status // ""')
|
|
2032
2341
|
RS_RUN_URL=$(printf '%s' "$newest" | jq -r '.url // ""')
|
|
2342
|
+
RS_RUN_CREATED_AT=$(printf '%s' "$newest" | jq -r '.createdAt // ""')
|
|
2033
2343
|
if [ "$RS_GH_STATUS" != completed ] && [ "$finished" != 1 ]; then
|
|
2034
2344
|
RS_STATE=running
|
|
2035
2345
|
return 0
|
|
@@ -2097,12 +2407,32 @@ remote_state() {
|
|
|
2097
2407
|
RS_STATE=paused
|
|
2098
2408
|
RS_PAUSE_REASON=killed
|
|
2099
2409
|
RS_DETAIL="run $RS_RUN_ID ended with no state bundle (killed, cancelled or replaced): $RS_RUN_URL"
|
|
2100
|
-
|
|
2410
|
+
else
|
|
2411
|
+
# Case 5 — no bundle in any run.
|
|
2412
|
+
RS_STATE=failed
|
|
2413
|
+
RS_DETAIL="no run of $branch ever uploaded a state bundle; newest: $RS_RUN_URL"
|
|
2101
2414
|
fi
|
|
2102
2415
|
|
|
2103
|
-
#
|
|
2104
|
-
|
|
2105
|
-
|
|
2416
|
+
# Cases 4 and 5 — a job GitHub never started keeps its case's state; only
|
|
2417
|
+
# the detail changes. A failed jobs lookup changes nothing but says so.
|
|
2418
|
+
run_not_started_var "$RS_RUN_ID"
|
|
2419
|
+
case $? in
|
|
2420
|
+
0)
|
|
2421
|
+
RS_NOT_STARTED=1
|
|
2422
|
+
RS_DETAIL="GitHub did not start the job of run $RS_RUN_ID ($NOT_STARTED_REASON): $RS_RUN_URL"
|
|
2423
|
+
# No bundle names the engine, so the dispatch's own comment does; with
|
|
2424
|
+
# one, case 5 becomes resumable from the ledger as case 4 is.
|
|
2425
|
+
if [ -z "$RS_ENGINE" ]; then
|
|
2426
|
+
forge_dispatch_engine_var "$branch" "$RS_RUN_CREATED_AT" || :
|
|
2427
|
+
RS_ENGINE="$FORGE_DISPATCH_ENGINE"
|
|
2428
|
+
if [ "$RS_STATE" = failed ] && [ -n "$RS_ENGINE" ]; then
|
|
2429
|
+
RS_STATE=paused
|
|
2430
|
+
RS_PAUSE_REASON=killed
|
|
2431
|
+
fi
|
|
2432
|
+
fi
|
|
2433
|
+
;;
|
|
2434
|
+
2) echo "remote-run.sh: could not tell whether GitHub started the job of run $RS_RUN_ID: $GH_ERR" >&2 ;;
|
|
2435
|
+
esac
|
|
2106
2436
|
return 0
|
|
2107
2437
|
}
|
|
2108
2438
|
|
|
@@ -2232,10 +2562,16 @@ verb_sync() {
|
|
|
2232
2562
|
return 0
|
|
2233
2563
|
fi
|
|
2234
2564
|
|
|
2235
|
-
# Case 4 — a newer run with no bundle, while some bundle exists
|
|
2565
|
+
# Case 4 — a newer run with no bundle, while some bundle exists; also a run
|
|
2566
|
+
# GitHub never started whose engine its dispatch's comment records.
|
|
2236
2567
|
if [ "$RS_STATE" = paused ]; then
|
|
2237
|
-
|
|
2238
|
-
|
|
2568
|
+
if [ "$RS_NOT_STARTED" = 1 ] && valid_engine "$RS_ENGINE"; then
|
|
2569
|
+
set_many_or_fail status paused pause_reason killed remote_run_id "$id" remote_run_url "$url" \
|
|
2570
|
+
remote_detail "$RS_DETAIL" remote_synced_at "$now" engine "$RS_ENGINE"
|
|
2571
|
+
else
|
|
2572
|
+
set_many_or_fail status paused pause_reason killed remote_run_id "$id" remote_run_url "$url" \
|
|
2573
|
+
remote_detail "$RS_DETAIL" remote_synced_at "$now"
|
|
2574
|
+
fi
|
|
2239
2575
|
echo "remote-run.sh: run $id of $branch left no bundle; the record is paused (killed), nothing restored"
|
|
2240
2576
|
return 0
|
|
2241
2577
|
fi
|
|
@@ -2489,12 +2825,9 @@ verb_save() {
|
|
|
2489
2825
|
return 0
|
|
2490
2826
|
}
|
|
2491
2827
|
|
|
2492
|
-
#
|
|
2493
|
-
#
|
|
2494
|
-
|
|
2495
|
-
# <forge_note> is the comment's, naming no slash command and no shell command,
|
|
2496
|
-
# and states only what happened, since `forge_report` adds the next action.
|
|
2497
|
-
notify() {
|
|
2828
|
+
# notify_push <event> <branch> <detail> — the push notification alone: it
|
|
2829
|
+
# posts no comment and sets no label. Never fails.
|
|
2830
|
+
notify_push() {
|
|
2498
2831
|
if [ -n "${HARNESS_REMOTE_SLUG-}" ]; then
|
|
2499
2832
|
HARNESS_REPO_SLUG="$HARNESS_REMOTE_SLUG"
|
|
2500
2833
|
export HARNESS_REPO_SLUG
|
|
@@ -2502,6 +2835,15 @@ notify() {
|
|
|
2502
2835
|
bash "$script_dir/autonomous-notify.sh" "$1" "$2" "" "$3" \
|
|
2503
2836
|
|| echo "remote-run.sh: the $1 notification for $2 could not be sent" >&2
|
|
2504
2837
|
echo "remote-run.sh: notified $1 for $2: $3"
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
# notify <event> <branch> <detail> <forge_note> — notify_push, then
|
|
2841
|
+
# `forge_report` of the same event; never fails. Every call site supplies
|
|
2842
|
+
# both texts: <detail> is the push notification's, slash commands included;
|
|
2843
|
+
# <forge_note> is the comment's, naming no slash command and no shell command,
|
|
2844
|
+
# and states only what happened, since `forge_report` adds the next action.
|
|
2845
|
+
notify() {
|
|
2846
|
+
notify_push "$1" "$2" "$3"
|
|
2505
2847
|
# stdin closed: `poll` calls this inside a loop reading its run list.
|
|
2506
2848
|
forge_report "$1" "$2" "$4" </dev/null
|
|
2507
2849
|
}
|
|
@@ -2698,7 +3040,10 @@ verb_continue() {
|
|
|
2698
3040
|
}
|
|
2699
3041
|
|
|
2700
3042
|
# POLL_STATE — the poller state carried between ticks, one object keyed by
|
|
2701
|
-
# branch: {"<branch>": {"run_id", "failures", "notified"}},
|
|
3043
|
+
# branch: {"<branch>": {"run_id", "failures", "notified", "download_failures"}},
|
|
3044
|
+
# every value a string. `failures` counts failed re-dispatches only;
|
|
3045
|
+
# `download_failures`, absent until a download fails, counts consecutive failed
|
|
3046
|
+
# downloads of the run's listed bundle.
|
|
2702
3047
|
POLL_STATE='{}'
|
|
2703
3048
|
POLL_STATE_FILE_NAME='poll_state.json'
|
|
2704
3049
|
|
|
@@ -2706,9 +3051,21 @@ poll_state_get() {
|
|
|
2706
3051
|
printf '%s' "$POLL_STATE" | jq -r --arg b "$1" --arg f "$2" '.[$b][$f] // "" | tostring'
|
|
2707
3052
|
}
|
|
2708
3053
|
|
|
3054
|
+
# poll_state_put <branch> <run_id> <failures> <notified> — keeps the entry's
|
|
3055
|
+
# `download_failures` when its `run_id` is the same.
|
|
2709
3056
|
poll_state_put() {
|
|
2710
|
-
POLL_STATE=$(printf '%s' "$POLL_STATE" | jq -c --arg b "$1" --arg r "$2" --arg f "$3" --arg n "$4"
|
|
2711
|
-
|
|
3057
|
+
POLL_STATE=$(printf '%s' "$POLL_STATE" | jq -c --arg b "$1" --arg r "$2" --arg f "$3" --arg n "$4" '
|
|
3058
|
+
(.[$b] // {}) as $o
|
|
3059
|
+
| .[$b] = {run_id: $r, failures: $f, notified: $n}
|
|
3060
|
+
+ (if $o.run_id == $r and ($o | has("download_failures")) then {download_failures: $o.download_failures} else {} end)')
|
|
3061
|
+
}
|
|
3062
|
+
|
|
3063
|
+
# poll_state_downloads_put <branch> <run_id> <count> — sets `download_failures`,
|
|
3064
|
+
# on a fresh entry when the stored `run_id` differs.
|
|
3065
|
+
poll_state_downloads_put() {
|
|
3066
|
+
POLL_STATE=$(printf '%s' "$POLL_STATE" | jq -c --arg b "$1" --arg r "$2" --arg c "$3" '
|
|
3067
|
+
(.[$b] // {}) as $o
|
|
3068
|
+
| .[$b] = (if $o.run_id == $r then $o else {run_id: $r, failures: "", notified: ""} end) + {download_failures: $c}')
|
|
2712
3069
|
}
|
|
2713
3070
|
|
|
2714
3071
|
poll_state_drop() {
|
|
@@ -2827,7 +3184,7 @@ poll_usage_paused() {
|
|
|
2827
3184
|
# With may_dispatch 0 nothing is sent and nothing notified: a due run that
|
|
2828
3185
|
# would be dispatched counts as waiting, one that would be refused does not.
|
|
2829
3186
|
poll_branch() {
|
|
2830
|
-
local id="$1" state="$2" may_dispatch="$3" at engine_value now failures
|
|
3187
|
+
local id="$1" state="$2" may_dispatch="$3" at engine_value now failures downloads listed
|
|
2831
3188
|
if [ "$may_dispatch" -eq 1 ] && [ -n "$(poll_state_get "$branch" run_id)" ] \
|
|
2832
3189
|
&& [ "$(poll_state_get "$branch" run_id)" != "$id" ]; then
|
|
2833
3190
|
poll_state_drop "$branch"
|
|
@@ -2862,10 +3219,40 @@ poll_branch() {
|
|
|
2862
3219
|
echo "remote-run.sh: poll: $branch's run $id was already reported as not re-dispatchable; skipped"
|
|
2863
3220
|
return 1
|
|
2864
3221
|
fi
|
|
2865
|
-
|
|
2866
|
-
|
|
3222
|
+
bundle_listed "$id"
|
|
3223
|
+
listed=$?
|
|
3224
|
+
if [ "$listed" -eq 1 ]; then
|
|
3225
|
+
if run_not_started_var "$id"; then
|
|
3226
|
+
echo "remote-run.sh: poll: run $id of $branch never started ($NOT_STARTED_REASON); its collect job reports it; not waiting"
|
|
3227
|
+
else
|
|
3228
|
+
echo "remote-run.sh: poll: $branch skipped"
|
|
3229
|
+
fi
|
|
3230
|
+
return 1
|
|
3231
|
+
fi
|
|
3232
|
+
[ "$listed" -ne 2 ] || echo "remote-run.sh: poll: reading the artifacts of run $id ($branch) failed ($GH_ERR)"
|
|
3233
|
+
downloads=""
|
|
3234
|
+
[ "$(poll_state_get "$branch" run_id)" != "$id" ] || downloads=$(poll_state_get "$branch" download_failures)
|
|
3235
|
+
if [ "$listed" -eq 2 ] || ! poll_fetch "$id"; then
|
|
3236
|
+
case "$downloads" in
|
|
3237
|
+
''|*[!0-9]*) downloads=0 ;;
|
|
3238
|
+
esac
|
|
3239
|
+
downloads=$((10#$downloads + 1))
|
|
3240
|
+
if [ "$downloads" -lt "$((10#$POLL_MAX_FAILURES))" ]; then
|
|
3241
|
+
[ "$may_dispatch" -eq 0 ] || poll_state_downloads_put "$branch" "$id" "$downloads"
|
|
3242
|
+
echo "remote-run.sh: poll: the bundle of run $id ($branch) could not be downloaded, attempt $downloads of $POLL_MAX_FAILURES; still waiting"
|
|
3243
|
+
return 0
|
|
3244
|
+
fi
|
|
3245
|
+
if [ "$may_dispatch" -eq 1 ]; then
|
|
3246
|
+
poll_state_put "$branch" "$id" "$(poll_state_get "$branch" failures)" 1
|
|
3247
|
+
# Push only, under a word outside autonomous-notify.sh's events: the run's
|
|
3248
|
+
# state is unknown, so neither a `failed` report nor a label is true of it.
|
|
3249
|
+
notify_push bundle_unreadable "$branch" "The resume poller could not download the state bundle of run $id after $downloads attempts ($GH_ERR); it no longer watches $branch. Its state is unknown: check the run, then run $RESUME_HINT $branch if it is paused; $(hr_github_resume_route "$branch" "")."
|
|
3250
|
+
fi
|
|
2867
3251
|
return 1
|
|
2868
3252
|
fi
|
|
3253
|
+
if [ -n "$downloads" ] && [ "$may_dispatch" -eq 1 ]; then
|
|
3254
|
+
poll_state_downloads_put "$branch" "$id" ""
|
|
3255
|
+
fi
|
|
2869
3256
|
poll_usage_paused || return 1
|
|
2870
3257
|
at=$(hr_remote_status_get "$POLL_STATUS_FILE" usage_resume_at) || at=""
|
|
2871
3258
|
case "$at" in
|
|
@@ -3099,8 +3486,12 @@ verb_start() {
|
|
|
3099
3486
|
status=0
|
|
3100
3487
|
hr_commit_placed "$script_dir/commit-on-branch.sh" "$worktree" "$rel" "$subject" >&2 || status=$?
|
|
3101
3488
|
[ "$status" -ne 1 ] || placement_fail "committing '$rel'"
|
|
3102
|
-
|
|
3103
|
-
|
|
3489
|
+
status=0
|
|
3490
|
+
hr_push_landed "$script_dir/push-branch.sh" "$worktree" "$branch" >&2 || status=$?
|
|
3491
|
+
[ "$status" -ne 2 ] \
|
|
3492
|
+
|| placement_fail "pushing $branch: origin/$branch moved to $HR_PUSH_REMOTE_TIP, which this branch does not have (something else pushed)"
|
|
3493
|
+
[ "$status" -eq 0 ] \
|
|
3494
|
+
|| placement_fail "pushing $branch: the remote refused the push (push-branch.sh's lines in this job's log name why)"
|
|
3104
3495
|
start_remove_copy
|
|
3105
3496
|
trap - EXIT
|
|
3106
3497
|
|
|
@@ -3253,8 +3644,12 @@ NAMES
|
|
|
3253
3644
|
status=0
|
|
3254
3645
|
hr_commit_placed "$script_dir/commit-on-branch.sh" "$worktree" "$rel" "$(hr_user_review_subject "$branch")" >&2 || status=$?
|
|
3255
3646
|
[ "$status" -eq 0 ] || review_fail "committing '$rel'"
|
|
3256
|
-
|
|
3257
|
-
|
|
3647
|
+
status=0
|
|
3648
|
+
hr_push_landed "$script_dir/push-branch.sh" "$worktree" "$branch" >&2 || status=$?
|
|
3649
|
+
[ "$status" -ne 2 ] \
|
|
3650
|
+
|| review_fail "pushing $branch: origin/$branch moved to $HR_PUSH_REMOTE_TIP, which this branch does not have (something else pushed)"
|
|
3651
|
+
[ "$status" -eq 0 ] \
|
|
3652
|
+
|| review_fail "pushing $branch: the remote refused the push (push-branch.sh's lines in this job's log name why)"
|
|
3258
3653
|
pushed=$(git -C "$worktree" rev-parse HEAD 2>/dev/null) || pushed=""
|
|
3259
3654
|
if [ "$use_mirror" -eq 0 ]; then
|
|
3260
3655
|
start_remove_copy
|
|
@@ -3472,13 +3867,73 @@ trigger_bot_listed() {
|
|
|
3472
3867
|
return 1
|
|
3473
3868
|
}
|
|
3474
3869
|
|
|
3870
|
+
# run_actor_listed <login> — 0 when HARNESS_RUN_ACTORS admits <login>: split on
|
|
3871
|
+
# `,`, each entry trimmed, empty entries dropped, matched case-insensitively,
|
|
3872
|
+
# `*` admitting everyone, an empty <login> admitted by `*` alone. A list with no
|
|
3873
|
+
# entries is unset, and admits only `.repository.owner.login` of the event file
|
|
3874
|
+
# when `.repository.owner.type` is `User`; any other owner, or an event file or
|
|
3875
|
+
# field that cannot be read, admits nobody. Otherwise 1, with RUN_ACTORS_WHY
|
|
3876
|
+
# holding one sentence. Lowercased with `tr`, never a parameter expansion:
|
|
3877
|
+
# bash 3.2 has no case-changing one.
|
|
3878
|
+
RUN_ACTORS_WHY=""
|
|
3879
|
+
run_actor_listed() {
|
|
3880
|
+
local login="$1" want rest="${HARNESS_RUN_ACTORS-}," entry listed="" owner="" owner_type=""
|
|
3881
|
+
RUN_ACTORS_WHY=""
|
|
3882
|
+
want=$(printf '%s' "$login" | tr '[:upper:]' '[:lower:]')
|
|
3883
|
+
while [ -n "$rest" ]; do
|
|
3884
|
+
entry=${rest%%,*}
|
|
3885
|
+
rest=${rest#*,}
|
|
3886
|
+
entry=${entry#"${entry%%[![:space:]]*}"}
|
|
3887
|
+
entry=${entry%"${entry##*[![:space:]]}"}
|
|
3888
|
+
[ -n "$entry" ] || continue
|
|
3889
|
+
listed=1
|
|
3890
|
+
[ "$entry" = '*' ] && return 0
|
|
3891
|
+
[ -n "$want" ] && [ "$(printf '%s' "$entry" | tr '[:upper:]' '[:lower:]')" = "$want" ] && return 0
|
|
3892
|
+
done
|
|
3893
|
+
if [ -n "$listed" ]; then
|
|
3894
|
+
RUN_ACTORS_WHY="@$login is not on the repository variable HARNESS_RUN_ACTORS, the allow-list of who may start, command, answer or review a run."
|
|
3895
|
+
return 1
|
|
3896
|
+
fi
|
|
3897
|
+
if [ -n "${GITHUB_EVENT_PATH-}" ] && [ -r "$GITHUB_EVENT_PATH" ]; then
|
|
3898
|
+
event_field '.repository.owner.type // ""' && owner_type="$EVENT_VALUE"
|
|
3899
|
+
event_field '.repository.owner.login // ""' && owner="$EVENT_VALUE"
|
|
3900
|
+
fi
|
|
3901
|
+
if [ "$owner_type" = User ] && [ -n "$owner" ]; then
|
|
3902
|
+
[ -n "$want" ] && [ "$(printf '%s' "$owner" | tr '[:upper:]' '[:lower:]')" = "$want" ] && return 0
|
|
3903
|
+
RUN_ACTORS_WHY="@$login is not the repository owner, and the repository variable HARNESS_RUN_ACTORS is unset, which admits the owner, @$owner, alone."
|
|
3904
|
+
return 1
|
|
3905
|
+
fi
|
|
3906
|
+
RUN_ACTORS_WHY="the repository variable HARNESS_RUN_ACTORS is unset, and this repository has no single owner to admit (its owner is ${owner_type:-unreadable}), so it admits nobody until it names the logins allowed, or * for every writer."
|
|
3907
|
+
return 1
|
|
3908
|
+
}
|
|
3909
|
+
|
|
3910
|
+
# rerun_actor_listed — 0 unless this job is a re-run (GITHUB_RUN_ATTEMPT above
|
|
3911
|
+
# 1) whose GITHUB_TRIGGERING_ACTOR is neither `github-actions[bot]` nor
|
|
3912
|
+
# admitted by run_actor_listed; then 1, with RUN_ACTORS_WHY naming the
|
|
3913
|
+
# re-runner. A re-run replays the original event, whose sender is the person
|
|
3914
|
+
# who first acted, so authorise_actor alone would check the wrong account.
|
|
3915
|
+
rerun_actor_listed() {
|
|
3916
|
+
local attempt="${GITHUB_RUN_ATTEMPT-}" who="${GITHUB_TRIGGERING_ACTOR-}"
|
|
3917
|
+
RUN_ACTORS_WHY=""
|
|
3918
|
+
case "$attempt" in ''|*[!0-9]*) return 0 ;; esac
|
|
3919
|
+
[ "$attempt" -gt 1 ] || return 0
|
|
3920
|
+
[ "$who" != 'github-actions[bot]' ] || return 0
|
|
3921
|
+
run_actor_listed "$who" && return 0
|
|
3922
|
+
RUN_ACTORS_WHY="this job is a re-run by @${who:-(no actor)}, and the re-runner is checked rather than the event's sender: $RUN_ACTORS_WHY"
|
|
3923
|
+
return 1
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3475
3926
|
# authorise_actor <login> <type> — the one actor check, shared by `trigger` and
|
|
3476
3927
|
# `control`: 0 when authorised. Otherwise AUTH_WHY holds one sentence and the
|
|
3477
3928
|
# status names the arm: 1 `ghost`, empty or not a login shape; 2 a non-`User`
|
|
3478
3929
|
# not listed in HARNESS_TRIGGER_ALLOWED_BOTS, decided with no permission call,
|
|
3479
|
-
# because the permission API answers `none` or 404 for a bot
|
|
3480
|
-
#
|
|
3481
|
-
#
|
|
3930
|
+
# because the permission API answers `none` or 404 for a bot, and never
|
|
3931
|
+
# consulting the run-actor list; 3 a `User` whose permission is not `admin` or
|
|
3932
|
+
# `write` (AUTH_PERMISSION holds it); 4 that permission call failed (GH_ERR
|
|
3933
|
+
# holds why); 5 a writer `run_actor_listed` refuses. The list is checked after
|
|
3934
|
+
# the permission call so a non-writer's refusal keeps naming write access, and
|
|
3935
|
+
# 5 is kept for a writer the maintainer has not named. Prints nothing and
|
|
3936
|
+
# never posts.
|
|
3482
3937
|
AUTH_WHY=""
|
|
3483
3938
|
AUTH_PERMISSION=""
|
|
3484
3939
|
authorise_actor() {
|
|
@@ -3502,7 +3957,10 @@ authorise_actor() {
|
|
|
3502
3957
|
fi
|
|
3503
3958
|
AUTH_PERMISSION=$(printf '%s' "$GH_OUT" | jq -r '.permission // empty' 2>/dev/null) || AUTH_PERMISSION=""
|
|
3504
3959
|
case "$AUTH_PERMISSION" in
|
|
3505
|
-
admin|write)
|
|
3960
|
+
admin|write)
|
|
3961
|
+
run_actor_listed "$login" && return 0
|
|
3962
|
+
AUTH_WHY="$RUN_ACTORS_WHY"
|
|
3963
|
+
return 5 ;;
|
|
3506
3964
|
esac
|
|
3507
3965
|
AUTH_WHY="GitHub reports the permission of @$login as ${AUTH_PERMISSION:-nothing}, not write or admin."
|
|
3508
3966
|
return 3
|
|
@@ -3602,7 +4060,9 @@ verb_trigger() {
|
|
|
3602
4060
|
{ event_field '.action // ""' && action="$EVENT_VALUE" \
|
|
3603
4061
|
&& event_field '.client_payload.title // ""' && title="$EVENT_VALUE" \
|
|
3604
4062
|
&& event_field '.client_payload.body // ""' && body="$EVENT_VALUE" \
|
|
3605
|
-
&& event_field '.client_payload.source // ""' && source="$EVENT_VALUE"
|
|
4063
|
+
&& event_field '.client_payload.source // ""' && source="$EVENT_VALUE" \
|
|
4064
|
+
&& event_field '.sender.login // ""' && login="$EVENT_VALUE" \
|
|
4065
|
+
&& event_field '.sender.type // ""' && sender_type="$EVENT_VALUE"; } || {
|
|
3606
4066
|
echo "remote-run.sh: trigger: '$GITHUB_EVENT_PATH' is not a readable event" >&2
|
|
3607
4067
|
exit "$EXIT_USAGE"
|
|
3608
4068
|
}
|
|
@@ -3638,6 +4098,26 @@ verb_trigger() {
|
|
|
3638
4098
|
"Set both keys on the default branch, then $retry_then."
|
|
3639
4099
|
fi
|
|
3640
4100
|
|
|
4101
|
+
# A re-run replays the event's sender; the re-runner is held to the list.
|
|
4102
|
+
if ! rerun_actor_listed; then
|
|
4103
|
+
trigger_refuse "$RUN_ACTORS_WHY" \
|
|
4104
|
+
"Only a person the repository variable \`HARNESS_RUN_ACTORS\` admits may re-run this job; one of them can $retry_then."
|
|
4105
|
+
fi
|
|
4106
|
+
|
|
4107
|
+
# A dispatch's `User` sender is held to the allow-list with no permission
|
|
4108
|
+
# call; a sender with no type fails closed, since an empty login is admitted
|
|
4109
|
+
# by `*` alone; any other sender type is the token's, and passes.
|
|
4110
|
+
if [ "$trigger_source" = dispatch ]; then
|
|
4111
|
+
if [ -z "$sender_type" ] && ! run_actor_listed ""; then
|
|
4112
|
+
trigger_refuse "the dispatch's sender carries no account type, so it cannot be checked against the allow-list." \
|
|
4113
|
+
"Set the repository variable \`HARNESS_RUN_ACTORS\` to \`*\` to admit every token holder, then $retry_then."
|
|
4114
|
+
fi
|
|
4115
|
+
if [ "$sender_type" = User ] && ! run_actor_listed "$login"; then
|
|
4116
|
+
trigger_refuse "$RUN_ACTORS_WHY" \
|
|
4117
|
+
"Add \`$login\` to the comma-separated repository variable \`HARNESS_RUN_ACTORS\`, or set it to \`*\` to admit every writer, then $retry_then."
|
|
4118
|
+
fi
|
|
4119
|
+
fi
|
|
4120
|
+
|
|
3641
4121
|
if [ "$trigger_source" = issue ]; then
|
|
3642
4122
|
if [ "$state" != open ]; then
|
|
3643
4123
|
trigger_refuse "this issue is not open." "Reopen it, then re-apply the label \`$trigger_label\`."
|
|
@@ -3653,6 +4133,8 @@ verb_trigger() {
|
|
|
3653
4133
|
"Add \`$login\` to that comma-separated list to let it start runs, or have a collaborator with write access apply the label \`$trigger_label\`." ;;
|
|
3654
4134
|
3) trigger_refuse "could not confirm write access for @$login: GitHub reports their permission as \`${AUTH_PERMISSION:-nothing}\`." \
|
|
3655
4135
|
"Only a collaborator with write, maintain or admin access starts a run by labelling an issue; one of them can re-apply the label \`$trigger_label\`." ;;
|
|
4136
|
+
5) trigger_refuse "$AUTH_WHY" \
|
|
4137
|
+
"Add \`$login\` to the comma-separated repository variable \`HARNESS_RUN_ACTORS\`, or set it to \`*\` to admit every writer, then re-apply the label \`$trigger_label\`." ;;
|
|
3656
4138
|
*) trigger_refuse "could not confirm write access for @$login: the permission check failed ($GH_ERR)." \
|
|
3657
4139
|
"Re-apply the label \`$trigger_label\` to try again." ;;
|
|
3658
4140
|
esac
|
|
@@ -3746,9 +4228,12 @@ The task is the dispatch's \`client_payload\` title and body. Sending the same d
|
|
|
3746
4228
|
# never exits, and reports a failure as one `remote-run.sh: …` line on stderr.
|
|
3747
4229
|
#
|
|
3748
4230
|
# THE TARGET RULE. A comment goes to the open same-repository pull request
|
|
3749
|
-
# whose head is the branch when `forge_recognised` holds for that branch
|
|
3750
|
-
#
|
|
3751
|
-
#
|
|
4231
|
+
# whose head is the branch when `forge_recognised` holds for that branch — its
|
|
4232
|
+
# origin tip carries its task prompt or its flow-progress ledger — else to the
|
|
4233
|
+
# issue the run was started from (`FORGE_ISSUE`), else nowhere. The task prompt
|
|
4234
|
+
# counts so that a pull request opened at the run's start is a target before
|
|
4235
|
+
# the ledger's first push. The state label goes on that issue and on that pull
|
|
4236
|
+
# request, each when known.
|
|
3752
4237
|
#
|
|
3753
4238
|
# THE LABEL IS A VIEW, NEVER AN AUTHORITY. The run list is the authority
|
|
3754
4239
|
# (`remote_state`); the harness overwrites any state label set by hand, and
|
|
@@ -3807,14 +4292,12 @@ forge_fetch_branch() {
|
|
|
3807
4292
|
return 0
|
|
3808
4293
|
}
|
|
3809
4294
|
|
|
3810
|
-
# forge_marker <event> <branch> [<question>] — the one
|
|
3811
|
-
# marker line, built from COMMENT_MARKER
|
|
4295
|
+
# forge_marker <event> <branch> [<question> [<engine> [<round>]]] — the one
|
|
4296
|
+
# producer of a comment's marker line, built from COMMENT_MARKER: ` question=<n>`,
|
|
4297
|
+
# ` engine=<engine>` then ` round=<n>`, each only when non-empty, before ` -->`.
|
|
3812
4298
|
forge_marker() {
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
else
|
|
3816
|
-
printf '%s event=%s branch=%s -->\n' "$COMMENT_MARKER" "$1" "$2"
|
|
3817
|
-
fi
|
|
4299
|
+
printf '%s event=%s branch=%s%s%s%s -->\n' "$COMMENT_MARKER" "$1" "$2" \
|
|
4300
|
+
"${3:+ question=$3}" "${4:+ engine=$4}" "${5:+ round=$5}"
|
|
3818
4301
|
}
|
|
3819
4302
|
|
|
3820
4303
|
# forge_issue_var <branch> — FORGE_ISSUE from the last provenance line
|
|
@@ -3895,38 +4378,105 @@ forge_provenance_parse() {
|
|
|
3895
4378
|
}
|
|
3896
4379
|
|
|
3897
4380
|
# forge_recognised <branch> — the harness-branch test, read from committed
|
|
3898
|
-
# state: 0 when origin's copy of the branch carries its
|
|
4381
|
+
# state: 0 when origin's copy of the branch carries its task prompt or its
|
|
4382
|
+
# flow-progress ledger, 1 otherwise.
|
|
3899
4383
|
forge_recognised() {
|
|
3900
4384
|
local state_rel
|
|
3901
4385
|
state_rel=$(hr_state_dir "$root" 2>/dev/null) || return 1
|
|
3902
4386
|
[ -n "$state_rel" ] || return 1
|
|
3903
|
-
git -C "$root" cat-file -e "refs/remotes/origin/$1:${state_rel%/}/flow_progress/$1_progress.md" 2>/dev/null
|
|
4387
|
+
git -C "$root" cat-file -e "refs/remotes/origin/$1:${state_rel%/}/flow_progress/$1_progress.md" 2>/dev/null \
|
|
4388
|
+
|| git -C "$root" cat-file -e "refs/remotes/origin/$1:$(hr_task_prompt_rel "$state_rel" "$1")" 2>/dev/null
|
|
3904
4389
|
}
|
|
3905
4390
|
|
|
3906
4391
|
# forge_pr_var <branch> — FORGE_PR, the open pull request whose head is
|
|
3907
|
-
# <branch> in this repository (a fork's same-named head is skipped), or empty
|
|
4392
|
+
# <branch> in this repository (a fork's same-named head is skipped), or empty;
|
|
4393
|
+
# FORGE_PR_DRAFT, that same pull request's `isDraft` as `true` or `false`, or
|
|
4394
|
+
# empty when FORGE_PR is.
|
|
3908
4395
|
FORGE_PR=""
|
|
4396
|
+
FORGE_PR_DRAFT=""
|
|
3909
4397
|
forge_pr_var() {
|
|
4398
|
+
local sel
|
|
3910
4399
|
FORGE_PR=""
|
|
3911
|
-
|
|
4400
|
+
FORGE_PR_DRAFT=""
|
|
4401
|
+
if ! gh_call pr list --repo "$FORGE_REPO" --head "$1" --state open --json number,isCrossRepository,isDraft --limit 10; then
|
|
3912
4402
|
echo "remote-run.sh: listing the open pull requests of $1 failed: $GH_ERR" >&2
|
|
3913
4403
|
return 1
|
|
3914
4404
|
fi
|
|
3915
|
-
if !
|
|
3916
|
-
'if type == "array" then [.[] | select(.isCrossRepository == false
|
|
3917
|
-
FORGE_PR=""
|
|
4405
|
+
if ! sel=$(printf '%s' "$GH_OUT" | jq -r \
|
|
4406
|
+
'if type == "array" then [.[] | select(.isCrossRepository == false and (.number | type) == "number")] | first // empty | "\(.number) \(.isDraft == true)" else error end' 2>/dev/null); then
|
|
3918
4407
|
GH_ERR="its pr list is not the expected JSON"
|
|
3919
4408
|
echo "remote-run.sh: listing the open pull requests of $1 failed: $GH_ERR" >&2
|
|
3920
4409
|
return 1
|
|
3921
4410
|
fi
|
|
4411
|
+
if [ -n "$sel" ]; then
|
|
4412
|
+
FORGE_PR="${sel%% *}"
|
|
4413
|
+
FORGE_PR_DRAFT="${sel#* }"
|
|
4414
|
+
fi
|
|
3922
4415
|
return 0
|
|
3923
4416
|
}
|
|
3924
4417
|
|
|
3925
|
-
#
|
|
3926
|
-
#
|
|
4418
|
+
# forge_dispatch_engine_var <branch> <created_at_iso> — FORGE_DISPATCH_ENGINE,
|
|
4419
|
+
# the engine the dispatch that created the run at <created_at_iso> recorded in
|
|
4420
|
+
# its comment on the branch's issue or open pull request, or empty. Only a
|
|
4421
|
+
# `github-actions[bot]` comment counts, whose last non-empty line is exactly a
|
|
4422
|
+
# `started` or `round` marker for <branch>, either with or without an engine,
|
|
4423
|
+
# or a `reply` marker for <branch> carrying an engine, and whose `created_at`
|
|
4424
|
+
# is no earlier than <created_at_iso> less DISPATCH_MARKER_SLACK_SECS. The
|
|
4425
|
+
# newest one answers: `started` is `task`, `round` is `user_review`, `reply`
|
|
4426
|
+
# its own engine. 1 when the forge is off or the repository is unknown. Fetches
|
|
4427
|
+
# origin's <branch> into the root checkout; replaces GH_OUT.
|
|
4428
|
+
FORGE_DISPATCH_ENGINE=""
|
|
4429
|
+
forge_dispatch_engine_var() {
|
|
4430
|
+
local b="$1" created="${2-}" since markers listed="" n engine answer
|
|
4431
|
+
FORGE_DISPATCH_ENGINE=""
|
|
4432
|
+
forge_on || return 1
|
|
4433
|
+
forge_repo_var || return 1
|
|
4434
|
+
if ! since=$(jq -n -r --arg t "$created" --argjson o "$DISPATCH_MARKER_SLACK_SECS" '($t | fromdateiso8601) - $o' 2>/dev/null) \
|
|
4435
|
+
|| [ -z "$since" ]; then
|
|
4436
|
+
echo "remote-run.sh: the newest run of $b has no readable createdAt ('$created'); its dispatch's engine is not read" >&2
|
|
4437
|
+
return 0
|
|
4438
|
+
fi
|
|
4439
|
+
# Every marker line that qualifies, each with the engine it answers.
|
|
4440
|
+
markers=""
|
|
4441
|
+
for engine in task user_review docs; do
|
|
4442
|
+
markers="$markers$(forge_marker started "$b" "" "$engine")"$'\t'task$'\n'
|
|
4443
|
+
markers="$markers$(forge_marker round "$b" "" "$engine")"$'\t'user_review$'\n'
|
|
4444
|
+
markers="$markers$(forge_marker reply "$b" "" "$engine")"$'\t'"$engine"$'\n'
|
|
4445
|
+
done
|
|
4446
|
+
markers="$markers$(forge_marker started "$b")"$'\t'task$'\n'
|
|
4447
|
+
markers="$markers$(forge_marker round "$b")"$'\t'user_review$'\n'
|
|
4448
|
+
markers=$(printf '%s' "$markers" | jq -R -s -c \
|
|
4449
|
+
'[split("\n")[] | select(length > 0) | split("\t") | {(.[0]): .[1]}] | add')
|
|
4450
|
+
forge_fetch_branch "$b"
|
|
4451
|
+
forge_issue_var "$b" || FORGE_ISSUE=""
|
|
4452
|
+
forge_pr_var "$b" || FORGE_PR=""
|
|
4453
|
+
for n in $FORGE_ISSUE $FORGE_PR; do
|
|
4454
|
+
if ! gh_call api --paginate "repos/$FORGE_REPO/issues/$n/comments" --jq '.[] | {login: .user.login, at: .created_at, body: .body}'; then
|
|
4455
|
+
echo "remote-run.sh: listing the comments of #$n failed, so the engine of $b's dispatch is not read: $GH_ERR" >&2
|
|
4456
|
+
return 0
|
|
4457
|
+
fi
|
|
4458
|
+
listed="$listed$GH_OUT"$'\n'
|
|
4459
|
+
done
|
|
4460
|
+
if ! answer=$(printf '%s' "$listed" | jq -s -r --argjson m "$markers" --argjson since "$since" '
|
|
4461
|
+
[.[] | select(type == "object" and .login == "github-actions[bot]" and (.at | type) == "string")
|
|
4462
|
+
| ((.body // "") | gsub("\r"; "") | split("\n") | map(select(test("^\\s*$") | not)) | last // "") as $last
|
|
4463
|
+
| select($m[$last] != null)
|
|
4464
|
+
| (.at | try fromdateiso8601 catch null) as $t
|
|
4465
|
+
| select($t != null and $t >= $since)
|
|
4466
|
+
| {t: $t, e: $m[$last]}]
|
|
4467
|
+
| sort_by(.t) | last | .e // ""' 2>/dev/null); then
|
|
4468
|
+
echo "remote-run.sh: the comments of $b's issue and pull request are not the expected JSON, so the engine of its dispatch is not read" >&2
|
|
4469
|
+
return 0
|
|
4470
|
+
fi
|
|
4471
|
+
FORGE_DISPATCH_ENGINE="$answer"
|
|
4472
|
+
return 0
|
|
4473
|
+
}
|
|
4474
|
+
|
|
4475
|
+
# forge_comment <number> <event> <branch> <body_file> [<question> [<engine> [<round>]]]
|
|
4476
|
+
# — append the marker to <body_file> and post it on issue or pull request <number>.
|
|
3927
4477
|
forge_comment() {
|
|
3928
|
-
local number="$1" event="$2" branch="$3" file="$4" question="${5-}" status
|
|
3929
|
-
if ! { printf '\n'; forge_marker "$event" "$branch" "$question"; } >>"$file"; then
|
|
4478
|
+
local number="$1" event="$2" branch="$3" file="$4" question="${5-}" engine="${6-}" round="${7-}" status
|
|
4479
|
+
if ! { printf '\n'; forge_marker "$event" "$branch" "$question" "$engine" "$round"; } >>"$file"; then
|
|
3930
4480
|
echo "remote-run.sh: cannot append the marker to '$file'" >&2
|
|
3931
4481
|
return 1
|
|
3932
4482
|
fi
|
|
@@ -4039,13 +4589,22 @@ forge_question_body() {
|
|
|
4039
4589
|
# comment (on `parked`, one per open question) and the state label, by the
|
|
4040
4590
|
# target rule above. Read on `stopped` only: <pr>, an explicit pull request
|
|
4041
4591
|
# that is the target whatever its state; `gone`, the branch deleted on GitHub,
|
|
4042
|
-
# its issue read from the task prompt at <sha>.
|
|
4592
|
+
# its issue read from the task prompt at <sha>. Every event but `stopped` is
|
|
4593
|
+
# withheld when the branch's newest `harness stop` run is newer than its newest
|
|
4594
|
+
# `harness run` run, read from a fresh listing. `not_started` reads
|
|
4595
|
+
# REPORT_NOT_STARTED_STATE (`paused`, else `failed`) and
|
|
4596
|
+
# REPORT_NOT_STARTED_ENGINE (empty or the recovered engine), set by its caller.
|
|
4597
|
+
# Always 0.
|
|
4598
|
+
REPORT_NOT_STARTED_STATE=""
|
|
4599
|
+
REPORT_NOT_STARTED_ENGINE=""
|
|
4043
4600
|
forge_report() {
|
|
4044
4601
|
local event="$1" br="$2" note="${3-}" pr="${4-}" gone="${5-}" gone_sha="${6-}" state reason="" resume_at="" when registry_file
|
|
4045
|
-
local target kind text tmp made_tmp="" file trigger_label stopped state_rel="" count n
|
|
4602
|
+
local target kind text tmp made_tmp="" file trigger_label stopped state_rel="" count n route engine=""
|
|
4046
4603
|
case "$event" in
|
|
4047
4604
|
parked|park_loop) state=parked ;;
|
|
4048
4605
|
paused) state=paused ;;
|
|
4606
|
+
not_started)
|
|
4607
|
+
if [ "$REPORT_NOT_STARTED_STATE" = paused ]; then state=paused; else state=failed; fi ;;
|
|
4049
4608
|
resumed) state=running ;;
|
|
4050
4609
|
failed) state=failed ;;
|
|
4051
4610
|
stopped) state=stopped ;;
|
|
@@ -4066,7 +4625,9 @@ forge_report() {
|
|
|
4066
4625
|
fi
|
|
4067
4626
|
forge_repo_var || return 0
|
|
4068
4627
|
|
|
4069
|
-
if [ "$event"
|
|
4628
|
+
if [ "$event" != stopped ]; then
|
|
4629
|
+
# Re-listed: `review` dispatches a run and then reports `round` in one invocation.
|
|
4630
|
+
ALL_RUNS_LISTED=0
|
|
4070
4631
|
remote_branch_stopped "$br"
|
|
4071
4632
|
stopped=$?
|
|
4072
4633
|
if [ "$stopped" -eq 0 ]; then
|
|
@@ -4074,7 +4635,7 @@ forge_report() {
|
|
|
4074
4635
|
return 0
|
|
4075
4636
|
fi
|
|
4076
4637
|
[ "$stopped" -eq 1 ] \
|
|
4077
|
-
|| echo "remote-run.sh: report: whether $br was stopped is unknown ($GH_ERR); reporting the
|
|
4638
|
+
|| echo "remote-run.sh: report: whether $br was stopped is unknown ($GH_ERR); reporting the $event" >&2
|
|
4078
4639
|
fi
|
|
4079
4640
|
|
|
4080
4641
|
[ "$event" = stopped ] || { pr=""; gone=""; }
|
|
@@ -4093,7 +4654,7 @@ forge_report() {
|
|
|
4093
4654
|
forge_pr_var "$br" || FORGE_PR=""
|
|
4094
4655
|
fi
|
|
4095
4656
|
if [ -z "$pr" ] && [ -n "$FORGE_PR" ] && ! forge_recognised "$br"; then
|
|
4096
|
-
echo "remote-run.sh: report: pull request #$FORGE_PR's head carries
|
|
4657
|
+
echo "remote-run.sh: report: pull request #$FORGE_PR's head carries neither a task prompt nor a flow-progress ledger; it is not a target"
|
|
4097
4658
|
FORGE_PR=""
|
|
4098
4659
|
fi
|
|
4099
4660
|
if [ -n "$FORGE_PR" ]; then
|
|
@@ -4110,6 +4671,7 @@ forge_report() {
|
|
|
4110
4671
|
if [ -n "$registry_file" ] && [ -f "$registry_file" ]; then
|
|
4111
4672
|
reason=$(hr_registry_get "$registry_file" "$br" pause_reason)
|
|
4112
4673
|
resume_at=$(hr_registry_get "$registry_file" "$br" usage_resume_at)
|
|
4674
|
+
[ "$event" != failed ] || engine=$(hr_registry_get "$registry_file" "$br" engine)
|
|
4113
4675
|
fi
|
|
4114
4676
|
|
|
4115
4677
|
case "$event" in
|
|
@@ -4125,10 +4687,13 @@ forge_report() {
|
|
|
4125
4687
|
resumed)
|
|
4126
4688
|
text="The harness run on \`$br\` resumed." ;;
|
|
4127
4689
|
failed)
|
|
4128
|
-
|
|
4129
|
-
|
|
4690
|
+
trigger_label="${FORGE_TRIGGER_LABEL:-${HARNESS_TRIGGER_LABEL:-$DEFAULT_TRIGGER_LABEL}}"
|
|
4691
|
+
if [ "$kind" = pr ] && [ "$engine" = user_review ]; then
|
|
4692
|
+
text="The harness run on \`$br\` failed. Its log is \`run.log\` in the run's \`$STATE_ARTIFACT_NAME\` artifact. To start again, submit a review on this pull request requesting changes. This pull request stays open."
|
|
4693
|
+
elif [ "$kind" = pr ]; then
|
|
4694
|
+
# No review route: a round would start over a task run that never completed.
|
|
4695
|
+
text="The harness run on \`$br\` failed. Its log is \`run.log\` in the run's \`$STATE_ARTIFACT_NAME\` artifact. This draft pull request stays open: close it to discard the run${FORGE_ISSUE:+, or re-apply the label \`$trigger_label\` to issue #$FORGE_ISSUE to start a new run on the next indexed branch}."
|
|
4130
4696
|
else
|
|
4131
|
-
trigger_label="${FORGE_TRIGGER_LABEL:-${HARNESS_TRIGGER_LABEL:-$DEFAULT_TRIGGER_LABEL}}"
|
|
4132
4697
|
text="The harness run on \`$br\` failed. Its log is \`run.log\` in the run's \`$STATE_ARTIFACT_NAME\` artifact. To start again, re-apply the label \`$trigger_label\` to this issue; that starts a new run, on the next indexed branch."
|
|
4133
4698
|
fi ;;
|
|
4134
4699
|
stopped)
|
|
@@ -4139,9 +4704,28 @@ forge_report() {
|
|
|
4139
4704
|
text="The harness run on \`$br\` was stopped. While the branch exists, reopen this pull request and comment \`${COMMAND_HANDLE} resume\` here, or comment it on the run's issue, to continue it from its committed ledger. A merged pull request cannot be reopened; after a merge, use the issue."
|
|
4140
4705
|
else
|
|
4141
4706
|
text="The harness run on \`$br\` was stopped. Comment \`${COMMAND_HANDLE} resume\` to continue it from its committed ledger. A review that requests changes is collected now, and its round starts once the resumed run finishes."
|
|
4707
|
+
[ "$kind" != pr ] || text="$text Its draft pull request stays open; closing it discards the run."
|
|
4142
4708
|
fi ;;
|
|
4143
4709
|
round)
|
|
4144
|
-
text="A user-review round started on \`$br\`; a \`completed\` comment follows when the branch is ready for review again."
|
|
4710
|
+
text="A user-review round started on \`$br\`; a \`completed\` comment follows when the branch is ready for review again."
|
|
4711
|
+
# The undo runs before the comment is written, so the comment states its outcome.
|
|
4712
|
+
if [ "$kind" = pr ] && [ "$FORGE_PR_DRAFT" = false ]; then
|
|
4713
|
+
if gh_call pr ready "$FORGE_PR" --repo "$FORGE_REPO" --undo; then
|
|
4714
|
+
text="$text This pull request is a draft again until the round completes."
|
|
4715
|
+
else
|
|
4716
|
+
echo "::warning::remote-run.sh: report: turning pull request #$FORGE_PR back to a draft was refused: $GH_ERR"
|
|
4717
|
+
text="$text Turning this pull request back to a draft was refused, so it stays ready for review while the round works."
|
|
4718
|
+
fi
|
|
4719
|
+
fi ;;
|
|
4720
|
+
not_started)
|
|
4721
|
+
text="GitHub did not start the job of the harness run on \`$br\`, so nothing ran and the branch is unchanged."
|
|
4722
|
+
if [ "$state" = paused ] && [ -n "$REPORT_NOT_STARTED_ENGINE" ]; then
|
|
4723
|
+
text="$text Comment \`${COMMAND_HANDLE} resume\` to start it again."
|
|
4724
|
+
else
|
|
4725
|
+
# A job that never started uploaded no artifact to read an engine from.
|
|
4726
|
+
route=$(hr_github_resume_route "$br" "${REPORT_NOT_STARTED_ENGINE:-<task, user_review or docs: the one the run was started with>}")
|
|
4727
|
+
text="$text Start it again with the **Run workflow** form: ${route#or from GitHub: }."
|
|
4728
|
+
fi ;;
|
|
4145
4729
|
esac
|
|
4146
4730
|
|
|
4147
4731
|
OPEN_QUESTIONS=""
|
|
@@ -4193,8 +4777,122 @@ forge_report() {
|
|
|
4193
4777
|
return 0
|
|
4194
4778
|
}
|
|
4195
4779
|
|
|
4780
|
+
# forge_progress <branch> — upsert the one progress comment of the run or round
|
|
4781
|
+
# on its pull request, rendered from the job checkout's flow-progress ledger:
|
|
4782
|
+
# created when none is listed, edited in place only when its body differs. No
|
|
4783
|
+
# issue fallback, no label. Always 0.
|
|
4784
|
+
forge_progress() {
|
|
4785
|
+
local br="$1" status state_rel phases engine round p line i first=1 marker
|
|
4786
|
+
local tmp made_tmp="" file pick id same
|
|
4787
|
+
local -a labels states
|
|
4788
|
+
if ! forge_on; then
|
|
4789
|
+
echo "remote-run.sh: report: the forge coupling is off (forge github and execution.target github-actions); nothing posted"
|
|
4790
|
+
return 0
|
|
4791
|
+
fi
|
|
4792
|
+
status=0
|
|
4793
|
+
hr_progress_comments "$root" || status=$?
|
|
4794
|
+
case "$status" in
|
|
4795
|
+
0) ;;
|
|
4796
|
+
1) echo "remote-run.sh: report: progress comments are off by execution.progressComments; nothing posted"; return 0 ;;
|
|
4797
|
+
*) echo "remote-run.sh: report: execution.progressComments is unreadable; nothing posted"; return 0 ;;
|
|
4798
|
+
esac
|
|
4799
|
+
forge_repo_var || return 0
|
|
4800
|
+
ALL_RUNS_LISTED=0
|
|
4801
|
+
status=0
|
|
4802
|
+
remote_branch_stopped "$br" || status=$?
|
|
4803
|
+
if [ "$status" -eq 0 ]; then
|
|
4804
|
+
echo "remote-run.sh: report: $br was stopped, and the stop already reported the run; nothing posted"
|
|
4805
|
+
return 0
|
|
4806
|
+
fi
|
|
4807
|
+
[ "$status" -eq 1 ] \
|
|
4808
|
+
|| echo "remote-run.sh: report: whether $br was stopped is unknown ($GH_ERR); reporting the progress" >&2
|
|
4809
|
+
state_rel=$(hr_state_dir "$root" 2>/dev/null) || state_rel=""
|
|
4810
|
+
if [ -z "$state_rel" ] || ! phases=$(hr_ledger_phases "$root/${state_rel%/}/flow_progress/${br}_progress.md"); then
|
|
4811
|
+
echo "remote-run.sh: report: $br has no task or user-review ledger; nothing posted"
|
|
4812
|
+
return 0
|
|
4813
|
+
fi
|
|
4814
|
+
read -r engine round states[0] states[1] states[2] states[3] <<<"$phases"
|
|
4815
|
+
forge_fetch_branch "$br"
|
|
4816
|
+
forge_pr_var "$br" || FORGE_PR=""
|
|
4817
|
+
if [ -z "$FORGE_PR" ] || ! forge_recognised "$br"; then
|
|
4818
|
+
echo "remote-run.sh: report: $br has no recognised open pull request; nothing posted"
|
|
4819
|
+
return 0
|
|
4820
|
+
fi
|
|
4821
|
+
|
|
4822
|
+
if [ "$engine" = user_review ]; then
|
|
4823
|
+
labels=('Fix plan' 'Fix implementation' 'Branch review' 'Done')
|
|
4824
|
+
line="Progress of user-review round $round on \`$br\`:"
|
|
4825
|
+
else
|
|
4826
|
+
labels=('Planning' 'Implementation' 'Branch review' 'Done')
|
|
4827
|
+
line="Progress of the harness run on \`$br\`:"
|
|
4828
|
+
round=""
|
|
4829
|
+
fi
|
|
4830
|
+
marker=$(forge_marker progress "$br" "" "" "$round")
|
|
4831
|
+
|
|
4832
|
+
tmp="${RUNNER_TEMP-}"
|
|
4833
|
+
if [ -z "$tmp" ] || [ ! -d "$tmp" ]; then
|
|
4834
|
+
tmp=$(mktemp -d) || tmp=""
|
|
4835
|
+
made_tmp="$tmp"
|
|
4836
|
+
fi
|
|
4837
|
+
if [ -z "$tmp" ] || ! file=$(mktemp "$tmp/harness-progress-comment.XXXXXX"); then
|
|
4838
|
+
echo "::warning::remote-run.sh: report: cannot create the progress comment file for #$FORGE_PR; nothing posted"
|
|
4839
|
+
[ -z "$made_tmp" ] || rmdir "$made_tmp" 2>/dev/null || :
|
|
4840
|
+
return 0
|
|
4841
|
+
fi
|
|
4842
|
+
# No timestamp and no run URL: an unchanged ledger must render byte-identical.
|
|
4843
|
+
# Plain list items: a `- [ ]` box is clickable by anyone with write access.
|
|
4844
|
+
{
|
|
4845
|
+
printf '%s\n\n' "$line"
|
|
4846
|
+
for i in 0 1 2 3; do
|
|
4847
|
+
if [ "${states[$i]}" = done ]; then
|
|
4848
|
+
p=done
|
|
4849
|
+
elif [ "$first" -eq 1 ]; then
|
|
4850
|
+
p="in progress"; first=0
|
|
4851
|
+
else
|
|
4852
|
+
p="not started"
|
|
4853
|
+
fi
|
|
4854
|
+
printf -- '- %s: %s\n' "${labels[$i]}" "$p"
|
|
4855
|
+
done
|
|
4856
|
+
} >"$file"
|
|
4857
|
+
{ cat "$file"; printf '\n%s\n' "$marker"; } >"$file.full"
|
|
4858
|
+
|
|
4859
|
+
if ! gh_call api --paginate "repos/$FORGE_REPO/issues/$FORGE_PR/comments" --jq '.[] | {id, login: .user.login, body}'; then
|
|
4860
|
+
echo "::warning::remote-run.sh: report: listing the comments of #$FORGE_PR was refused, so the progress is not posted: $GH_ERR"
|
|
4861
|
+
elif ! pick=$(printf '%s' "$GH_OUT" | jq -s -r --arg m "$marker" --arg want "$(cat "$file.full")" '
|
|
4862
|
+
def norm: gsub("\r"; "") | sub("\n+$"; "");
|
|
4863
|
+
[.[] | select(type == "object" and .login == "github-actions[bot]" and (.id | type) == "number")
|
|
4864
|
+
| select(((.body // "") | gsub("\r"; "") | split("\n") | map(select(test("^\\s*$") | not)) | last // "") == $m)]
|
|
4865
|
+
| max_by(.id) // empty
|
|
4866
|
+
| "\(.id) \(if ((.body // "") | norm) == ($want | norm) then "same" else "differs" end)"' 2>/dev/null); then
|
|
4867
|
+
echo "::warning::remote-run.sh: report: the comments of #$FORGE_PR are not the expected JSON, so the progress is not posted"
|
|
4868
|
+
elif [ -z "$pick" ]; then
|
|
4869
|
+
if forge_comment "$FORGE_PR" progress "$br" "$file" "" "" "$round"; then
|
|
4870
|
+
echo "remote-run.sh: report: progress on $br posted on #$FORGE_PR"
|
|
4871
|
+
else
|
|
4872
|
+
echo "::warning::remote-run.sh: report: posting the progress comment on #$FORGE_PR was refused: $GH_ERR"
|
|
4873
|
+
fi
|
|
4874
|
+
else
|
|
4875
|
+
id="${pick%% *}"
|
|
4876
|
+
same="${pick#* }"
|
|
4877
|
+
if [ "$same" = same ]; then
|
|
4878
|
+
echo "remote-run.sh: report: the progress comment on #$FORGE_PR is already current"
|
|
4879
|
+
elif gh_call api --method PATCH "repos/$FORGE_REPO/issues/comments/$id" -F "body=@$file.full"; then
|
|
4880
|
+
echo "remote-run.sh: report: progress on $br edited in comment $id on #$FORGE_PR"
|
|
4881
|
+
else
|
|
4882
|
+
echo "::warning::remote-run.sh: report: editing the progress comment $id on #$FORGE_PR was refused: $GH_ERR"
|
|
4883
|
+
fi
|
|
4884
|
+
fi
|
|
4885
|
+
rm -f "$file" "$file.full"
|
|
4886
|
+
[ -z "$made_tmp" ] || rmdir "$made_tmp" 2>/dev/null || :
|
|
4887
|
+
return 0
|
|
4888
|
+
}
|
|
4889
|
+
|
|
4196
4890
|
verb_report() {
|
|
4197
|
-
|
|
4891
|
+
if [ "$report_event" = progress ]; then
|
|
4892
|
+
forge_progress "$branch"
|
|
4893
|
+
else
|
|
4894
|
+
forge_report "$report_event" "$branch" "$report_note"
|
|
4895
|
+
fi
|
|
4198
4896
|
exit "$EXIT_OK"
|
|
4199
4897
|
}
|
|
4200
4898
|
|
|
@@ -4229,7 +4927,7 @@ deliver_pr_body() {
|
|
|
4229
4927
|
verbs="$verbs${verbs:+, }\`$COMMAND_HANDLE $v\`"
|
|
4230
4928
|
done
|
|
4231
4929
|
{
|
|
4232
|
-
printf 'This pull request carries the harness run on `%s
|
|
4930
|
+
printf 'This pull request carries the harness run on `%s`. It stays a draft while the run works, and the harness marks it ready for review when the run completes; the harness never merges it.\n' "$2"
|
|
4233
4931
|
[ -z "$FORGE_ISSUE" ] || printf '\nStarted from #%s.\n' "$FORGE_ISSUE"
|
|
4234
4932
|
printf '\nA review that requests changes starts a user-review round on this branch.\n'
|
|
4235
4933
|
printf 'Comment %s to act on the run; each says when it applies (docs/github-run-control.md in the harness documentation).\n' "$verbs"
|
|
@@ -4251,11 +4949,280 @@ deliver_create() {
|
|
|
4251
4949
|
fi
|
|
4252
4950
|
}
|
|
4253
4951
|
|
|
4254
|
-
#
|
|
4255
|
-
#
|
|
4952
|
+
# forge_open_pr <branch> — the one opener of a run's pull request, against
|
|
4953
|
+
# `defaultBranch`: a `--draft` create, then on any failure but
|
|
4954
|
+
# PR_CREATE_FORBIDDEN one retry without `--draft`. The caller has set
|
|
4955
|
+
# FORGE_REPO, FORGE_SERVER and FORGE_ISSUE. 0 when opened, setting FORGE_PR,
|
|
4956
|
+
# FORGE_PR_URL and FORGE_PR_DRAFT (`false` after the retry); 1 when not,
|
|
4957
|
+
# setting OPEN_ERR (one line) and OPEN_FORBIDDEN (1 on PR_CREATE_FORBIDDEN).
|
|
4958
|
+
# Always sets OPEN_BASE, empty when unreadable. Never posts, never exits.
|
|
4959
|
+
FORGE_PR_URL=""
|
|
4960
|
+
OPEN_ERR=""
|
|
4961
|
+
OPEN_FORBIDDEN=0
|
|
4962
|
+
OPEN_BASE=""
|
|
4963
|
+
forge_open_pr() {
|
|
4964
|
+
local br="$1" tmp made_tmp="" body="" draft=""
|
|
4965
|
+
FORGE_PR=""
|
|
4966
|
+
FORGE_PR_URL=""
|
|
4967
|
+
FORGE_PR_DRAFT=""
|
|
4968
|
+
OPEN_ERR=""
|
|
4969
|
+
OPEN_FORBIDDEN=0
|
|
4970
|
+
OPEN_BASE=$(hr_default_branch "$root" 2>/dev/null) || OPEN_BASE=""
|
|
4971
|
+
deliver_title_var "$br"
|
|
4972
|
+
if [ -z "$OPEN_BASE" ]; then
|
|
4973
|
+
OPEN_ERR="the configured defaultBranch could not be read"
|
|
4974
|
+
return 1
|
|
4975
|
+
fi
|
|
4976
|
+
tmp="${RUNNER_TEMP-}"
|
|
4977
|
+
if [ -z "$tmp" ] || [ ! -d "$tmp" ]; then
|
|
4978
|
+
tmp=$(mktemp -d) || tmp=""
|
|
4979
|
+
made_tmp="$tmp"
|
|
4980
|
+
fi
|
|
4981
|
+
if [ -z "$tmp" ] || ! body=$(mktemp "$tmp/harness-pr-body.XXXXXX") || ! deliver_pr_body "$body" "$br"; then
|
|
4982
|
+
OPEN_ERR="its body file could not be written"
|
|
4983
|
+
elif deliver_create "$OPEN_BASE" "$br" "$body" --draft; then
|
|
4984
|
+
draft=true
|
|
4985
|
+
else
|
|
4986
|
+
OPEN_ERR="$GH_ERR"
|
|
4987
|
+
case "$GH_ERR" in
|
|
4988
|
+
*"$PR_CREATE_FORBIDDEN"*) OPEN_FORBIDDEN=1 ;;
|
|
4989
|
+
*)
|
|
4990
|
+
# Drafts depend on the account's plan (C3, not measured): one retry as ready.
|
|
4991
|
+
echo "remote-run.sh: $verb: opening a draft pull request failed ($GH_ERR); retrying once without --draft" >&2
|
|
4992
|
+
if deliver_create "$OPEN_BASE" "$br" "$body"; then
|
|
4993
|
+
draft=false
|
|
4994
|
+
else
|
|
4995
|
+
OPEN_ERR="$GH_ERR"
|
|
4996
|
+
fi ;;
|
|
4997
|
+
esac
|
|
4998
|
+
fi
|
|
4999
|
+
[ -z "$body" ] || rm -f "$body"
|
|
5000
|
+
[ -z "$made_tmp" ] || rmdir "$made_tmp" 2>/dev/null || :
|
|
5001
|
+
[ -n "$draft" ] || return 1
|
|
5002
|
+
FORGE_PR=$(printf '%s\n' "$GH_OUT" | sed -n 's|.*/pull/\([0-9][0-9]*\).*|\1|p' | tail -n 1)
|
|
5003
|
+
if [ -z "$FORGE_PR" ]; then
|
|
5004
|
+
OPEN_ERR="gh printed no pull request URL"
|
|
5005
|
+
return 1
|
|
5006
|
+
fi
|
|
5007
|
+
FORGE_PR_URL="$FORGE_SERVER/$FORGE_REPO/pull/$FORGE_PR"
|
|
5008
|
+
FORGE_PR_DRAFT="$draft"
|
|
5009
|
+
return 0
|
|
5010
|
+
}
|
|
5011
|
+
|
|
5012
|
+
# deliver_comment <number> <tmp> <text> — one `completed` comment on <number>:
|
|
5013
|
+
# <text>, the `phases.qa` sentence when that phase is on, this run's URL when
|
|
5014
|
+
# GITHUB_RUN_ID is set, then the marker. A failure is one line.
|
|
5015
|
+
deliver_comment() {
|
|
5016
|
+
local number="$1" tmp="$2" text="$3" file
|
|
5017
|
+
if hr_phase_enabled "$root" qa; then
|
|
5018
|
+
text="$text
|
|
5019
|
+
|
|
5020
|
+
The interactive-test phase was skipped on GitHub Actions. Before merging, run \`/autonomous-sdlc-harness:branch-qa-test $branch\` locally."
|
|
5021
|
+
fi
|
|
5022
|
+
if [ -z "$tmp" ] || ! file=$(mktemp "$tmp/harness-deliver-comment.XXXXXX"); then
|
|
5023
|
+
echo "remote-run.sh: deliver: cannot create the comment file for #$number; no comment posted" >&2
|
|
5024
|
+
return 0
|
|
5025
|
+
fi
|
|
5026
|
+
{
|
|
5027
|
+
printf '%s\n' "$text"
|
|
5028
|
+
[ -z "${GITHUB_RUN_ID-}" ] || printf '\nRun: %s\n' "$(this_run_url)"
|
|
5029
|
+
} >"$file"
|
|
5030
|
+
forge_comment "$number" completed "$branch" "$file" || :
|
|
5031
|
+
rm -f "$file"
|
|
5032
|
+
}
|
|
5033
|
+
|
|
5034
|
+
# deliver_thread_reply <tmp> <comment_id> <text> — one reply to the review
|
|
5035
|
+
# thread holding <comment_id>, <text> then the `thread` marker. 0 when posted;
|
|
5036
|
+
# otherwise one `::warning::` line and 1.
|
|
5037
|
+
deliver_thread_reply() {
|
|
5038
|
+
local tmp="$1" id="$2" text="$3" file
|
|
5039
|
+
if [ -z "$tmp" ] || ! file=$(mktemp "$tmp/harness-thread-reply.XXXXXX"); then
|
|
5040
|
+
echo "::warning::remote-run.sh: deliver: cannot create the reply file for comment $id; no reply posted"
|
|
5041
|
+
return 1
|
|
5042
|
+
fi
|
|
5043
|
+
if ! { printf '%s\n\n' "$text"; forge_marker thread "$branch" "" "" "$RC_MARKED_N"; } >"$file"; then
|
|
5044
|
+
rm -f "$file"
|
|
5045
|
+
echo "::warning::remote-run.sh: deliver: cannot write the reply file for comment $id; no reply posted"
|
|
5046
|
+
return 1
|
|
5047
|
+
fi
|
|
5048
|
+
if ! gh_call api --method POST "repos/$FORGE_REPO/pulls/$FORGE_PR/comments/$id/replies" -F "body=@$file"; then
|
|
5049
|
+
rm -f "$file"
|
|
5050
|
+
echo "::warning::remote-run.sh: deliver: the reply to review comment $id was refused: $GH_ERR"
|
|
5051
|
+
return 1
|
|
5052
|
+
fi
|
|
5053
|
+
rm -f "$file"
|
|
5054
|
+
return 0
|
|
5055
|
+
}
|
|
5056
|
+
|
|
5057
|
+
# deliver_round_threads <tmp> — at a round's completion, handle each inline
|
|
5058
|
+
# review thread the round collected by the fix plan's verdict on its comment:
|
|
5059
|
+
# `fixed` is a reply naming the fix commit, then the thread resolved; `reason`
|
|
5060
|
+
# is a reply quoting it, the thread left open. The wire it reads is the
|
|
5061
|
+
# header's THE REVIEW-COMMENT LINE. Never dismisses a review; never fails.
|
|
5062
|
+
deliver_round_threads() {
|
|
5063
|
+
local tmp="$1" plan index_rel dir_rel index names path name k line rest id ids
|
|
5064
|
+
local sha tip since pages threads row t_id t_resolved t_ids t_root t_later t_own
|
|
5065
|
+
local i found handled="," own_marker re
|
|
5066
|
+
local -a v_id=() v_kind=() v_val=()
|
|
5067
|
+
if ! round_markers_read ""; then
|
|
5068
|
+
echo "::warning::remote-run.sh: deliver: the round's review threads were not read: $RC_ERR"
|
|
5069
|
+
return 0
|
|
5070
|
+
fi
|
|
5071
|
+
if [ "$RC_NEWEST_N" -eq 0 ]; then
|
|
5072
|
+
echo "remote-run.sh: deliver: $branch has no round file; no review thread handled"
|
|
5073
|
+
return 0
|
|
5074
|
+
fi
|
|
5075
|
+
if [ "$RC_NEWEST_N" -ne "$RC_MARKED_N" ]; then
|
|
5076
|
+
echo "remote-run.sh: deliver: round $RC_NEWEST_N of $branch was not placed from a pull-request review; no review thread handled"
|
|
5077
|
+
return 0
|
|
5078
|
+
fi
|
|
5079
|
+
if [ -z "$RC_MARKED_C" ]; then
|
|
5080
|
+
echo "remote-run.sh: deliver: round $RC_MARKED_N of $branch collected no inline comment; no review thread handled"
|
|
5081
|
+
return 0
|
|
5082
|
+
fi
|
|
5083
|
+
|
|
5084
|
+
plan="${branch}_fix_plan"
|
|
5085
|
+
[ "$RC_MARKED_N" -eq 1 ] || plan="${plan}_$RC_MARKED_N"
|
|
5086
|
+
index_rel="$RC_STATE_REL/user_reviews/$plan.md"
|
|
5087
|
+
dir_rel="$RC_STATE_REL/user_reviews/$plan"
|
|
5088
|
+
if ! index=$(git -C "$root" show "refs/remotes/origin/$branch:$index_rel" 2>/dev/null); then
|
|
5089
|
+
echo "remote-run.sh: deliver: round $RC_MARKED_N of $branch has no fix plan at $index_rel; no review thread handled"
|
|
5090
|
+
return 0
|
|
5091
|
+
fi
|
|
5092
|
+
tip=$(git -C "$root" rev-parse "refs/remotes/origin/$branch" 2>/dev/null) || tip=""
|
|
5093
|
+
|
|
5094
|
+
# Verdicts, kept only for ids the round collected; the first verdict an id gets wins.
|
|
5095
|
+
names=$(git -C "$root" ls-tree --name-only "refs/remotes/origin/$branch" -- "$dir_rel/" 2>/dev/null) || names=""
|
|
5096
|
+
while IFS= read -r path; do
|
|
5097
|
+
name="${path##*/}"
|
|
5098
|
+
[[ "$name" =~ ^finding_([0-9]+)\.md$ ]] || continue
|
|
5099
|
+
k=$((10#${BASH_REMATCH[1]}))
|
|
5100
|
+
case "$index" in *"[x] **Finding $k**"*) ;; *) continue ;; esac
|
|
5101
|
+
line=$(git -C "$root" show "refs/remotes/origin/$branch:$path" 2>/dev/null | grep -E '^\*\*Review comments:\*\* ' | head -n 1) || line=""
|
|
5102
|
+
[ -n "$line" ] || continue
|
|
5103
|
+
sha=$(git -C "$root" log --reverse --format=%H -S "[x] **Finding $k**" "refs/remotes/origin/$branch" -- "$index_rel" 2>/dev/null | head -n 1) || sha=""
|
|
5104
|
+
[ -n "$sha" ] || sha="$tip"
|
|
5105
|
+
rest="${line#'**Review comments:** '}"
|
|
5106
|
+
rest="${rest%$'\r'}"
|
|
5107
|
+
ids="${rest//[[:space:]]/}"
|
|
5108
|
+
for id in ${ids//,/ }; do
|
|
5109
|
+
[[ "$id" =~ ^[0-9]+$ ]] || continue
|
|
5110
|
+
v_id+=("$id"); v_kind+=(fixed); v_val+=("$sha")
|
|
5111
|
+
done
|
|
5112
|
+
done <<NAMES
|
|
5113
|
+
$names
|
|
5114
|
+
NAMES
|
|
5115
|
+
re='^- (.*) \*\*Review comments:\*\* ([0-9][0-9, ]*)$'
|
|
5116
|
+
found=0
|
|
5117
|
+
while IFS= read -r line; do
|
|
5118
|
+
line="${line%$'\r'}"
|
|
5119
|
+
case "$line" in
|
|
5120
|
+
'## Out of scope / verified-OK'*) found=1; continue ;;
|
|
5121
|
+
'## '*) found=0; continue ;;
|
|
5122
|
+
esac
|
|
5123
|
+
[ "$found" -eq 1 ] || continue
|
|
5124
|
+
[[ "$line" =~ $re ]] || continue
|
|
5125
|
+
rest="${BASH_REMATCH[1]}"
|
|
5126
|
+
ids="${BASH_REMATCH[2]//[[:space:]]/}"
|
|
5127
|
+
for id in ${ids//,/ }; do
|
|
5128
|
+
[[ "$id" =~ ^[0-9]+$ ]] || continue
|
|
5129
|
+
v_id+=("$id"); v_kind+=(reason); v_val+=("$rest")
|
|
5130
|
+
done
|
|
5131
|
+
done <<INDEX
|
|
5132
|
+
$index
|
|
5133
|
+
INDEX
|
|
5134
|
+
|
|
5135
|
+
if ! since=$(jq -n -r --arg t "$RC_MARKED_AT" '$t | fromdateiso8601' 2>/dev/null) || [ -z "$since" ]; then
|
|
5136
|
+
echo "::warning::remote-run.sh: deliver: round $RC_MARKED_N's collected_at '$RC_MARKED_AT' is not a UTC time; no review thread handled"
|
|
5137
|
+
return 0
|
|
5138
|
+
fi
|
|
5139
|
+
if ! gh_call api graphql --paginate -f "owner=${FORGE_REPO%%/*}" -f "name=${FORGE_REPO#*/}" -F "number=$FORGE_PR" \
|
|
5140
|
+
-f 'query=query($owner: String!, $name: String!, $number: Int!, $endCursor: String) { repository(owner: $owner, name: $name) { pullRequest(number: $number) { reviewThreads(first: 100, after: $endCursor) { pageInfo { hasNextPage endCursor } nodes { id isResolved comments(first: 100) { nodes { databaseId createdAt body } } } } } } }'; then
|
|
5141
|
+
echo "::warning::remote-run.sh: deliver: listing the review threads of pull request #$FORGE_PR was refused: $GH_ERR"
|
|
5142
|
+
return 0
|
|
5143
|
+
fi
|
|
5144
|
+
pages="$GH_OUT"
|
|
5145
|
+
own_marker=$(forge_marker thread "$branch" "" "" "$RC_MARKED_N")
|
|
5146
|
+
# One `|`-joined row per thread (a GraphQL node id carries no `|`): id,
|
|
5147
|
+
# resolved, its comment ids, its first comment's id, whether a comment came
|
|
5148
|
+
# after the round, whether it carries this harness's reply.
|
|
5149
|
+
if ! threads=$(printf '%s' "$pages" | jq -s -r --argjson since "$since" --arg collected ",$RC_MARKED_C," \
|
|
5150
|
+
--arg marker "$COMMENT_MARKER" --arg own "$own_marker" '
|
|
5151
|
+
[ .[] | .data.repository.pullRequest.reviewThreads.nodes[] ]
|
|
5152
|
+
| .[] | (.comments.nodes // []) as $c
|
|
5153
|
+
| [ .id, (.isResolved == true | tostring),
|
|
5154
|
+
($c | map(.databaseId | tostring) | join(",")),
|
|
5155
|
+
(($c[0].databaseId // "") | tostring),
|
|
5156
|
+
(any($c[]; ("," + (.databaseId | tostring) + ",") as $k
|
|
5157
|
+
| ((.createdAt // "") | try fromdateiso8601 catch 0) > $since
|
|
5158
|
+
and (($collected | contains($k)) | not)
|
|
5159
|
+
and (((.body // "") | contains($marker)) | not)) | tostring),
|
|
5160
|
+
(any($c[]; (.body // "") | contains($own)) | tostring) ]
|
|
5161
|
+
| join("|")' 2>/dev/null); then
|
|
5162
|
+
echo "::warning::remote-run.sh: deliver: the review threads of pull request #$FORGE_PR are not the expected JSON; no review thread handled"
|
|
5163
|
+
return 0
|
|
5164
|
+
fi
|
|
5165
|
+
|
|
5166
|
+
for id in ${RC_MARKED_C//,/ }; do
|
|
5167
|
+
i=0; found=""
|
|
5168
|
+
while [ "$i" -lt "${#v_id[@]}" ]; do
|
|
5169
|
+
if [ "${v_id[$i]}" = "$id" ]; then found="$i"; break; fi
|
|
5170
|
+
i=$((i + 1))
|
|
5171
|
+
done
|
|
5172
|
+
if [ -z "$found" ]; then
|
|
5173
|
+
echo "remote-run.sh: deliver: review comment $id has no verdict in the fix plan; left alone"
|
|
5174
|
+
continue
|
|
5175
|
+
fi
|
|
5176
|
+
row=""
|
|
5177
|
+
while IFS= read -r line; do
|
|
5178
|
+
[ -n "$line" ] || continue
|
|
5179
|
+
IFS='|' read -r t_id t_resolved t_ids t_root t_later t_own <<<"$line"
|
|
5180
|
+
case ",$t_ids," in *",$id,"*) row="$line"; break ;; esac
|
|
5181
|
+
done <<THREADS
|
|
5182
|
+
$threads
|
|
5183
|
+
THREADS
|
|
5184
|
+
if [ -z "$row" ]; then
|
|
5185
|
+
echo "remote-run.sh: deliver: review comment $id is in no review thread of pull request #$FORGE_PR; left alone"
|
|
5186
|
+
continue
|
|
5187
|
+
fi
|
|
5188
|
+
case "$handled" in
|
|
5189
|
+
*",$t_id,"*) echo "remote-run.sh: deliver: review comment $id's thread was handled for another comment; left alone"; continue ;;
|
|
5190
|
+
esac
|
|
5191
|
+
handled="$handled$t_id,"
|
|
5192
|
+
if [ "$t_resolved" = true ]; then
|
|
5193
|
+
echo "remote-run.sh: deliver: review comment $id's thread is already resolved; left alone"
|
|
5194
|
+
continue
|
|
5195
|
+
fi
|
|
5196
|
+
if [ "$t_later" = true ]; then
|
|
5197
|
+
echo "remote-run.sh: deliver: review comment $id's thread has a reply newer than round $RC_MARKED_N; left alone"
|
|
5198
|
+
continue
|
|
5199
|
+
fi
|
|
5200
|
+
if [ "$t_own" = true ]; then
|
|
5201
|
+
echo "remote-run.sh: deliver: review comment $id's thread already carries this harness's reply; left alone"
|
|
5202
|
+
continue
|
|
5203
|
+
fi
|
|
5204
|
+
# Replies attach to a thread's first comment: GitHub does not take a reply to a reply.
|
|
5205
|
+
if [ "${v_kind[$found]}" = fixed ]; then
|
|
5206
|
+
deliver_thread_reply "$tmp" "$t_root" "Addressed in \`${v_val[$found]}\`." || continue
|
|
5207
|
+
if gh_call api graphql -f 'query=mutation($id: ID!) { resolveReviewThread(input: {threadId: $id}) { thread { isResolved } } }' -f "id=$t_id"; then
|
|
5208
|
+
echo "remote-run.sh: deliver: replied to review comment $id and resolved its thread"
|
|
5209
|
+
else
|
|
5210
|
+
echo "::warning::remote-run.sh: deliver: resolving review comment $id's thread was refused: $GH_ERR"
|
|
5211
|
+
fi
|
|
5212
|
+
else
|
|
5213
|
+
deliver_thread_reply "$tmp" "$t_root" "Not changed in this round: ${v_val[$found]}" || continue
|
|
5214
|
+
echo "remote-run.sh: deliver: replied to review comment $id with the reason; its thread stays open"
|
|
5215
|
+
fi
|
|
5216
|
+
done
|
|
5217
|
+
return 0
|
|
5218
|
+
}
|
|
5219
|
+
|
|
5220
|
+
# verb_deliver — after a `completed` bundle: reuse the branch's pull request or
|
|
5221
|
+
# open it, mark it ready, post `completed` on it and on the issue, and set
|
|
5222
|
+
# `done`. Always exit 0.
|
|
4256
5223
|
verb_deliver() {
|
|
4257
|
-
local status
|
|
4258
|
-
local tmp made_tmp=""
|
|
5224
|
+
local status engine noun verb_done pr_url="" lookup_failed=0 create_err="" forbidden=0 base=""
|
|
5225
|
+
local tmp made_tmp="" ready="" text readiness compare posted=""
|
|
4259
5226
|
if ! forge_on; then
|
|
4260
5227
|
echo "remote-run.sh: deliver: the forge coupling is off (forge github and execution.target github-actions); nothing posted"
|
|
4261
5228
|
exit "$EXIT_OK"
|
|
@@ -4266,99 +5233,159 @@ verb_deliver() {
|
|
|
4266
5233
|
echo "remote-run.sh: deliver: the bundle's status is '${status:-unreadable}', not completed; nothing posted"
|
|
4267
5234
|
exit "$EXIT_OK"
|
|
4268
5235
|
fi
|
|
5236
|
+
engine=$(hr_remote_status_get "$bundle_dir/$HR_REMOTE_STATUS_FILE" engine 2>/dev/null) || engine=""
|
|
5237
|
+
if [ "$engine" = user_review ]; then
|
|
5238
|
+
noun=round; verb_done=finished
|
|
5239
|
+
else
|
|
5240
|
+
noun=run; verb_done=completed
|
|
5241
|
+
fi
|
|
4269
5242
|
forge_repo_var || exit "$EXIT_OK"
|
|
4270
5243
|
|
|
4271
5244
|
forge_fetch_branch "$branch"
|
|
4272
5245
|
forge_issue_var "$branch" || FORGE_ISSUE=""
|
|
4273
|
-
forge_pr_var "$branch" || { FORGE_PR=""; lookup_failed=1; create_err="whether a pull request is already open could not be read ($GH_ERR)"; }
|
|
4274
|
-
|
|
4275
|
-
tmp="${RUNNER_TEMP-}"
|
|
4276
|
-
if [ -z "$tmp" ] || [ ! -d "$tmp" ]; then
|
|
4277
|
-
tmp=$(mktemp -d) || tmp=""
|
|
4278
|
-
made_tmp="$tmp"
|
|
4279
|
-
fi
|
|
5246
|
+
forge_pr_var "$branch" || { FORGE_PR=""; FORGE_PR_DRAFT=""; lookup_failed=1; create_err="whether a pull request is already open could not be read ($GH_ERR)"; }
|
|
4280
5247
|
|
|
4281
5248
|
if [ -n "$FORGE_PR" ]; then
|
|
4282
5249
|
pr_url="$FORGE_SERVER/$FORGE_REPO/pull/$FORGE_PR"
|
|
4283
5250
|
echo "remote-run.sh: deliver: $branch already has pull request #$FORGE_PR; none opened"
|
|
4284
5251
|
elif [ "$lookup_failed" -eq 0 ]; then
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
elif [ -z "$tmp" ] || ! body=$(mktemp "$tmp/harness-deliver-body.XXXXXX") || ! deliver_pr_body "$body" "$branch"; then
|
|
4290
|
-
create_err="its body file could not be written"
|
|
4291
|
-
elif deliver_create "$base" "$branch" "$body" --draft; then
|
|
4292
|
-
opened=1
|
|
5252
|
+
echo "remote-run.sh: deliver: $branch has no open pull request at completion — the start's attempt failed or the workflow predates the open step; opening it now"
|
|
5253
|
+
if forge_open_pr "$branch"; then
|
|
5254
|
+
pr_url="$FORGE_PR_URL"
|
|
5255
|
+
echo "remote-run.sh: deliver: opened pull request #$FORGE_PR for $branch"
|
|
4293
5256
|
else
|
|
4294
|
-
create_err="$
|
|
4295
|
-
|
|
4296
|
-
*"$PR_CREATE_FORBIDDEN"*) forbidden=1 ;;
|
|
4297
|
-
*)
|
|
4298
|
-
# Drafts depend on the account's plan (C3, not measured): one retry as ready.
|
|
4299
|
-
echo "remote-run.sh: deliver: opening a draft pull request failed ($GH_ERR); retrying once without --draft" >&2
|
|
4300
|
-
if deliver_create "$base" "$branch" "$body"; then
|
|
4301
|
-
opened=1
|
|
4302
|
-
else
|
|
4303
|
-
create_err="$GH_ERR"
|
|
4304
|
-
fi ;;
|
|
4305
|
-
esac
|
|
5257
|
+
create_err="$OPEN_ERR"
|
|
5258
|
+
forbidden="$OPEN_FORBIDDEN"
|
|
4306
5259
|
fi
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
5260
|
+
base="$OPEN_BASE"
|
|
5261
|
+
fi
|
|
5262
|
+
|
|
5263
|
+
# The flip runs with the job's token, never HARNESS_PR_TOKEN.
|
|
5264
|
+
if [ -n "$FORGE_PR" ]; then
|
|
5265
|
+
if [ "$FORGE_PR_DRAFT" = true ]; then
|
|
5266
|
+
if gh_call pr ready "$FORGE_PR" --repo "$FORGE_REPO"; then
|
|
5267
|
+
ready=flipped
|
|
5268
|
+
echo "remote-run.sh: deliver: marked pull request #$FORGE_PR ready for review"
|
|
4313
5269
|
else
|
|
4314
|
-
|
|
4315
|
-
|
|
5270
|
+
ready=refused
|
|
5271
|
+
echo "::warning::remote-run.sh: deliver: marking pull request #$FORGE_PR ready for review was refused: $GH_ERR"
|
|
4316
5272
|
fi
|
|
5273
|
+
else
|
|
5274
|
+
ready=not-draft
|
|
4317
5275
|
fi
|
|
4318
5276
|
fi
|
|
4319
5277
|
|
|
4320
|
-
|
|
4321
|
-
if [ -z "$
|
|
4322
|
-
|
|
5278
|
+
tmp="${RUNNER_TEMP-}"
|
|
5279
|
+
if [ -z "$tmp" ] || [ ! -d "$tmp" ]; then
|
|
5280
|
+
tmp=$(mktemp -d) || tmp=""
|
|
5281
|
+
made_tmp="$tmp"
|
|
5282
|
+
fi
|
|
5283
|
+
|
|
5284
|
+
# After the flip, before the comments.
|
|
5285
|
+
if [ "$noun" = round ] && [ -n "$FORGE_PR" ]; then
|
|
5286
|
+
deliver_round_threads "$tmp"
|
|
5287
|
+
fi
|
|
5288
|
+
|
|
5289
|
+
if [ -n "$FORGE_PR" ]; then
|
|
5290
|
+
case "$ready" in
|
|
5291
|
+
flipped)
|
|
5292
|
+
if [ "$noun" = round ]; then
|
|
5293
|
+
readiness="The harness round on \`$branch\` finished, and this pull request is marked ready for review again."
|
|
5294
|
+
else
|
|
5295
|
+
readiness="The harness run on \`$branch\` completed, and this pull request is now marked ready for your review."
|
|
5296
|
+
fi ;;
|
|
5297
|
+
not-draft)
|
|
5298
|
+
readiness="The harness $noun on \`$branch\` $verb_done. This pull request is not a draft — drafts may not be available on this repository's plan, or someone marked it ready — so it was left as it is." ;;
|
|
5299
|
+
*)
|
|
5300
|
+
readiness="The harness $noun on \`$branch\` $verb_done. Marking this draft ready for review was refused; mark it ready by hand." ;;
|
|
5301
|
+
esac
|
|
5302
|
+
deliver_comment "$FORGE_PR" "$tmp" "$readiness A review that requests changes starts another round."
|
|
5303
|
+
posted="#$FORGE_PR"
|
|
5304
|
+
if [ -n "$FORGE_ISSUE" ]; then
|
|
5305
|
+
if [ "$ready" = refused ]; then
|
|
5306
|
+
text="The harness $noun on \`$branch\` $verb_done. Its pull request #$FORGE_PR is still a draft — marking it ready for review was refused, so mark it ready by hand: $pr_url"
|
|
5307
|
+
elif [ "$noun" = round ]; then
|
|
5308
|
+
text="The harness round on \`$branch\` finished. Pull request #$FORGE_PR is ready for review again: $pr_url"
|
|
5309
|
+
else
|
|
5310
|
+
text="The harness run on \`$branch\` completed. Its pull request #$FORGE_PR is ready for your review: $pr_url"
|
|
5311
|
+
fi
|
|
5312
|
+
deliver_comment "$FORGE_ISSUE" "$tmp" "$text
|
|
5313
|
+
Review it there; a review that requests changes starts another round."
|
|
5314
|
+
posted="$posted and #$FORGE_ISSUE"
|
|
5315
|
+
fi
|
|
5316
|
+
elif [ -n "$FORGE_ISSUE" ]; then
|
|
5317
|
+
compare="$FORGE_SERVER/$FORGE_REPO/compare/${base:-<default branch>}...$branch?expand=1"
|
|
4323
5318
|
if [ "$forbidden" -eq 1 ]; then
|
|
4324
|
-
text="The harness
|
|
5319
|
+
text="The harness $noun on \`$branch\` $verb_done, but its pull request could not be opened: GitHub Actions is not permitted to create pull requests in this repository. Turn on *$PR_CREATE_SETTING* under $PR_CREATE_SETTING_PATH, or set the \`HARNESS_GIT_TOKEN\` secret, for the next run. For this one, open the pull request from the branch: $compare"
|
|
4325
5320
|
else
|
|
4326
|
-
text="The harness
|
|
5321
|
+
text="The harness $noun on \`$branch\` $verb_done, but its pull request could not be opened: $create_err. Open it by hand from the branch: $compare"
|
|
4327
5322
|
fi
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
text="The harness round on \`$branch\` finished. Review this pull request; a review that requests changes starts another round."
|
|
4331
|
-
elif [ -n "$FORGE_ISSUE" ]; then
|
|
4332
|
-
target="$FORGE_ISSUE"
|
|
4333
|
-
text="The harness run on \`$branch\` completed. Its pull request is ready for your review: $pr_url
|
|
4334
|
-
Review it there; a review that requests changes starts another round."
|
|
5323
|
+
deliver_comment "$FORGE_ISSUE" "$tmp" "$text"
|
|
5324
|
+
posted="#$FORGE_ISSUE"
|
|
4335
5325
|
else
|
|
4336
|
-
|
|
4337
|
-
text="The harness run on \`$branch\` completed and opened this pull request. Review it; a review that requests changes starts another round."
|
|
5326
|
+
echo "remote-run.sh: deliver: $branch has no pull request and no issue it was started from; nothing posted"
|
|
4338
5327
|
fi
|
|
4339
|
-
|
|
4340
|
-
text="$text
|
|
5328
|
+
[ -z "$made_tmp" ] || rmdir "$made_tmp" 2>/dev/null || :
|
|
4341
5329
|
|
|
4342
|
-
|
|
4343
|
-
|
|
5330
|
+
[ -z "$FORGE_ISSUE" ] || forge_set_state "$FORGE_ISSUE" done || :
|
|
5331
|
+
[ -z "$FORGE_PR" ] || forge_set_state "$FORGE_PR" done || :
|
|
5332
|
+
echo "remote-run.sh: deliver: completed on $branch reported${posted:+ on $posted}"
|
|
5333
|
+
exit "$EXIT_OK"
|
|
5334
|
+
}
|
|
4344
5335
|
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
5336
|
+
# verb_open — at the run's start: open the branch's draft pull request when none
|
|
5337
|
+
# is open, set it `running`, and name it once on the source issue. Always exit 0.
|
|
5338
|
+
verb_open() {
|
|
5339
|
+
local tmp made_tmp="" file
|
|
5340
|
+
if ! forge_on; then
|
|
5341
|
+
echo "remote-run.sh: open: the forge coupling is off (forge github and execution.target github-actions); nothing opened"
|
|
5342
|
+
exit "$EXIT_OK"
|
|
5343
|
+
fi
|
|
5344
|
+
forge_repo_var || exit "$EXIT_OK"
|
|
5345
|
+
forge_fetch_branch "$branch"
|
|
5346
|
+
forge_issue_var "$branch" || FORGE_ISSUE=""
|
|
5347
|
+
if ! forge_pr_var "$branch"; then
|
|
5348
|
+
echo "::warning::remote-run.sh: open: whether $branch already has a pull request could not be read ($GH_ERR); none opened"
|
|
5349
|
+
exit "$EXIT_OK"
|
|
5350
|
+
fi
|
|
5351
|
+
if [ -n "$FORGE_PR" ]; then
|
|
5352
|
+
echo "remote-run.sh: open: $branch already has pull request #$FORGE_PR; none opened"
|
|
5353
|
+
exit "$EXIT_OK"
|
|
5354
|
+
fi
|
|
5355
|
+
if ! forge_open_pr "$branch"; then
|
|
5356
|
+
echo "::warning::remote-run.sh: open: the pull request of $branch could not be opened: $OPEN_ERR. The run goes on and its comments reach its issue; deliver tries again when the run completes"
|
|
5357
|
+
exit "$EXIT_OK"
|
|
5358
|
+
fi
|
|
5359
|
+
echo "remote-run.sh: open: opened pull request #$FORGE_PR for $branch"
|
|
5360
|
+
forge_set_state "$FORGE_PR" running || :
|
|
5361
|
+
if [ -z "$FORGE_ISSUE" ]; then
|
|
5362
|
+
echo "remote-run.sh: open: $branch was started from no issue; nothing posted"
|
|
5363
|
+
exit "$EXIT_OK"
|
|
5364
|
+
fi
|
|
5365
|
+
tmp="${RUNNER_TEMP-}"
|
|
5366
|
+
if [ -z "$tmp" ] || [ ! -d "$tmp" ]; then
|
|
5367
|
+
tmp=$(mktemp -d) || tmp=""
|
|
5368
|
+
made_tmp="$tmp"
|
|
5369
|
+
fi
|
|
5370
|
+
if [ -n "$tmp" ] && file=$(mktemp "$tmp/harness-open-comment.XXXXXX"); then
|
|
4348
5371
|
{
|
|
4349
|
-
|
|
5372
|
+
if [ "$FORGE_PR_DRAFT" = false ]; then
|
|
5373
|
+
printf 'The harness run on `%s` opened its pull request #%s, not as a draft — this repository'"'"'s plan may not offer drafts: %s\n' \
|
|
5374
|
+
"$branch" "$FORGE_PR" "$FORGE_PR_URL"
|
|
5375
|
+
else
|
|
5376
|
+
printf 'The harness run on `%s` opened its draft pull request #%s: %s\n' "$branch" "$FORGE_PR" "$FORGE_PR_URL"
|
|
5377
|
+
fi
|
|
5378
|
+
printf '\nThe run'"'"'s questions, lifecycle comments and progress are posted there from now on; `%s` commands keep working on this issue.\n' \
|
|
5379
|
+
"$COMMAND_HANDLE"
|
|
4350
5380
|
[ -z "${GITHUB_RUN_ID-}" ] || printf '\nRun: %s\n' "$(this_run_url)"
|
|
4351
5381
|
} >"$file"
|
|
4352
|
-
forge_comment "$
|
|
5382
|
+
forge_comment "$FORGE_ISSUE" opened "$branch" "$file" || :
|
|
4353
5383
|
rm -f "$file"
|
|
4354
5384
|
else
|
|
4355
|
-
echo "remote-run.sh:
|
|
5385
|
+
echo "remote-run.sh: open: cannot create the comment file for #$FORGE_ISSUE; no comment posted" >&2
|
|
4356
5386
|
fi
|
|
4357
5387
|
[ -z "$made_tmp" ] || rmdir "$made_tmp" 2>/dev/null || :
|
|
4358
|
-
|
|
4359
|
-
[ -z "$FORGE_ISSUE" ] || forge_set_state "$FORGE_ISSUE" done || :
|
|
4360
|
-
[ -z "$FORGE_PR" ] || forge_set_state "$FORGE_PR" done || :
|
|
4361
|
-
echo "remote-run.sh: deliver: completed on $branch reported${target:+ on #$target}"
|
|
5388
|
+
echo "remote-run.sh: open: pull request #$FORGE_PR named on #$FORGE_ISSUE"
|
|
4362
5389
|
exit "$EXIT_OK"
|
|
4363
5390
|
}
|
|
4364
5391
|
|
|
@@ -4385,8 +5412,13 @@ control_cleanup() {
|
|
|
4385
5412
|
return 0
|
|
4386
5413
|
}
|
|
4387
5414
|
|
|
4388
|
-
#
|
|
4389
|
-
#
|
|
5415
|
+
# The engine a dispatch child named, set only once that child exited 0; a
|
|
5416
|
+
# non-empty value adds ` engine=<engine>` to every later reply's marker.
|
|
5417
|
+
CONTROL_REPLY_ENGINE=""
|
|
5418
|
+
|
|
5419
|
+
# control_post <text> — post <text> on CONTROL_NUMBER as a `reply` comment,
|
|
5420
|
+
# its marker carrying CONTROL_REPLY_ENGINE; 1, after an `::error::` line, when
|
|
5421
|
+
# it cannot be posted.
|
|
4390
5422
|
control_post() {
|
|
4391
5423
|
local text="$1" file status=0
|
|
4392
5424
|
if ! forge_repo_var; then
|
|
@@ -4401,7 +5433,7 @@ control_post() {
|
|
|
4401
5433
|
printf '%s\n' "$text"
|
|
4402
5434
|
[ -z "${GITHUB_RUN_ID-}" ] || printf '\nRun: %s\n' "$(this_run_url)"
|
|
4403
5435
|
} >"$file"
|
|
4404
|
-
if ! forge_comment "$CONTROL_NUMBER" reply "$CONTROL_BRANCH" "$file"; then
|
|
5436
|
+
if ! forge_comment "$CONTROL_NUMBER" reply "$CONTROL_BRANCH" "$file" "" "$CONTROL_REPLY_ENGINE"; then
|
|
4405
5437
|
echo "::error::remote-run.sh: control: the reply on #$CONTROL_NUMBER could not be posted: $GH_ERR"
|
|
4406
5438
|
status=1
|
|
4407
5439
|
fi
|
|
@@ -4538,11 +5570,47 @@ control_stopped_refuse() {
|
|
|
4538
5570
|
esac
|
|
4539
5571
|
}
|
|
4540
5572
|
|
|
4541
|
-
#
|
|
4542
|
-
#
|
|
4543
|
-
#
|
|
5573
|
+
# control_run_in_flight <branch> — 0 when a run titled exactly
|
|
5574
|
+
# `harness run <branch>` is queued, in_progress, waiting, requested or
|
|
5575
|
+
# pending; 1 when none is; 2 with GH_ERR set when the listing failed or was
|
|
5576
|
+
# not a JSON array. Passes <branch> explicitly rather than reading the global
|
|
5577
|
+
# `branch` list_runs reads, and leaves GH_OUT as it found it.
|
|
5578
|
+
control_run_in_flight() {
|
|
5579
|
+
local saved="$GH_OUT" verdict status=0
|
|
5580
|
+
if ! gh_call run list --workflow "$WORKFLOW_RUN_FILE" --branch "$1" \
|
|
5581
|
+
--json displayTitle,status --limit "$RUN_LIST_LIMIT"; then
|
|
5582
|
+
GH_OUT="$saved"
|
|
5583
|
+
return 2
|
|
5584
|
+
fi
|
|
5585
|
+
verdict=$(printf '%s' "$GH_OUT" | jq -r --arg t "harness run $1" '
|
|
5586
|
+
if type != "array" then error("not an array") else
|
|
5587
|
+
if any(.[]; .displayTitle == $t
|
|
5588
|
+
and (.status == "queued" or .status == "in_progress" or .status == "waiting"
|
|
5589
|
+
or .status == "requested" or .status == "pending"))
|
|
5590
|
+
then "yes" else "no" end
|
|
5591
|
+
end' 2>/dev/null) || verdict=""
|
|
5592
|
+
GH_OUT="$saved"
|
|
5593
|
+
case "$verdict" in
|
|
5594
|
+
yes) status=0 ;;
|
|
5595
|
+
no) status=1 ;;
|
|
5596
|
+
*) GH_ERR="its run list is not the expected JSON"; status=2 ;;
|
|
5597
|
+
esac
|
|
5598
|
+
return "$status"
|
|
5599
|
+
}
|
|
5600
|
+
|
|
5601
|
+
# control_check_branch <branch> [started] — the refusals every path shares, in
|
|
5602
|
+
# order: a branch not answered 1 by hr_branch_is_protected, then (after a
|
|
5603
|
+
# fetch) one that is not a harness branch. A branch passes with its task
|
|
5604
|
+
# prompt or the flow-progress ledger on its origin tip (`forge_recognised`);
|
|
5605
|
+
# or, with `started` (passed only by
|
|
5606
|
+
# control_branch_from_issue, whose issue's genuine `started` marker names it),
|
|
5607
|
+
# while it still exists on origin — gone is refused as deleted, a failed
|
|
5608
|
+
# existence check names the read, and no run list is read; or with a
|
|
5609
|
+
# `harness run <branch>` run in flight. Otherwise the refusal reads "neither a
|
|
5610
|
+
# task prompt nor a ledger, and no `harness run` in flight", and a failed listing names the read. Each
|
|
5611
|
+
# refusal is a reply and exit 2, or 3 for a failed read.
|
|
4544
5612
|
control_check_branch() {
|
|
4545
|
-
local b="$1" protected=0
|
|
5613
|
+
local b="$1" protected=0 exists=0 inflight=0
|
|
4546
5614
|
if ! valid_branch "$b" || ! git check-ref-format --branch "$b" >/dev/null 2>&1; then
|
|
4547
5615
|
control_refuse "$EXIT_REFUSED" "\`$b\` is not a valid branch name" "Comment on the pull request of the run's branch instead."
|
|
4548
5616
|
fi
|
|
@@ -4556,8 +5624,25 @@ control_check_branch() {
|
|
|
4556
5624
|
esac
|
|
4557
5625
|
forge_fetch_branch "$b"
|
|
4558
5626
|
if ! forge_recognised "$b"; then
|
|
4559
|
-
|
|
4560
|
-
"
|
|
5627
|
+
if [ "${2-}" = started ]; then
|
|
5628
|
+
remote_branch_exists "$b" || exists=$?
|
|
5629
|
+
case "$exists" in
|
|
5630
|
+
0) ;;
|
|
5631
|
+
1) control_refuse "$EXIT_REFUSED" "\`$b\` no longer exists on origin, so its run cannot be resumed or commanded" \
|
|
5632
|
+
"Start a new run from a new issue or the Run workflow form." ;;
|
|
5633
|
+
*) control_refuse "$EXIT_GH" "whether \`$b\` still exists on origin could not be read ($REMOTE_BRANCH_ERR)" \
|
|
5634
|
+
"Comment again to retry." ;;
|
|
5635
|
+
esac
|
|
5636
|
+
else
|
|
5637
|
+
control_run_in_flight "$b" || inflight=$?
|
|
5638
|
+
case "$inflight" in
|
|
5639
|
+
0) ;;
|
|
5640
|
+
1) control_refuse "$EXIT_REFUSED" "\`$b\` is not a harness branch: its tip carries neither a task prompt nor a flow-progress ledger, and no \`harness run $b\` run is queued or in progress" \
|
|
5641
|
+
"Only a branch a harness run works on can be commanded." ;;
|
|
5642
|
+
*) control_refuse "$EXIT_GH" "whether \`$b\` has a harness run in flight could not be read ($GH_ERR)" \
|
|
5643
|
+
"Comment again to retry." ;;
|
|
5644
|
+
esac
|
|
5645
|
+
fi
|
|
4561
5646
|
fi
|
|
4562
5647
|
CONTROL_BRANCH="$b"
|
|
4563
5648
|
}
|
|
@@ -4633,7 +5718,8 @@ control_issue_branch_var() {
|
|
|
4633
5718
|
}
|
|
4634
5719
|
|
|
4635
5720
|
# control_branch_from_issue <number> — control_issue_branch_var, its failures
|
|
4636
|
-
# refused, then control_check_branch
|
|
5721
|
+
# refused, then control_check_branch with `started`: the verified marker makes
|
|
5722
|
+
# the branch a harness branch while it still exists on origin.
|
|
4637
5723
|
control_branch_from_issue() {
|
|
4638
5724
|
if ! control_issue_branch_var "$1"; then
|
|
4639
5725
|
if [ -n "$ISSUE_BRANCH_ERR" ]; then
|
|
@@ -4642,7 +5728,7 @@ control_branch_from_issue() {
|
|
|
4642
5728
|
control_refuse "$EXIT_REFUSED" "no harness run was started from this issue" \
|
|
4643
5729
|
"Comment on the pull request of the run's branch instead."
|
|
4644
5730
|
fi
|
|
4645
|
-
control_check_branch "$ISSUE_BRANCH"
|
|
5731
|
+
control_check_branch "$ISSUE_BRANCH" started
|
|
4646
5732
|
}
|
|
4647
5733
|
|
|
4648
5734
|
# control_verb_handled <verb> — 0 when an arm below carries out <verb>.
|
|
@@ -4677,6 +5763,7 @@ control_resume_dispatch() {
|
|
|
4677
5763
|
*) control_refuse "$EXIT_GH" "the dispatch could not be sent ($CHILD_LAST)" "Comment \`$COMMAND_HANDLE $CONTROL_VERB\` again to retry." ;;
|
|
4678
5764
|
esac
|
|
4679
5765
|
status="$EXIT_OK"
|
|
5766
|
+
CONTROL_REPLY_ENGINE="$CS_ENGINE"
|
|
4680
5767
|
control_post "$done_text" || status="$EXIT_GH"
|
|
4681
5768
|
# The job posts its own `resumed` comment; the labels say `running` now.
|
|
4682
5769
|
forge_issue_var "$CONTROL_BRANCH" || FORGE_ISSUE=""
|
|
@@ -4799,6 +5886,26 @@ control_ledger_next_var() {
|
|
|
4799
5886
|
return 0
|
|
4800
5887
|
}
|
|
4801
5888
|
|
|
5889
|
+
# control_round_newer_than_ledger — whether origin's tip of CONTROL_BRANCH
|
|
5890
|
+
# carries a user-review round commit newer than the ledger's last change: the
|
|
5891
|
+
# round's job writes its own ledger only after that commit. 0 when it does; 1
|
|
5892
|
+
# otherwise, a failed git read included. Subjects are compared as whole lines.
|
|
5893
|
+
control_round_newer_than_ledger() {
|
|
5894
|
+
local state_rel ledger_sha subjects subject line
|
|
5895
|
+
state_rel=$(hr_state_dir "$root" 2>/dev/null) || return 1
|
|
5896
|
+
[ -n "$state_rel" ] || return 1
|
|
5897
|
+
ledger_sha=$(git -C "$root" log -1 --format=%H "refs/remotes/origin/$CONTROL_BRANCH" \
|
|
5898
|
+
-- "${state_rel%/}/flow_progress/${CONTROL_BRANCH}_progress.md" 2>/dev/null) || return 1
|
|
5899
|
+
[ -n "$ledger_sha" ] || return 1
|
|
5900
|
+
subjects=$(git -C "$root" log --format=%s "$ledger_sha..refs/remotes/origin/$CONTROL_BRANCH" 2>/dev/null) \
|
|
5901
|
+
|| return 1
|
|
5902
|
+
subject=$(hr_user_review_subject "$CONTROL_BRANCH")
|
|
5903
|
+
while IFS= read -r line; do
|
|
5904
|
+
[ "$line" != "$subject" ] || return 0
|
|
5905
|
+
done <<<"$subjects"
|
|
5906
|
+
return 1
|
|
5907
|
+
}
|
|
5908
|
+
|
|
4802
5909
|
# control_status — the read-only reply: the state, the next ledger entry, the
|
|
4803
5910
|
# open questions and the latest run. Sets no label and dispatches nothing.
|
|
4804
5911
|
control_status() {
|
|
@@ -4824,6 +5931,10 @@ control_status() {
|
|
|
4824
5931
|
if control_ledger_next_var; then
|
|
4825
5932
|
if [ -n "$LEDGER_NEXT" ]; then
|
|
4826
5933
|
text="$text${para}Next in the flow-progress ledger: $LEDGER_NEXT${LEDGER_SECTION:+ (under \`$LEDGER_SECTION\`)}"
|
|
5934
|
+
elif control_round_newer_than_ledger; then
|
|
5935
|
+
text="$text${para}A user-review round has started on \`$CONTROL_BRANCH\`, and its flow-progress ledger is not written yet."
|
|
5936
|
+
elif [ "$CS_WORD" = running ]; then
|
|
5937
|
+
text="$text${para}The run is still running, and its flow-progress ledger has no open entry: it is finishing its last step, or a new stage has not written its ledger yet."
|
|
4827
5938
|
else
|
|
4828
5939
|
text="$text${para}Every entry of the flow-progress ledger is ticked."
|
|
4829
5940
|
fi
|
|
@@ -4955,6 +6066,7 @@ control_answer() {
|
|
|
4955
6066
|
control_refuse "$EXIT_GH" "the dispatch could not be sent ($CHILD_LAST)" \
|
|
4956
6067
|
"Comment \`$COMMAND_HANDLE answer $n\` again to retry." ;;
|
|
4957
6068
|
esac
|
|
6069
|
+
CONTROL_REPLY_ENGINE="$CS_ENGINE"
|
|
4958
6070
|
|
|
4959
6071
|
cmds=""
|
|
4960
6072
|
for v in $CS_OPEN; do
|
|
@@ -5006,7 +6118,9 @@ control_review_story() {
|
|
|
5006
6118
|
# `branch`'s pull request <pr_number>, shared by `control` and `collect`: every
|
|
5007
6119
|
# submitted review (never `PENDING`) that requests changes or carries a
|
|
5008
6120
|
# non-blank body, and every inline comment, no earlier round consumed, by
|
|
5009
|
-
# every author `authorise_actor` accepts
|
|
6121
|
+
# every author `authorise_actor` accepts, the `HARNESS_RUN_ACTORS` allow-list
|
|
6122
|
+
# included; a refused author's items are dropped with one line, while a
|
|
6123
|
+
# failed permission call (status 4) fails the collection. What earlier rounds consumed is
|
|
5010
6124
|
# read from the marker lines of their files on origin's tip; with none marked,
|
|
5011
6125
|
# the boundary is the committer time of the newest round file. A comment
|
|
5012
6126
|
# belonging to a pending review is pending whatever its `created_at`: a draft
|
|
@@ -5028,18 +6142,27 @@ RC_ERR=""
|
|
|
5028
6142
|
# origin's tip consumed, from their marker lines: RC_SEEN_R and RC_SEEN_C (the
|
|
5029
6143
|
# recorded review and comment ids, comma-wrapped), RC_MARKED_AT (the
|
|
5030
6144
|
# highest-numbered marked round's `collected_at`, empty when none is marked),
|
|
5031
|
-
#
|
|
5032
|
-
#
|
|
5033
|
-
#
|
|
6145
|
+
# RC_MARKED_N and RC_MARKED_C (that round's number, 0 when none, and its
|
|
6146
|
+
# `comments=` ids as written), RC_NEWEST_N (the highest round number of any
|
|
6147
|
+
# round file, marked or not, 0 when none), RC_EVENT_ROUND (the round recording
|
|
6148
|
+
# <event_review_id>), and RC_STATE_REL. 4 with RC_ERR when a round cannot be
|
|
6149
|
+
# listed or read. `control` also calls it on its own, to name the round a
|
|
6150
|
+
# review in flight is already part of, and `deliver` to find a round's threads.
|
|
5034
6151
|
RC_SEEN_R=","
|
|
5035
6152
|
RC_SEEN_C=","
|
|
5036
6153
|
RC_MARKED_AT=""
|
|
6154
|
+
RC_MARKED_N=0
|
|
6155
|
+
RC_MARKED_C=""
|
|
6156
|
+
RC_NEWEST_N=0
|
|
5037
6157
|
RC_STATE_REL=""
|
|
5038
6158
|
round_markers_read() {
|
|
5039
|
-
local event_id="${1-}" names name path n line
|
|
6159
|
+
local event_id="${1-}" names name path n line
|
|
5040
6160
|
RC_SEEN_R=","
|
|
5041
6161
|
RC_SEEN_C=","
|
|
5042
6162
|
RC_MARKED_AT=""
|
|
6163
|
+
RC_MARKED_N=0
|
|
6164
|
+
RC_MARKED_C=""
|
|
6165
|
+
RC_NEWEST_N=0
|
|
5043
6166
|
RC_EVENT_ROUND=""
|
|
5044
6167
|
RC_STATE_REL=$(hr_state_dir "$root" 2>/dev/null) || RC_STATE_REL=""
|
|
5045
6168
|
RC_STATE_REL="${RC_STATE_REL%/}"
|
|
@@ -5057,6 +6180,7 @@ round_markers_read() {
|
|
|
5057
6180
|
[ "${BASH_REMATCH[1]}" = "$branch" ] || continue
|
|
5058
6181
|
n="${BASH_REMATCH[3]:-1}"
|
|
5059
6182
|
n=$((10#$n))
|
|
6183
|
+
[ "$n" -le "$RC_NEWEST_N" ] || RC_NEWEST_N="$n"
|
|
5060
6184
|
if ! line=$(git -C "$root" show "refs/remotes/origin/$branch:$path" 2>/dev/null); then
|
|
5061
6185
|
RC_ERR="the previous round \`$path\` could not be read"
|
|
5062
6186
|
return 4
|
|
@@ -5066,9 +6190,10 @@ round_markers_read() {
|
|
|
5066
6190
|
[[ "$line" =~ ^"$COMMENT_MARKER round collected_at="([0-9T:Z-]+)" reviews="([0-9,]*)" comments="([0-9,]*)" -->"$ ]] || continue
|
|
5067
6191
|
RC_SEEN_R="$RC_SEEN_R${BASH_REMATCH[2]}${BASH_REMATCH[2]:+,}"
|
|
5068
6192
|
RC_SEEN_C="$RC_SEEN_C${BASH_REMATCH[3]}${BASH_REMATCH[3]:+,}"
|
|
5069
|
-
if [ "$n" -gt "$
|
|
5070
|
-
|
|
6193
|
+
if [ "$n" -gt "$RC_MARKED_N" ]; then
|
|
6194
|
+
RC_MARKED_N="$n"
|
|
5071
6195
|
RC_MARKED_AT="${BASH_REMATCH[1]}"
|
|
6196
|
+
RC_MARKED_C="${BASH_REMATCH[3]}"
|
|
5072
6197
|
fi
|
|
5073
6198
|
case ",${BASH_REMATCH[2]}," in
|
|
5074
6199
|
*",$event_id,"*) [ -z "$event_id" ] || RC_EVENT_ROUND="$n" ;;
|
|
@@ -5242,12 +6367,16 @@ REVIEW_RETRY_WAY="The reviews stay on the pull request and are collected by the
|
|
|
5242
6367
|
# control_settled_var — `branch_settled_var` for CONTROL_BRANCH, with no run
|
|
5243
6368
|
# counted as settled, run in a command substitution so that its exit on a
|
|
5244
6369
|
# failed read reaches this process as a status, not as an exit with no reply:
|
|
5245
|
-
# BS_SETTLED, BS_STATE and BS_REASON
|
|
5246
|
-
#
|
|
6370
|
+
# BS_SETTLED, BS_STATE and BS_REASON, and RS_NOT_STARTED, RS_RUN_ID, RS_ENGINE
|
|
6371
|
+
# and RS_DETAIL as BS_NOT_STARTED, BS_RUN_ID, BS_ENGINE and BS_DETAIL; 1 with
|
|
6372
|
+
# BS_ERR, its last stderr line, on a failed read. BS_DETAIL is read last, so a
|
|
6373
|
+
# `|` inside it shifts no other field.
|
|
5247
6374
|
BS_SETTLED=0; BS_STATE=""; BS_REASON=""; BS_ERR=""
|
|
6375
|
+
BS_NOT_STARTED=0; BS_RUN_ID=""; BS_ENGINE=""; BS_DETAIL=""
|
|
5248
6376
|
control_settled_var() {
|
|
5249
6377
|
local dir errfile line status=0
|
|
5250
6378
|
BS_SETTLED=0; BS_STATE=""; BS_REASON=""; BS_ERR=""
|
|
6379
|
+
BS_NOT_STARTED=0; BS_RUN_ID=""; BS_ENGINE=""; BS_DETAIL=""
|
|
5251
6380
|
if ! dir=$(mktemp -d "$control_tmp/harness-control-settled.XXXXXX"); then
|
|
5252
6381
|
BS_ERR="a state directory could not be created under '$control_tmp'"
|
|
5253
6382
|
return 1
|
|
@@ -5256,14 +6385,15 @@ control_settled_var() {
|
|
|
5256
6385
|
control_dirs="$control_dirs $dir $errfile"
|
|
5257
6386
|
branch="$CONTROL_BRANCH"
|
|
5258
6387
|
line=$(branch_settled_var "$dir" 1 >/dev/null 2>"$errfile" \
|
|
5259
|
-
&& printf '%s|%s|%s\n' "$SETTLED" "$RS_STATE" "$RS_PAUSE_REASON"
|
|
6388
|
+
&& printf '%s|%s|%s|%s|%s|%s|%s\n' "$SETTLED" "$RS_STATE" "$RS_PAUSE_REASON" \
|
|
6389
|
+
"$RS_NOT_STARTED" "$RS_RUN_ID" "$RS_ENGINE" "$RS_DETAIL") || status=$?
|
|
5260
6390
|
cat "$errfile" >&2 2>/dev/null || :
|
|
5261
6391
|
if [ "$status" -ne 0 ] || [ -z "$line" ]; then
|
|
5262
6392
|
BS_ERR=$(grep -v '^[[:space:]]*$' "$errfile" 2>/dev/null | tail -n 1)
|
|
5263
6393
|
[ -n "$BS_ERR" ] || BS_ERR="exit $status, no message"
|
|
5264
6394
|
return 1
|
|
5265
6395
|
fi
|
|
5266
|
-
IFS='|' read -r BS_SETTLED BS_STATE BS_REASON <<<"$line"
|
|
6396
|
+
IFS='|' read -r BS_SETTLED BS_STATE BS_REASON BS_NOT_STARTED BS_RUN_ID BS_ENGINE BS_DETAIL <<<"$line"
|
|
5267
6397
|
return 0
|
|
5268
6398
|
}
|
|
5269
6399
|
|
|
@@ -5551,7 +6681,8 @@ control_close() {
|
|
|
5551
6681
|
fi
|
|
5552
6682
|
forge_on || control_close_ignore "the default branch's harness.config.json does not turn run control on"
|
|
5553
6683
|
if [ "$CLOSE_KIND" = deleted ]; then
|
|
5554
|
-
# Deleting a branch already needs write access; only a bot is screened
|
|
6684
|
+
# Deleting a branch already needs write access; only a bot is screened,
|
|
6685
|
+
# never HARNESS_RUN_ACTORS (the header, THE CLOSE gate 3).
|
|
5555
6686
|
if [ "$CONTROL_SENDER_TYPE" = Bot ] && ! trigger_bot_listed "$CONTROL_ACTOR"; then
|
|
5556
6687
|
control_close_ignore "@$CONTROL_ACTOR is a bot not listed in HARNESS_TRIGGER_ALLOWED_BOTS"
|
|
5557
6688
|
fi
|
|
@@ -5608,7 +6739,7 @@ control_close() {
|
|
|
5608
6739
|
fi
|
|
5609
6740
|
|
|
5610
6741
|
# No forge_recognised check: a merged or deleted branch may no longer carry
|
|
5611
|
-
# its ledger, and a listed `harness run <b>` run is what proves a harness run.
|
|
6742
|
+
# its task prompt or ledger, and a listed `harness run <b>` run is what proves a harness run.
|
|
5612
6743
|
if ! control_state_var "$b"; then
|
|
5613
6744
|
echo "::error::remote-run.sh: control: the state of the run on \`$b\` could not be read ($CS_ERR)"
|
|
5614
6745
|
exit "$EXIT_GH"
|
|
@@ -5695,11 +6826,16 @@ verb_control() {
|
|
|
5695
6826
|
"Set both keys on the default branch, then comment again."
|
|
5696
6827
|
fi
|
|
5697
6828
|
|
|
6829
|
+
if ! rerun_actor_listed; then
|
|
6830
|
+
control_refuse "$EXIT_REFUSED" "${RUN_ACTORS_WHY%.}" \
|
|
6831
|
+
"Only a person the repository variable \`HARNESS_RUN_ACTORS\` admits may re-run this job; one of them can comment again."
|
|
6832
|
+
fi
|
|
6833
|
+
|
|
5698
6834
|
status=0
|
|
5699
6835
|
authorise_actor "$CONTROL_ACTOR" "$CONTROL_SENDER_TYPE" || status=$?
|
|
5700
6836
|
if [ "$status" -ne 0 ]; then
|
|
5701
6837
|
control_refuse "$EXIT_REFUSED" "${AUTH_WHY%.}" \
|
|
5702
|
-
"Only a collaborator with write, maintain or admin access, or a bot listed in
|
|
6838
|
+
"Only a collaborator with write, maintain or admin access whom the repository variable \`HARNESS_RUN_ACTORS\` admits (when unset, the owner alone of a repository a personal account owns, and nobody in an organisation-owned one), or a bot listed in \`HARNESS_TRIGGER_ALLOWED_BOTS\`, commands a run."
|
|
5703
6839
|
fi
|
|
5704
6840
|
|
|
5705
6841
|
if [ "$review" -eq 0 ] && { [ -z "$CONTROL_VERB" ] || ! control_verb_handled "$CONTROL_VERB"; }; then
|
|
@@ -5755,7 +6891,7 @@ collect_notify() {
|
|
|
5755
6891
|
}
|
|
5756
6892
|
|
|
5757
6893
|
verb_collect() {
|
|
5758
|
-
local status=0 dir file out
|
|
6894
|
+
local status=0 dir file out reason route way
|
|
5759
6895
|
if ! forge_on; then
|
|
5760
6896
|
echo "remote-run.sh: collect: the forge coupling is off (forge github and execution.target github-actions); nothing collected"
|
|
5761
6897
|
exit "$EXIT_OK"
|
|
@@ -5780,17 +6916,7 @@ verb_collect() {
|
|
|
5780
6916
|
exit "$EXIT_OK" ;;
|
|
5781
6917
|
esac
|
|
5782
6918
|
|
|
5783
|
-
|
|
5784
|
-
FORGE_PR="$pr_arg"
|
|
5785
|
-
elif ! forge_pr_var "$branch"; then
|
|
5786
|
-
echo "remote-run.sh: collect: the pull request of $branch could not be read; nothing collected"
|
|
5787
|
-
exit "$EXIT_OK"
|
|
5788
|
-
fi
|
|
5789
|
-
if [ -z "$FORGE_PR" ]; then
|
|
5790
|
-
echo "remote-run.sh: collect: no open pull request; nothing to collect"
|
|
5791
|
-
exit "$EXIT_OK"
|
|
5792
|
-
fi
|
|
5793
|
-
|
|
6919
|
+
# Read before the pull request is required: a first run has none.
|
|
5794
6920
|
control_tmp="${RUNNER_TEMP-}"
|
|
5795
6921
|
if [ -z "$control_tmp" ] || [ ! -d "$control_tmp" ]; then
|
|
5796
6922
|
control_tmp=$(mktemp -d) || { echo "remote-run.sh: collect: mktemp failed; nothing collected"; exit "$EXIT_OK"; }
|
|
@@ -5803,6 +6929,37 @@ verb_collect() {
|
|
|
5803
6929
|
echo "remote-run.sh: collect: the state of the run on $branch could not be read ($BS_ERR); no round started"
|
|
5804
6930
|
exit "$EXIT_OK"
|
|
5805
6931
|
fi
|
|
6932
|
+
if [ "$BS_NOT_STARTED" = 1 ]; then
|
|
6933
|
+
if [ -n "${GITHUB_RUN_ID-}" ] && [ "$BS_RUN_ID" != "$GITHUB_RUN_ID" ]; then
|
|
6934
|
+
echo "remote-run.sh: collect: run $BS_RUN_ID of $branch never started; its own collect reports it"
|
|
6935
|
+
exit "$EXIT_OK"
|
|
6936
|
+
fi
|
|
6937
|
+
reason="${BS_DETAIL#"GitHub did not start the job of run $BS_RUN_ID ("}"
|
|
6938
|
+
if [ "$reason" = "$BS_DETAIL" ]; then reason=""; else reason="${reason%"): "*}"; fi
|
|
6939
|
+
REPORT_NOT_STARTED_STATE="$BS_STATE"
|
|
6940
|
+
REPORT_NOT_STARTED_ENGINE="$BS_ENGINE"
|
|
6941
|
+
route=$(hr_github_resume_route "$branch" "${BS_ENGINE:-<task, user_review or docs: the one the run was started with>}")
|
|
6942
|
+
if [ "$BS_STATE" = paused ]; then
|
|
6943
|
+
way="Run $RESUME_HINT $branch to start it again; $route."
|
|
6944
|
+
else
|
|
6945
|
+
way="Start it again ${route#or }."
|
|
6946
|
+
fi
|
|
6947
|
+
notify not_started "$branch" "$BS_DETAIL. $way" "$reason"
|
|
6948
|
+
# The reviews stay for the resumed run's own end.
|
|
6949
|
+
echo "remote-run.sh: collect: run ${BS_RUN_ID} of $branch never started; reported, and no round collected"
|
|
6950
|
+
exit "$EXIT_OK"
|
|
6951
|
+
fi
|
|
6952
|
+
|
|
6953
|
+
if [ -n "$pr_arg" ]; then
|
|
6954
|
+
FORGE_PR="$pr_arg"
|
|
6955
|
+
elif ! forge_pr_var "$branch"; then
|
|
6956
|
+
echo "remote-run.sh: collect: the pull request of $branch could not be read; nothing collected"
|
|
6957
|
+
exit "$EXIT_OK"
|
|
6958
|
+
fi
|
|
6959
|
+
if [ -z "$FORGE_PR" ]; then
|
|
6960
|
+
echo "remote-run.sh: collect: no open pull request; nothing to collect"
|
|
6961
|
+
exit "$EXIT_OK"
|
|
6962
|
+
fi
|
|
5806
6963
|
if [ "$BS_SETTLED" != 1 ]; then
|
|
5807
6964
|
echo "remote-run.sh: collect: $branch is ${BS_STATE:-in flight}${BS_REASON:+ ($BS_REASON)}; that run's own end collects"
|
|
5808
6965
|
exit "$EXIT_OK"
|
|
@@ -5839,7 +6996,11 @@ verb_collect() {
|
|
|
5839
6996
|
echo "remote-run.sh: collect: started the next round of $branch from @${RC_REVIEWERS//,/, @}"
|
|
5840
6997
|
exit "$EXIT_OK"
|
|
5841
6998
|
fi
|
|
5842
|
-
|
|
6999
|
+
if [ -n "${GITHUB_RUN_ID-}" ]; then
|
|
7000
|
+
collect_notify "The reviews requesting changes collected during the run on \`$branch\` could not start the next round: ${CHILD_LAST%.}. They stay on the pull request. To retry, re-run this run's \`collect\` job (no new review is needed), or submit a review requesting changes."
|
|
7001
|
+
else
|
|
7002
|
+
collect_notify "The reviews requesting changes collected during the run on \`$branch\` could not start the next round: ${CHILD_LAST%.}. They stay on the pull request; submit a review requesting changes to retry."
|
|
7003
|
+
fi
|
|
5843
7004
|
echo "remote-run.sh: collect: review exited $CHILD_STATUS; the pull request was told, and nothing is retried"
|
|
5844
7005
|
exit "$EXIT_OK"
|
|
5845
7006
|
}
|
|
@@ -5923,6 +7084,7 @@ case "$verb" in
|
|
|
5923
7084
|
list) verb_list ;;
|
|
5924
7085
|
discard) verb_discard ;;
|
|
5925
7086
|
report) verb_report ;;
|
|
7087
|
+
open) verb_open ;;
|
|
5926
7088
|
deliver) verb_deliver ;;
|
|
5927
7089
|
collect) verb_collect ;;
|
|
5928
7090
|
control) verb_control ;;
|