fix(reset): refuse id-less claude/grok targets, gate submit during a reset, validate the spec (#78)
Review round 1 of PR #82: - A running claude/grok row without a recorded id is refused by the precheck (stop would fill it with a folder-wide guess the purge then deletes); record-id never records a pre-assigned guess, and a target is skipped (id_unverified) when the row's id after the normal stop is not the one recorded before it. - reset takes the update admission lock exclusively while it publishes .mam/loop-guard-active and .mam/reset-in-progress; delegate submit refuses on a live reset marker (mam_reset_admit). Before each target's stop its open jobs are re-read (skipped as open_job, never cancelled), and a row whose agent/server no longer matches the spec is skipped. - read_spec validates every stored field (herdr_server required, never defaulted) and rejects duplicate names. - Signal tests wait 60 s for the kill-session gate (this Mac needs ~18 s). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f90a705316
commit
5c3c2eb861
6 files changed
+529
-62
No files matched your search
@@ -42,6 +42,7 @@ fi
|
||||
|
||||
MAM="$WS/.mam"
|
||||
MARKER="${MAM_LOOP_MARKER:-$MAM/loop-guard-active}"
|
||||
RESET_MARKER="$MAM/reset-in-progress"
|
||||
SPEC_FILE="$MAM/reset-spec.json"
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
@@ -82,26 +83,38 @@ fi
|
||||
source "$WS/.agents/skills/lib.sh"
|
||||
PY="$(_delegate_py_bin)"
|
||||
|
||||
mam_update_admit "$WS" || exit 1
|
||||
_refuse_locked() {
|
||||
exec 7>&-
|
||||
echo "ERROR: reset refused; nothing was changed:" >&2
|
||||
echo " - $1" >&2
|
||||
exit 1
|
||||
}
|
||||
# The update admission lock is taken exclusively, as update.sh does, while the
|
||||
# reset publishes its markers: a delegate submit admitted earlier has written
|
||||
# its job record (the precheck below sees it), and one admitted later sees
|
||||
# .mam/reset-in-progress and refuses.
|
||||
mkdir -p "$MAM"
|
||||
exec 7>>"$MAM/update-admission.lock"
|
||||
case "$("$PY" -m lib_py.pane_lock acquire --fd 7 --wait-env MAM_UPDATE_ADMISSION_WAIT_S)" in
|
||||
locked) ;;
|
||||
timeout) _refuse_locked "a create, resume, loop, delegate submit, stop or orc-onboard kept the admission lock past MAM_UPDATE_ADMISSION_WAIT_S; try again" ;;
|
||||
*) _refuse_locked "the admission lock $MAM/update-admission.lock could not be decided (lib_py.pane_lock)" ;;
|
||||
esac
|
||||
if [ -f "$MAM/update-in-progress" ]; then
|
||||
upd_st="$(_mam_lock_holder_state "$MAM/update-in-progress")"
|
||||
if [ "$upd_st" != "stale" ]; then
|
||||
_refuse_locked "an update of $WS is in progress (pid $(_mam_lock_field "$MAM/update-in-progress" pid), holder $upd_st); wait for it to finish"
|
||||
fi
|
||||
fi
|
||||
_acquire_rc=0
|
||||
mam_acquire_loop_lock "$MARKER" || _acquire_rc=$?
|
||||
case "$_acquire_rc" in
|
||||
0) ;;
|
||||
3)
|
||||
mam_update_admit_release
|
||||
_holder_pid="$(_mam_lock_field "$MARKER" "pid" 2>/dev/null || echo "unknown")"
|
||||
echo "ERROR: reset refused; nothing was changed:" >&2
|
||||
echo " - a review loop or another reset is running (pid $_holder_pid); wait for it to end" >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
mam_update_admit_release
|
||||
echo "ERROR: reset refused; nothing was changed:" >&2
|
||||
echo " - cannot determine whether a review loop or another reset is running (ps unavailable?)" >&2
|
||||
exit 1
|
||||
;;
|
||||
3) _refuse_locked "a review loop or another reset is running (pid $(_mam_lock_field "$MARKER" "pid" 2>/dev/null || echo "unknown")); wait for it to end" ;;
|
||||
*) _refuse_locked "cannot determine whether a review loop or another reset is running (ps unavailable?)" ;;
|
||||
esac
|
||||
_release_reset_lock() {
|
||||
mam_release_loop_lock "$RESET_MARKER" || true
|
||||
mam_release_loop_lock "$MARKER" || true
|
||||
}
|
||||
_on_reset_signal() {
|
||||
@@ -113,7 +126,10 @@ trap _release_reset_lock EXIT
|
||||
trap '_on_reset_signal 130' INT
|
||||
trap '_on_reset_signal 143' TERM
|
||||
trap '_on_reset_signal 129' HUP
|
||||
mam_update_admit_release
|
||||
if ! mam_acquire_loop_lock "$RESET_MARKER"; then
|
||||
_refuse_locked "cannot publish $RESET_MARKER"
|
||||
fi
|
||||
exec 7>&-
|
||||
|
||||
STATE_JSON=""
|
||||
if ! STATE_JSON="$(load_state_json strict)"; then
|
||||
@@ -189,6 +205,17 @@ for target_name in ${ENTRY_NAMES[@]+"${ENTRY_NAMES[@]}"}; do
|
||||
continue
|
||||
;;
|
||||
pending)
|
||||
if [ "$R_ROW_AGENT" != "$R_AGENT" ] || [ "$R_ROW_SERVER" != "$R_SERVER" ]; then
|
||||
echo "skipped $R_NAME: its row ($R_ROW_AGENT on herdr server $R_ROW_SERVER) no longer matches $SPEC_FILE ($R_AGENT on $R_SERVER); nothing was changed for it; check the row, then rerun the reset"
|
||||
SKIPPED_ARGS+=(--skipped "$R_NAME:changed")
|
||||
continue
|
||||
fi
|
||||
jobs_out=""
|
||||
jobs_out="$(MAM_STATE_JSON="$STATE_JSON" "$PY" -m lib_py.reset open-jobs --workspace "$WS" --session "$R_NAME")" || {
|
||||
echo "skipped $R_NAME: a job was registered for it after the precheck (${jobs_out//$'\n'/; }); nothing was changed for it; rerun the reset once the job has ended"
|
||||
SKIPPED_ARGS+=(--skipped "$R_NAME:open_job")
|
||||
continue
|
||||
}
|
||||
STOP_CMD=(bash "$SKILL_DIR/multi-agent-mux-stop/scripts/stop_session.sh" --session "$R_NAME" --agent "$R_AGENT")
|
||||
if [ -n "$R_SERVER" ]; then
|
||||
STOP_CMD+=(--herdr-session "$R_SERVER")
|
||||
@@ -223,6 +250,11 @@ for target_name in ${ENTRY_NAMES[@]+"${ENTRY_NAMES[@]}"}; do
|
||||
SKIPPED_ARGS+=(--skipped "$R_NAME:no_id")
|
||||
continue
|
||||
fi
|
||||
if [ "$R_ROW_ID" != "$R_OLD_ID" ]; then
|
||||
echo "skipped $R_NAME: the stop left id $R_ROW_ID on its row, not the one recorded before the reset (${R_OLD_ID:-none}); it may be another conversation's, so nothing is purged; check it, purge it by hand if it is this agent's, and rerun the reset"
|
||||
SKIPPED_ARGS+=(--skipped "$R_NAME:id_unverified")
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "reset [$R_NAME]: ${STOP_CMD[*]} --purge-conversation --yes"
|
||||
purge_rc=0
|
||||
|
||||
Reference in new issue
Block a user