Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-76ca3b1b.md
T
GodopuandClaude Opus 5.5 3563a6fc17 release(v4.8.2): bump framework and eight skills to 4.8.2 in lockstep; archive the follow-up loop's reports
PR #41/#43 review follow-ups F1-F6, implemented by creator-claude-01 in a
loop and reviewed by claude, agy, grok and codex: round 1 (@95eabc6)
codex NOT PASS (a failed env load cached as loaded), fixed in 75f364e;
round 2 4/4 PASS. Full suite at 75f364e 1380 passed / 0 failed. Reviewer
reports (round 2 with identity sidecars) and the creator's reports go to
.agents/reports/<session>/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-03 00:27:24 +09:00

113 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 🔍 Review Report — PR #41·#43 후속 `fix/followups-41-43` (job `76ca3b1b`)
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-10-02
- **대상**: `0051fd6..95eabc6` (8커밋, 27파일 `+397/−88`, `git diff --check` 클린), base `main 0051fd6`.
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20261001T230324Z-b511bf/1`. **identity 확인**: `HEAD = 95eabc69…d5f4f`, `HEAD^{tree} = 46399844…7998` — 브리프와 일치.
- **안전 규칙 준수**: 검증 경로·소스 워크트리(`multi-agent-mux-wt/followups`)·본 체크아웃 모두 수정·커밋·checkout 없음. 되돌린 사본·합성 파일·`ps` 대역은 scratchpad 에서만. 실제 `.mam.env` 를 읽거나 출력하지 않았고 export 하지 않았다. 실제 `.mam`·데몬·세션·herdr 조작 없음, push·merge·PR 상태 변경 없음.
## 결론 요약
**F1–F6 모두 완료 조건을 만족한다. 범위 밖 동작 변경은 없다.** 제품 코드를 바꾸는 항목(F2·F3·F4·F5)의 회귀는 제품 파일만 base 로 되돌린 사본에서 **47건 실패**한다(아래 §7). 테스트만 고치는 항목(F1·F6)은 base 제품 코드에서도 통과하는 것이 정상이며, F1 은 옛 테스트가 날짜에 따라 실패하던 원인을 `ps` 대역으로 직접 재현했다. 비차단 관찰 3건(T1–T3).
## F1 — A8 의 날짜 의존 (`tests/test_issue21_acceptance.py:258-300`)
- 제품 코드(`loop_lock.sh` 의 `mam_lstart`)는 **그대로**다. `loop_lock.sh` 의 diff 는 F4b 의 `MAM_LOCK_STEAL_RETRIES` 한 줄뿐이다.
- 테스트는 마커의 lstart 를 `" ".join(out.split())` 로 써서 제품의 `tr -s ' '` + 앞뒤 공백 제거와 같게 맞췄다.
- 날짜와 무관하게 고정하는 장치: `padded` 파라미터가 PATH 앞에 `ps` 대역을 둔다. 이 대역은 `lstart=` 질의에만 `Thu Oct 1 08:02:03 2026`(두 칸)을 돌려주고, 대역이 실제로 쓰였는지도 단언한다.
- **옛 버그 재현(직접)**: 같은 대역 아래에서 제품 `mam_lstart` 는 `Thu Oct 1 …`(한 칸), 옛 테스트 방식(`.strip()`)은 `Thu Oct 1 …`(두 칸)을 낸다 → 불일치 → 잠금이 "stale" 로 보여 두 번째 루프가 거부되지 않는다. 즉 옛 A8 이 1~9일에 실패한 원인이 정확히 이것이고, 새 `padded` 케이스는 매일 그 조건을 돈다.
- 같은 패턴의 다른 테스트: `test_o3_scoped_guard.py:_get_lstart` 는 이미 `" ".join(...split())` 로 정규화한다. `test_o2_race_free_lock.py:get_lstart` 는 정규화하지 않지만 **어디서도 호출되지 않는 죽은 헬퍼**다(T1, 지우거나 정규화 권고).
## F2 — 진입점의 한 줄 오류 (`mqtt_common.py:129-140` 외 7곳)
- 공통 헬퍼 `load_env_or_report()` 가 `EnvFileError` 를 잡아 `ERROR: <path>: cannot read (<type>)` 한 줄을 출력하고 False 를 돌려준다. 라이브러리 함수는 `sys.exit` 하지 않는다 — 진입점(`publish_event`·`registry`·`job_subscriber`·`cli`·`bench`·`heartbeat`·`remote` 의 `main`)이 `return 1` 한다. `job_subscriber` 는 `RC_ERROR`(=1)로 이름을 썼다.
- 값·파일 내용은 출력되지 않는다(예외 메시지가 경로와 예외 종류뿐).
- 회귀 `test_a1_…[7 진입점 × permission/utf8]` 14건이 base 에서 모두 실패한다.
- reconcile.sh 의 내장 구독자는 여전히 `ensure_env_loaded()` 를 직접 부른다(브리프 목록 밖, 문서화된 그대로).
## F3 — `.mam.env` 세부
- (a) BOM: `envfile.py:60-61` 이 `utf-8-sig` 로 연다. 공통 파서라 **두 로더 모두** 적용된다. 파일 맨 앞의 BOM 만 벗기므로 중간의 U+FEFF 는 그대로다.
- (b) 중복 키는 첫 값 우선: 동작은 그대로(`apply` 가 `if key not in environ`)이고 `.mam.env.example` FORMAT 절에 한 줄 추가했다.
- (c) `MESSAGING.md:369`: "명시한 파일이 없으면 오류" 를 "조용히 건너뜀(주변 탐색은 안 함), 있는데 못 읽으면 EnvFileError" 로 실제 동작에 맞췄다.
- 회귀 `test_a7_*` 3건이 base 에서 실패한다.
## F4 — 정수 노브
- (a) 러너(`runner.py:627`, `:630-643`, `:762-768`): `_int_knob` 이 `_positive_int` 규칙으로 읽고, 잘못된 값이면 노브당 한 번 경고하고 기본값을 쓴다. `MAM_CHILD_WAIT_S=abc` 의 `ValueError` 가 사라졌다.
- 최솟값의 의미도 맞다.
| 노브 | 최솟값 | 의미 |
|---|---|---|
| `MAM_WAIT_GRACE_S` | 0 | 예산만큼 정확히 대기 |
| `MAM_CHILD_WAIT_S` | 0 | 하위 잡을 한 번만 확인 |
| `MAM_JOB_TREE_MAX` | 1 | 0 이면 모든 트리가 "초과"; 대기하지 않으려면 `MAM_CHILD_WAIT_S=0` |
세 값 모두 표 `RUNNER_INT_KNOBS` 에 들어갔다.
- (b) bash 노브: `MAM_READY_EMPTY_GIVEUP`·`SKS_EMPTY_GIVEUP`(min 1), `SKS_IDLE_RETRIES`·`SKS_IDLE_RETRY_BACKOFF_S`(min 0), `MAM_LOCK_STEAL_RETRIES`(min 0), `MAM_DIFF_MAX_BYTES/LINES`(min 1), `RECONCILE_POLL_INTERVAL`(min 1 — 0 이면 바쁜 루프)가 `mam_int_knob` 을 거친다.
- `SKS_QUIESCENT_INTERVAL` 은 기본값이 소수(0.5)라 **그대로 두고 표에 사유**를 남겼다 — 브리프 지시대로다.
- `RECONCILE_POLL_INTERVAL` 은 저장소 안의 모든 사용처(테스트 `60`, 문서 15 s)가 정수라 의미가 깨지지 않는다.
- `diff_collect.sh`·`loop_lock.sh` 는 lib.sh 없이 source 되므로 규칙을 `lib_int.sh` 로 분리했다. lib.sh 도 같은 파일을 source 한다(`declare -F` 가드로 이중 정의를 막음). `deploy/remove.sh` 의 폴백 자산 목록에 `lib_int.sh` 를 추가한 것은 새 파일을 배포 제거 대상에 넣는 **필수 동반 변경**이다(범위 안). `test_issue21_loop_b.py` 의 한 줄도 L15 가 `diff_collect.sh` 를 복사해 쓰므로 필요한 동반 수정이다.
- (c) loop SKILL.md 의 Job budget 행에 `MAM_WAIT_GRACE_S`(기본 300, 0 이상, 잘못된 값은 경고 후 300)를 문서화했다.
- 회귀 `test_b1_runner_*`·`test_b5_*` 가 base 에서 실패한다.
## F5 — 테스트 스위치 제거 (`stall_watch.py`, `test_issue30_stall_watch.py`)
- `MAM_TEST_UNRELIABLE_AGENT` 두 줄을 삭제했다.
- S13 은 `-c` 진입점에서 어댑터 클래스의 `agent_status_reliable` 을 `False` 로 바꾼 뒤 **실제** `stall_watch.main` 을 돈다. 제품에는 분기가 없고, 검증하는 내용(idle 판정 끔, blocked 알림 유지)도 같다.
- 새 `test_s13_the_unreliable_case_needs_no_switch_in_the_product`(`MAM_TEST_` 문자열 부재)가 base 에서 실패한다.
## F6 — 문서·테스트 정밀도
- delegate-job SKILL.md:53 은 이제 "≤ 60 s 는 herdr wait 한정, 알림 **발행 중**에 래퍼가 사라지면 그 발행이 끝날 때까지" 라고 적는다 — 코드(`_wrapper_gone` 이 wait 직후와 `_may_prompt` 에만 있음)와 일치한다.
- "대기 중 래퍼 소멸" 테스트는 이제 **두 번째 wait 대역 안에서** `gone` 을 세우고, `ticks == [1, 2]` 와 마지막 alive 관측이 `(2, False)` 임을 단언한다. 이름 그대로를 강제한다.
- 테스트 정밀화라 base 제품 코드에서도 통과하는 것이 맞다(제품 동작은 PR #41 에서 이미 옳았다).
## 7. 테스트
### 7-1. 검증 경로에서의 실행
```
tests/test_issue26_envfile.py tests/test_issue26_ints.py tests/test_issue21_acceptance.py tests/test_issue30_stall_watch.py
tests/test_issue30_stall_watch_stop.py tests/test_tier1_unit.py tests/test_version_consistency.py tests/test_issue5_phase1_delegation.py
(.mam.env 미export, MQTT_* 미설정, followups .venv)
→ 335 passed, 1 skipped in 451.38s failed 0 (skip = S6, /proc)
```
브리프 목록 7파일에 `test_issue5_phase1_delegation.py`(registry CLI 스냅샷·help 캡처)를 더해, 진입점 변경이 CLI 계약을 깨지 않음을 확인했다. 로그에 외부 브로커 흔적 0건.
### 7-2. 수정 없이는 실패하는가 — 확인함
검증 경로 사본에서 `tests/` 밖의 바뀐 파일 전부를 `0051fd6` 판으로 되돌리고(새 `lib_int.sh` 는 삭제) 새·변경 테스트를 돌렸다:
```
47 failed, 88 passed in 110.20s
```
| 항목 | base 에서 실패한 회귀 |
|---|---|
| F2 | `test_a1_python_entry_points_report_an_unreadable_file_in_one_line` 14건(7 진입점 × 권한/UTF-8) |
| F3 | `test_a7_a_leading_bom_…`, `…_repeated_key_keeps_its_first_value…`, `…_missing_explicit_file_…` |
| F4 | `test_b1_runner_wait_grace/child_wait/job_tree_max_*`, `test_b5_*`(표·최솟값·lib.sh 없이 source 되는 두 스크립트·`_pane_quiescent`) |
| F5 | `test_s13_the_unreliable_case_needs_no_switch_in_the_product` |
| F1·F6 | 테스트만 고친 항목이라 base 제품 코드에서 통과하는 것이 정상. F1 은 §F1 의 `ps` 대역 실험으로 옛 테스트의 실패 원인을 직접 재현 |
### 7-3. 격리
실제 `.mam/jobs` 는 이 잡 자신의 파일 외 **추가 0**. 검증 경로 tracked 파일 변화 없음(HEAD `95eabc6`), 본 체크아웃 `main 0051fd6`.
## 8. 범위
바뀐 파일 27개를 모두 F1–F6 또는 그 필수 동반 변경으로 귀속할 수 있다.
- `lib_int.sh` 분리와 그에 따른 `deploy/remove.sh`·`test_issue21_loop_b.py` 의 한 줄은 F4b 의 동반 변경이다.
- #17 항목 4·#39·#40·#42 범위의 변경과 버전 bump 는 없다.
## 9. 판정
| # | 비차단 관찰 | 근거 |
|---|---|---|
| T1 | `test_o2_race_free_lock.py:get_lstart` 는 정규화하지 않는 **호출되지 않는** 헬퍼 — 지우거나 같은 정규화로 맞추면 재발 여지가 없다 | `tests/test_o2_race_free_lock.py:40-43` |
| T2 | reconcile.sh 의 내장 구독자는 `ensure_env_loaded()` 를 직접 불러 읽기 실패 시 여전히 트레이스백 | 브리프 진입점 목록 밖; 다음 정리 때 `load_env_or_report` 로 |
| T3 | (확인 완료, 조치 불필요) `deploy/install.sh` 는 트리 전체를 복사하므로(`cp -R "$REPO_URL/."`, :128) `lib_int.sh` 도 함께 배포된다. `check_assets_present`(:98-108)는 존재 확인용 목록이라 `lib.sh` 만 있어도 되지만, lib.sh 가 lib_int.sh 를 source 하므로 목록에 추가해 두면 부분 설치를 더 일찍 잡는다 | `deploy/install.sh:98-108` |
[VERDICT: PASS]