autonomous-sdlc-harness 0.6.1 → 0.6.2
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 +72 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/core/writer.js +3 -2
- package/dist/core/writer.js.map +1 -1
- package/dist/doctor/checks.js +255 -12
- 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 +56 -1
- 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-run.yml +116 -2
- package/templates/github/workflows/harness-trigger.yml +4 -1
- package/templates/scripts/remote-run.sh +182 -27
|
@@ -191,13 +191,18 @@
|
|
|
191
191
|
# a refusal can still be commented. It reads, only from the environment:
|
|
192
192
|
# GITHUB_EVENT_NAME, GITHUB_EVENT_PATH the event; each field is read by `jq`
|
|
193
193
|
# into a variable and is only ever an argument or file
|
|
194
|
-
# bytes, never shell source
|
|
194
|
+
# bytes, never shell source; `.repository.owner.login`
|
|
195
|
+
# and `.repository.owner.type` only when
|
|
196
|
+
# `HARNESS_RUN_ACTORS` is unset
|
|
195
197
|
# GITHUB_REPOSITORY, GITHUB_SERVER_URL, GITHUB_RUN_ID the `gh` target and
|
|
196
198
|
# the URLs its comments name
|
|
197
199
|
# HARNESS_REMOTE_STOP non-empty: every start is refused
|
|
198
200
|
# HARNESS_TRIGGER_LABEL the trigger label; when empty, `DEFAULT_TRIGGER_LABEL`
|
|
199
201
|
# or `LEGACY_TRIGGER_LABEL`, per the paragraph below
|
|
200
202
|
# HARNESS_TRIGGER_ALLOWED_BOTS comma-separated bot logins allowed to start
|
|
203
|
+
# HARNESS_RUN_ACTORS comma-separated people allowed to act on a run, `*`
|
|
204
|
+
# for every writer; unset admits a `User` owner alone
|
|
205
|
+
# (`run_actor_listed`)
|
|
201
206
|
# HARNESS_TRIGGER_LOOKUP_SECS seconds between run lookups; `5` when empty.
|
|
202
207
|
# A test seam
|
|
203
208
|
# RUNNER_TEMP where the prompt snapshot is written; a `mktemp -d`
|
|
@@ -226,7 +231,13 @@
|
|
|
226
231
|
# 6. a `User` whose `collaborators/<login>/permission` is not `admin` or
|
|
227
232
|
# `write` — `maintain` reads as `write` and `triage` as `read` there; a
|
|
228
233
|
# failed call is "could not confirm write access", never a pass
|
|
229
|
-
#
|
|
234
|
+
# 7. a writer `HARNESS_RUN_ACTORS` does not admit — after refusal 6, so a
|
|
235
|
+
# non-writer is still told about write access
|
|
236
|
+
# Refusals 4 to 7 are `authorise_actor`, the one actor check `control` reuses.
|
|
237
|
+
# Before them, after refusal 2, a re-run (GITHUB_RUN_ATTEMPT above 1) whose
|
|
238
|
+
# GITHUB_TRIGGERING_ACTOR is not `github-actions[bot]` and is not admitted by
|
|
239
|
+
# `HARNESS_RUN_ACTORS` is refused (`rerun_actor_listed`), on both event kinds:
|
|
240
|
+
# a re-run replays the event, so its sender is whoever first acted.
|
|
230
241
|
# Then it fetches `origin <defaultBranch>` (a failure tolerated), derives the
|
|
231
242
|
# branch with `hr_derive_branch <title> issue_<number>`, passing `gh` so a name
|
|
232
243
|
# with run-workflow history counts as taken (2 or 3 refused), writes
|
|
@@ -252,7 +263,10 @@
|
|
|
252
263
|
# and exit 0 with no `gh` call; an empty or missing `title` is refused. It has
|
|
253
264
|
# no labeller: GitHub sends one only for a fine-grained token with Contents
|
|
254
265
|
# write or a classic token with `repo`, so the token holder is the authority,
|
|
255
|
-
# and refusals 3 to 6 do not apply.
|
|
266
|
+
# and refusals 3 to 6 do not apply. After refusal 2 a `User` sender
|
|
267
|
+
# (`.sender.login`, `.sender.type`) that `HARNESS_RUN_ACTORS` does not admit is
|
|
268
|
+
# refused with no permission call, and so is a sender with no type unless the
|
|
269
|
+
# list is `*`, failing closed; any other sender type passes. The fallback name is `task_<GITHUB_RUN_ID>`
|
|
256
270
|
# and the snapshot's provenance sentence names the event type, `source` when
|
|
257
271
|
# set, and the time. There is no issue, so no comment and no label: every
|
|
258
272
|
# outcome is printed to stdout and appended as a Markdown block to
|
|
@@ -280,9 +294,11 @@
|
|
|
280
294
|
# label is a view, and the run list stays the authority. The state map:
|
|
281
295
|
# `parked` and `park_loop` -> parked, `paused` -> paused, `resumed` -> running,
|
|
282
296
|
# `failed` -> failed, `stopped` -> stopped, `round` (review's) -> running.
|
|
283
|
-
# `
|
|
284
|
-
# `
|
|
285
|
-
#
|
|
297
|
+
# Every job event — `parked`, `park_loop`, `paused`, `resumed`, `round` and
|
|
298
|
+
# `failed` — posts nothing and sets no label when `remote_branch_stopped`,
|
|
299
|
+
# asked afresh, finds the branch stopped, so a job a stop overtook never
|
|
300
|
+
# overwrites `stopped`; a failed listing reports anyway, and `stopped` is never
|
|
301
|
+
# withheld. `completed` (deliver's) and `launched` (the trigger's
|
|
286
302
|
# own comment) are one line each, as is any other event. The comment names the
|
|
287
303
|
# next GitHub action — never a slash command — then <note> byte for byte, then
|
|
288
304
|
# this run's URL when `GITHUB_RUN_ID` is set, then the marker line
|
|
@@ -379,7 +395,10 @@
|
|
|
379
395
|
# 1. `HARNESS_REMOTE_STOP` is set
|
|
380
396
|
# 2. `forge_on` fails — before any authorisation, so a disabled coupling asks
|
|
381
397
|
# GitHub nothing about the commenter
|
|
382
|
-
#
|
|
398
|
+
# then a re-run whose GITHUB_TRIGGERING_ACTOR `rerun_actor_listed`
|
|
399
|
+
# refuses, before the event's own actor is checked
|
|
400
|
+
# 3. `authorise_actor` fails: `AUTH_WHY`, and who may command a run — a
|
|
401
|
+
# writer the `HARNESS_RUN_ACTORS` allow-list admits, or a listed bot
|
|
383
402
|
# 4. the verb is empty, not a `COMMAND_VERBS` word, or one no arm carries out
|
|
384
403
|
# yet: the reply lists every command and names `docs/github-run-control.md`
|
|
385
404
|
# THE BRANCH. On a pull request (`.issue.pull_request.url` set), its head, by
|
|
@@ -451,8 +470,12 @@
|
|
|
451
470
|
# first `- [ ] ` entry of the flow-progress ledger on origin's tip with the
|
|
452
471
|
# `## ` heading above it, and replies once — the state by `CS_WORD` with its
|
|
453
472
|
# pause reason, the state underneath a stopped one, an expired bundle's detail,
|
|
454
|
-
# the next ledger entry (or
|
|
455
|
-
#
|
|
473
|
+
# the next ledger entry (or, on a fully ticked ledger, that a user-review round
|
|
474
|
+
# has started and its ledger is not written yet, when origin's tip carries the
|
|
475
|
+
# round's `chore: add user review for <branch>` commit after the ledger's last
|
|
476
|
+
# change; else, for a `running` run, that it is still finishing or starting a
|
|
477
|
+
# stage, never that every entry is ticked; else that every entry is ticked; or
|
|
478
|
+
# that the ledger could not be read), each open question with its `answer <n>` form, and the
|
|
456
479
|
# latest run's URL. It sets no label and runs no child verb but `fetch`; a
|
|
457
480
|
# failed state read is a refusal and exit 3.
|
|
458
481
|
# THE REVIEW. A `pull_request_review` event reads `.action`, `.review.state`,
|
|
@@ -496,9 +519,10 @@
|
|
|
496
519
|
# inline comment, by any author and whatever its review's state, whose id is
|
|
497
520
|
# unrecorded, whose body carries no `COMMENT_MARKER`, and whose `created_at` is
|
|
498
521
|
# at or after the boundary or whose review is pending. Every distinct author
|
|
499
|
-
# of a pending item passes `authorise_actor
|
|
500
|
-
#
|
|
501
|
-
#
|
|
522
|
+
# of a pending item passes `authorise_actor`, the `HARNESS_RUN_ACTORS`
|
|
523
|
+
# allow-list included; a refused author's items — a writer the list refuses
|
|
524
|
+
# among them — are dropped with one line naming the login, `AUTH_WHY` and the
|
|
525
|
+
# count, and a failed permission call (status 4) fails the collection. A round is placed only when at
|
|
502
526
|
# least one kept review has state `REVIEW_ROUND_STATE`; a *Comment* or
|
|
503
527
|
# *Approve* review rides along in the next round one requesting changes starts.
|
|
504
528
|
# The file: one `## Review by @<login>` section per pending review, oldest
|
|
@@ -548,9 +572,11 @@
|
|
|
548
572
|
# 1. `HARNESS_REMOTE_STOP` is set
|
|
549
573
|
# 2. `forge_on` fails
|
|
550
574
|
# 3. the actor: on `issues` and `pull_request`, `authorise_actor` refused
|
|
551
|
-
# (statuses 1–3
|
|
552
|
-
#
|
|
553
|
-
#
|
|
575
|
+
# (statuses 1–3 and 5, so a writer the `HARNESS_RUN_ACTORS` allow-list
|
|
576
|
+
# refuses is ignored too), the line naming the login and `AUTH_WHY`; on
|
|
577
|
+
# `delete`, only a `Bot` sender `trigger_bot_listed` does not list, and
|
|
578
|
+
# never the allow-list: deleting a branch already needs write access, the
|
|
579
|
+
# run's branch is gone, and a stop spends no credential
|
|
554
580
|
# 4. no branch: an issue's from `control_issue_branch_var` (no genuine start
|
|
555
581
|
# comment), a pull request's head ref, a deletion's `.ref`; then not a
|
|
556
582
|
# valid branch name
|
|
@@ -936,6 +962,7 @@
|
|
|
936
962
|
# DEFAULT_TRIGGER_LABEL mirrors DEFAULT_TRIGGER_LABEL
|
|
937
963
|
# LEGACY_TRIGGER_LABEL mirrors LEGACY_TRIGGER_LABEL
|
|
938
964
|
# HARNESS_TRIGGER_ALLOWED_BOTS mirrors TRIGGER_ALLOWED_BOTS_VARIABLE
|
|
965
|
+
# HARNESS_RUN_ACTORS mirrors RUN_ACTORS_VARIABLE
|
|
939
966
|
# TRIGGER_DISPATCH_EVENT_TYPE mirrors TRIGGER_DISPATCH_EVENT_TYPE ('harness-task')
|
|
940
967
|
# WORKFLOW_CONTROL_FILE mirrors WORKFLOW_CONTROL_FILE
|
|
941
968
|
# COMMAND_HANDLE mirrors COMMAND_HANDLE
|
|
@@ -1123,13 +1150,18 @@
|
|
|
1123
1150
|
# "html_url":"https://github.com/o/r/issues/7","state":"open"}}, and a stub
|
|
1124
1151
|
# answering `api repos/o/r/collaborators/alice/permission` with
|
|
1125
1152
|
# {"permission":"write"}; export GITHUB_EVENT_NAME=issues GITHUB_EVENT_PATH=e.json
|
|
1126
|
-
# GITHUB_REPOSITORY=o/r HARNESS_TRIGGER_LOOKUP_SECS=0:
|
|
1153
|
+
# GITHUB_REPOSITORY=o/r HARNESS_TRIGGER_LOOKUP_SECS=0 HARNESS_RUN_ACTORS='*':
|
|
1127
1154
|
# trigger bash scripts/remote-run.sh trigger -> 0; origin/add_comments gains
|
|
1128
1155
|
# the prompt commit, "$s.log" gains `workflow run harness-run.yml
|
|
1129
1156
|
# --ref add_comments ...`, `issue comment 7 ...` naming the branch,
|
|
1130
1157
|
# then `issue edit 7 ... --remove-label sdlc-harness`
|
|
1131
1158
|
# read the permission answer {"permission":"read"} -> 2, no `workflow
|
|
1132
1159
|
# run`, one comment naming write access, the label removed
|
|
1160
|
+
# not listed HARNESS_RUN_ACTORS=bob -> 2, no `workflow run`, one comment
|
|
1161
|
+
# naming HARNESS_RUN_ACTORS, the label removed
|
|
1162
|
+
# unset HARNESS_RUN_ACTORS= and e.json gaining
|
|
1163
|
+
# "repository":{"owner":{"login":"alice","type":"User"}} -> 0, as
|
|
1164
|
+
# `trigger`; the owner `bob` instead -> 2, the comment naming @bob
|
|
1133
1165
|
# ignored e.json's label name `bug` -> 0, one line, "$s.log" unchanged
|
|
1134
1166
|
# dispatch GITHUB_EVENT_NAME=repository_dispatch GITHUB_RUN_ID=9
|
|
1135
1167
|
# GITHUB_STEP_SUMMARY=/tmp/s, e.json {"action":<TRIGGER_DISPATCH_EVENT_TYPE>,
|
|
@@ -3472,13 +3504,73 @@ trigger_bot_listed() {
|
|
|
3472
3504
|
return 1
|
|
3473
3505
|
}
|
|
3474
3506
|
|
|
3507
|
+
# run_actor_listed <login> — 0 when HARNESS_RUN_ACTORS admits <login>: split on
|
|
3508
|
+
# `,`, each entry trimmed, empty entries dropped, matched case-insensitively,
|
|
3509
|
+
# `*` admitting everyone, an empty <login> admitted by `*` alone. A list with no
|
|
3510
|
+
# entries is unset, and admits only `.repository.owner.login` of the event file
|
|
3511
|
+
# when `.repository.owner.type` is `User`; any other owner, or an event file or
|
|
3512
|
+
# field that cannot be read, admits nobody. Otherwise 1, with RUN_ACTORS_WHY
|
|
3513
|
+
# holding one sentence. Lowercased with `tr`, never a parameter expansion:
|
|
3514
|
+
# bash 3.2 has no case-changing one.
|
|
3515
|
+
RUN_ACTORS_WHY=""
|
|
3516
|
+
run_actor_listed() {
|
|
3517
|
+
local login="$1" want rest="${HARNESS_RUN_ACTORS-}," entry listed="" owner="" owner_type=""
|
|
3518
|
+
RUN_ACTORS_WHY=""
|
|
3519
|
+
want=$(printf '%s' "$login" | tr '[:upper:]' '[:lower:]')
|
|
3520
|
+
while [ -n "$rest" ]; do
|
|
3521
|
+
entry=${rest%%,*}
|
|
3522
|
+
rest=${rest#*,}
|
|
3523
|
+
entry=${entry#"${entry%%[![:space:]]*}"}
|
|
3524
|
+
entry=${entry%"${entry##*[![:space:]]}"}
|
|
3525
|
+
[ -n "$entry" ] || continue
|
|
3526
|
+
listed=1
|
|
3527
|
+
[ "$entry" = '*' ] && return 0
|
|
3528
|
+
[ -n "$want" ] && [ "$(printf '%s' "$entry" | tr '[:upper:]' '[:lower:]')" = "$want" ] && return 0
|
|
3529
|
+
done
|
|
3530
|
+
if [ -n "$listed" ]; then
|
|
3531
|
+
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."
|
|
3532
|
+
return 1
|
|
3533
|
+
fi
|
|
3534
|
+
if [ -n "${GITHUB_EVENT_PATH-}" ] && [ -r "$GITHUB_EVENT_PATH" ]; then
|
|
3535
|
+
event_field '.repository.owner.type // ""' && owner_type="$EVENT_VALUE"
|
|
3536
|
+
event_field '.repository.owner.login // ""' && owner="$EVENT_VALUE"
|
|
3537
|
+
fi
|
|
3538
|
+
if [ "$owner_type" = User ] && [ -n "$owner" ]; then
|
|
3539
|
+
[ -n "$want" ] && [ "$(printf '%s' "$owner" | tr '[:upper:]' '[:lower:]')" = "$want" ] && return 0
|
|
3540
|
+
RUN_ACTORS_WHY="@$login is not the repository owner, and the repository variable HARNESS_RUN_ACTORS is unset, which admits the owner, @$owner, alone."
|
|
3541
|
+
return 1
|
|
3542
|
+
fi
|
|
3543
|
+
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."
|
|
3544
|
+
return 1
|
|
3545
|
+
}
|
|
3546
|
+
|
|
3547
|
+
# rerun_actor_listed — 0 unless this job is a re-run (GITHUB_RUN_ATTEMPT above
|
|
3548
|
+
# 1) whose GITHUB_TRIGGERING_ACTOR is neither `github-actions[bot]` nor
|
|
3549
|
+
# admitted by run_actor_listed; then 1, with RUN_ACTORS_WHY naming the
|
|
3550
|
+
# re-runner. A re-run replays the original event, whose sender is the person
|
|
3551
|
+
# who first acted, so authorise_actor alone would check the wrong account.
|
|
3552
|
+
rerun_actor_listed() {
|
|
3553
|
+
local attempt="${GITHUB_RUN_ATTEMPT-}" who="${GITHUB_TRIGGERING_ACTOR-}"
|
|
3554
|
+
RUN_ACTORS_WHY=""
|
|
3555
|
+
case "$attempt" in ''|*[!0-9]*) return 0 ;; esac
|
|
3556
|
+
[ "$attempt" -gt 1 ] || return 0
|
|
3557
|
+
[ "$who" != 'github-actions[bot]' ] || return 0
|
|
3558
|
+
run_actor_listed "$who" && return 0
|
|
3559
|
+
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"
|
|
3560
|
+
return 1
|
|
3561
|
+
}
|
|
3562
|
+
|
|
3475
3563
|
# authorise_actor <login> <type> — the one actor check, shared by `trigger` and
|
|
3476
3564
|
# `control`: 0 when authorised. Otherwise AUTH_WHY holds one sentence and the
|
|
3477
3565
|
# status names the arm: 1 `ghost`, empty or not a login shape; 2 a non-`User`
|
|
3478
3566
|
# 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
|
-
#
|
|
3567
|
+
# because the permission API answers `none` or 404 for a bot, and never
|
|
3568
|
+
# consulting the run-actor list; 3 a `User` whose permission is not `admin` or
|
|
3569
|
+
# `write` (AUTH_PERMISSION holds it); 4 that permission call failed (GH_ERR
|
|
3570
|
+
# holds why); 5 a writer `run_actor_listed` refuses. The list is checked after
|
|
3571
|
+
# the permission call so a non-writer's refusal keeps naming write access, and
|
|
3572
|
+
# 5 is kept for a writer the maintainer has not named. Prints nothing and
|
|
3573
|
+
# never posts.
|
|
3482
3574
|
AUTH_WHY=""
|
|
3483
3575
|
AUTH_PERMISSION=""
|
|
3484
3576
|
authorise_actor() {
|
|
@@ -3502,7 +3594,10 @@ authorise_actor() {
|
|
|
3502
3594
|
fi
|
|
3503
3595
|
AUTH_PERMISSION=$(printf '%s' "$GH_OUT" | jq -r '.permission // empty' 2>/dev/null) || AUTH_PERMISSION=""
|
|
3504
3596
|
case "$AUTH_PERMISSION" in
|
|
3505
|
-
admin|write)
|
|
3597
|
+
admin|write)
|
|
3598
|
+
run_actor_listed "$login" && return 0
|
|
3599
|
+
AUTH_WHY="$RUN_ACTORS_WHY"
|
|
3600
|
+
return 5 ;;
|
|
3506
3601
|
esac
|
|
3507
3602
|
AUTH_WHY="GitHub reports the permission of @$login as ${AUTH_PERMISSION:-nothing}, not write or admin."
|
|
3508
3603
|
return 3
|
|
@@ -3602,7 +3697,9 @@ verb_trigger() {
|
|
|
3602
3697
|
{ event_field '.action // ""' && action="$EVENT_VALUE" \
|
|
3603
3698
|
&& event_field '.client_payload.title // ""' && title="$EVENT_VALUE" \
|
|
3604
3699
|
&& event_field '.client_payload.body // ""' && body="$EVENT_VALUE" \
|
|
3605
|
-
&& event_field '.client_payload.source // ""' && source="$EVENT_VALUE"
|
|
3700
|
+
&& event_field '.client_payload.source // ""' && source="$EVENT_VALUE" \
|
|
3701
|
+
&& event_field '.sender.login // ""' && login="$EVENT_VALUE" \
|
|
3702
|
+
&& event_field '.sender.type // ""' && sender_type="$EVENT_VALUE"; } || {
|
|
3606
3703
|
echo "remote-run.sh: trigger: '$GITHUB_EVENT_PATH' is not a readable event" >&2
|
|
3607
3704
|
exit "$EXIT_USAGE"
|
|
3608
3705
|
}
|
|
@@ -3638,6 +3735,26 @@ verb_trigger() {
|
|
|
3638
3735
|
"Set both keys on the default branch, then $retry_then."
|
|
3639
3736
|
fi
|
|
3640
3737
|
|
|
3738
|
+
# A re-run replays the event's sender; the re-runner is held to the list.
|
|
3739
|
+
if ! rerun_actor_listed; then
|
|
3740
|
+
trigger_refuse "$RUN_ACTORS_WHY" \
|
|
3741
|
+
"Only a person the repository variable \`HARNESS_RUN_ACTORS\` admits may re-run this job; one of them can $retry_then."
|
|
3742
|
+
fi
|
|
3743
|
+
|
|
3744
|
+
# A dispatch's `User` sender is held to the allow-list with no permission
|
|
3745
|
+
# call; a sender with no type fails closed, since an empty login is admitted
|
|
3746
|
+
# by `*` alone; any other sender type is the token's, and passes.
|
|
3747
|
+
if [ "$trigger_source" = dispatch ]; then
|
|
3748
|
+
if [ -z "$sender_type" ] && ! run_actor_listed ""; then
|
|
3749
|
+
trigger_refuse "the dispatch's sender carries no account type, so it cannot be checked against the allow-list." \
|
|
3750
|
+
"Set the repository variable \`HARNESS_RUN_ACTORS\` to \`*\` to admit every token holder, then $retry_then."
|
|
3751
|
+
fi
|
|
3752
|
+
if [ "$sender_type" = User ] && ! run_actor_listed "$login"; then
|
|
3753
|
+
trigger_refuse "$RUN_ACTORS_WHY" \
|
|
3754
|
+
"Add \`$login\` to the comma-separated repository variable \`HARNESS_RUN_ACTORS\`, or set it to \`*\` to admit every writer, then $retry_then."
|
|
3755
|
+
fi
|
|
3756
|
+
fi
|
|
3757
|
+
|
|
3641
3758
|
if [ "$trigger_source" = issue ]; then
|
|
3642
3759
|
if [ "$state" != open ]; then
|
|
3643
3760
|
trigger_refuse "this issue is not open." "Reopen it, then re-apply the label \`$trigger_label\`."
|
|
@@ -3653,6 +3770,8 @@ verb_trigger() {
|
|
|
3653
3770
|
"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
3771
|
3) trigger_refuse "could not confirm write access for @$login: GitHub reports their permission as \`${AUTH_PERMISSION:-nothing}\`." \
|
|
3655
3772
|
"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\`." ;;
|
|
3773
|
+
5) trigger_refuse "$AUTH_WHY" \
|
|
3774
|
+
"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
3775
|
*) trigger_refuse "could not confirm write access for @$login: the permission check failed ($GH_ERR)." \
|
|
3657
3776
|
"Re-apply the label \`$trigger_label\` to try again." ;;
|
|
3658
3777
|
esac
|
|
@@ -4039,7 +4158,9 @@ forge_question_body() {
|
|
|
4039
4158
|
# comment (on `parked`, one per open question) and the state label, by the
|
|
4040
4159
|
# target rule above. Read on `stopped` only: <pr>, an explicit pull request
|
|
4041
4160
|
# that is the target whatever its state; `gone`, the branch deleted on GitHub,
|
|
4042
|
-
# its issue read from the task prompt at <sha>.
|
|
4161
|
+
# its issue read from the task prompt at <sha>. Every event but `stopped` is
|
|
4162
|
+
# withheld when the branch's newest `harness stop` run is newer than its newest
|
|
4163
|
+
# `harness run` run, read from a fresh listing. Always 0.
|
|
4043
4164
|
forge_report() {
|
|
4044
4165
|
local event="$1" br="$2" note="${3-}" pr="${4-}" gone="${5-}" gone_sha="${6-}" state reason="" resume_at="" when registry_file
|
|
4045
4166
|
local target kind text tmp made_tmp="" file trigger_label stopped state_rel="" count n
|
|
@@ -4066,7 +4187,9 @@ forge_report() {
|
|
|
4066
4187
|
fi
|
|
4067
4188
|
forge_repo_var || return 0
|
|
4068
4189
|
|
|
4069
|
-
if [ "$event"
|
|
4190
|
+
if [ "$event" != stopped ]; then
|
|
4191
|
+
# Re-listed: `review` dispatches a run and then reports `round` in one invocation.
|
|
4192
|
+
ALL_RUNS_LISTED=0
|
|
4070
4193
|
remote_branch_stopped "$br"
|
|
4071
4194
|
stopped=$?
|
|
4072
4195
|
if [ "$stopped" -eq 0 ]; then
|
|
@@ -4074,7 +4197,7 @@ forge_report() {
|
|
|
4074
4197
|
return 0
|
|
4075
4198
|
fi
|
|
4076
4199
|
[ "$stopped" -eq 1 ] \
|
|
4077
|
-
|| echo "remote-run.sh: report: whether $br was stopped is unknown ($GH_ERR); reporting the
|
|
4200
|
+
|| echo "remote-run.sh: report: whether $br was stopped is unknown ($GH_ERR); reporting the $event" >&2
|
|
4078
4201
|
fi
|
|
4079
4202
|
|
|
4080
4203
|
[ "$event" = stopped ] || { pr=""; gone=""; }
|
|
@@ -4799,6 +4922,26 @@ control_ledger_next_var() {
|
|
|
4799
4922
|
return 0
|
|
4800
4923
|
}
|
|
4801
4924
|
|
|
4925
|
+
# control_round_newer_than_ledger — whether origin's tip of CONTROL_BRANCH
|
|
4926
|
+
# carries a user-review round commit newer than the ledger's last change: the
|
|
4927
|
+
# round's job writes its own ledger only after that commit. 0 when it does; 1
|
|
4928
|
+
# otherwise, a failed git read included. Subjects are compared as whole lines.
|
|
4929
|
+
control_round_newer_than_ledger() {
|
|
4930
|
+
local state_rel ledger_sha subjects subject line
|
|
4931
|
+
state_rel=$(hr_state_dir "$root" 2>/dev/null) || return 1
|
|
4932
|
+
[ -n "$state_rel" ] || return 1
|
|
4933
|
+
ledger_sha=$(git -C "$root" log -1 --format=%H "refs/remotes/origin/$CONTROL_BRANCH" \
|
|
4934
|
+
-- "${state_rel%/}/flow_progress/${CONTROL_BRANCH}_progress.md" 2>/dev/null) || return 1
|
|
4935
|
+
[ -n "$ledger_sha" ] || return 1
|
|
4936
|
+
subjects=$(git -C "$root" log --format=%s "$ledger_sha..refs/remotes/origin/$CONTROL_BRANCH" 2>/dev/null) \
|
|
4937
|
+
|| return 1
|
|
4938
|
+
subject=$(hr_user_review_subject "$CONTROL_BRANCH")
|
|
4939
|
+
while IFS= read -r line; do
|
|
4940
|
+
[ "$line" != "$subject" ] || return 0
|
|
4941
|
+
done <<<"$subjects"
|
|
4942
|
+
return 1
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4802
4945
|
# control_status — the read-only reply: the state, the next ledger entry, the
|
|
4803
4946
|
# open questions and the latest run. Sets no label and dispatches nothing.
|
|
4804
4947
|
control_status() {
|
|
@@ -4824,6 +4967,10 @@ control_status() {
|
|
|
4824
4967
|
if control_ledger_next_var; then
|
|
4825
4968
|
if [ -n "$LEDGER_NEXT" ]; then
|
|
4826
4969
|
text="$text${para}Next in the flow-progress ledger: $LEDGER_NEXT${LEDGER_SECTION:+ (under \`$LEDGER_SECTION\`)}"
|
|
4970
|
+
elif control_round_newer_than_ledger; then
|
|
4971
|
+
text="$text${para}A user-review round has started on \`$CONTROL_BRANCH\`, and its flow-progress ledger is not written yet."
|
|
4972
|
+
elif [ "$CS_WORD" = running ]; then
|
|
4973
|
+
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
4974
|
else
|
|
4828
4975
|
text="$text${para}Every entry of the flow-progress ledger is ticked."
|
|
4829
4976
|
fi
|
|
@@ -5006,7 +5153,9 @@ control_review_story() {
|
|
|
5006
5153
|
# `branch`'s pull request <pr_number>, shared by `control` and `collect`: every
|
|
5007
5154
|
# submitted review (never `PENDING`) that requests changes or carries a
|
|
5008
5155
|
# non-blank body, and every inline comment, no earlier round consumed, by
|
|
5009
|
-
# every author `authorise_actor` accepts
|
|
5156
|
+
# every author `authorise_actor` accepts, the `HARNESS_RUN_ACTORS` allow-list
|
|
5157
|
+
# included; a refused author's items are dropped with one line, while a
|
|
5158
|
+
# failed permission call (status 4) fails the collection. What earlier rounds consumed is
|
|
5010
5159
|
# read from the marker lines of their files on origin's tip; with none marked,
|
|
5011
5160
|
# the boundary is the committer time of the newest round file. A comment
|
|
5012
5161
|
# belonging to a pending review is pending whatever its `created_at`: a draft
|
|
@@ -5551,7 +5700,8 @@ control_close() {
|
|
|
5551
5700
|
fi
|
|
5552
5701
|
forge_on || control_close_ignore "the default branch's harness.config.json does not turn run control on"
|
|
5553
5702
|
if [ "$CLOSE_KIND" = deleted ]; then
|
|
5554
|
-
# Deleting a branch already needs write access; only a bot is screened
|
|
5703
|
+
# Deleting a branch already needs write access; only a bot is screened,
|
|
5704
|
+
# never HARNESS_RUN_ACTORS (the header, THE CLOSE gate 3).
|
|
5555
5705
|
if [ "$CONTROL_SENDER_TYPE" = Bot ] && ! trigger_bot_listed "$CONTROL_ACTOR"; then
|
|
5556
5706
|
control_close_ignore "@$CONTROL_ACTOR is a bot not listed in HARNESS_TRIGGER_ALLOWED_BOTS"
|
|
5557
5707
|
fi
|
|
@@ -5695,11 +5845,16 @@ verb_control() {
|
|
|
5695
5845
|
"Set both keys on the default branch, then comment again."
|
|
5696
5846
|
fi
|
|
5697
5847
|
|
|
5848
|
+
if ! rerun_actor_listed; then
|
|
5849
|
+
control_refuse "$EXIT_REFUSED" "${RUN_ACTORS_WHY%.}" \
|
|
5850
|
+
"Only a person the repository variable \`HARNESS_RUN_ACTORS\` admits may re-run this job; one of them can comment again."
|
|
5851
|
+
fi
|
|
5852
|
+
|
|
5698
5853
|
status=0
|
|
5699
5854
|
authorise_actor "$CONTROL_ACTOR" "$CONTROL_SENDER_TYPE" || status=$?
|
|
5700
5855
|
if [ "$status" -ne 0 ]; then
|
|
5701
5856
|
control_refuse "$EXIT_REFUSED" "${AUTH_WHY%.}" \
|
|
5702
|
-
"Only a collaborator with write, maintain or admin access, or a bot listed in
|
|
5857
|
+
"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
5858
|
fi
|
|
5704
5859
|
|
|
5705
5860
|
if [ "$review" -eq 0 ] && { [ -z "$CONTROL_VERB" ] || ! control_verb_handled "$CONTROL_VERB"; }; then
|