Merge-workspace review: round 1 (@518beda) claude agy grok PASS, codex NOT PASS (finalize wait measured on the wall clock); fixed in4c86107. Round 2 delta (@4c86107) claude agy grok PASS; codex (24b21b9c, after 817d1650 closed deadline_timeout with no report) found R1 closed and no new defect, but withheld PASS only because its sandbox could not bind the suite's loopback broker. The same two-file suite run here at4c86107: 278 passed. Full suite at518bedaon macOS / Python 3.9: 1711 passed / 1 failed / 38 skipped; the failure (test_issue45_restore, herdr 0.9.3 cwd restore) also fails on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
43 lines
3.9 KiB
Markdown
43 lines
3.9 KiB
Markdown
# Review: PR #51 round 2 @ 4c86107
|
||
|
||
- Role: Reviewer (grok)
|
||
- Identity: loop `loop-20261007T155310Z-56d5a7`, base `518bedac5d8ab832347bfc6682b146f3201021f1`, head `4c86107846d1e037f9a69a1201257725e8673179`, tree `e6f67f6136b8f0cec15ce98400ae7d7710cedf1e`, one commit, clean snapshot
|
||
- Scope: `git diff 518beda HEAD`. Two files, +43/−3, `git diff --check` clean. Main stayed `7209750`.
|
||
- Verified read-only at `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261007T155310Z-56d5a7/1`. Python 3.9.6 from the pr-51 venv. Inherited `HERDR_*`, `MAM_*`, and `MQTT_*` were unset. `HOME` and `WORKSPACE_ROOT` were the basetemp. `.mam.env` was not exported.
|
||
|
||
## Codex R1
|
||
|
||
| Codex | 4c86107 |
|
||
|---|---|
|
||
| R1. `_sleep` measured the remaining time with `time.time()`. A wall clock stepped back during the broker re-observe (up to 3 s) or the disk-heartbeat grace (1 s) stretched that one wait by the same amount. A 10 s step back turned 1 s into 11 s and 3 s into 13 s. Bash `sleep 1` / `sleep "$left"` does not follow the wall clock. | `_sleep` (`wrapper.py:145-156`) sets `end = time.monotonic() + seconds` and computes `left` from `time.monotonic()`. The 0.5 s owner poll is `min(left, 0.5)` on that same remainder. `test_u_a_wait_is_not_stretched_by_a_wall_clock_step_back` steps the wall clock back 10 s on the first sleep for both 1 s and 3 s. |
|
||
|
||
## Wall clock stays the deadline
|
||
|
||
The monotonic clock is used only inside `_sleep` (`:150`, `:153`). Everything that decides whether the job is finished still reads the wall clock:
|
||
|
||
- `_now` is `int(time.time())` (`:45-46`), the same value bash's `$(date +%s)` returns.
|
||
- `finalize_begin` builds `deadline` and `evidence_until` from `_now` (`:182-184`).
|
||
- The broker loop takes `left = deadline - _now()` and only then calls `_sleep(a, min(left, 3))` (`:208-211`). After the sleep it labels the clock wall when `_now() >= deadline` (`:218`).
|
||
- The grace loop's condition, the heartbeat reopen, and the idle-window check all use `_now` (`:226-240`). The sleep inside that loop is `_sleep(a, 1)` (`:239`).
|
||
- `recent_disk_heartbeat` still ages the events.log timestamp with `time.time()` (`:115`).
|
||
|
||
A wall step back during one sleep no longer lengthens that sleep. The next loop pass still sees the stepped wall clock and can sleep again until `START_TIME + TIMEOUT`, which is what the old bash loop did after each fixed `sleep`. A wall step forward is noticed on the next `_now()` check, after the current monotonic slice returns. The owner poll does not become a deadline: it only splits the slice `_sleep` was given.
|
||
|
||
`subprocess.run(..., timeout=10)` on `ps` (`:133-134`) is unchanged and is not the job deadline.
|
||
|
||
## The test
|
||
|
||
`_SteppedClock` (`tests/test_issue17_item4_decisions.py:598-616`) is the module's `time`. `sleep(dt)` adds `dt` to both clocks and to `slept`, and subtracts 10 s from the wall clock once. There is no real `time.sleep` and no thread. `monkeypatch.setattr(decide, "time", clock)` (`:628`) replaces the `import time` the wrapper calls (`wrapper.py:35`).
|
||
|
||
For 1 s the fake clock records two 0.5 s sleeps (`slept == 1`). For 3 s it records six (`slept == 3`). `0.5` is exact in binary floating point, so the result does not depend on the scheduler. The bound `seconds <= slept <= seconds + 0.5` (`:630`) rejects the old 11 s / 13 s waits. `watch_pid=None` skips the process check. The same two durations are the ones `finalize_end` passes: `min(left, 3)` and `1`.
|
||
|
||
## Scope
|
||
|
||
The commit changes `_sleep` and adds this test. No label, registry write, event, or stall-watch decision moves. The notes left for a later change (the wrapper importing a package that loads paho, `exit ""` → 255, `lib_int.sh` using `python3` on `PATH`, and the 0.2 s text versus the 0.5 s poll) are not in this diff.
|
||
|
||
## Tests
|
||
|
||
`--basetemp=/tmp/b78afollow.yeVKnK`. `tests/test_issue17_item4_decisions.py` and `tests/test_issue33_budget.py`: **278 passed** in 318.09 s, exit 0. Snapshot `git status` was empty after the run.
|
||
|
||
[VERDICT: PASS]
|