feat: update admission gate - entries refuse while an update runs (Issue #72, decision 8, U2)
The update skill publishes .mam/update-in-progress (pid + lstart, the loop_lock.sh rules) while it holds .mam/update-admission.lock exclusively. create, resume, run_loop.sh and delegate submit (decision 8) and stop and orc-onboard (U2: a rollback would restore the registry without their write) take that lock shared (lib.sh mam_update_admit, fd 7), refuse while the marker is live or unknown, and hold the lock until their record is written. A stale marker refuses nothing. lib_py.pane_lock acquire gains --shared and --wait-env (MAM_UPDATE_ADMISSION_WAIT_S, default 120); create's wrapper path closes fd 7 for the long-lived child. The exit-1 rows of create/resume/stop name the refusal; skill_code_blocks.json pins follow the shifted lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
179473e5d5
commit
6359d65999
13 files changed
+311
-25
No files matched your search
@@ -3247,3 +3247,55 @@ mam_control_plane_prefix() {
|
||||
[ -n "${1:-}" ] || return 0
|
||||
PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}" "$(_delegate_py_bin)" -m lib_py.control_plane prefix "$1"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Issue #72 (decision 8): the update admission gate.
|
||||
#
|
||||
# The update skill publishes .mam/update-in-progress (pid + lstart, loop_lock.sh
|
||||
# rules) while it holds .mam/update-admission.lock exclusively, then runs its
|
||||
# pre-check. Every entry that starts something the pre-check must see - create,
|
||||
# resume, run_loop.sh, delegate submit - or writes the registry - stop,
|
||||
# orc-onboard - takes that lock shared on fd 7, refuses while the marker is
|
||||
# `live` or `unknown`, and keeps fd 7 until its record is written
|
||||
# (mam_update_admit_release). An update therefore either waits for that record
|
||||
# and sees it, or publishes first and the entry refuses. The lock is a BSD
|
||||
# flock taken by lib_py.pane_lock on the open fd: it ends when every process
|
||||
# holding fd 7 has ended, however that ends, so nothing long-lived may inherit
|
||||
# fd 7 (`7>&-` on such a child, as pane_lock's fd 9). The wait is bounded by
|
||||
# MAM_UPDATE_ADMISSION_WAIT_S (default 120).
|
||||
#
|
||||
# mam_update_admit [workspace]: rc 0 admitted (fd 7 held), 1 refused (stderr).
|
||||
mam_update_admit() {
|
||||
local root="${1:-$WORKSPACE_ROOT}"
|
||||
local lock="$root/.mam/update-admission.lock" marker="$root/.mam/update-in-progress" st
|
||||
if ! { mkdir -p "$root/.mam" && exec 7>>"$lock"; }; then
|
||||
echo "ERROR: cannot open the update admission lock $lock" >&2
|
||||
return 1
|
||||
fi
|
||||
case "$(PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}" python3 -m lib_py.pane_lock acquire --fd 7 --shared --wait-env MAM_UPDATE_ADMISSION_WAIT_S)" in
|
||||
locked) ;;
|
||||
timeout)
|
||||
exec 7>&-
|
||||
echo "ERROR: an update of $root holds its admission lock (MAM_UPDATE_ADMISSION_WAIT_S passed); try again when it has finished" >&2
|
||||
return 1 ;;
|
||||
*)
|
||||
exec 7>&-
|
||||
echo "ERROR: the update admission lock $lock could not be decided (lib_py.pane_lock)" >&2
|
||||
return 1 ;;
|
||||
esac
|
||||
[ -f "$marker" ] || return 0
|
||||
declare -F _mam_lock_holder_state >/dev/null || source "$SKILL_DIR/multi-agent-mux-loop/scripts/loop_lock.sh"
|
||||
st="$(_mam_lock_holder_state "$marker")"
|
||||
case "$st" in
|
||||
stale)
|
||||
echo "NOTE: stale update marker $marker (its update is gone); ignored" >&2
|
||||
return 0 ;;
|
||||
esac
|
||||
exec 7>&-
|
||||
echo "ERROR: an update of $root is in progress (marker $marker, holder $st); try again when it has finished" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
mam_update_admit_release() {
|
||||
exec 7>&-
|
||||
}
|
||||
Reference in new issue
Block a user