release(v4.8.8): Issue #17 follow-up - archive the review reports and bump the lockstep version

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 in 8b47cc7.
Round 2 claude grok NOT PASS (test_o2_race_free_lock, test_o1_rebuttal
missed), agy codex PASS; fixed in ac48cb7. Round 3 (@ac48cb7) 4/4 PASS.
Full suite at ac48cb7 on 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
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-09 05:01:05 +09:00
1 parent ac48cb7861
commit e0181ea9be
32 files changed
+1192 -26

No files matched your search

@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261008T133858Z-9e7f5b",
"base_sha": "6b00b6e06f55299cb60f156ccef09ede4bae5bdd",
"head_sha": "ac48cb78617af88584db529b5766acf02f4af73c",
"tree_sha": "b043034eda891bd83b547dcb34d7422a6977b89d",
"review_commit": "ac48cb78617af88584db529b5766acf02f4af73c",
"reviewer": "planner-reviewer-codex-01",
"job_id": "05537aa1",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-08T18:40:20Z"
}
@@ -0,0 +1,121 @@
# Review — 05537aa1
## 판정
PASS. 이전 라운드에서 빠진 O1/O2의 run_loop 실행 경로에 격리가 추가되었으며, 새로운 차단 결함은 확인하지 못했다. 브리프가 허용한 정적 검토와 제한 환경 실행으로 판정했다. 전체 통합 스위트 통과를 주장하지 않는다.
## Identity 및 변경 범위
- loop: `loop-20261008T133858Z-9e7f5b`
- base: `6b00b6e06f55299cb60f156ccef09ede4bae5bdd`
- head: `ac48cb78617af88584db529b5766acf02f4af73c`
- tree: `b043034eda891bd83b547dcb34d7422a6977b89d`
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/3`
- Python: `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/issue17-wsroot/.venv/bin/python` (3.9).
위 identity를 확인했다. 검증 전후 git status가 비어 있고 `git diff --check 6b00b6e HEAD` 및 변경된 Python 17개 파일 AST 파싱이 통과했다. 제품 코드 변경은 없다. creator 작업 트리에서는 실행/수정하지 않았고 검증 소스도 수정하지 않았다.
이전 검토 head `8b47cc76eed4582b1848b2eec529523a61497320` 이후 diff는 다음 세 파일뿐이다:
1. `tests/test_o1_rebuttal.py:10` — 모듈에 own_workspace_root fixture opt-in 추가.
2. `tests/test_o2_race_free_lock.py:196` — run_loop를 실행하는 O2-12에만 fixture opt-in 추가.
3. `tests/test_issue17_workspace_root_isolation.py:41` — W1의 nested pytest 목록에 O1-V3와 O2-12 추가.
O2 모듈 전체에 fixture를 적용하지 않은 것은 적절하다. `test_o2_7_no_leftover_temp_files`는 tmp_path가 비어 있어야 하므로 새 workspace 디렉터리가 만들어지면 불필요한 실패가 된다. O2-7은 이번 두 실행에서 모두 통과했다. 나머지 O2 잠금 함수 테스트는 lib.sh가 아닌 loop_lock.sh를 source한다.
## 이전 보고서 정정 및 누적 검토
이전 c20f3d1f 보고서의 O2 제외 근거는 일부 상태 helper에만 해당했으며, `test_o2_12_run_loop_exits_on_lock_failure`의 직접 run_loop 실행까지 포괄하지 못했다. 그 제외 설명과 '추가 동일 결함 없음'이라는 결론은 범위가 너무 넓었다. 이번에는 O1-V3와 O2-12의 이전 코드가 실제 sentinel에 shim을 생성하는 것을 확인했고, 현재 추가된 fixture로 격리되는 것을 확인했다.
기존 누적 수정은 이전 head와 동일하다:
- `test_issue17_item4_decisions.py:224`: `_env`에서 상속 WORKSPACE_ROOT를 제외하고 tmp_path로 고정. unset만 하면 lib.sh가 checkout을 기본값으로 쓰므로 명시적 임시 root가 맞다.
- `conftest.py:180`: opt-in own_workspace_root. 기존 mam_sandbox와 테스트 본문의 명시적인 root 설정이 나중에 적용되는 구조를 유지한다.
- `test_orc_onboard.py:19,27`: import 시점 캐시에서 WORKSPACE_ROOT를 제거하고 호출 시점의 root를 적용한다.
- `test_workspace_scope.py:31`, `test_issue21_worktree_prereqs.py:544`: fixture opt-in 대신 해당 helper의 임시 root를 직접 지정한다.
- b19/b8/loop CLI/phase3/phase4/O3/version/issue25/fail_closed 모듈의 셸 실행은 own_workspace_root를 사용한다.
이전 head에 대한 상세 fixture 우선순위 검증과 135 passed × 2, 추가 84 passed, 별도 3 passed 결과는 다음 이전 보고서에 있다. 이를 현재 head에서 새로 실행한 결과로 세지 않았다:
`/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.mam/jobs/c20f3d1f/codex-reports/report-final.md`
## 이번 실행
환경에서 HERDR_*, MAM_*, MQTT_*, SKS_*, WORKSPACE_ROOT 및 control-plane 경로 변수를 제거하고 HOME을 임시 경로로 지정했다. sentinel은 미리 생성한 빈 디렉터리다. 모든 산출물과 basetemp는 검증 트리 밖에 두고 bytecode/cache 쓰기를 껐다. 실제 .mam.env를 export하거나 출력하지 않았다.
샌드박스는 MQTT 연결/로컬 broker socket bind 및 ps 실행을 제한한다. 따라서 `/tmp/05537aa1-run.py`는 현재 검증 트리의 실제 conftest를 import하여 local_mqtt_broker fixture만 메모리에서 제외하고, `--noconftest`, `-p no:cacheprovider`, 임시 basetemp와 `plugins=[module]`로 실행했다. 나머지 fixture/hook/상태 guard는 그대로 등록했다. 소스 파일 변경이나 권한 우회는 하지 않았다.
실행 대상:
- `tests/test_o1_rebuttal.py` 전체.
- `tests/test_o2_race_free_lock.py` 전체.
- `test_issue17_workspace_root_isolation.py::test_w2_onboard_does_not_carry_its_import_time_root`.
- `test_issue17_item4_decisions.py::test_c_helpers_write_nothing_to_an_inherited_workspace`.
| 조건 | 결과 | sentinel 상태 |
|---|---|---|
| WORKSPACE_ROOT unset | 24 passed, 10 failed, 6.54s | 0 entries |
| WORKSPACE_ROOT=sentinel | 24 passed, 10 failed, 6.56s | 0 entries |
O1의 10개 테스트와 두 기존 격리 회귀 테스트는 모두 통과했다. 실패한 O2 테스트는 두 조건에서 동일하게 O2-2/3/4/11/12/16/17/18/19/20이다. `loop_lock.sh:39` 부근에서 자신의 ps lstart를 얻지 못하면 holder state가 unknown이 되고 `:108` 부근에서 rc 4를 반환한다. 따라서 live/stale 판정을 기대하는 테스트가 실패한다. O2-12 로그도 명확히 `Cannot determine whether another loop is running (ps unavailable?)`를 출력한다. 이는 이번 WORKSPACE_ROOT 수정으로 잠금 동작이 바뀐 것이 아니다.
기능 assert가 ps 제한으로 실패한 O2-12도 lib.sh bootstrap은 실행된다. 실행 후 O1-V3와 O2-12 각각의 `.mam-test-state/workspace/.mam/shim/herdr`가 존재하고 외부 sentinel이 비어 있음을 두 조건에서 직접 확인했다. 검증 checkout의 `.mam` 아래에도 항목이 없다.
로그/드라이버:
- `/tmp/05537aa1-run.py`
- `/tmp/05537aa1-unset.log`
- `/tmp/05537aa1-sentinel.log`
- unset root: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/05537aa1-unset-ecvw8t06`
- sentinel root는 `/tmp/05537aa1-sentinel.log` 첫 ROOT 행에 기록되어 있다.
## 수정 전 실패 확인
`/tmp/05537aa1-negative.py`가 `git show`로 읽은 이전 코드를 메모리에 로드했다. __file__은 현재 검증 트리 경로로 지정하고, 실행되는 제품 코드는 현재 검증 트리의 변경 없는 코드다. 검증 트리를 checkout/reset하거나 수정하지 않았다.
| 시나리오 | 수정 전 실제 결과 | 현재 실제 결과 |
|---|---|---|
| base `_env`를 현재 decisions 회귀 함수에 적용 | `AssertionError: written into the sentinel` | 동일 회귀 함수 통과 |
| onboard를 sentinel root에서 import한 후 fixture root로 변경하고 run_find_uuid 호출 | base: sentinel 3 entries, own shim 없음 | head: sentinel 0 entries, own shim 있음 |
| 이전 head의 O1-V3 함수 직접 실행 | 함수 자체는 성공하지만 sentinel에 `.mam/shim/herdr` 생성 | pytest fixture 적용 시 own root에만 shim 생성 |
| 이전 head의 O2-12 함수 직접 실행 | ps assert 실패 전 sentinel에 `.mam/shim/herdr` 생성 | 같은 ps 제약하에서도 own root에만 shim 생성 |
기대 결과는 모두 외부 sentinel 불변이다. 새 opt-in이 없던 O1/O2는 이 기대를 위반하고, 현재 pytest 실행에서는 만족한다. O1/O2 negative control은 함수를 직접 호출한 탐침이며 W1 전체 nested pytest의 성공/실패 결과라고 표현하지 않는다.
negative log: `/tmp/05537aa1-negative.log`
negative root: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/05537aa1-negative-_c2j_av4`
W1은 actual conftest/broker를 사용하는 nested pytest이므로 이 샌드박스에서 완주하지 않았다. 새 두 node가 실제 수집 가능한 함수명과 일치하며, subprocess 성공 및 통과 수 검사 뒤 copied checkout의 `.mam` 비어 있음을 검사하는 구조를 정적으로 확인했다.
## 같은 결함 후보 재조사
환경 복사만 검색하지 않고 `run_loop.sh`, `lib.sh`, WRAPPER, source, subprocess 실행도 교차 검색했다. 아래는 이번에 재확인한 미수정 후보의 제외 이유다. 이전 보고서의 O2 포괄 제외는 이 표로 대체한다.
| 파일:라인 | 미수정 이유 |
|---|---|
| tests/test_o2_race_free_lock.py:14,23 | 일반 lock helper는 loop_lock.sh만 실행. 직접 run_loop를 실행하는 :199는 이번에 수정됨. |
| tests/test_herdr_shim_contract.py:10,139 | lib.sh를 실행하는 계약 테스트/공유 helper 호출은 mam_sandbox/mock fixture를 사용한다. |
| tests/test_sanitize_and_mock_errors.py:28 | bash parity test가 mam_sandbox를 사용한다. |
| tests/test_issue7_workspace_packing.py:49 | 셸 helper 호출이 mam_sandbox 기반이다. |
| tests/test_issue21_acceptance.py:280 | run_loop 실행 환경에 임시 main root를 명시한다. |
| tests/test_issue26_envfile.py:73 | `_env(home, ...)`가 HOME과 WORKSPACE_ROOT를 home으로 설정한다. |
| tests/test_issue30_stall_watch.py:124,231 | box/mam_sandbox와 격리된 환경 helper를 사용한다. |
| tests/test_issue30_stall_watch_stop.py:29 | `_env(home)`이 WORKSPACE_ROOT=home을 지정한다. |
| tests/test_issue45_restore.py:93,172 | run_lib의 rig는 mam_sandbox에 의존한다. 실제 서버 테스트는 이번 리뷰에서 실행하지 않았다. |
| tests/test_issue24_resume_dialogs.py:70,82 | create/stop/resume 실행이 mam_sandbox/mock fixture를 사용한다. |
| tests/test_issue15_registry_isolation.py:75 | 별도 subprocess 환경에 임시 WORKSPACE_ROOT를 명시한다. |
| tests/test_issue5_phase0_contracts.py:481 | run_loop 언급은 정적 문서 계약 검사다. |
| tests/test_issue14_delivery_ack.py:33; test_issue18_liveness_contract.py:54 | 환경 helper에서 cwd를 root로 지정한다. |
| tests/test_issue34_job_tree.py:50; test_issue36_prompt_transport.py:47 | 환경 helper가 임시 home/root를 고정한다. |
| tests/test_c1_tui_readiness.py:12; test_layout.py:310 | 별도 임시 root를 source 전에 지정한다. |
| tests/test_tier1_unit.py:14; test_issue5_phase1_delegation.py:49 | 셸 helper 호출은 mam_sandbox를 사용한다. |
| tests/test_b7_diff_untracked.py:11 | diff_collect는 lib.sh의 shim bootstrap 경로가 아니다. |
추가 동일 결함은 확인하지 못했다. 이번 변경은 pytest fixture 사용 추가여서 Bash 제품 문법을 바꾸지 않는다. O1의 bash 3.2 empty-array 검증도 이번 실행에 포함되어 통과했다.
## 검증 한계 / Lifecycle
새 변경은 3개 파일이므로 그 변경과 회귀 탐침을 재검증했으며, 이전 head와 동일한 전체 파일 묶음은 반복하지 않았다. 변경 파일 전부와 issue33_budget/phase1_delegation/tier1 전체의 두 환경 통합 실행을 이번 리뷰가 모두 통과시켰다는 주장은 하지 않는다. 제한 없는 전체 실행은 브리프에 따라 오케스트레이터가 수행해야 한다. 환경 제한 자체를 NOT PASS 사유로 삼지 않았다.
started 명령을 먼저 실행했으나 MQTT 연결이 Operation not permitted로 3회 실패하여 exit 2였다. 보고서 작성 후 요청된 completed 명령을 실행한다.
[VERDICT: PASS]
@@ -0,0 +1,102 @@
# Issue #17 WORKSPACE_ROOT test isolation — Codex review
Job: 9bc1f200
Loop: loop-20261008T133858Z-9e7f5b
Base: 6b00b6e06f55299cb60f156ccef09ede4bae5bdd
Head: d198dd50aaf6119dc21f36d88704e64850e67413
Tree: 114ec21d3fc7de8932f5e1d34f895edd24ad7c01
Validation path: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/1
## Conclusion
NOT PASS. The local _env fix and its new regression are correct, but task item 2 remains incomplete: equivalent unsafe test helpers are still present, including paths not protected by mam_sandbox and a cached environment that bypasses that fixture. These are independently reproduced isolation defects, not a verdict based on sandbox restrictions. Product-code changes are not required.
## R1 — other helpers still write into an inherited workspace (P2)
Evidence:
- tests/test_b19_headless_reconcile_fixes.py:124-134 copies os.environ and sources the checkout's lib.sh before replacing _init_herdr_isolation with a no-op. That replacement is too late: source-time bootstrap has already written the shim. Several callers, e.g. test_fullscreen_tip_is_not_a_blocking_dialog, do not request mam_sandbox.
- tests/test_loop_cli.py:13-23 copies os.environ and executes the checkout's run_loop.sh. Its test_help_lists_creator_not_target_agent (:30) has no mam_sandbox. run_loop.sh:17 sources lib.sh even for --help.
- lib.sh:26 defaults an absent WORKSPACE_ROOT to the checkout, and :144-152 creates .mam/shim and writes a shim temporary file. Thus merely deleting the inherited variable is insufficient.
Reproduction, performed only with temporary directories:
1. Scrub inherited HERDR_*, MAM_*, MQTT_*, SKS_* and control-plane bindings, set HOME to a temporary directory, and set WORKSPACE_ROOT to a new empty temporary sentinel.
2. Import test_b19_headless_reconcile_fixes and call _run_lib_helpers('echo probe-ok').
3. In a fresh sentinel, import test_loop_cli and call _run_loop(['--help']).
4. Expected: sentinel remains empty. Actual: both create sentinel/.mam/shim/herdr; the helper exits 0 and the help invocation exits its normal 1.
A normal pytest session does not solve the unset case: tests/conftest.py:138-146 drops an external root at configure time, but _isolate_mam_state_dirs (:161-178) sets only registry/log/env-file paths. Only mam_sandbox (:215) pins WORKSPACE_ROOT. These callers do not use that fixture, so the checkout fallback remains. The state guard snapshots jobs and audit logs, not shim files (tests/_mam_state_guard.py:79-86).
Fix direction: establish a temporary per-test WORKSPACE_ROOT before every source/bootstrap call, preferably via the common function-scoped isolation fixture for ordinary runtime environment copies; preserve later explicit test overrides. Helpers intended for direct/--noconftest use should also build their environment from their own temporary path, as the revised Issue #17 helper does. Cover the candidates below, add sentinel checks, and audit intentional unset-workspace tests separately instead of changing their behavior blindly.
## R2 — onboard's import-time environment bypasses later fixture isolation (P2)
Evidence: tests/test_orc_onboard.py:15 captures SCRUBBED_ENV at import; :22, :34, :47 and :65 reuse it. The helpers select temporary YAML/HOME_DIR paths but never set WORKSPACE_ROOT before sourcing the original LIB_SH (:41, :55, :64).
Reproduction:
1. Set WORKSPACE_ROOT to an empty temporary sentinel before importing test_orc_onboard.
2. After import, set os.environ['WORKSPACE_ROOT'] to a different temporary fixture workspace, as mam_sandbox would.
3. Call run_find_uuid(str(fixture_workspace), 'claude') with no real user data or agent session.
4. Expected: the sentinel stays empty and any bootstrap shim belongs to the fixture workspace. Actual: the cached sentinel receives .mam/shim/herdr, and the fixture workspace receives no shim.
With normal pytest's configure-time scrub, the cached dictionary instead lacks WORKSPACE_ROOT; later mam_sandbox updates still cannot change that dictionary, so lib.sh falls back to the checkout. This needs explicit treatment even if R1 is solved with a function-scoped autouse fixture.
Fix direction: preserve the intended removal of agent identity variables, but set WORKSPACE_ROOT explicitly from ws/cwd/mam_sandbox in every sourced-shell environment, including dump_state and the inline shell call sites. Alternatively build the scrubbed environment at invocation time after fixtures run, still pinning the temporary root. Add a regression where import-time and invocation-time roots differ.
Probe source: /tmp/9bc1f200-probes.py.
All three reproduced leaked shims are under /private/tmp/9bc1f200-probe-z4ewh5mh; no real workspace was used as a sentinel.
## Candidate audit
I searched tests for os.environ copies/comprehensions, WORKSPACE_ROOT, source/lib.sh, wrapper and run_loop.sh calls, then checked the caller fixtures and environment overrides.
| Candidate / file:line | Disposition and reason |
| --- | --- |
| test_issue17_item4_decisions.py:224 | Fixed correctly: pins tmp_path; extra explicit overrides still apply last. |
| test_b19_headless_reconcile_fixes.py:124, :329, :369, :430 | Unfixed; R1 helper and direct shell calls source original lib.sh without a temporary root. |
| test_loop_cli.py:13 | Unfixed; R1. Later _loop_env at :91 pins a sandbox, but early CLI tests do not use it. |
| test_issue5_phase3_loop.py:145, :154, :172 | Same unfixed bootstrap class: checkout run_loop.sh with ambient environment and no mam_sandbox. |
| test_issue5_phase4_runtime.py:126, :152 | Same unfixed class in run_loop.sh help/freeze checks. tmp_path is available but not bound to WORKSPACE_ROOT. |
| test_o3_scoped_guard.py:376, :412, :458 | Same unfixed class in checkout run_loop.sh invocations; --help also sources lib.sh. loop_lock-only snippets are a separate, non-shim path. |
| test_version_consistency.py:28, :131 | Same unfixed class: version checks source original LIB_SH with implicit/copied environment, without mam_sandbox. |
| test_workspace_scope.py:22-30 | Same unfixed class: unittest creates a temporary project but does not bind WORKSPACE_ROOT before two sources of original lib.sh. |
| test_b8_send_keys_verification.py:5-34 | Same unfixed class: tmp_path argument is unused for workspace binding before source. |
| test_issue6_fail_closed.py:1220-1244 | Same unfixed class: the direct source in the pinned-token test binds MAM_JOBS_DIR but not WORKSPACE_ROOT. Other _env-based cases in this file bind cwd explicitly. |
| test_orc_onboard.py:15-65 | Unfixed cached environment; R2. A later mam_sandbox fixture alone does not protect it. |
| test_issue33_budget.py:43-47; test_issue14_delivery_ack.py:33-36; test_issue18_liveness_contract.py:55-58 | No equivalent defect in these helpers: explicitly pin home/cwd as WORKSPACE_ROOT. |
| test_issue34_job_tree.py:51-64; test_issue36_prompt_transport.py:48-61; test_issue26_envfile.py:74-76; test_issue26_ints.py:123-124; test_issue30_stall_watch_stop.py:30-32 | Explicitly pin temporary home/workspace. Later Issue #34 _run_lib users request mam_sandbox. |
| test_issue21_phase2_loop_a.py:49, :59 | Explicit temporary root override. |
| test_issue21_worktree_prereqs.py:41-55 | Removal is intentional for publisher-command tests that must operate without workspace bindings; actual _dry_submit callers at :92, :114, :143 supply temporary WORKSPACE_ROOT. Not a blanket reason to change this helper. |
| test_issue5_phase1_delegation.py:49; test_tier1_unit.py:14; tier2/3/4 helpers; test_issue19_subscriber_daemon_lifecycle.py:52 | Ordinary helpers consume mam_sandbox's runtime environment; fixture :215 pins the workspace. Unlike onboard, these copies are made at invocation time. |
| test_issue9_server_env.py:84, :108; test_issue23_24_startup_path.py:36; test_a4_adapter_contract.py:558 | Source/bootstrap callers use mam_sandbox; intentional server-marker tests retain that temporary workspace binding. |
| test_issue45_restore.py:55, :179 | rig depends on mam_sandbox, then creates disposable HOME/server state. |
| test_issue45_herdr_config.py:302; test_issue36_prompt_transport.py:427 | Python config CLI or diff_collect/lib_int paths; do not bootstrap lib.sh's shim. |
| test_deploy_layout.py:16 and deploy tests | Installer uses explicit temporary installation targets; it does not source lib.sh for bootstrap. |
| test_issue5_phase2a_loop.py:750-757 | The source invocation is a string for a static scanner test, not executed. |
| test_issue20_state_isolation.py:199-201 | Explicit temporary WORKSPACE_ROOT for its source probe. |
The unfixed candidates above are within the requested same-defect audit scope; they are not unrelated cleanup requests.
## Local fix and verification
Setting WORKSPACE_ROOT to tmp_path, rather than deleting it, is the correct local choice because lib.sh derives its default from the library's location, not HOME or cwd. The new test validates the empty sentinel, unchanged checkout-shim mtime, expected shell workspace and actual shim in the test root.
The exact base _env function was loaded from git and substituted only in memory into the current test module. The new regression failed with "written into the sentinel" and the sentinel shim existed. With head _env it passed and no sentinel shim existed. This negative control changed no repository files.
- Changed file, WORKSPACE_ROOT absent at pytest launch: **286 passed, 4 deselected in 262.11s**; exit 0. Log: /tmp/9bc1f200-unset.log.
- Same file, WORKSPACE_ROOT set to an existing external temporary sentinel: **286 passed, 4 deselected in 262.02s**; exit 0. Log: /tmp/9bc1f200-sentinel.log.
- Both runs used --noconftest, -p no:cacheprovider and separate external basetemp directories, with no plugin/fixture pinning WORKSPACE_ROOT. This tests the helper's fix itself.
- At completion, /private/tmp/9bc1f200-unset-7yqs28zl/sentinel and /private/tmp/9bc1f200-sentinel-otbahzmr/sentinel each contained **zero entries**.
- Supplementary tests: 13 broker-independent issue33_budget cases plus the wrapper mapping contract from test_issue5_phase1_delegation and the G10 mapping contract from test_tier1_unit: **15 passed, 7 deselected in 29.57s**. Log: /tmp/9bc1f200-other.log. Only the two contract tests used a minimal temporary mam_sandbox fixture; this is not a full-suite pass claim for those two files.
- Full broker-dependent suites were not executed in the restricted environment; the orchestrator retains their integration validation.
- Independent negative-control regression and the three omitted-helper probes completed successfully, producing the results detailed above.
- The changed Python file parses on Python 3.9; git diff --check passes. Final git status --porcelain was empty and HEAD/tree still matched the brief.
The four unreaped-owner cases were deselected because this sandbox blocks ps, as independently diagnosed in the earlier review. The brief permits network-free review where broker integration is blocked. Neither these omissions nor MQTT publication restrictions are the reason for NOT PASS.
## Scope and lifecycle
Only tests/test_issue17_item4_decisions.py changed (36 insertions, 2 deletions). Product files, versions and branches are unchanged. All test runs and probes used the designated review path, Python 3.9 and native Bash 3.2; artifacts, test registries, HOME and all sentinel roots were temporary. Inherited control-plane/server variables were scrubbed. No .mam.env was exported, no real agent/server was contacted, and no reviewer source edits were made.
The requested started command was executed first; MQTT exhausted three attempts with Operation not permitted and exited 2. The requested completed command was also executed after saving the report; all three MQTT attempts failed with Operation not permitted and the command exited 2. The report is saved, but successful delivery of started/completed to the broker is not confirmed.
[VERDICT: NOT PASS]
@@ -0,0 +1,114 @@
# Review — c20f3d1f
## Verdict
PASS. 이번 누적 변경에서 차단할 결함을 찾지 못했다. 이전 리뷰 9bc1f200의 R1(상속 환경으로 lib.sh를 source하는 다른 테스트 누락), R2(onboard의 import 시점 WORKSPACE_ROOT 캐시)는 수정되었다. 이 판정은 아래 정적 검토와 제한 환경 실행에 근거하며 전체 통합 스위트 통과를 뜻하지 않는다. 브리프가 허용한 대로 소켓/프로세스 조회 제한 자체를 NOT PASS 사유로 삼지 않았다.
## Review identity / scope
- loop: `loop-20261008T133858Z-9e7f5b`
- base: `6b00b6e06f55299cb60f156ccef09ede4bae5bdd`
- head: `8b47cc76eed4582b1848b2eec529523a61497320`
- tree: `bfe5fcbbefbde9562334515b4a9803bee96f84fd`
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/2`
- 실행 Python: `/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/issue17-wsroot/.venv/bin/python` (Python 3.9).
- creator 작업 트리에서 실행하거나 소스를 수정하지 않았다. 검증 전후 identity가 일치하고 최종 `git status --porcelain`은 비어 있다. `git diff --check 6b00b6e HEAD` 통과. 변경 15개 Python 파일 AST 파싱 통과. 제품 코드 `.agents/skills`의 base..head diff는 없다.
## 요구사항 및 이전 지적 확인
1. `tests/test_issue17_item4_decisions.py:224`의 `_env`가 WORKSPACE_ROOT를 제외하고 테스트 tmp_path로 지정한다. 단순 unset이면 lib.sh가 checkout을 기본값으로 삼으므로 명시적 임시 경로가 맞다. 마지막 `extra` 적용은 테스트가 의도한 override를 유지한다.
2. `tests/conftest.py:180`의 opt-in `own_workspace_root`와 10개 모듈의 usefixtures 표시는 기존 mam_sandbox를 쓰지 않던 셸 테스트를 격리한다. 전역 autouse가 아니므로 WORKSPACE_ROOT 기본값을 검사하는 다른 Python 테스트를 일괄 변경하지 않는다.
3. 실제 conftest fixture를 등록한 별도 탐침에서 module fixture가 tmp_path/.mam-test-state/workspace를 설정하고, 명시적인 mam_sandbox가 그 뒤 자신의 root로 덮어쓰는 것을 각각 확인했다. 테스트 본문 monkeypatch도 이후 적용된다.
4. `tests/test_orc_onboard.py:19`에서 캐시의 WORKSPACE_ROOT를 제거하고 `:27`의 `_scrubbed_env`가 호출 시점의 root를 적용한다. run_onboard/run_find_uuid/run_verify_uuid/dump_state 및 inline 호출이 이 경로로 전환되었다. import 시점 sentinel을 이후 fixture root로 바꾸는 W2 탐침이 통과했다.
5. `tests/test_workspace_scope.py:31`은 unittest 경로를 자신의 ws_dir로 고정한다. `tests/test_issue21_worktree_prereqs.py:544`의 `_mutate`도 자신의 tmp root를 사용한다. 후자는 실제 mam_sandbox와 별도 sentinel을 사용한 탐침으로 검증했다.
6. 새 `tests/test_issue17_workspace_root_isolation.py:54` W1은 checkout 복사본과 real conftest를 사용하는 nested pytest로 이전 fallback-to-checkout 문제를 검사한다. nested pytest의 성공/통과 수를 요구하므로 환경 실패를 격리 성공으로 오인하지 않는다. W1 자체는 로컬 브로커가 필요하여 이 샌드박스에서 완주하지 않았으며 아래 표의 실행 결과에 포함하지 않았다.
7. 추가된 코드는 Python 3.9 AST 파싱 및 실제 실행을 통과했다. Bash 제품 로직이나 Bash 버전 의존 문법은 변경하지 않았다.
## 실행 결과와 한계
실험 전 HERDR_*, MAM_*, MQTT_*, SKS_*, WORKSPACE_ROOT 및 control-plane 경로 환경을 제거하고 HOME을 테스트별 임시 디렉터리로 설정했다. 모든 pytest 실행은 bytecode/cache 쓰기를 끄고 별도 임시 basetemp를 썼다. 실제 .mam.env 값을 읽어 export하거나 출력하지 않았다.
정상 conftest의 session-autouse local MQTT broker는 sandbox의 socket bind 제한 때문에 사용할 수 없다. `/tmp/c20f3d1f-run.py`는 검증 트리의 실제 conftest를 import하고 **local_mqtt_broker fixture만 메모리에서 제외**한 뒤 `pytest.main(..., --noconftest, plugins=[module])`를 실행한다. 나머지 fixture, 환경 처리 hook, 상태 guard는 유지했다. 이는 수정하지 않은 전체 스위트 실행과 다르다.
| 실행 | 결과 | sentinel |
|---|---|---|
| WORKSPACE_ROOT unset, 주 검증 묶음 | 135 passed, 21 failed, 107.10s | 0 entries |
| WORKSPACE_ROOT=기존 sentinel, 동일 묶음 | 135 passed, 21 failed, 107.53s | 0 entries |
| 추가 검증 묶음, unset | 84 passed, 69.36s | 0 entries |
| fixture 순서 및 `_mutate` 직접 탐침 | 3 passed, 0.24s | 0 entries |
주 검증 묶음은 다음 파일 전체와 두 회귀 node다:
- test_b19_headless_reconcile_fixes, test_b8_send_keys_verification, test_loop_cli, test_version_consistency, test_workspace_scope, test_orc_onboard, test_o3_scoped_guard, test_issue5_phase3_loop, test_issue5_phase4_runtime, test_issue25_plan_reuse, test_issue33_budget.
- test_issue17_workspace_root_isolation::test_w2_onboard_does_not_carry_its_import_time_root.
- test_issue17_item4_decisions::test_c_helpers_write_nothing_to_an_inherited_workspace.
두 환경의 실패 21건은 동일하다:
- O3 guard 12건: `ps` 실행 시 PermissionError / Operation not permitted.
- onboard O18/O28/O29 3건: 프로세스 command/parent를 `ps`로 읽는 autodetection 불가로 exit 3. `orc_onboard.sh:108,185`의 조회와 해당 환경 의존 테스트 경로를 확인했다. explicit UUID 및 나머지 onboard 검증은 통과했다.
- issue33 budget 6건: local_broker_env()가 실제 로컬 브로커를 요구하여 실패. 제외한 broker fixture에 따른 실행 제약이다. 나머지 budget 13건은 통과했다.
추가 묶음 84건에는 test_tier1_unit 전체, phase1 delegation C01/C02/C04/C05 및 event/exit mapping 두 contract node, fail_closed의 T1-25와 T3-1/T3-2, worktree_prereqs T2-8이 포함된다. 해당 phase1 node의 파라미터 케이스를 모두 포함한다.
따라서 수정 파일 전체와 phase1 delegation 전체의 두 환경 통합 실행을 모두 통과했다고 주장하지 않는다. 특히 W1 nested pytest와 실제 broker/process-inspection 경로의 전체 확인은 브리프에 따라 오케스트레이터의 제한 없는 실행으로 남긴다.
임시 로그 및 재현 드라이버:
- `/tmp/c20f3d1f-run.py`
- `/tmp/c20f3d1f-unset.log`
- `/tmp/c20f3d1f-sentinel.log`
- `/tmp/c20f3d1f-extra.log`
- `/tmp/c20f3d1f-probe-tests/test_order.py`
실행 후 확인한 sentinel:
- `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/c20f3d1f-unset-ffhmus4t/sentinel`
- `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/c20f3d1f-sentinel-bi_df5cb/sentinel`
모두 비어 있으며 검증 checkout의 `.mam` 아래에도 파일/하위 디렉터리가 없다. 별도 탐침의 첫 시도는 /tmp 상위 수집 중 접근 제한 디렉터리 때문에 collection 오류였고, 전용 probe 디렉터리와 rootdir를 지정한 재실행에서 3건 통과했다. 소스 변경은 하지 않았다.
## 회귀 negative control
`/tmp/c20f3d1f-negative.py`로 현재 검증 트리에서 base 코드를 `git show`로 읽어 메모리에만 로드했다. 실제 저장소/작업 트리를 되돌리지 않았다.
| 탐침 | 수정 전 base | 현재 head |
|---|---|---|
| W2와 같은 import-time onboard root 시나리오 | sentinel 3 entries (`.mam/shim/herdr`), own shim 없음 | sentinel 0 entries, own shim 존재 |
| 현재 decisions 회귀 테스트에 base `_env`만 교체 | `AssertionError: written into the sentinel` | 동일 테스트 통과 |
절차: 기존 빈 sentinel을 WORKSPACE_ROOT로 설정 → onboard 모듈 import → 환경을 fixture-ws로 변경 → run_find_uuid(fixture-ws, 'claude'). 두 번째 탐침은 현재 회귀 함수에 base `_env` 함수 AST를 넣고 실행한 다음 head 함수로 복원해 재실행했다. 기대 결과는 sentinel 불변 및 자기 root에만 shim 생성이다. 현재 코드는 두 시나리오 모두 기대와 일치한다.
산출물: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/c20f3d1f-negative-opdzrib1`.
## 같은 결함 후보 재조사 / 수정하지 않은 후보
`rg`로 환경 복사, lib.sh source, wrapper/run_loop 실행 지점을 다시 조사하고 fixture 또는 명시적 root 경로를 대조했다. 아래는 변경하지 않은 주요 후보의 제외 근거다. 단순히 환경을 복사한다는 이유만으로 안전하다고 판단하지 않았다.
| 후보 파일:라인 | 제외 근거 |
|---|---|
| tests/test_issue14_delivery_ack.py:33 | `_env(cwd)`가 WORKSPACE_ROOT=cwd를 지정한다. |
| tests/test_issue18_liveness_contract.py:54 | `_env(cwd)`가 WORKSPACE_ROOT=cwd를 지정한다. |
| tests/test_issue33_budget.py:43 | 독립 `_env`가 테스트 root를 지정한다. |
| tests/test_issue34_job_tree.py:50,587 | `_env(home)`은 root를 고정하고 `_run_lib` 호출은 mam_sandbox를 사용한다. |
| tests/test_issue36_prompt_transport.py:47,532 | `_env(home)` 및 shell-loop 실행이 명시적인 임시 control root를 쓴다. |
| tests/test_issue26_ints.py:124 | HOME과 WORKSPACE_ROOT를 같은 테스트 home으로 설정한다. |
| tests/test_c1_tui_readiness.py:12 | helper 내부 mkdtemp를 WORKSPACE_ROOT로 설정한 뒤 source한다. |
| tests/test_layout.py:310 | generated-shim 테스트가 임시 ws_dir를 source 전에 export한다. |
| tests/test_issue6_turn_handshake.py:24,43 | cwd를 준 helper는 root/home을 그 경로로 바꾸며 mock_herdr 경로는 mam_sandbox에 의존한다. |
| tests/test_issue5_phase1_delegation.py:49 | lib helper 호출은 mam_sandbox에서 실행되고 root는 fixture의 임시 경로다. Python-only 계약 검사는 shim bootstrap이 없다. |
| tests/test_tier1_unit.py:14,464 | 셸 helper 호출은 mam_sandbox 사용. :464의 별도 환경은 Python workspace_uuid만 실행하며 lib.sh를 source하지 않는다. |
| tests/test_uuid_target.py:11 | mutation helper와 셸 호출이 mam_sandbox를 사용한다. |
| tests/test_o2_race_free_lock.py:16 | 상태 helper 호출은 mam_sandbox fixture를 사용한다. |
| tests/test_issue20_state_isolation.py:150,199 | 격리 회귀 목적으로 자체 임시 repo/root를 명시한다. |
| tests/test_b7_diff_untracked.py:11 | diff_collect는 lib.sh shim bootstrap 경로가 아니다. |
| tests/test_issue8_update_state.py:24 | 설치/업데이트 대상은 별도 임시 workspace이며 lib.sh source 결함과 다르다. |
| tests/test_issue5_phase2a_loop.py:754 | 해당 source 문자열은 static guard 검사용 텍스트이며 실제 셸 실행이 아니다. |
이번에 수정된 누락 후보(b19/b8/loop CLI/phase3/phase4/O3/version/issue25/fail_closed)는 own_workspace_root에 opt-in했다. unittest workspace_scope와 worktree_prereqs mutation은 직접 고정하고, import-time 캐시를 쓰던 onboard는 runtime root 적용으로 별도 해결했다. 추가 동일 결함은 확인하지 못했다.
## Lifecycle
요청한 started 명령을 먼저 실행했으나 MQTT 연결이 sandbox의 Operation not permitted로 실패하여 exit 2였다. 리뷰 결과 파일을 쓴 뒤 요청된 completed 명령도 실행했다. 3회 연결 시도 후 같은 Operation not permitted로 exit 2를 반환했다. 따라서 MQTT 전달 성공은 확인하지 못했다. 보고서의 PASS와 MQTT 전달 성공 여부는 별개다.
[VERDICT: PASS]