autonomous-sdlc-harness 0.4.0 → 0.4.1
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/doctor.js +32 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +8 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/core/defaultBranchPush.js +26 -0
- package/dist/core/defaultBranchPush.js.map +1 -0
- package/dist/core/git.js +35 -3
- package/dist/core/git.js.map +1 -1
- package/dist/doctor/checks.js +148 -54
- package/dist/doctor/checks.js.map +1 -1
- package/dist/generators/permissionProfile.js +60 -29
- package/dist/generators/permissionProfile.js.map +1 -1
- package/dist/generators/projectSettings.js +2 -1
- package/dist/generators/projectSettings.js.map +1 -1
- package/dist/generators/repoRoot.js +5 -2
- package/dist/generators/repoRoot.js.map +1 -1
- package/dist/retrieval/runtime.js +34 -11
- package/dist/retrieval/runtime.js.map +1 -1
- package/package.json +2 -2
- package/templates/github/workflows/harness-run.yml +1 -1
- package/templates/repo/gitignore +6 -1
- package/templates/scripts/autonomous-watcher.sh +180 -51
- package/templates/scripts/lib/harness-run-lib.sh +182 -20
- package/templates/scripts/remote-run.sh +28 -29
- package/templates/state-dir/autonomous_logs/README.md +1 -1
|
@@ -58,8 +58,12 @@
|
|
|
58
58
|
# and are written only by the `hr_lane_*` functions.
|
|
59
59
|
# 2. THE RUN REGISTRY writes the registry file its caller names. Fence: that
|
|
60
60
|
# file is `<root>/<state_dir>/autonomous_logs/registry.json`, resolved
|
|
61
|
-
# through `hr_state_path`,
|
|
62
|
-
# and `
|
|
61
|
+
# through `hr_state_path`, plus the lock directory `<file>.lock` (and its
|
|
62
|
+
# `.stale.*` move-aside and `.break` mutex while a stale one is broken)
|
|
63
|
+
# and the temp files
|
|
64
|
+
# `.registry.*` in the registry's own directory. Written only by
|
|
65
|
+
# `hr_registry_init`, `hr_registry_set` and the `hr_registry_lock` /
|
|
66
|
+
# `hr_registry_unlock` pair `hr_registry_set` calls.
|
|
63
67
|
# 3. THE REMOTE STATE BUNDLE writes the files its format lists. Fence: inside
|
|
64
68
|
# `<root>/<state_dir>/` (resolved through `hr_state_dir`), only
|
|
65
69
|
# `autonomous_logs/remote_status.json`, `clarifications/<branch>/`,
|
|
@@ -71,8 +75,8 @@
|
|
|
71
75
|
# there but a writer's own failed temp file is ever removed.
|
|
72
76
|
#
|
|
73
77
|
# A caller that calls no `hr_lane_*`, `hr_registry_init`, `hr_registry_set`,
|
|
74
|
-
# `
|
|
75
|
-
# `hr_remote_bundle_restore` function still gets a library that only reads. The
|
|
78
|
+
# `hr_registry_lock`, `hr_registry_unlock`, `hr_remote_status_write`,
|
|
79
|
+
# `hr_remote_bundle_write` or `hr_remote_bundle_restore` function still gets a library that only reads. The
|
|
76
80
|
# lane's ceilings are the only environment values here that carry policy, because
|
|
77
81
|
# the lane is machine-scoped and has no configuration key to carry them; each is
|
|
78
82
|
# named where it is used. `XDG_STATE_HOME`, `XDG_CONFIG_HOME`, `XDG_CACHE_HOME`,
|
|
@@ -165,7 +169,9 @@
|
|
|
165
169
|
# because callers capture stdout. The one pass-through is the registry's two
|
|
166
170
|
# writers, `hr_registry_init` and `hr_registry_set`, which leave the shell's,
|
|
167
171
|
# `mktemp`'s and `jq`'s own stderr on a failed write to the caller, as the
|
|
168
|
-
# watcher's bodies they replaced did — that stream is the watcher's log
|
|
172
|
+
# watcher's bodies they replaced did — that stream is the watcher's log — and
|
|
173
|
+
# add one line of their own, naming the lock, when `hr_registry_set` cannot
|
|
174
|
+
# take the registry lock. That silence is why the lane reports a lock it BROKE through a
|
|
169
175
|
# variable instead of a log line — the caller owns the log.
|
|
170
176
|
#
|
|
171
177
|
# NAMING. Every function is prefixed `hr_`; every variable this file touches
|
|
@@ -1110,31 +1116,187 @@ hr_push_env_files() {
|
|
|
1110
1116
|
# Each function takes the registry file as its first argument — the caller
|
|
1111
1117
|
# resolves it as `hr_state_path <root> autonomous_logs/registry.json` — and is
|
|
1112
1118
|
# write exception 2 in the header. Every value is written as a JSON string;
|
|
1113
|
-
# every write stamps `branch` and `updated_at` on the record
|
|
1114
|
-
#
|
|
1115
|
-
#
|
|
1119
|
+
# every write stamps `branch` and `updated_at` on the record. No shell option is
|
|
1120
|
+
# assumed: the caller may set `-e`, `-u` or neither.
|
|
1121
|
+
#
|
|
1122
|
+
# EVERY WRITE IS A LOCKED READ-MODIFY-WRITE. Every script sharing the file
|
|
1123
|
+
# writes it, and so does the watcher's own `( … ) &` engine subshell, so two
|
|
1124
|
+
# unserialized writers each read the same record and the second `mv` silently
|
|
1125
|
+
# drops the first one's key. The lock is a `mkdir` of `<file>.lock` holding an
|
|
1126
|
+
# `owner` file with a per-call token — the `mktemp` name of that call's temp
|
|
1127
|
+
# file, because `$$` inside a subshell is the parent's pid and bash 3.2 has no
|
|
1128
|
+
# per-subshell pid variable — and only a call whose token is still in `owner`
|
|
1129
|
+
# releases it.
|
|
1130
|
+
#
|
|
1131
|
+
# STALENESS IS THE LOCK DIRECTORY'S AGE, NEVER A PID. The stall watchdog kills
|
|
1132
|
+
# the engine subshell mid-write, so a dead holder is expected, and a pid says
|
|
1133
|
+
# nothing about a lock the killed subshell took under its parent's `$$`. A write
|
|
1134
|
+
# is one `jq` over a file of a few records, well under a second, so a lock 10 s
|
|
1135
|
+
# old is a crashed holder; a waiter polls every 0.05 s for up to 12 s — past the
|
|
1136
|
+
# stale age, so a waiter that arrives just after a crash breaks the lock rather
|
|
1137
|
+
# than giving up. Both are constants here, not environment values: the header
|
|
1138
|
+
# allows policy-carrying environment values for the lane only.
|
|
1139
|
+
#
|
|
1140
|
+
# BREAKERS ARE SERIALIZED, AND NONE RENAMES A LIVE LOCK. A waiter that judges
|
|
1141
|
+
# the lock stale takes a second `mkdir` mutex, `<file>.lock.break`, judges the
|
|
1142
|
+
# lock's age again under it, and only then renames the lock aside and empties
|
|
1143
|
+
# it, as `hr_lane_acquire` does. A lock re-taken since the first judgement is
|
|
1144
|
+
# fresh, so the second leaves it alone. Between the second judgement and the
|
|
1145
|
+
# rename, nothing but a breaker or the lock's own holder frees the path:
|
|
1146
|
+
# breakers are serialized, and the holder of a lock that old is dead by the
|
|
1147
|
+
# stale rule. The watchdog can kill a breaker inside the mutex, so a mutex 10 s
|
|
1148
|
+
# old is removed by the next waiter. That removal is the one window left open.
|
|
1149
|
+
# Two waiters that judge one abandoned mutex old together can both remove it,
|
|
1150
|
+
# and the second can remove the fresh mutex the first has just taken. Two
|
|
1151
|
+
# breakers then judge the lock at once, so reaching it takes a breaker killed
|
|
1152
|
+
# inside the mutex, a stale lock and three concurrent writers.
|
|
1153
|
+
#
|
|
1154
|
+
# A write that cannot take the lock prints one stderr line naming it, writes
|
|
1155
|
+
# nothing and returns 1.
|
|
1156
|
+
#
|
|
1157
|
+
# ONE CALL, SEVERAL KEYS. `hr_registry_set <file> <branch> <key> <value> [<key>
|
|
1158
|
+
# <value> …]` writes every pair in one `jq` pass under one lock and one `mv`.
|
|
1159
|
+
# A caller writing two keys that a concurrent reader must never see apart — a
|
|
1160
|
+
# status and the reason for it — MUST pass them in one call.
|
|
1161
|
+
#
|
|
1162
|
+
# READERS TAKE NO LOCK. The temp file sits beside the registry, so the `mv` is a
|
|
1163
|
+
# same-filesystem rename and `hr_registry_get`, `hr_registry_branches` and any
|
|
1164
|
+
# other `jq` over the file see the old record or the new one, never a partial
|
|
1165
|
+
# one. `hr_registry_init` creates the file by `ln`-ing a complete temp file to
|
|
1166
|
+
# the registry name, which fails when the name exists, so a reader's create
|
|
1167
|
+
# never truncates a registry a writer has just created.
|
|
1116
1168
|
# ---------------------------------------------------------------------------
|
|
1117
1169
|
|
|
1118
|
-
#
|
|
1170
|
+
# The directory holding <file> — where its temp files go, so every `mv` over it
|
|
1171
|
+
# is a same-filesystem rename.
|
|
1172
|
+
hr_registry_dir() {
|
|
1173
|
+
local file="${1-}" dir
|
|
1174
|
+
case "$file" in
|
|
1175
|
+
*/*)
|
|
1176
|
+
dir="${file%/*}"
|
|
1177
|
+
[ -n "$dir" ] || dir=/
|
|
1178
|
+
;;
|
|
1179
|
+
*) dir=. ;;
|
|
1180
|
+
esac
|
|
1181
|
+
printf '%s\n' "$dir"
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
# Create an empty registry at <file> when none exists. Atomic: the name is
|
|
1185
|
+
# either absent or a complete `{"runs":{}}` — never truncated, never partial.
|
|
1119
1186
|
hr_registry_init() {
|
|
1120
|
-
local file="${1-}"
|
|
1187
|
+
local file="${1-}" dir tmp
|
|
1121
1188
|
[ -n "$file" ] || return 1
|
|
1122
|
-
[ -f "$file" ]
|
|
1189
|
+
[ -f "$file" ] && return 0
|
|
1190
|
+
dir=$(hr_registry_dir "$file")
|
|
1191
|
+
tmp=$(mktemp "$dir/.registry.XXXXXX") || return 1
|
|
1192
|
+
if printf '{"runs":{}}\n' >"$tmp"; then
|
|
1193
|
+
ln "$tmp" "$file" 2>/dev/null || :
|
|
1194
|
+
fi
|
|
1195
|
+
rm -f "$tmp"
|
|
1196
|
+
[ -f "$file" ]
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
# hr_registry_lock <file> <token> — take `<file>.lock` for <token>, breaking a
|
|
1200
|
+
# stale one (see the section comment). 0 when held; 1, with one stderr line,
|
|
1201
|
+
# when the wait ceiling passed.
|
|
1202
|
+
hr_registry_lock() {
|
|
1203
|
+
local lock="${1-}.lock" token="${2-}" polls=0 start="" now m stale bm
|
|
1204
|
+
# 12 — the wait ceiling and 10 — the stale age, both in seconds. The wait is
|
|
1205
|
+
# judged by the clock, because each poll's forks cost more than its sleep;
|
|
1206
|
+
# 240 polls is the ceiling only when `date` gives no epoch.
|
|
1207
|
+
while :; do
|
|
1208
|
+
if mkdir "$lock" 2>/dev/null; then
|
|
1209
|
+
if printf '%s\n' "$token" >"$lock/owner" 2>/dev/null; then
|
|
1210
|
+
return 0
|
|
1211
|
+
fi
|
|
1212
|
+
rm -f "$lock/owner" 2>/dev/null || :
|
|
1213
|
+
rmdir "$lock" 2>/dev/null || :
|
|
1214
|
+
break
|
|
1215
|
+
fi
|
|
1216
|
+
m=$(hr_lane_mtime "$lock")
|
|
1217
|
+
now=$(date +%s 2>/dev/null) || now=0
|
|
1218
|
+
case "$now" in '' | *[!0-9]*) now=0 ;; esac
|
|
1219
|
+
[ -n "$start" ] || start="$now"
|
|
1220
|
+
if [ "$now" -gt 0 ]; then
|
|
1221
|
+
[ $((now - start)) -lt 12 ] || break
|
|
1222
|
+
else
|
|
1223
|
+
[ "$polls" -lt 240 ] || break
|
|
1224
|
+
fi
|
|
1225
|
+
if [ "$m" -gt 0 ] && [ "$now" -gt 0 ] && [ $((now - m)) -ge 10 ]; then
|
|
1226
|
+
if mkdir "$lock.break" 2>/dev/null; then
|
|
1227
|
+
# One breaker at a time: judge the age again under the mutex, so a
|
|
1228
|
+
# lock re-taken since the judgement above is fresh and left alone.
|
|
1229
|
+
m=$(hr_lane_mtime "$lock")
|
|
1230
|
+
now=$(date +%s 2>/dev/null) || now=0
|
|
1231
|
+
case "$now" in '' | *[!0-9]*) now=0 ;; esac
|
|
1232
|
+
if [ "$m" -gt 0 ] && [ "$now" -gt 0 ] && [ $((now - m)) -ge 10 ]; then
|
|
1233
|
+
stale="$lock.stale.${token##*.}"
|
|
1234
|
+
if mv "$lock" "$stale" 2>/dev/null; then
|
|
1235
|
+
rm -f "$stale/owner" 2>/dev/null || :
|
|
1236
|
+
rmdir "$stale" 2>/dev/null || :
|
|
1237
|
+
fi
|
|
1238
|
+
fi
|
|
1239
|
+
rmdir "$lock.break" 2>/dev/null || :
|
|
1240
|
+
polls=$((polls + 1))
|
|
1241
|
+
continue
|
|
1242
|
+
fi
|
|
1243
|
+
# Another breaker holds the mutex. One the watchdog killed inside it
|
|
1244
|
+
# left it behind: a mutex 10 s old is removed here.
|
|
1245
|
+
bm=$(hr_lane_mtime "$lock.break")
|
|
1246
|
+
if [ "$bm" -gt 0 ] && [ $((now - bm)) -ge 10 ]; then
|
|
1247
|
+
rmdir "$lock.break" 2>/dev/null || :
|
|
1248
|
+
fi
|
|
1249
|
+
fi
|
|
1250
|
+
sleep 0.05
|
|
1251
|
+
polls=$((polls + 1))
|
|
1252
|
+
done
|
|
1253
|
+
printf 'hr_registry_set: could not take the registry lock %s\n' "$lock" >&2
|
|
1254
|
+
return 1
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
# hr_registry_unlock <file> <token> — release `<file>.lock` only while its
|
|
1258
|
+
# `owner` still holds <token>; a lock broken and re-taken is someone else's.
|
|
1259
|
+
hr_registry_unlock() {
|
|
1260
|
+
local lock="${1-}.lock" token="${2-}" owner=""
|
|
1261
|
+
[ -r "$lock/owner" ] && { IFS= read -r owner <"$lock/owner" || :; } 2>/dev/null
|
|
1262
|
+
[ -n "$token" ] && [ "$owner" = "$token" ] || return 0
|
|
1263
|
+
rm -f "$lock/owner" 2>/dev/null || :
|
|
1264
|
+
rmdir "$lock" 2>/dev/null || :
|
|
1123
1265
|
}
|
|
1124
1266
|
|
|
1125
|
-
# hr_registry_set <file> <branch> <key> <value>
|
|
1267
|
+
# hr_registry_set <file> <branch> <key> <value> [<key> <value> …]
|
|
1268
|
+
# 1, writing nothing, on no key/value pair or an odd count of them. Each pair
|
|
1269
|
+
# reaches `jq` as `--arg kN` / `--arg vN` — jq 1.5 has no `$ARGS` — and only
|
|
1270
|
+
# those generated names enter the program text, never a value.
|
|
1126
1271
|
hr_registry_set() {
|
|
1127
|
-
local file="${1-}" branch="${2-}"
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1272
|
+
local file="${1-}" branch="${2-}" dir tmp fields="" i=0 status=0
|
|
1273
|
+
local -a args
|
|
1274
|
+
[ -n "$file" ] && [ "$#" -ge 4 ] && [ $(($# % 2)) -eq 0 ] || return 1
|
|
1275
|
+
shift 2
|
|
1276
|
+
args=(--arg b "$branch" --arg now "$(date '+%Y-%m-%dT%H:%M:%S')")
|
|
1277
|
+
while [ "$#" -gt 0 ]; do
|
|
1278
|
+
args+=(--arg "k$i" "$1" --arg "v$i" "$2")
|
|
1279
|
+
fields="$fields(\$k$i): \$v$i, "
|
|
1280
|
+
i=$((i + 1))
|
|
1281
|
+
shift 2
|
|
1282
|
+
done
|
|
1283
|
+
dir=$(hr_registry_dir "$file")
|
|
1284
|
+
tmp=$(mktemp "$dir/.registry.XXXXXX") || return 1
|
|
1285
|
+
if ! hr_registry_lock "$file" "$tmp"; then
|
|
1135
1286
|
rm -f "$tmp"
|
|
1136
1287
|
return 1
|
|
1137
1288
|
fi
|
|
1289
|
+
hr_registry_init "$file" || :
|
|
1290
|
+
if jq "${args[@]}" "
|
|
1291
|
+
.runs[\$b] = ((.runs[\$b] // {}) + {$fields\"branch\": \$b, \"updated_at\": \$now})
|
|
1292
|
+
" "$file" >"$tmp"; then
|
|
1293
|
+
mv "$tmp" "$file" || status=1
|
|
1294
|
+
else
|
|
1295
|
+
status=1
|
|
1296
|
+
fi
|
|
1297
|
+
[ "$status" -eq 0 ] || rm -f "$tmp"
|
|
1298
|
+
hr_registry_unlock "$file" "$tmp"
|
|
1299
|
+
return "$status"
|
|
1138
1300
|
}
|
|
1139
1301
|
|
|
1140
1302
|
# hr_registry_get <file> <branch> <key> -> the value, or nothing
|
|
@@ -276,7 +276,7 @@
|
|
|
276
276
|
# no job to stop and may complete before its cancel lands — trying every one even
|
|
277
277
|
# after a failure. (3) Only when (1) and (2) all succeeded, and only when a
|
|
278
278
|
# local registry record exists, it writes `remote_stopped_at` and sets `status`
|
|
279
|
-
# to `failed`
|
|
279
|
+
# to `failed` in one `hr_registry_set` call; a partial stop leaves the record alone
|
|
280
280
|
# and exits 3, so running `stop` again is the remedy.
|
|
281
281
|
#
|
|
282
282
|
# `warm` dispatches action=warm on GitHub's OWN default branch (`gh repo view
|
|
@@ -811,8 +811,7 @@ verb_stop() {
|
|
|
811
811
|
|
|
812
812
|
registry=$(hr_state_path "$root" autonomous_logs/registry.json) || registry=""
|
|
813
813
|
if [ -n "$registry" ] && [ -f "$registry" ] && [ -n "$(hr_registry_get "$registry" "$branch" branch)" ]; then
|
|
814
|
-
hr_registry_set "$registry" "$branch" remote_stopped_at "$stopped_at" \
|
|
815
|
-
&& hr_registry_set "$registry" "$branch" status failed \
|
|
814
|
+
hr_registry_set "$registry" "$branch" remote_stopped_at "$stopped_at" status failed \
|
|
816
815
|
|| echo "remote-run.sh: stopped on GitHub, but the local record of $branch could not be updated" >&2
|
|
817
816
|
fi
|
|
818
817
|
echo "remote-run.sh: stopped $branch"
|
|
@@ -904,6 +903,17 @@ set_or_fail() {
|
|
|
904
903
|
}
|
|
905
904
|
}
|
|
906
905
|
|
|
906
|
+
# set_many_or_fail <key> <value> [<key> <value> …] — every pair in one write, so
|
|
907
|
+
# a concurrent reader never sees an outcome half-applied.
|
|
908
|
+
set_many_or_fail() {
|
|
909
|
+
local keys="" i
|
|
910
|
+
hr_registry_set "$registry" "$branch" "$@" || {
|
|
911
|
+
for ((i = 1; i <= $#; i += 2)); do keys="$keys${keys:+, }${!i}"; done
|
|
912
|
+
echo "remote-run.sh: writing $keys of $branch to '$registry' failed" >&2
|
|
913
|
+
exit "$EXIT_USAGE"
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
907
917
|
verb_status() {
|
|
908
918
|
local runs finished_id synced_id field
|
|
909
919
|
list_runs
|
|
@@ -935,18 +945,14 @@ verb_status() {
|
|
|
935
945
|
sync_expired() {
|
|
936
946
|
local line
|
|
937
947
|
line=$(expired_line "$1")
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
set_or_fail remote_run_id "$1"
|
|
941
|
-
set_or_fail remote_run_url "$2"
|
|
942
|
-
set_or_fail remote_detail "$line"
|
|
943
|
-
set_or_fail remote_synced_at "$3"
|
|
948
|
+
set_many_or_fail status paused pause_reason expired remote_run_id "$1" \
|
|
949
|
+
remote_run_url "$2" remote_detail "$line" remote_synced_at "$3"
|
|
944
950
|
echo "remote-run.sh: $line"
|
|
945
951
|
}
|
|
946
952
|
|
|
947
953
|
verb_sync() {
|
|
948
954
|
local worktree runs newest id state url synced_id now older download status_file
|
|
949
|
-
local status reason detail
|
|
955
|
+
local status reason detail resume_at cycles
|
|
950
956
|
worktree=$(hr_registry_get "$registry" "$branch" worktree)
|
|
951
957
|
if [ -z "$worktree" ] || [ ! -d "$worktree" ]; then
|
|
952
958
|
echo "remote-run.sh: refused, nothing written: the mirror working copy '$worktree' of $branch is missing" >&2
|
|
@@ -965,8 +971,7 @@ verb_sync() {
|
|
|
965
971
|
now=$(date +%s)
|
|
966
972
|
|
|
967
973
|
if [ "$state" != completed ]; then
|
|
968
|
-
|
|
969
|
-
set_or_fail remote_synced_at "$now"
|
|
974
|
+
set_many_or_fail status running remote_synced_at "$now"
|
|
970
975
|
echo "remote-run.sh: run $id of $branch is $state; the record is running, nothing downloaded"
|
|
971
976
|
return 0
|
|
972
977
|
fi
|
|
@@ -1039,14 +1044,11 @@ verb_sync() {
|
|
|
1039
1044
|
detail="the job ended mid-run (its bundle still says running): $url"
|
|
1040
1045
|
fi
|
|
1041
1046
|
[ -n "$detail" ] || detail="synced from $url"
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
set_or_fail remote_run_url "$url"
|
|
1048
|
-
set_or_fail remote_detail "$detail"
|
|
1049
|
-
set_or_fail remote_synced_at "$now"
|
|
1047
|
+
resume_at=$(hr_remote_status_get "$status_file" usage_resume_at) || resume_at=""
|
|
1048
|
+
cycles=$(hr_remote_status_get "$status_file" park_loop_cycles) || cycles=""
|
|
1049
|
+
set_many_or_fail status "$status" pause_reason "$reason" usage_resume_at "$resume_at" \
|
|
1050
|
+
park_loop_cycles "$cycles" remote_run_id "$id" remote_run_url "$url" \
|
|
1051
|
+
remote_detail "$detail" remote_synced_at "$now"
|
|
1050
1052
|
echo "remote-run.sh: synced run $id of $branch: $status${reason:+ ($reason)}"
|
|
1051
1053
|
return 0
|
|
1052
1054
|
fi
|
|
@@ -1061,20 +1063,17 @@ verb_sync() {
|
|
|
1061
1063
|
done
|
|
1062
1064
|
fi
|
|
1063
1065
|
if [ "$bundle_exists" -eq 1 ]; then
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
set_or_fail remote_run_url "$url"
|
|
1068
|
-
set_or_fail remote_detail "run $id ended with no state bundle (killed, cancelled or replaced): $url"
|
|
1069
|
-
set_or_fail remote_synced_at "$now"
|
|
1066
|
+
set_many_or_fail status paused pause_reason killed remote_run_id "$id" remote_run_url "$url" \
|
|
1067
|
+
remote_detail "run $id ended with no state bundle (killed, cancelled or replaced): $url" \
|
|
1068
|
+
remote_synced_at "$now"
|
|
1070
1069
|
echo "remote-run.sh: run $id of $branch left no bundle; the record is paused (killed), nothing restored"
|
|
1071
1070
|
return 0
|
|
1072
1071
|
fi
|
|
1073
1072
|
|
|
1074
1073
|
# Case 5 — no bundle in any run.
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1074
|
+
set_many_or_fail status failed \
|
|
1075
|
+
remote_detail "no run of $branch ever uploaded a state bundle; newest: $url" \
|
|
1076
|
+
remote_synced_at "$now"
|
|
1078
1077
|
echo "remote-run.sh: no run of $branch carries a state bundle; the record is failed"
|
|
1079
1078
|
}
|
|
1080
1079
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# autonomous_logs/
|
|
2
2
|
|
|
3
|
-
One readable transcript `<branch>.log` and one raw event log `<branch>.stream.jsonl` per dispatched run, plus the daemon's own `watcher.log`, the service manager's capture of its standard output and error as `watcher.out.log` and `watcher.err.log`, and `registry.json` — the run registry that indexes every run's status. All of it resolves to `<state_dir>/autonomous_logs/` **in the main checkout**, whichever sibling working copy a run actually executes in, so one repository has one set of these files however many runs it has in flight.
|
|
3
|
+
One readable transcript `<branch>.log` and one raw event log `<branch>.stream.jsonl` per dispatched run, plus the daemon's own `watcher.log`, the service manager's capture of its standard output and error as `watcher.out.log` and `watcher.err.log`, and `registry.json` — the run registry that indexes every run's status. All of it resolves to `<state_dir>/autonomous_logs/` **in the main checkout**, whichever sibling working copy a run actually executes in, so one repository has one set of these files however many runs it has in flight. A `registry.json.lock` directory beside the registry exists only for the width of one registry write; one left over after a crash is broken automatically by the next writer, and is never removed by hand while a watcher runs.
|
|
4
4
|
|
|
5
5
|
Everything here is written by the run daemon and read by the operator watching or diagnosing a run: the transcript is formatted live and tailable while the run is going, and the raw stream beside it is the deep-debug copy and the one thing the usage gate can parse, since a run cannot read the stream it is itself producing. The registry has readers beyond the operator, and that is what makes it different in kind from its neighbours: it is the single record of which runs exist and what state each is in, and the harness's status command, its pause and resume commands, `<scripts_dir>/restart-watcher.sh` and `<scripts_dir>/cleanup-merged-worktrees.sh` all read it before they act, and `<scripts_dir>/remote-run.sh` reads and writes it for a run executing on GitHub Actions.
|
|
6
6
|
|