refactor(issue-17): item 4 - the delegate-job wrapper's decisions move to Python

The wrapper keeps spawn / wait / kill, key injection and the herdr
diagnostic probe; what it decided in bash is now lib_py/delegation/wrapper.py,
reached through one `_decide` helper:

- finalize_unfinished: the clock choice, the disk grace and broker
  re-observation, the close request / label, the close and the event
  (finalize-begin / finalize-end; bash runs only the busy probe between them)
- start_stall_watch: whether to watch (MAM_STALL_WATCH, remote, human)
- _map_sub_rc, _rc_for (+ MAM_DELEGATE_FAIL_CLOSED), the subscriber
  early-exit check and cmd_wait's rc normalisation
- mam_int / mam_int_knob run lib_py/ints.py, which is also the runner's
  _positive_int: one implementation of the Issue #26 rule

No behaviour change: tests/test_issue17_item4_decisions.py runs the old
bash functions (copied verbatim from 7209750) and the new wrapper on the
same inputs and compares output, FINAL_JOB_STATUS and the registry /
publish calls. Three static tests that pinned the old bash literals now
pin the Python entry points instead.

Refs #17

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-06 04:59:58 +00:00
1 parent 7209750ac7
commit 63d9c205ff
13 files changed
+1131 -289

No files matched your search

+9 -23
View File
@@ -2,28 +2,18 @@
# lib_int.sh - the integer rule for knobs (Issue #26), on its own so scripts
# that run without lib.sh (diff_collect.sh and loop_lock.sh, which the Python
# runner and the tests source alone) read their knobs the same way. lib.sh
# sources it. Functions only: sourcing it has no other effect.
# sources it. Sourcing it defines the functions and the path they run.
#
# Issue #17 item 4: the rule itself is lib_py/ints.py, which the loop runner
# imports too (runner.py::_positive_int); these functions only call it.
_MAM_INTS_PY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib_py/ints.py"
# mam_int <value> <min>
#
# Issue #26: the one integer rule for knobs that reach bash arithmetic (the
# Python twin is lib_py/loop/runner.py::_positive_int). Surrounding blanks are
# allowed, then digits only, read as DECIMAL (bash would read 0700 as octal
# 448 and fail on 0900), and min <= n <= 999999999. Prints n and returns 0, or
# returns 1. The per-knob minimum lives in tests/test_issue26_ints.py.
# Blanks around, digits only, read as DECIMAL, min <= n <= 999999999 (see
# lib_py/ints.py). Prints n and returns 0, or returns 1.
mam_int() {
# Spelled-out ASCII sets, not [[:space:]]/[0-9]: those follow the locale, and
# the runner's _positive_int takes ASCII only (review R2: U+2003 30 U+2003
# was 30 here under en_US.UTF-8 and invalid there).
local _ws=$' \t\n\r\v\f' _re
_re="^[${_ws}]*([0123456789]+)[${_ws}]*\$"
[[ "${1-}" =~ $_re ]] || return 1
local d="${BASH_REMATCH[1]}"
d="${d#"${d%%[!0]*}"}"
[ ${#d} -le 9 ] || return 1
local n=$(( 10#${d:-0} ))
[ "$n" -ge "$2" ] || return 1
printf '%s\n' "$n"
python3 -I "$_MAM_INTS_PY" int "${1-}" "$2"
}
# mam_int_knob <ENV_NAME> <default> <min>
@@ -31,9 +21,5 @@ mam_int() {
# The value of $ENV_NAME through mam_int; unset or empty gives <default>, an
# invalid value gives <default> with a warning on stderr.
mam_int_knob() {
local raw="${!1-}"
[ -n "$raw" ] || { printf '%s\n' "$2"; return 0; }
mam_int "$raw" "$3" && return 0
echo "WARNING: $1=$raw ignored (not a decimal integer >= $3); using $2" >&2
printf '%s\n' "$2"
python3 -I "$_MAM_INTS_PY" knob "$1" "${!1-}" "$2" "$3"
}