레지스트리를 --registry-dir(또는 MAM_JOBS_DIR)로 다른 곳에 두어도 감사 로그는 따라가지 않는다. 그 결과 레지스트리 레코드가 없는 감사 로그 디렉터리가 실제 제어 평면의 .mam/delegate_job_logs/ 에 남는다. 테스트·재현·실험을 격리했다고 믿은 실행이 실제 제어 평면을 더럽히고, #20 의 control-plane 가드가 이를 "audit log without a registry record" 로 잡는다.
관측
날짜
실행
결과
2026-10-06
오케스트레이터의 #17 항목 4 옛/새 래퍼 비교. submit --dry-run --registry-dir <임시> 를 179 회
감사 로그 88 개가 테스트 중이던 트리의 .mam/delegate_job_logs 에 생겼다. 그 트리의 전체 스위트 teardown 이 test_workspace_scope ERROR 로 잡았다
실제 .mam/delegate_job_logs 에 감사 전용 디렉터리 5 개가 생겼다. #20 가드가 잡았고 codex 가 직접 지웠다
2026-10-11
이전 리뷰 하네스
실제 .mam/delegate_job_logs 에 ffffffff·00000001·11111111·22222222·33333333 이 남았다(prompt "test"/"review boundary", herdr:s/herdr:t, 레지스트리 레코드 없음). claude 리뷰어가 보고했고 오케스트레이터가 확인한 뒤 지웠다
모두 사람이 아니라 에이전트·오케스트레이터가 "레지스트리를 격리했으니 안전하다"고 판단한 실행이었다.
원인
감사 로그 루트는 레지스트리 위치와 무관하게 정해진다.
Python: lib_py/delegation/mqtt_common.py:185 get_logs_dir() 은 DELEGATE_JOB_LOGS_DIR → $WORKSPACE_ROOT/.mam/delegate_job_logs → <cwd>/.mam/delegate_job_logs 순이다.
래퍼: multi-agent-mux-delegate-job 344·352·364행 등은 ${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs} 다.
registry.py logs --logs-dir 의 도움말도 같은 기본값을 쓴다.
--registry-dir 는 레지스트리만 옮긴다. 감사 로그를 옮기려면 DELEGATE_JOB_LOGS_DIR 를 따로 줘야 하는데, 이 사실을 모르면 레지스트리 없는 감사 로그가 실제 제어 평면에 생긴다.
submit --dry-run 도 잡을 등록하고 감사 로그를 남긴다. 이름과 달리 영속 기록을 만들기 때문에 같은 문제가 커진다.
제안 — 코드로 강제
감사 로그는 레지스트리를 따라간다.--registry-dir·MAM_JOBS_DIR 로 레지스트리를 옮겼는데 DELEGATE_JOB_LOGS_DIR 가 없으면, 감사 로그 루트를 그 레지스트리에 딸린 위치로 정한다. 정확한 위치(예: 레지스트리 디렉터리의 형제 delegate_job_logs)는 계획서에서 정한다.
해석 규칙은 한 곳(Python get_logs_dir)에서 정하고, 래퍼 bash 의 기본값 중복을 없앤다(#17 경계 원칙).
registry.py logs·status·래퍼 출력 경로가 같은 규칙을 쓴다.
DELEGATE_JOB_LOGS_DIR 를 명시하면 지금처럼 그 값이 이긴다.
레지스트리 없는 감사 로그를 만들지 않는다. 감사 로그를 쓰는 쪽이, 그 잡의 레지스트리 레코드가 같은 짝 위치에 있는지 확인하는 가드를 둘지 계획서에서 정한다.
--dry-run 의 의미를 정한다. 지금처럼 등록·감사 로그를 남길지, 아니면 아무 영속 기록도 남기지 않을지 정한다. 바꾸면 SKILL.md 표와 #42 인벤토리를 함께 고친다.
회귀 테스트.
--registry-dir <임시> 로 submit·dry-run·wait·logs 를 돌린 뒤 실제(또는 샌드박스의 기본) .mam/delegate_job_logs 에 아무것도 생기지 않아야 한다.
## 증상
레지스트리를 `--registry-dir`(또는 `MAM_JOBS_DIR`)로 다른 곳에 두어도 감사 로그는 따라가지 않는다. 그 결과 **레지스트리 레코드가 없는 감사 로그 디렉터리**가 실제 제어 평면의 `.mam/delegate_job_logs/` 에 남는다. 테스트·재현·실험을 격리했다고 믿은 실행이 실제 제어 평면을 더럽히고, #20 의 control-plane 가드가 이를 "audit log without a registry record" 로 잡는다.
## 관측
| 날짜 | 실행 | 결과 |
|---|---|---|
| 2026-10-06 | 오케스트레이터의 #17 항목 4 옛/새 래퍼 비교. `submit --dry-run --registry-dir <임시>` 를 179 회 | 감사 로그 88 개가 테스트 중이던 트리의 `.mam/delegate_job_logs` 에 생겼다. 그 트리의 전체 스위트 teardown 이 `test_workspace_scope` ERROR 로 잡았다 |
| 2026-10-11 | codex 리뷰어의 #50 재현 하네스(리포트 `.mam/jobs/aa1bff92/codex-reports/report-final.md` 49행) | 실제 `.mam/delegate_job_logs` 에 감사 전용 디렉터리 5 개가 생겼다. #20 가드가 잡았고 codex 가 직접 지웠다 |
| 2026-10-11 | 이전 리뷰 하네스 | 실제 `.mam/delegate_job_logs` 에 `ffffffff`·`00000001`·`11111111`·`22222222`·`33333333` 이 남았다(prompt "test"/"review boundary", `herdr:s`/`herdr:t`, 레지스트리 레코드 없음). claude 리뷰어가 보고했고 오케스트레이터가 확인한 뒤 지웠다 |
모두 사람이 아니라 에이전트·오케스트레이터가 "레지스트리를 격리했으니 안전하다"고 판단한 실행이었다.
## 원인
- 감사 로그 루트는 레지스트리 위치와 무관하게 정해진다.
- Python: `lib_py/delegation/mqtt_common.py:185 get_logs_dir()` 은 `DELEGATE_JOB_LOGS_DIR` → `$WORKSPACE_ROOT/.mam/delegate_job_logs` → `<cwd>/.mam/delegate_job_logs` 순이다.
- 래퍼: `multi-agent-mux-delegate-job` 344·352·364행 등은 `${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}` 다.
- `registry.py logs --logs-dir` 의 도움말도 같은 기본값을 쓴다.
- `--registry-dir` 는 레지스트리만 옮긴다. 감사 로그를 옮기려면 `DELEGATE_JOB_LOGS_DIR` 를 따로 줘야 하는데, 이 사실을 모르면 레지스트리 없는 감사 로그가 실제 제어 평면에 생긴다.
- `submit --dry-run` 도 잡을 등록하고 감사 로그를 남긴다. 이름과 달리 영속 기록을 만들기 때문에 같은 문제가 커진다.
## 제안 — 코드로 강제
1. **감사 로그는 레지스트리를 따라간다.** `--registry-dir`·`MAM_JOBS_DIR` 로 레지스트리를 옮겼는데 `DELEGATE_JOB_LOGS_DIR` 가 없으면, 감사 로그 루트를 그 레지스트리에 딸린 위치로 정한다. 정확한 위치(예: 레지스트리 디렉터리의 형제 `delegate_job_logs`)는 계획서에서 정한다.
- 해석 규칙은 **한 곳**(Python `get_logs_dir`)에서 정하고, 래퍼 bash 의 기본값 중복을 없앤다(#17 경계 원칙).
- `registry.py logs`·`status`·래퍼 출력 경로가 같은 규칙을 쓴다.
- `DELEGATE_JOB_LOGS_DIR` 를 명시하면 지금처럼 그 값이 이긴다.
2. **레지스트리 없는 감사 로그를 만들지 않는다.** 감사 로그를 쓰는 쪽이, 그 잡의 레지스트리 레코드가 같은 짝 위치에 있는지 확인하는 가드를 둘지 계획서에서 정한다.
3. **`--dry-run` 의 의미를 정한다.** 지금처럼 등록·감사 로그를 남길지, 아니면 아무 영속 기록도 남기지 않을지 정한다. 바꾸면 SKILL.md 표와 #42 인벤토리를 함께 고친다.
4. **회귀 테스트.**
- `--registry-dir <임시>` 로 submit·dry-run·wait·logs 를 돌린 뒤 실제(또는 샌드박스의 기본) `.mam/delegate_job_logs` 에 아무것도 생기지 않아야 한다.
- 기존 #20 가드를 이 경우까지 덮도록 확인한다.
## 비범위
- 운영 기본 경로(`$WORKSPACE_ROOT/.mam/jobs`, `$WORKSPACE_ROOT/.mam/delegate_job_logs`)는 바꾸지 않는다. 레지스트리를 옮기지 않은 평소 실행은 그대로다.
## 관련
- #15: 레지스트리·감사 로그 기본 경로
- #20: control-plane 가드
- #55: 테스트가 실제 홈에 쓰는 문제(같은 종류의 격리 누수)
래퍼 multi-agent-mux-delegate-job 에 ${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs} 가 16곳 있습니다. 이 경로는 실제로 쓰이지 않습니다(.mam 이 빠져 있음). 그런데도 submit 의 마지막 줄로 출력됩니다.
_logs_dir_abs 를 $REGISTRY_DIR 로 한 번 해석해 그 값을 쓰고, 자식에게 export 하면 정리됩니다.
래퍼는 set -u 이므로, 해당 변수를 쓰는 곳이 모두 submit 경로 안인지 확인해야 합니다(_registry_wait 는 submit 에서만 호출).
래퍼 logs 서브커맨드는 --registry-dir 를 받지 않습니다.
lib.sh: delegate_publish_cmd 의 get_logs_dir() 호출, delegate_started_recorded 의 bash 기본값.
lib_py/loop/runner.py_fingerprint 의 stale-review 제외 경로 (DELEGATE_JOB_LOGS_DIR or mam_dir/delegate_job_logs).
4. 놓치기 쉬운 지점 (리뷰 NOT PASS 사유)
세션 바인딩이 옛 경로를 명시값으로 굳힌다 (codex R1, grok)
lib_py/control_plane.pyfrom_env() 는 defaults() 의 $WORKSPACE_ROOT/.mam/delegate_job_logs 에서 시작해, MAM_JOBS_DIR 만 따로 덮어씁니다.
그래서 MAM_JOBS_DIR 만 옮긴 상태로 세션을 만들면 옛 로그 경로가 행에 기록되고, prefix() 를 거쳐 DELEGATE_JOB_LOGS_DIR 로 pane 에 전달됩니다. 결국 자식은 다시 실제 .mam/delegate_job_logs 에 씁니다.
create_session.sh (바인딩 저장), update_yaml_resumed.sh, stop_session.sh (행의 값을 export) 도 이 값을 그대로 씁니다.
고칠 방향: 로그 env 가 없으면 해석된 mam_jobs_dir 의 형제를 쓰면 됩니다.
worktree_prepare.sh (codex R1, grok)
MAM_JOBS_DIR 는 보존하면서 DELEGATE_JOB_LOGS_DIR 는 무조건 $MAIN/.mam/delegate_job_logs 로 export 합니다.
MAM_JOBS_DIR 의 형제를 기본값으로 써야 합니다.
status.sh 가 레지스트리 옆 감사 로그를 보지 않는다 (codex R2, grok, claude)
get_job_status 두 구현(JSON·텍스트, 약 166–176행·374–384행)의 후보에 MAM_JOBS_DIR·행 mam_jobs_dir 의 형제 delegate_job_logs/<id>/status.json 이 없습니다.
그래서 레지스트리 레코드가 정리된 뒤에는 unknown 으로 보입니다.
옛 규칙을 단언하는 기존 테스트 2개 (claude F1·F2, codex R3)
conftest autouse _isolate_mam_state_dirs 가 모든 테스트에 MAM_JOBS_DIR 를 주므로, 새 규칙에서는 아래 두 테스트가 실패합니다.
각 테스트에 monkeypatch.delenv("MAM_JOBS_DIR", raising=False) 를 추가하면 됩니다.
(L3) 래퍼 submit --dry-run·wait·logs (출력 경로가 실제로 쓰인 경로와 같은지 포함)
(L4) publish_event.py --registry-dir
마지막에 ws/.mam/delegate_job_logs 가 없음을 단언합니다.
시험 수정에서는 이 4개가 수정 전 트리에서 모두 실패하고, 수정 후에는 통과했습니다.
여기에 control_plane.from_env(MAM_JOBS_DIR 만 이동)와 status.sh 감사 로그 전용 조회 테스트를 더하면 4절의 누락까지 잡습니다.
## 참고: 시험 수정과 리뷰에서 확인한 놓치기 쉬운 지점
이 이슈를 시험 삼아 고쳐 보고(브랜치는 폐기) 4인 리뷰를 받았습니다. 결과는 PASS 1, NOT PASS 3입니다. 실제로 수정할 때 참고할 내용을 정리합니다.
### 1. 규칙 하나로 기본 경로를 유지하면서 고칠 수 있다
- 규칙: `DELEGATE_JOB_LOGS_DIR` 가 없으면 감사 로그 루트 = **사용 중인 레지스트리 디렉터리의 형제 `delegate_job_logs`**.
- 레지스트리는 `--registry-dir` 인자, 없으면 `default_registry_dir()` (`MAM_JOBS_DIR` → `$WORKSPACE_ROOT/.mam/jobs` → `<cwd>/.mam/jobs`) 입니다.
- 기본 레지스트리 `.mam/jobs` 의 형제는 기존 `.mam/delegate_job_logs` 와 같습니다. 그래서 평소 실행은 바뀌지 않습니다.
- 리뷰어 확인: `WORKSPACE_ROOT` 와 cwd 가 다를 때, 상대 `MAM_JOBS_DIR` 일 때 모두 기존과 같았습니다.
- `mqtt_common.get_logs_dir(registry_dir=None)` 한 곳에서 이 규칙을 정하면 됩니다. `DELEGATE_JOB_LOGS_DIR` 명시값은 지금처럼 우선합니다.
### 2. 쓰기·읽기 지점: `get_logs_dir()` 만 고쳐서는 부족하다
대부분의 쓰기 호출이 `logs_dir=None` 으로 부르고, `--registry-dir` 는 CLI 인자라 resolver 가 알 수 없습니다. 레지스트리를 알고 있는 쪽이 `logs_dir=get_logs_dir(registry_dir)` 를 넘겨야 합니다.
- `registry.register_job` (`init_job_log`), `_pick_pending_legacy`
- `mqtt_common._update_job_status_legacy`, `engine._flush` (상태 미러, `conflict_refused` 포함)
- `publish_event.record_outcome`
- `job_subscriber` (`_Watcher` 의 `append_event` 3곳, 재접속 이벤트, `_check_disk_fallback` 읽기)
- `remote.publish_dispatch`
- `multi-agent-mux-monitor/scripts/reconcile.sh` 의 모니터 `append_event`
- 읽기: `registry.get_feedback`, `registry.py logs` (`--logs-dir` 도움말 문구 포함)
### 3. bash 쪽 중복 기본값
- 래퍼 `multi-agent-mux-delegate-job` 에 `${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}` 가 **16곳** 있습니다. 이 경로는 실제로 쓰이지 않습니다(`.mam` 이 빠져 있음). 그런데도 submit 의 마지막 줄로 출력됩니다.
- `_logs_dir_abs` 를 `$REGISTRY_DIR` 로 한 번 해석해 그 값을 쓰고, 자식에게 export 하면 정리됩니다.
- 래퍼는 `set -u` 이므로, 해당 변수를 쓰는 곳이 모두 submit 경로 안인지 확인해야 합니다(`_registry_wait` 는 submit 에서만 호출).
- 래퍼 `logs` 서브커맨드는 `--registry-dir` 를 받지 않습니다.
- `lib.sh`: `delegate_publish_cmd` 의 `get_logs_dir()` 호출, `delegate_started_recorded` 의 bash 기본값.
- `lib_py/loop/runner.py` `_fingerprint` 의 stale-review 제외 경로 (`DELEGATE_JOB_LOGS_DIR or mam_dir/delegate_job_logs`).
### 4. 놓치기 쉬운 지점 (리뷰 NOT PASS 사유)
- **세션 바인딩이 옛 경로를 명시값으로 굳힌다** (codex R1, grok)
- `lib_py/control_plane.py` `from_env()` 는 `defaults()` 의 `$WORKSPACE_ROOT/.mam/delegate_job_logs` 에서 시작해, `MAM_JOBS_DIR` 만 따로 덮어씁니다.
- 그래서 `MAM_JOBS_DIR` 만 옮긴 상태로 세션을 만들면 옛 로그 경로가 행에 기록되고, `prefix()` 를 거쳐 `DELEGATE_JOB_LOGS_DIR` 로 pane 에 전달됩니다. 결국 자식은 다시 실제 `.mam/delegate_job_logs` 에 씁니다.
- `create_session.sh` (바인딩 저장), `update_yaml_resumed.sh`, `stop_session.sh` (행의 값을 export) 도 이 값을 그대로 씁니다.
- 고칠 방향: 로그 env 가 없으면 해석된 `mam_jobs_dir` 의 형제를 쓰면 됩니다.
- **`worktree_prepare.sh`** (codex R1, grok)
- `MAM_JOBS_DIR` 는 보존하면서 `DELEGATE_JOB_LOGS_DIR` 는 무조건 `$MAIN/.mam/delegate_job_logs` 로 export 합니다.
- `MAM_JOBS_DIR` 의 형제를 기본값으로 써야 합니다.
- **`status.sh` 가 레지스트리 옆 감사 로그를 보지 않는다** (codex R2, grok, claude)
- `get_job_status` 두 구현(JSON·텍스트, 약 166–176행·374–384행)의 후보에 `MAM_JOBS_DIR`·행 `mam_jobs_dir` 의 형제 `delegate_job_logs/<id>/status.json` 이 없습니다.
- 그래서 레지스트리 레코드가 정리된 뒤에는 `unknown` 으로 보입니다.
- **옛 규칙을 단언하는 기존 테스트 2개** (claude F1·F2, codex R3)
- conftest autouse `_isolate_mam_state_dirs` 가 모든 테스트에 `MAM_JOBS_DIR` 를 주므로, 새 규칙에서는 아래 두 테스트가 실패합니다.
- 각 테스트에 `monkeypatch.delenv("MAM_JOBS_DIR", raising=False)` 를 추가하면 됩니다.
- `tests/test_issue15_registry_isolation.py::test_logs_dir_follows_the_same_order`
- `tests/test_tier1_unit.py::test_b9_logs_dir_follows_cwd_changes` (`LOGS_DIR` 별칭 단언 포함)
### 5. 이번 변경 전부터 있던 문제 (범위 밖, grok 보고)
- `job_subscriber._check_disk_fallback` 은 `read_logged_status()` 가 돌려준 **dict** 를 문자열 튜플과 비교합니다(`disk_status in ("completed", ...)`).
- 그래서 감사 로그에만 남은 종료 상태는 디스크 폴백에서 인식되지 않습니다.
### 6. 이슈의 결정 항목 (시험 수정에서 고른 방향)
- **`--dry-run`:** 의미를 유지했습니다(등록과 감사 로그 시드). 형제 규칙 덕분에 둘이 같은 위치에 생깁니다. #50 의 u1e 테스트가 dry-run 레코드의 존재를 전제합니다.
- **쓰기 시 레지스트리 레코드 존재 가드:** 두지 않았습니다. 감사 로그는 원래 레지스트리 정리 후에도 남도록 설계됐고, 실제 루트에 생기는 레지스트리 없는 감사 로그는 #20 가드(`tests/_mam_state_guard.py`)가 이미 잡습니다.
### 7. 회귀 테스트 구성 예
- 픽스처에서 `DELEGATE_JOB_LOGS_DIR`·`MAM_JOBS_DIR` 를 **지워야** 합니다. 그러지 않으면 conftest 기본값에 가려 폴백 경로가 검증되지 않습니다.
- 그 위에 `WORKSPACE_ROOT=<tmp>/ws`(`.mam` 생성), 레지스트리는 `<tmp>/elsewhere/jobs`, cwd 는 ws 로 둡니다.
- 확인할 것:
- (L1) resolver 규칙
- (L2) `registry.py --registry-dir … register/pick/close/logs`
- (L3) 래퍼 `submit --dry-run`·`wait`·`logs` (출력 경로가 실제로 쓰인 경로와 같은지 포함)
- (L4) `publish_event.py --registry-dir`
- 마지막에 `ws/.mam/delegate_job_logs` 가 **없음**을 단언합니다.
- 시험 수정에서는 이 4개가 수정 전 트리에서 모두 실패하고, 수정 후에는 통과했습니다.
- 여기에 `control_plane.from_env`(`MAM_JOBS_DIR` 만 이동)와 `status.sh` 감사 로그 전용 조회 테스트를 더하면 4절의 누락까지 잡습니다.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
증상
레지스트리를
--registry-dir(또는MAM_JOBS_DIR)로 다른 곳에 두어도 감사 로그는 따라가지 않는다. 그 결과 레지스트리 레코드가 없는 감사 로그 디렉터리가 실제 제어 평면의.mam/delegate_job_logs/에 남는다. 테스트·재현·실험을 격리했다고 믿은 실행이 실제 제어 평면을 더럽히고, #20 의 control-plane 가드가 이를 "audit log without a registry record" 로 잡는다.관측
submit --dry-run --registry-dir <임시>를 179 회.mam/delegate_job_logs에 생겼다. 그 트리의 전체 스위트 teardown 이test_workspace_scopeERROR 로 잡았다.mam/jobs/aa1bff92/codex-reports/report-final.md49행).mam/delegate_job_logs에 감사 전용 디렉터리 5 개가 생겼다. #20 가드가 잡았고 codex 가 직접 지웠다.mam/delegate_job_logs에ffffffff·00000001·11111111·22222222·33333333이 남았다(prompt "test"/"review boundary",herdr:s/herdr:t, 레지스트리 레코드 없음). claude 리뷰어가 보고했고 오케스트레이터가 확인한 뒤 지웠다모두 사람이 아니라 에이전트·오케스트레이터가 "레지스트리를 격리했으니 안전하다"고 판단한 실행이었다.
원인
lib_py/delegation/mqtt_common.py:185 get_logs_dir()은DELEGATE_JOB_LOGS_DIR→$WORKSPACE_ROOT/.mam/delegate_job_logs→<cwd>/.mam/delegate_job_logs순이다.multi-agent-mux-delegate-job344·352·364행 등은${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}다.registry.py logs --logs-dir의 도움말도 같은 기본값을 쓴다.--registry-dir는 레지스트리만 옮긴다. 감사 로그를 옮기려면DELEGATE_JOB_LOGS_DIR를 따로 줘야 하는데, 이 사실을 모르면 레지스트리 없는 감사 로그가 실제 제어 평면에 생긴다.submit --dry-run도 잡을 등록하고 감사 로그를 남긴다. 이름과 달리 영속 기록을 만들기 때문에 같은 문제가 커진다.제안 — 코드로 강제
--registry-dir·MAM_JOBS_DIR로 레지스트리를 옮겼는데DELEGATE_JOB_LOGS_DIR가 없으면, 감사 로그 루트를 그 레지스트리에 딸린 위치로 정한다. 정확한 위치(예: 레지스트리 디렉터리의 형제delegate_job_logs)는 계획서에서 정한다.get_logs_dir)에서 정하고, 래퍼 bash 의 기본값 중복을 없앤다(#17 경계 원칙).registry.py logs·status·래퍼 출력 경로가 같은 규칙을 쓴다.DELEGATE_JOB_LOGS_DIR를 명시하면 지금처럼 그 값이 이긴다.--dry-run의 의미를 정한다. 지금처럼 등록·감사 로그를 남길지, 아니면 아무 영속 기록도 남기지 않을지 정한다. 바꾸면 SKILL.md 표와 #42 인벤토리를 함께 고친다.--registry-dir <임시>로 submit·dry-run·wait·logs 를 돌린 뒤 실제(또는 샌드박스의 기본).mam/delegate_job_logs에 아무것도 생기지 않아야 한다.비범위
$WORKSPACE_ROOT/.mam/jobs,$WORKSPACE_ROOT/.mam/delegate_job_logs)는 바꾸지 않는다. 레지스트리를 옮기지 않은 평소 실행은 그대로다.관련
참고: 시험 수정과 리뷰에서 확인한 놓치기 쉬운 지점
이 이슈를 시험 삼아 고쳐 보고(브랜치는 폐기) 4인 리뷰를 받았습니다. 결과는 PASS 1, NOT PASS 3입니다. 실제로 수정할 때 참고할 내용을 정리합니다.
1. 규칙 하나로 기본 경로를 유지하면서 고칠 수 있다
DELEGATE_JOB_LOGS_DIR가 없으면 감사 로그 루트 = 사용 중인 레지스트리 디렉터리의 형제delegate_job_logs.--registry-dir인자, 없으면default_registry_dir()(MAM_JOBS_DIR→$WORKSPACE_ROOT/.mam/jobs→<cwd>/.mam/jobs) 입니다..mam/jobs의 형제는 기존.mam/delegate_job_logs와 같습니다. 그래서 평소 실행은 바뀌지 않습니다.WORKSPACE_ROOT와 cwd 가 다를 때, 상대MAM_JOBS_DIR일 때 모두 기존과 같았습니다.mqtt_common.get_logs_dir(registry_dir=None)한 곳에서 이 규칙을 정하면 됩니다.DELEGATE_JOB_LOGS_DIR명시값은 지금처럼 우선합니다.2. 쓰기·읽기 지점:
get_logs_dir()만 고쳐서는 부족하다대부분의 쓰기 호출이
logs_dir=None으로 부르고,--registry-dir는 CLI 인자라 resolver 가 알 수 없습니다. 레지스트리를 알고 있는 쪽이logs_dir=get_logs_dir(registry_dir)를 넘겨야 합니다.registry.register_job(init_job_log),_pick_pending_legacymqtt_common._update_job_status_legacy,engine._flush(상태 미러,conflict_refused포함)publish_event.record_outcomejob_subscriber(_Watcher의append_event3곳, 재접속 이벤트,_check_disk_fallback읽기)remote.publish_dispatchmulti-agent-mux-monitor/scripts/reconcile.sh의 모니터append_eventregistry.get_feedback,registry.py logs(--logs-dir도움말 문구 포함)3. bash 쪽 중복 기본값
multi-agent-mux-delegate-job에${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}가 16곳 있습니다. 이 경로는 실제로 쓰이지 않습니다(.mam이 빠져 있음). 그런데도 submit 의 마지막 줄로 출력됩니다._logs_dir_abs를$REGISTRY_DIR로 한 번 해석해 그 값을 쓰고, 자식에게 export 하면 정리됩니다.set -u이므로, 해당 변수를 쓰는 곳이 모두 submit 경로 안인지 확인해야 합니다(_registry_wait는 submit 에서만 호출).logs서브커맨드는--registry-dir를 받지 않습니다.lib.sh:delegate_publish_cmd의get_logs_dir()호출,delegate_started_recorded의 bash 기본값.lib_py/loop/runner.py_fingerprint의 stale-review 제외 경로 (DELEGATE_JOB_LOGS_DIR or mam_dir/delegate_job_logs).4. 놓치기 쉬운 지점 (리뷰 NOT PASS 사유)
lib_py/control_plane.pyfrom_env()는defaults()의$WORKSPACE_ROOT/.mam/delegate_job_logs에서 시작해,MAM_JOBS_DIR만 따로 덮어씁니다.MAM_JOBS_DIR만 옮긴 상태로 세션을 만들면 옛 로그 경로가 행에 기록되고,prefix()를 거쳐DELEGATE_JOB_LOGS_DIR로 pane 에 전달됩니다. 결국 자식은 다시 실제.mam/delegate_job_logs에 씁니다.create_session.sh(바인딩 저장),update_yaml_resumed.sh,stop_session.sh(행의 값을 export) 도 이 값을 그대로 씁니다.mam_jobs_dir의 형제를 쓰면 됩니다.worktree_prepare.sh(codex R1, grok)MAM_JOBS_DIR는 보존하면서DELEGATE_JOB_LOGS_DIR는 무조건$MAIN/.mam/delegate_job_logs로 export 합니다.MAM_JOBS_DIR의 형제를 기본값으로 써야 합니다.status.sh가 레지스트리 옆 감사 로그를 보지 않는다 (codex R2, grok, claude)get_job_status두 구현(JSON·텍스트, 약 166–176행·374–384행)의 후보에MAM_JOBS_DIR·행mam_jobs_dir의 형제delegate_job_logs/<id>/status.json이 없습니다.unknown으로 보입니다._isolate_mam_state_dirs가 모든 테스트에MAM_JOBS_DIR를 주므로, 새 규칙에서는 아래 두 테스트가 실패합니다.monkeypatch.delenv("MAM_JOBS_DIR", raising=False)를 추가하면 됩니다.tests/test_issue15_registry_isolation.py::test_logs_dir_follows_the_same_ordertests/test_tier1_unit.py::test_b9_logs_dir_follows_cwd_changes(LOGS_DIR별칭 단언 포함)5. 이번 변경 전부터 있던 문제 (범위 밖, grok 보고)
job_subscriber._check_disk_fallback은read_logged_status()가 돌려준 dict 를 문자열 튜플과 비교합니다(disk_status in ("completed", ...)).6. 이슈의 결정 항목 (시험 수정에서 고른 방향)
--dry-run: 의미를 유지했습니다(등록과 감사 로그 시드). 형제 규칙 덕분에 둘이 같은 위치에 생깁니다. #50 의 u1e 테스트가 dry-run 레코드의 존재를 전제합니다.tests/_mam_state_guard.py)가 이미 잡습니다.7. 회귀 테스트 구성 예
DELEGATE_JOB_LOGS_DIR·MAM_JOBS_DIR를 지워야 합니다. 그러지 않으면 conftest 기본값에 가려 폴백 경로가 검증되지 않습니다.WORKSPACE_ROOT=<tmp>/ws(.mam생성), 레지스트리는<tmp>/elsewhere/jobs, cwd 는 ws 로 둡니다.registry.py --registry-dir … register/pick/close/logssubmit --dry-run·wait·logs(출력 경로가 실제로 쓰인 경로와 같은지 포함)publish_event.py --registry-dirws/.mam/delegate_job_logs가 없음을 단언합니다.control_plane.from_env(MAM_JOBS_DIR만 이동)와status.sh감사 로그 전용 조회 테스트를 더하면 4절의 누락까지 잡습니다.