release(v4.9.4): the audit log follows a moved registry (Issue #57, PR #58) - archive the review reports and bump the lockstep version
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
This commit is contained in:
1 parent
a7cb90e63a
commit
7cf728e9f8
28 files changed
+619
-26
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261015T123502Z-1b9df6",
|
||||
"base_sha": "e32c9574644a9185813a4dc881812694d62b6204",
|
||||
"head_sha": "a7cb90e63abf454ccf22f6ff887cc25393755402",
|
||||
"tree_sha": "96c5c5f82cb89e4383011d52fb1dcb21d4e787e6",
|
||||
"review_commit": "a7cb90e63abf454ccf22f6ff887cc25393755402",
|
||||
"reviewer": "planner-reviewer-codex-01",
|
||||
"job_id": "2e06828a",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-15T13:18:35Z"
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
# PR #58 Mac 2차 리뷰 — PASS
|
||||
|
||||
Job `2e06828a`, Reviewer. 이전 네 지적 사항(F1/F2/R1/R2)이 해결됐음을 확인했다. 관련 테스트 83건이 통과했으며, 수정 직전 커밋에서는 해당 회귀 테스트 6건이 각각 예상한 이유로 실패했다. 이번 검토 범위에서 새 차단 이슈는 발견하지 못했다. 소스 수정은 하지 않았다.
|
||||
|
||||
## 검증 identity
|
||||
|
||||
- loop: `loop-20261015T123502Z-1b9df6`
|
||||
- base: `e32c9574644a9185813a4dc881812694d62b6204`
|
||||
- head: `a7cb90e63abf454ccf22f6ff887cc25393755402`
|
||||
- tree: `96c5c5f82cb89e4383011d52fb1dcb21d4e787e6`
|
||||
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261015T123502Z-1b9df6/1`
|
||||
- 누적 범위: base..head 15 commits / 23 files. 이전 검토 head `63d8164` 이후 4개 수정 커밋을 집중 검토하고 누적 변경과의 정합성을 대조했다.
|
||||
- 실제 환경: macOS, Python 3.9.6, GNU Bash 3.2.57(arm64-apple-darwin25).
|
||||
|
||||
## 지적 사항별 확인
|
||||
|
||||
| 항목 | 수정 전 검증 | 현재 head 검증 | 판정 |
|
||||
|---|---|---|---|
|
||||
| F1: paho 없는 Python 경로 계산 | `63d8164`에서 binding 1건 및 status json/text 2건이 ModuleNotFoundError로 실패 | 같은 3건 통과. 별도로 실제 `/usr/bin/python3`의 paho spec이 None임을 확인하고 `mam_control_plane_json` 및 status의 두 `get_job_status` 함수를 실행해 성공 | 해결 |
|
||||
| F2: 다른 cwd의 기본 logs | `75c0042`에서 WORKSPACE_ROOT의 잡 대신 elsewhere/.mam 로그 루트를 조회하여 실패 | WORKSPACE_ROOT=ws, cwd=elsewhere에서 `logs --list`와 `logs <id>` 모두 잡/registered 이벤트 조회 성공 | 해결 |
|
||||
| R1: 다른 dispatch 완료 오인 | `151af99`에서 A error → redispatch → B completed interleaving이 A를 completed로 종료시켜 실패 | 동일 interleaving에서 A는 pending 유지, terminal 비어 있음. B 토큰으로 다음 조회 시 completed. 별도 탐침도 동일 결과 | 해결 |
|
||||
| R2: registry 자체의 symlink | `1c598bb`에서 alias/jobs의 sibling이 actual이 아닌 alias로 계산되어 실패 | alias/jobs → actual/jobs에 submit --dry-run 후 같은 alias 인수로 logs 조회 성공. alias 옆에는 로그를 생성하지 않음 | 해결 |
|
||||
|
||||
수정 전 테스트는 검증 저장소에서 `git archive <각 수정 커밋의 부모>`로 만든 임시 복사본에서 실행했다. 해당 복사본에는 현재 head의 `tests/test_issue57_logs_follow_registry.py`만 복사해 새 회귀 테스트를 실행했다. 실제 코드의 이전 동작 때문에 실패했으며 수집 오류나 환경 오류로 실패한 것이 아니다. 원본 검증 트리나 creator 브랜치는 변경하지 않았다.
|
||||
|
||||
각 회귀 테스트:
|
||||
|
||||
- F1: `test_control_plane_binding_works_without_paho`, `test_status_works_without_paho[json]`, `test_status_works_without_paho[text]`.
|
||||
- F2: `test_logs_without_registry_dir_follows_the_workspace_from_any_cwd`.
|
||||
- R1: `test_disk_fallback_rejects_a_terminal_that_belongs_to_a_later_dispatch`.
|
||||
- R2: `test_a_symlinked_registry_dir_resolves_to_one_audit_log`.
|
||||
|
||||
## 코드 및 추가 탐침 근거
|
||||
|
||||
F1: `.agents/skills/lib_py/delegation/logroot.py`가 표준 라이브러리만으로 경로 규칙을 정의한다. `control_plane.py`, lib.sh의 경로 helper, status.sh가 이 모듈을 사용한다. 패키지 초기화의 전이 import도 `mqtt_common.py:33` 및 `publisher.py:17`의 선택적 paho import로 통과한다. 실제 paho 미설치 `/usr/bin/python3` + `AGENT_PYTHON_BIN=/usr/bin/python3`에서 lib.sh의 `mam_control_plane_json`을 실행했다. status.sh의 두 `get_job_status` 정의를 그대로 추출해 동일 인터프리터에서 audit-only completed 레코드를 읽었으며 둘 다 `('probejob', 'completed')`를 반환했다. 경로 조회가 MQTT 접속을 필요로 하지 않고, 실제 client 생성은 paho가 없으면 명시적으로 실패한다.
|
||||
|
||||
F2: wrapper의 `cmd_logs`는 `--registry-dir`가 명시된 경우에만 배열에 넣어 registry.py로 넘긴다. 기본 실행은 registry.py의 MAM_JOBS_DIR → WORKSPACE_ROOT → cwd 규칙을 사용한다. 비어 있는 배열은 `${reg[@]+"${reg[@]}"}` 형태로 전개되므로 Bash 3.2 + set -u에서도 동작한다. 기본 인수 및 명시적 registry 인수 두 경우 모두 실제 테스트로 통과했다.
|
||||
|
||||
R1: `job_subscriber.py:128`에서 감사 상태를 읽은 뒤 레지스트리를 재조회하고 `:132`에서 dispatch_token/attempt_id/transition_seq를 비교한다. 첫 관찰과 다르면 그 라운드에서는 채택하지 않는다. 따라서 정상 엔진이 토큰을 교체하고 seq를 증가시키는 재디스패치가 감사 read 전후에 끼어들어도 이전 토큰에 다음 시도의 성공을 붙이지 않는다. 추가 재조회 이후에 새로운 전이가 일어나더라도 이미 읽어 둔 감사 상태를 새 상태로 바꾸지는 않는다. 기존 레지스트리 terminal 경로는 추가 read 없이 처리한다. 감사 상태를 채택할 후보에만 파일 read 최대 1회가 추가되며 새 내부 polling loop, sleep, 장시간 lock은 없다. 성능 판단은 이 코드 경로에 근거하며 대규모 잡 부하 벤치마크를 수행했다는 뜻은 아니다.
|
||||
|
||||
R2: `logroot.py:51`이 registry의 realpath에서 sibling을 구한다. submit의 `pwd -P`, control_plane의 realpath와 일치한다. `control_plane.py:82`의 비교도 로그 경로 양쪽을 정규화한다. 추가 탐침에서 `/tmp`/`/private/tmp`, `/var`/`/private/var` 부모 별칭 및 lexical 별칭을 사용하는 기존 row에서도 파생 로그 키를 제외하는 동작이 일치했다. 명시적 DELEGATE_JOB_LOGS_DIR 우선권은 유지된다. 일반 기본 경로는 같은 물리적 위치이며 Mac에서 문자열의 `/private` 접두사가 정규화되는 것은 의도된 변화이다.
|
||||
|
||||
기존 import 호환성: 실제 `/usr/bin/python3`에서 `mqtt_common.sibling_logs_dir`, `get_logs_dir`, `default_registry_dir`가 logroot의 해당 함수와 동일 객체임을 확인했다. venv Python에서 `lib_py.loop.eager.eager_load()` 실행 후 `lib_py.delegation.logroot`가 검증 트리 경로로 sys.modules에 적재됐음을 확인했다. 명시적 eager 목록에 logroot를 중복 추가하지 않아도 mqtt_common의 최상위 import를 통해 시작 시 적재된다.
|
||||
|
||||
이전 R1/R2 임시 탐침 파일은 더 이상 존재하지 않아, 이전 리포트에 기록한 동일한 전이 순서 및 symlink submit/logs 절차를 새 임시 스크립트로 재구성했다. R1은 실제 engine 전이를 사용하고 audit read 경계에만 순서를 삽입했으며, R2는 실제 wrapper의 dry-run/logs를 호출했다.
|
||||
|
||||
## 실행 결과와 한계
|
||||
|
||||
- 현재 head 선택 테스트: **83 passed in 181.30s**, exit 0.
|
||||
- 모듈 전체: `test_issue57_logs_follow_registry.py`, `test_issue15_registry_isolation.py`, `test_issue12_registry_secrets.py`, `test_issue21_worktree_prereqs.py`, `test_issue21_acceptance.py`.
|
||||
- 개별 테스트: tier1의 `test_b9_logs_dir_follows_cwd_changes`; issue18의 `test_subscriber_records_seq_gaps_and_recovers_terminal_from_state`, `test_subscriber_state_renews_liveness_only_when_it_advances`, `test_remote_runner_ignores_a_resent_dispatch_of_a_finished_attempt`, `test_remote_runner_never_rolls_a_newer_attempt_back`.
|
||||
- 변경 Python 파일 전체에 실제 Python 3.9.6의 AST parse 적용, 변경 shell/wrapper 각각 `/bin/bash -n` 검사 통과. `git diff --check base HEAD` 통과.
|
||||
- 테스트마다 브리프의 지정 환경변수를 `env -u`로 제거했고, 추가로 에이전트 관련 환경과 HOME/XDG 위치를 격리했다. 지정 venv Python, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 검증 트리 밖 basetemp 사용. create/resume은 mock fixture로 검증했다.
|
||||
- 실제 herdr 서버/monitor/에이전트를 시작하지 않았다. 실제 제어 평면에는 지시된 job lifecycle 이벤트와 본 리포트만 기록했다. creator 작업 트리에서 실행하지 않았다.
|
||||
- 전체 스위트는 이 리뷰에서 다시 실행하지 않았다. 오케스트레이터의 별도 전체 스위트 결과를 이 리뷰 결과로 포함하지 않았다. 선택 테스트에서 #55 가드 오탐 및 teardown 오류는 없었다.
|
||||
- 최종 검증 트리 HEAD/tree는 브리프와 일치하고 git status는 clean이다.
|
||||
|
||||
검증 산출물 디렉터리: `/tmp/review-2e06828a-66fn_mpg/`.
|
||||
|
||||
- `before-F1.log`: 3 failed, 각각 paho import 실패.
|
||||
- `before-F2.log`: 1 failed, 잘못된 cwd 로그 루트.
|
||||
- `before-R1.log`: 1 failed, B 완료를 A에 귀속.
|
||||
- `before-R2.log`: 1 failed, alias와 actual sibling 불일치.
|
||||
- `head.log`: 83 passed.
|
||||
- `system-probe.py`, `system-probe.log`: 실제 시스템 Python/no-paho 및 import/eager/구문 검증.
|
||||
- `repro-r1-r2.py`, `repro-r1-r2.log`: 이전 두 재현 절차 및 Mac 별칭 검증.
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,93 @@
|
||||
# PR #58 Mac review — NOT PASS
|
||||
|
||||
Job: `863e2edf`. Role: Reviewer. 소스 수정 없이 리뷰 및 임시 디렉터리 안의 검증만 수행했다.
|
||||
|
||||
- loop: `loop-20261014T152849Z-f1a415`
|
||||
- base: `e32c9574644a9185813a4dc881812694d62b6204`
|
||||
- head: `63d816468e6d48d01bd96f31d8a25eeadfbaab60`
|
||||
- tree: `e8f162f74b7ce8ee8cc6ba7954b7d28dfa3f89f6`
|
||||
- 검증 트리: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261014T152849Z-f1a415/1`
|
||||
- 범위: base..head 11 commits / 21 files. 아래 파일:라인은 이 head 기준이다.
|
||||
|
||||
관련 테스트 **77 passed in 177.28s**, exit 0. 그러나 테스트가 다루지 않는 두 경우를 추가 탐침으로 재현했다. 병합 전에 아래 두 항목을 수정하고 회귀 테스트를 추가해야 한다. 국소 수정으로 해결 가능하므로 설계 재계획 에스컬레이션은 필요하지 않다.
|
||||
|
||||
## R1 — [P1] 디스크 폴백이 다른 dispatch의 완료를 이전 dispatch의 성공으로 오인한다
|
||||
|
||||
근거:
|
||||
|
||||
- `.agents/skills/lib_py/delegation/job_subscriber.py:89`: 레지스트리의 status/token/attempt/transition_seq를 먼저 읽는다.
|
||||
- 같은 파일 `:100`, `:114`, `:121`: 나중에 읽은 감사 상태의 seq가 그 스냅샷 이상이면 상태를 채택한다.
|
||||
- 같은 파일 `:126`, `:132`: 토큰/attempt 검증에는 여전히 처음 읽은 레코드만 사용한다.
|
||||
- `.agents/skills/lib_py/delegation/engine.py:322`: redispatch는 새 토큰을 발급한다. 감사 상태에는 토큰/attempt가 기록되지 않는다(`:383`).
|
||||
|
||||
재현 절차:
|
||||
|
||||
1. 임시 cwd/WORKSPACE_ROOT 및 별도 registry에서 토큰 A로 잡을 등록하고 `started`로 전이시켜 running 상태로 만든다.
|
||||
2. `_check_disk_fallback`을 expected token A로 실행한다.
|
||||
3. `read_logged_status`를 감싸서, 레지스트리 read가 끝나고 감사 read가 시작되는 지점에 다음 실제 engine 전이를 실행한다: A의 `error` → `redispatch(attempt_id=2)` → 새 토큰 B로 `completed`.
|
||||
4. 실제 `read_logged_status` 결과를 반환한다. 즉 감사 파일을 조작한 것이 아니라 정상 engine 전이 사이에 subscriber가 일시 중단된 순서를 결정적으로 재현했다.
|
||||
|
||||
기대: 토큰 B의 성공으로 A의 대기를 완료하면 안 된다. 최신 레코드와 비교해 토큰 변경을 거부하거나 A의 실제 실패를 구분해야 한다.
|
||||
|
||||
실제: 초기 레코드는 A/running이고 최종 레코드는 B/completed인데, `pending=[]`, `terminal={job_id: "completed"}`가 된다. 출력도 `completed [disk-fallback]`이다. 이전 시도가 실패했는데도 호출자가 성공으로 종료할 수 있다.
|
||||
|
||||
탐침 출력 발췌:
|
||||
|
||||
```text
|
||||
RACE {"initial_status": "running", "initial_token": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "latest_token": "7b761cb2ea2d4514bbd631467997ad9d", "pending": [], "terminal": {"5b5cd6e8": "completed"}}
|
||||
```
|
||||
|
||||
위 토큰은 임시 테스트 잡의 식별자이다. 이전 감사 상태를 거부하는 현재 회귀 테스트는 통과하지만, **레코드를 읽은 이후 다음 attempt의 더 새로운 감사 상태가 도착하는 경우**는 검사하지 않는다. seq는 최신성만 나타내며 dispatch identity를 보장하지 않는다.
|
||||
|
||||
수정 방향: 감사 상태를 채택하기 직전에 레지스트리를 다시 읽어 token/attempt/seq가 같은 관찰에 속하는지 확인하고 변경 시 재시도/거부한다. 또는 레지스트리 lock 아래에서 일관된 관찰을 보장한다. 감사 상태에 dispatch identity를 기록·검증하는 방식도 가능하다. 단순히 `aud_seq >= rec_seq`만으로 다른 시도의 상태를 채택하지 않도록 위 interleaving을 회귀 테스트로 추가한다.
|
||||
|
||||
## R2 — [P2] 레지스트리 자체가 심볼릭 링크이면 submit과 logs의 로그 경로가 갈라진다
|
||||
|
||||
근거:
|
||||
|
||||
- `.agents/skills/lib_py/delegation/mqtt_common.py:189`: sibling 계산은 `abspath`만 사용한다.
|
||||
- `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:84`: submit의 `_abs_registry_dir`는 `cd -P`/`pwd -P`로 링크를 해석한다.
|
||||
- 같은 파일 `:280`: 물리 경로로 바꾼 다음 로그 루트를 계산한다.
|
||||
- 같은 파일 `:1108`: logs는 전달받은 링크 경로 그대로 registry CLI에 넘긴다.
|
||||
- `.agents/skills/lib_py/control_plane.py:34`, `:52`: control-plane binding도 realpath로 정규화하므로 직접 Python resolver와 다를 수 있다.
|
||||
|
||||
재현 절차(모두 임시 디렉터리 안에서, DELEGATE_JOB_LOGS_DIR 없이):
|
||||
|
||||
1. `<tmp>/actual/jobs`와 `<tmp>/alias`를 만들고 `<tmp>/alias/jobs -> <tmp>/actual/jobs` 심볼릭 링크를 만든다.
|
||||
2. cwd와 WORKSPACE_ROOT를 `<tmp>`로 둔다.
|
||||
3. `/bin/bash <review>/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job submit --dry-run --agent human --registry-dir <tmp>/alias/jobs --workdir <tmp> --prompt symlink-wrapper` 실행.
|
||||
4. 생성된 job ID로 같은 wrapper의 `logs <id> --registry-dir <tmp>/alias/jobs` 실행.
|
||||
|
||||
기대: 동일한 registry 인수로 만든 잡의 감사 로그를 조회할 수 있어야 한다.
|
||||
|
||||
실제: submit은 rc 0이며 `<tmp>/actual/delegate_job_logs/<id>`에 감사 로그를 생성하지만, logs는 `<tmp>/alias/delegate_job_logs`에서 찾아 rc 1로 실패한다.
|
||||
|
||||
```text
|
||||
job_id: a6cdb85e
|
||||
submit_log_root: /private/tmp/review-863e2edf-m7fmcbsw/probes-igd0iy09/actual/delegate_job_logs/a6cdb85e
|
||||
actual_audit_exists: true
|
||||
logs_rc: 1
|
||||
no audit log for job a6cdb85e under /tmp/review-863e2edf-m7fmcbsw/probes-igd0iy09/alias/delegate_job_logs
|
||||
```
|
||||
|
||||
별도 직접 registry 호출도 같은 레코드를 가리키면서 alias 옆에 로그를 쓰고, control_plane.from_env는 actual 옆을 기록함을 확인했다. `/tmp -> /private/tmp`처럼 **부모 전체가 같은 위치를 가리키는 별칭**은 통과하며, 이 문제는 **jobs 디렉터리 자체가 다른 부모 아래로 연결되는 링크**에서 발생한다.
|
||||
|
||||
수정 방향: 기본 감사 경로를 산출하기 전에 registry 경로를 정규화하는 정책을 중앙 resolver에 통일한다. 예를 들어 registry의 realpath에서 sibling을 구하면 submit/control_plane과 일치한다. 명시적 DELEGATE_JOB_LOGS_DIR 우선권은 유지한다. `submit --dry-run` 후 동일한 symlink 인수로 `logs`를 조회하는 회귀 테스트를 추가한다.
|
||||
|
||||
## 검증 결과 및 범위
|
||||
|
||||
- 실제 Mac Python 3.9.6에서 변경된 Python 13개 파일 AST 파싱 통과. 변경된 shell/wrapper 5개를 각각 `/bin/bash -n`으로 검사하여 통과. Bash는 `3.2.57(1)-release (arm64-apple-darwin25)`이다.
|
||||
- 관련 pytest 77건 통과: `test_issue57_logs_follow_registry.py`, `test_issue15_registry_isolation.py`, `test_issue12_registry_secrets.py`, `test_issue21_worktree_prereqs.py`, `test_issue21_acceptance.py` 전체; `test_tier1_unit.py::test_b9_logs_dir_follows_cwd_changes`; issue18의 subscriber state/seq/liveness 및 remote redispatch 관련 4건.
|
||||
- 실행은 지정 venv Python, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 검증 트리 밖 `/tmp/review-863e2edf-m7fmcbsw/pytest` basetemp를 사용했다. 지정된 HERDR/WORKSPACE_ROOT/MAM/DELEGATE 환경변수를 `env -u`로 제거했고, HOME도 임시 위치로 격리했다. create/resume은 mock fixture를 사용했다. 실제 에이전트/monitor/herdr 서버를 시작하지 않았다.
|
||||
- 일반 기본 경로, 명시적 로그 override, 상대 registry, 이동 registry, control-plane row 및 prefix, worktree create/resume, audit-only status, 오래된 감사 seq 거부는 관련 테스트로 검증했다.
|
||||
- 추가 Mac 부모 경로 별칭 탐침(`/tmp`와 `/var`): 정규화된 binding에서 sibling 로그 키를 전달하지 않는 동작 통과.
|
||||
- `git diff --check base HEAD` 통과. 검증 후 HEAD/tree 일치 및 clean git status 확인. 소스/문서 변경 없음.
|
||||
- 전체 스위트는 이 리뷰에서 실행하지 않았다(브리프에 따라 오케스트레이터가 별도 실행). 알려진 purge 모듈도 이번 선택에 포함되지 않았다. 선택 테스트에서는 #55 가드 오탐이나 teardown 오류가 없었다.
|
||||
- 테스트의 Linux 결과를 Mac 결과로 대신 인용하지 않았다. 검증 트리 바깥 creator 소스에서 테스트를 실행하지 않았다.
|
||||
|
||||
재현 스크립트: `/tmp/review-863e2edf-m7fmcbsw/probes.py` (실제 대상 모듈 import, 임시 디렉터리 안에서 실행).
|
||||
탐침 로그: `/tmp/review-863e2edf-m7fmcbsw/probes.log`.
|
||||
선택 테스트 로그: `/tmp/review-863e2edf-m7fmcbsw/pytest.log`.
|
||||
재실행: 검증 트리 cwd에서 `PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python /tmp/review-863e2edf-m7fmcbsw/probes.py`. 스크립트가 제어 평면 관련 환경을 제거하고 cwd/WORKSPACE_ROOT/HOME을 새 임시 디렉터리로 설정한다.
|
||||
|
||||
[VERDICT: NOT PASS]
|
||||
Reference in new issue
Block a user