Review loop (--review-only, --all-reviewer, focused on this Mac: macOS, /usr/bin/python3 3.9.6, bash 3.2): round 1 (@63d8164) agy and grok PASS, claude and codex NOT PASS (F1 paho via the /usr/bin/python3 fallback, F2 logs default path, R1 disk-fallback redispatch interleaving, R2 symlinked registry), sent as a PR comment and fixed by the author in four commits; round 2 (@a7cb90e) 4/4 PASS. Full suite on macOS / Python 3.9.6 @a7cb90e: 1867 passed, 56 skipped, 1 error (the known #55 false positive: WAL/SHM of an agy conversation running in another workspace). Real herdr panes and ~/.claude/projects unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
59 lines
8.1 KiB
Markdown
59 lines
8.1 KiB
Markdown
# PR #58 (fix/issue-57) 병합 전 2차 리뷰 (job b48be8a6)
|
|
|
|
- 리뷰어: planner-reviewer-claude-01 (Reviewer)
|
|
- 리뷰 identity: loop-20261015T123502Z-1b9df6 / base e32c9574 .. head a7cb90e6 (커밋 15개; 1차 이후 대응 4개: 75c0042 F1, 151af99 F2, 1c598bb R1, a7cb90e R2) / tree 96c5c5f8
|
|
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261015T123502Z-1b9df6/1` — HEAD·tree 일치, `git status --porcelain` 0 줄, `.mam` 없음(시작·종료 모두). 본 체크아웃 HEAD e32c957(main) 변동 없음.
|
|
- 환경: macOS, `/usr/bin/python3` 3.9.6(paho 없음; 이 Mac 의 PATH `python3`), 체크아웃 `.venv` 3.9.6(paho 2.1.0), PATH `bash` = `/bin/bash` 3.2.57.
|
|
- 실행 안전: 테스트·탐침은 모두 `env -i`(PATH/HOME/LANG/TMPDIR 만, 필요한 값만 명시)로 브리프의 `-u` 목록 전부를 비웠고, basetemp 는 scratchpad 아래(이름에 "shim" 없음). 수동 재현은 scratchpad 또는 `/tmp/jb48-r2`(종료 후 삭제)의 임시 워크스페이스·cwd 에서만 했다. 수정 전 대조군은 `git archive HEAD` 복사본에 제품 파일만 되돌려 만들었다. 실제 `.mam`·`.mam.env`·모니터·herdr 서버·에이전트 홈 미접촉.
|
|
|
|
## 1. 1차 지적 4건 — 재현을 다시 돌린 결과
|
|
|
|
| 지적 | 1차 재현 (63d8164) | head (a7cb90e) | 수정 전 코드에서 회귀 테스트 |
|
|
|---|---|---|---|
|
|
| **F1** paho 없는 폴백에서 control_plane·status 실패 | `AGENT_PYTHON_BIN=/usr/bin/python3` 로 `mam_control_plane_json` rc=1 `ModuleNotFoundError: paho`; `get_job_status` 예외 | rc=0, 바인딩 JSON 에 로그 루트 포함; `status.sh` 의 `get_job_status` 두 복사본 모두 `('abc12345', 'unknown')` | `test_control_plane_binding_works_without_paho`, `test_status_works_without_paho[json/text]` **실패**(paho 미존재로 control_plane·service import 실패) |
|
|
| **F2** 래퍼 `logs` 기본 경로가 cwd 상대 | `WORKSPACE_ROOT=ws`, cwd=elsewhere 에서 `logs --list` 가 잡을 못 찾음 | `logs --list` 가 ws 의 잡을 나열, `logs <id>` 에 `registered` 이벤트, `--registry-dir` 를 주면 그 레지스트리를 따름, elsewhere 에 아무것도 안 생김 | `test_logs_without_registry_dir_follows_the_workspace_from_any_cwd` **실패**(`(no logged jobs under …)`) |
|
|
| **R1** 디스크 폴백이 이후 디스패치의 terminal 을 이전 시도로 오인 | (codex 지적) | 감사 상태 채택 직전에 레코드를 다시 읽어 `(dispatch_token, attempt_id, transition_seq)` 가 처음 읽은 것과 같을 때만 채택 | `test_disk_fallback_rejects_a_terminal_that_belongs_to_a_later_dispatch` **실패**(`B's completion was attributed to A`) |
|
|
| **R2** jobs 디렉터리가 심볼릭 링크일 때 submit 과 logs 불일치 | (codex 지적) | `/tmp/jb48-r2/alias/jobs -> actual/jobs` 로 `submit --dry-run`: 출력 경로·감사 로그 모두 `actual/delegate_job_logs/<id>`; `logs --registry-dir alias/jobs` 와 `registry.py --registry-dir actual/jobs logs --list` 가 같은 잡을 찾음 | `test_a_symlinked_registry_dir_resolves_to_one_audit_log` 는 63d8164 에서는 `logroot` 부재로 ImportError 라 증거가 안 되므로, **a7cb90e^ (logroot 있음, `abspath` 규칙)** 로 다시 돌려 **실패**(`…/alias/… ≠ …/actual/delegate_job_logs`) 확인 |
|
|
|
|
## 2. 수정이 새 문제를 만들지 않았는가
|
|
|
|
**F1 (모듈 이동)**
|
|
- `lib_py/delegation/logroot.py` 는 stdlib(`os`, `typing`)만 쓴다. `mqtt_common` 이 `DEFAULT_REGISTRY_DIR`·`_workspace_mam_dir`·`default_registry_dir`·`get_logs_dir`·`sibling_logs_dir` 를 그대로 재노출하므로 기존 import 경로(`mqtt_common.get_logs_dir` 등)는 불변이다.
|
|
- `from lib_py.delegation.logroot import …` 는 패키지 `__init__` 을 거쳐 `daemon`·`service`·`registry`·`publish_event`·`publisher` 까지 로드한다. 이 Mac 의 `/usr/bin/python3` 로 직접 import 해 보니 12 개 `lib_py` 모듈이 모두 올라오고 `paho` 는 로드되지 않았다. 직접 paho 를 import 하는 곳은 `mqtt_common`·`publisher` 둘뿐이고 둘 다 `try/except ImportError` 로 감쌌으며 `from __future__ import annotations` 라 `-> mqtt.Client` 주석도 import 시 평가되지 않는다. 실제로 브로커가 필요하면 `make_client` 가 명확한 `ImportError` 를 낸다.
|
|
- eager import: `logroot` 는 `eager.CORE_MODULES` 에 없지만 목록에 있는 `mqtt_common` 이 import 시점에 로드하므로 동결 런타임에 함께 들어간다(아래 비차단 메모).
|
|
|
|
**F2**: `local -a reg=()` + `${reg[@]+"${reg[@]}"}` 를 `/bin/bash -u` 로 직접 확인 — 빈 배열이면 인자 없음, 주어지면 `--registry-dir <dir>`. 래퍼의 다른 subcommand 는 바뀌지 않았다.
|
|
|
|
**R1 (재읽기)**
|
|
- 경합: B 의 terminal 감사 상태는 재디스패치(레코드 토큰·seq 변경)가 커밋된 뒤에만 생긴다. 그러므로 감사 읽기에서 B 의 terminal 이 보였다면 레코드는 이미 그 전에 바뀌었고, 그 뒤의 재읽기가 반드시 차이를 본다. 재읽기 이후의 변경은 이미 읽은 감사 상태와 무관하다.
|
|
- 같은 시도 안에서 정상 전이(A running → A completed)가 두 읽기 사이에 끼면 seq 가 달라져 "deferred" 로 한 라운드 늦어질 뿐이고, 다음 라운드는 레코드 자체의 terminal 을 읽는다.
|
|
- 성능: 감사 상태가 terminal 이고 stale 이 아닐 때만 레코드 하나를 더 읽는다. 무시할 만하다.
|
|
|
|
**R2 (realpath)**
|
|
- 기본 경로: `WORKSPACE_ROOT=/tmp/...`(macOS 에서 `/tmp → /private/tmp`)일 때 `get_logs_dir()` 은 이제 `/private/tmp/.../.mam/delegate_job_logs` 문자열을 돌려준다. 물리적으로는 이전과 같은 디렉터리임을 확인했다. 결과를 문자열로 비교하는 곳은 `control_plane.forwarded_keys`(이제 양쪽 모두 `_abs`=realpath)와 루프 러너의 제외 목록(`runner.py` 가 `d.resolve().relative_to(root.resolve())`)뿐이라 영향이 없다.
|
|
- `forwarded_keys` 탐침: `/tmp`·`/private/tmp` 혼합 두 방향, 후행 `/`, 심볼릭 링크 alias 경유 모두 형제로 판정되어 전파하지 않는다. 비형제만 전파한다. 1차에서 비정규 입력이 "전파" 쪽으로 어긋나던 비대칭도 사라졌다.
|
|
- `from_env({"WORKSPACE_ROOT": "/tmp/..."})` 의 기본 바인딩은 `defaults()` 와 같아서, 평소 실행은 pane 에 아무것도 전파하지 않는다.
|
|
- 레지스트리 디렉터리 자체가 심볼릭 링크인 경우에만 감사 로그가 링크 대상 옆으로 간다. 이는 R2 가 의도한 동작이며, 래퍼(`pwd -P`)와 바인딩(realpath)이 이미 그렇게 보고 있었다.
|
|
|
|
## 3. 이 Mac 정합성
|
|
- 변경 py 15 개: `ast.parse(feature_version=(3,9))` 와 `/usr/bin/python3 -m py_compile` 모두 통과. 3.10+ 문법·API 없음.
|
|
- `lib.sh`·래퍼·`status.sh`: `/bin/bash -n`(3.2) 통과.
|
|
|
|
## 4. 테스트 (head, 검증 경로)
|
|
|
|
| 묶음 | 결과 |
|
|
|---|---|
|
|
| A: issue57·15·21_acceptance·21_worktree_prereqs·tier1·20·17_item4·17_workspace_root_isolation·36·14 | **475 passed, 2 skipped** |
|
|
| B: issue18·6_fail_closed·34·33·5_phase1/2a/2b/4_runtime·tier2·12·23_24·21_phase2·b7 | **493 passed** |
|
|
|
|
`5_phase4_runtime` 의 eager 모듈 검사도 통과했다. 어느 실행도 검증 경로에 `.mam` 을 남기지 않았고 #55 가드 오탐도 나오지 않았다.
|
|
|
|
## 5. 비차단 메모
|
|
- `lib_py.delegation.logroot` 를 `eager.CORE_MODULES` 에 명시하면 의도가 분명해진다. 지금도 `mqtt_common` 을 통해 로드되므로 기능 문제는 없다.
|
|
- paho 없이 `publisher` 의 브로커 경로를 직접 타면 `make_client` 의 `ImportError` 가 먼저 나는 구조다. 다른 진입점이 `mqtt.` 를 먼저 건드리면 `AttributeError: 'NoneType'` 로 보일 수 있지만, 브로커가 필요한 경로는 원래 paho 가 필수다.
|
|
|
|
## 결론
|
|
1차 지적 4건(F1·F2·R1·R2)이 모두 해소됐다. 1차 재현을 다시 돌려 확인했고, 각 회귀 테스트가 수정 전 코드에서 의도한 이유로 실패한다(R2 는 직전 부모 커밋 기준). 모듈 이동·재읽기·realpath 는 기본 경로, 바인딩·전파, 기존 import 경로에 새 문제를 만들지 않으며, 이 Mac 의 Python 3.9·bash 3.2 에서 정합하다.
|
|
|
|
[VERDICT: PASS]
|