Merge-workspace review loop (--all-reviewer): round 1 (@d198dd5) claude agy PASS, grok codex NOT PASS (the same inherited WORKSPACE_ROOT in eleven more modules; test_orc_onboard's import-time env); fixed in8b47cc7. Round 2 claude grok NOT PASS (test_o2_race_free_lock, test_o1_rebuttal missed), agy codex PASS; fixed inac48cb7. Round 3 (@ac48cb7) 4/4 PASS. Full suite atac48cb7on macOS / Python 3.9 / herdr 0.9.3 with WORKSPACE_ROOT set to an empty sentinel: 1751 passed / 0 failed / 38 skipped; the sentinel stayed empty and the main checkout's shim was not touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
71 lines
8.4 KiB
Markdown
71 lines
8.4 KiB
Markdown
# Review 42f6a97d — Issue #17 follow-up, inherited WORKSPACE_ROOT
|
||
|
||
Head `ac48cb78617af88584db529b5766acf02f4af73c` (tree `b043034eda891bd83b547dcb34d7422a6977b89d`) closes the two checkout `run_loop.sh` writers left at `8b47cc7`. The opt-in root is in place, the new nodes are in the isolation test, and both required pytest passes are green.
|
||
|
||
Snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/3` only. Base `6b00b6e06f55299cb60f156ccef09ede4bae5bdd` (control-plane main, still that commit, porcelain empty). Branch commits: `d198dd5`, `8b47cc7`, `ac48cb7`. Seventeen files, +221/−18, all under `tests/`. `git diff --check 6b00b6e..HEAD` is clean. `.agents/skills` and `VERSIONS.md` are outside the diff. No version bump.
|
||
|
||
Interpreter: worktree `.venv` Python 3.9.6, bash 3.2.57. `lib.sh:2050` uses `AGENT_PYTHON_BIN` when that path is executable. The suite set `AGENT_PYTHON_BIN` to the worktree interpreter and put its `bin` first on `PATH`.
|
||
|
||
## What ac48cb7 adds
|
||
|
||
`tests/test_o1_rebuttal.py:12` opts the module in with `pytestmark = pytest.mark.usefixtures("own_workspace_root")`. `test_v3_rebut_option_parsing_and_budget` (`:69-76`) still runs the checkout `run_loop.sh` twice (`--help`, and `--max-rebut abc`) with no `env` argument. The mark sets `WORKSPACE_ROOT` to `tmp_path/.mam-test-state/workspace` before the body, and a subprocess with no `env` inherits that value. `run_loop.sh:17` sources `lib.sh` before the option parser, so the shim is written under the test directory.
|
||
|
||
`tests/test_o2_race_free_lock.py:198` puts the same fixture on `test_o2_12_run_loop_exits_on_lock_failure` only. That test copies `os.environ` after the fixture's `setenv` (`:204-206`) and runs the checkout `run_loop.sh`. The other tests in the file source `loop_lock.sh`, which pulls in `lib_int.sh` and does not source `lib.sh`. `test_o2_14` (`:230`) is `bash -n`.
|
||
|
||
`tests/test_issue17_workspace_root_isolation.py:41-42` adds both node ids to W1:
|
||
|
||
- `tests/test_o2_race_free_lock.py::test_o2_12_run_loop_exits_on_lock_failure`
|
||
- `tests/test_o1_rebuttal.py::test_v3_rebut_option_parsing_and_budget`
|
||
|
||
The comment on the o2 mark says a module-wide mark would add an entry to `test_o2_7`'s `tmp_path`. `test_o2_7` (`:130`) asserts `list(tmp_path.glob("*")) == []`, and `Path.glob("*")` skips dot names, so `.mam-test-state` would not show up. The function-level mark is still the right scope: only `test_o2_12` executes `run_loop.sh`.
|
||
|
||
`d198dd5` and `8b47cc7` are unchanged by this commit. `tests/conftest.py:181-192` is the opt-in fixture (a temp workspace, not autouse). `tests/test_issue17_item4_decisions.py:224-233` drops an inherited `WORKSPACE_ROOT` and sets the helper's `tmp_path`. Unset would make `lib.sh:26` derive the checkout that contains the sourced script; `lib.sh:3088` calls `_init_herdr_isolation` (`lib.sh:143`), which rewrites `$WORKSPACE_ROOT/.mam/shim/herdr`. `pytest_configure` (`tests/conftest.py:144-146`) drops `WORKSPACE_ROOT` only when its real path is a different directory, so a value that names this checkout survives collection. A test that also requests `mam_sandbox` keeps the sandbox root, because that fixture runs later. The orc-onboard tests request `mam_sandbox` and passed in the suite below.
|
||
|
||
The module mark is on `test_o1_rebuttal.py`, `test_b19_headless_reconcile_fixes.py`, `test_b8_send_keys_verification.py`, `test_issue6_fail_closed.py`, `test_version_consistency.py`, `test_loop_cli.py`, `test_issue5_phase3_loop.py`, `test_issue5_phase4_runtime.py`, `test_o3_scoped_guard.py`, `test_issue25_plan_reuse.py`, and `test_orc_onboard.py`.
|
||
|
||
## Requirement 3 — the unfixed command writes the sentinel
|
||
|
||
Product `run_loop.sh` is unchanged, so the command `test_v3` used to run is the pre-fix case. With `WORKSPACE_ROOT=/tmp/42f6pre/sentinel` (the directory existed) and the inherited `HERDR_*` / `MAM_*` / `MQTT_*` / `SKS_*` bindings removed:
|
||
|
||
`bash <snapshot>/run_loop.sh --help` exited 1 (usage) and left `.mam`, `.mam/shim`, and `.mam/shim/herdr` under that sentinel. The snapshot's `.mam/shim/herdr` stayed absent. The snapshot copy of `run_loop.sh` is mode `644`; invoking it as the program raises `PermissionError`. The probe and the tests call it through `bash`.
|
||
|
||
W1 and W2, and `test_c_helpers_write_nothing_to_an_inherited_workspace`, passed inside both suites below. W1 is the check that still names the checkout: it sets `WORKSPACE_ROOT` to a copy of this tree, conftest keeps that value, and the copy must grow no `.mam`.
|
||
|
||
## Requirement 4 — suite
|
||
|
||
`AGENT_PYTHON_BIN` and `PATH` pointed at the worktree `.venv`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, and `WORKSPACE_ROOT` were cleared in the parent. `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`. Basetemp directories are `/tmp/42f6A.n4k8Qw` and `/tmp/42f6B.p2m7Lx`. Neither path contains `mam-review`.
|
||
|
||
(a) `WORKSPACE_ROOT` unset. The 16 modified test modules plus `tests/test_issue33_budget.py`, `tests/test_issue5_phase1_delegation.py`, and `tests/test_tier1_unit.py`: **675 passed** in 836.91s, exit 0.
|
||
|
||
(b) `WORKSPACE_ROOT=/tmp/42f6sent.t8c3Vr` (the directory existed). The 16 modified modules: **522 passed** in 672.98s, exit 0. The sentinel was empty afterwards.
|
||
|
||
`pytest_configure` drops a `WORKSPACE_ROOT` whose real path is some other directory, so the outer sentinel staying empty shows that nothing wrote there before collection. The runs that keep a checkout-named root are W1 and the item-4 test, which monkeypatches a sentinel after configure. Both passed inside (a) and (b). The +32 tests against the previous head's 643 / 490 are the two modules this commit brought into the run.
|
||
|
||
After the pair: snapshot porcelain empty, snapshot `.mam/shim/herdr` absent, control-plane main still `6b00b6e` with empty porcelain, control-plane shim mtime `1791482611` size `45360` (same before and after), `~/.config/herdr/config.toml` mtime `1784456394`, directory mtime `1791382452`.
|
||
|
||
## Survey — same pattern, already pinned
|
||
|
||
These execute `lib.sh`, `run_loop.sh`, or a skill script that sources `lib.sh`, and they already give that shell a temp root, or they do not execute the source. They stay as they are. No remaining writer runs the checkout `lib.sh` / `run_loop.sh` / wrapper on an inherited checkout root.
|
||
|
||
| Location | Why the checkout shim is not the target |
|
||
| --- | --- |
|
||
| `test_issue17_item4_decisions.py:224` | `_env` sets `WORKSPACE_ROOT` to the helper tmp |
|
||
| `test_issue21_worktree_prereqs.py:403` | `test_t2_8` sets `WORKSPACE_ROOT` to `mam_sandbox`; `_mutate` sets the temp tree |
|
||
| `test_workspace_scope.py:32` | subprocess env sets the test workspace |
|
||
| `test_issue18_liveness_contract.py:58` | `_env` sets the helper cwd |
|
||
| `test_issue21_phase2_loop_a.py:58` | sources a copy with `WORKSPACE_ROOT` set to that root |
|
||
| `test_issue21_loop_b.py:236` | sets `WORKSPACE_ROOT` to `tmp_path` before `run_loop.sh` |
|
||
| `test_layout.py:318` | the script exports the test workspace, then sources `lib.sh` |
|
||
| `test_issue20_state_isolation.py:201` | sets `WORKSPACE_ROOT` to the temp repo the test is about |
|
||
| `test_issue19_subscriber_daemon_lifecycle.py:57` | sources the sandbox copy; callers use `mam_sandbox` |
|
||
| `test_issue34_job_tree.py:589` | sources the sandbox copy after `mam_sandbox` has set the root |
|
||
| `test_tier3_integration.py:286` | sources the sandbox copy inside a `mam_sandbox` test; `:337` is a debug string that also sets the temp root |
|
||
| `test_b7_diff_untracked.py:166` | sources `diff_collect.sh`, which sources `lib_int.sh` (`diff_collect.sh:14`) |
|
||
| `test_o2_race_free_lock.py:10` and `:231` | `loop_lock.sh` sources `lib_int.sh`; `bash -n` does not execute `run_loop.sh` |
|
||
| `test_issue5_phase2a_loop.py:754` | the `source` line is text inside an assertion |
|
||
| `test_deploy_freshness.py:473` | reads `run_loop.sh` as text |
|
||
|
||
`test_issue6_fail_closed.py:1247` sources the checkout `lib.sh` with the process environment. The module mark supplies the temp root, and that node is in W1. `test_issue21_acceptance.py` runs a `run_loop.sh` inside a temp git repo. `test_issue33_budget.py`, `test_issue36_prompt_transport.py`, `test_issue6_turn_handshake.py`, `test_issue14_delivery_ack.py`, `test_issue26_envfile.py`, `test_issue26_ints.py`, and `test_c1_tui_readiness.py` set a temp root in the helper env. `test_issue10_13_purge.py` and `test_issue9_server_env.py` / `test_issue45_restore.py` run through `mam_sandbox` or the rig, which has already set the root. `test_issue30_stall_watch.py` reaches the wrapper through `mam_sandbox` or `test_issue18._submit`.
|
||
|
||
[VERDICT: PASS]
|