Issue #26: one .mam.env parser for the wrapper and the Python tools, decimal integer knobs, tests on a local MQTT broker. Merge-workspace review: round 1 (@b58edac) claude agy PASS, codex NOT PASS (R1-R4), grok no verdict (its late report, archived as report-c9ed5b14.md, found R1 too); round 2 (@0eec795) 4/4 PASS. Reports and round-2 identity sidecars go to .agents/reports/<session>/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
8.2 KiB
🔍 Review Report — PR #43 2회차 델타 (job b9600325)
- Reviewer:
planner-reviewer-claude-01(role: planner,reviewer) · 2026-10-01 - 대상 델타:
b58edac..0eec795(1커밋, 7파일+128/−19,git diff --check클린), basemain e1eb22b(v4.8.0). - 검증 경로:
/var/folders/.../T/mam-review/loop-20261001T140030Z-34381f/1. identity 확인:HEAD = 0eec795c…731f,HEAD^{tree} = 584eac55…bacb— 브리프와 일치. - 1회차 내 판정: PASS + R1–R5 (job
645adb2a). 브리프 지시대로0eec795만 본다. - 안전 규칙 준수: 검증 경로·소스 워크트리·본 체크아웃 모두 수정·커밋·checkout 없음. 실험은 scratchpad 의 합성 env 파일·임시 등록부·래퍼
--dry-run(도달 불가 브로커)에서만. 실제.mam.env를 읽거나 출력하지 않았다. 테스트는.mam.env미export·MQTT_*미설정. 실제.mam·데몬·세션·herdr 조작 없음, push·merge·PR 상태 변경 없음.
결론 요약
네 수정(codex R1–R4)과 내 R1·R2 지적은 모두 닫혔고, 1회차 PASS 를 뒤집을 요소는 없다. 각 수정을 독립 실험으로 확인했다. 읽기 실패 시 Python 진입점이 멈추는 설계에는 동의한다(§5). 비차단 관찰 2건(S1·S2).
1. codex R1 — 읽을 수 없는 파일은 오류 (envfile.py:56-61, :104-109)
| 입력 | --shell 결과 |
|---|---|
| 권한 0000 | rc 1, stdout 없음, stderr ERROR: …: cannot read (PermissionError) |
| 깨진 UTF-8 | rc 1, cannot read (UnicodeDecodeError) |
| 파일 없음 | rc 0, 출력 없음(종전대로 — "설정 없음" 은 정상) |
래퍼는 rc≠0 에 ERROR: cannot load $TARGET_ENV (see the line above) + exit 1(multi-agent-mux-delegate-job:44) — 1회차 내 R2(메시지가 원인과 어긋남)도 닫혔다: 이제 위 줄이 실제 원인(PermissionError/UnicodeDecodeError)이다. 값은 어디에도 찍히지 않는다.
2. codex R4 — NUL 이 든 줄 (envfile.py:66-68)
A=1 / B=x\0y / C=3 파일: --shell 이 export A=1·export C=3 만 내고 WARNING: …:2: skipped (NUL byte). parse 가 두 로더의 공통 경로이므로 Python 로더도 같은 줄을 같은 경고로 건너뛴다 — 두 로더 일치.
3. codex R2 — 로케일과 무관한 ASCII 정수 (lib.sh:2349-2351)
C 와 en_US.UTF-8 두 로케일에서 bash mam_int 와 Python _positive_int 비교:
| 입력 | C | en_US.UTF-8 |
|---|---|---|
30 |
30 / 30 | 30 / 30 |
U+2003 30 U+2003(em space) |
거부 / 거부 | 거부 / 거부 (수정 전 bash 는 30) |
아랍 숫자 ٣٠ |
거부 / 거부 | 거부 / 거부 |
' 30\t' |
30 / 30 | 30 / 30 |
전각 30 |
거부 / 거부 | 거부 / 거부 |
10/10 일치. 공백 집합 $' \t\n\r\v\f' 는 Python strip(" \t\n\r\v\f") 와 같다.
4. codex R3 — 명시한 빈 플래그 (multi-agent-mux-delegate-job:167, :201)
래퍼 --dry-run 실측: --timeout '' → rc 1 ERROR: --timeout : not a positive decimal integer, --ack-timeout '' → rc 1, --timeout 0 → rc 1. 플래그를 주지 않으면 기본값(종전대로). 값 없이 --timeout 만 주면 set -u 의 unbound variable 로 rc 1 — 이 PR 이전부터의 동작이고 실패 방향이라 무해하다(S2).
5. 설계 질문: 읽기 실패에 Python 진입점이 멈추는 것이 맞는가
멈추는 쪽에 동의한다. 근거:
.mam.env가 실어 나르는 것은 브로커 주소·계정과MAM_DISPATCH_SECRET이다. 읽기에 실패한 채 "경고 후 계속" 하면 기본 브로커(공개broker.hivemq.com)로, 서명 없이 잡 내용을 발행하게 된다 — 예제 파일의 SECURITY NOTE 가 경고하는 바로 그 상황이다. 설정 파일이 있는데 못 읽은 것은 "설정 없음" 과 다르다.- 에이전트 세션 안의 영향(실측): 권한 0000 파일로
publish_event --event started를 돌리면 rc 1 로 끝나고 레코드는pending,events.log는 생기지 않는다. 즉 래퍼의started대기가 fail closed(agent_blocked)로 잡을 닫는다 — 조용히 엉뚱한 브로커로 가는 것보다 보이는 실패가 낫다. heartbeat 타이머·구독자도 같은 진입점이라 같은 식으로 멈춘다. - S1 (비차단): Python 진입점에서는
EnvFileError가 트레이스백으로 나온다(실측: Traceback 1개, 마지막 줄lib_py.delegation.envfile.EnvFileError: …: cannot read (PermissionError)). 원인은 명확하지만, 에이전트 pane 에 그대로 찍히는 출력이다.ensure_env_loaded를 부르는 진입점(또는ensure_env_loaded자체)이EnvFileError를 잡아ERROR: <msg>한 줄 +sys.exit(1)로 끝내면 래퍼의--shell과 같은 모양이 된다.
6. 문서 — 내 1회차 R1 (.mam.env.example:14-26)
머리말에 FORMAT 절이 생겼다: 한 파서·source 안 함, 공백·따옴표 한 쌍 제거, 확장 없음($HOME·${X}·$(cmd)·백틱 글자 그대로 → 절대경로로 쓸 것), 줄 끝 # ... 은 값의 일부, export·잘못된 키·= 없는 줄·NUL·bash 예약 변수는 줄 번호 경고 후 건너뜀, 환경변수가 파일보다 우선, 읽기 실패는 오류. 1회차에 지적한 래퍼 사용자 동작 변경 4가지가 모두 적혔다 — 닫힘.
7. 1회차 내 지적의 처리
| # | 1회차 지적 | 0eec795 |
|---|---|---|
| R1 | .mam.env.example 에 규칙·env-wins 미기재 |
닫힘 — FORMAT 절 |
| R2 | cannot read 메시지가 원인과 어긋남 |
닫힘 — 실제 읽기 실패가 rc 1 + 원인 줄, 래퍼는 "see the line above" |
| R3 | [ -ge ] 전용 노브의 비숫자 처리 |
열림(범위 밖, PR 이 분류표로 의도 처리) |
| R4 | purge 12건 기준 실패 미재현 | 열림(델타와 무관; §8 재확인) |
| R5 | mam_int 를 항목 4 이전 목록에 |
열림(후속) |
8. 테스트 (검증 방법)
tests/test_issue26_envfile.py tests/test_issue26_ints.py tests/test_local_mqtt.py tests/test_issue18_liveness_contract.py tests/test_tier1_unit.py
(.mam.env 미export, MQTT_* 미설정)
→ 174 passed in 210.56s failed 0 · skipped 0
- 로그에 공개 브로커·실제 브로커 IP 흔적 0건 — 외부 브로커 없이 통과.
- 새 회귀(권한 0000·깨진 UTF-8 에서 래퍼와 Python 로더가 멈춤, NUL 줄을 두 로더가 같게 건너뜀, 두 로케일의 유니코드 공백·숫자 거부, 다섯 정수 플래그의 빈 값 거부)는 위 §1–§4 의 내 독립 실험과 같은 결과를 고정한다.
- 기준 실패 주장 재확인:
tests/test_issue10_13_purge.py를 이 head 에서 단독 실행 → 23 passed(1회차의 PR·base 결과와 같음). 이 리뷰어 조건에서는 여전히 재현되지 않는다. - 격리: 실제
.mam/jobs는 이 잡 자신의 파일 외 추가 0, 검증 경로 tracked 파일 변화 없음(HEAD0eec795), 본 체크아웃main e1eb22b.
9. 판정
0eec795 는 지적된 네 결함을 정확히 닫고(각각 실험으로 재현→해소 확인), 새 bash 판정을 만들지 않았다(정수 규칙의 문자 집합을 명시하고 플래그 존재를 기록하는 것은 기존 입력 정제의 보강). 버전 락스텝 bump 는 판정에서 제외한다.
| # | 비차단 관찰 | 근거 |
|---|---|---|
| S1 | Python 진입점에서 EnvFileError 가 트레이스백으로 나온다 — 한 줄 ERROR: + rc 1 로 잡아 주면 래퍼와 같은 모양 |
mqtt_common.py:98-104 → 각 main 의 ensure_env_loaded(); §5 실측 |
| S2 | 값 없는 --timeout(마지막 인자) 은 unbound variable rc 1 — 종전 동작이고 실패 방향 |
래퍼 parse_opts 의 shift 2 |
10. 교차검증 가이드
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261001T140030Z-34381f/1
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-43/.venv/bin/python
git -C "$RP" rev-parse HEAD 'HEAD^{tree}' # 0eec795… / 584eac5…
"$PY" -I "$RP/.agents/skills/lib_py/delegation/envfile.py" --shell <권한0000 파일> # rc 1, cannot read (PermissionError)
printf 'A=1\nB=x\0y\nC=3\n' > n.env; "$PY" -I …/envfile.py --shell n.env # export A, C 만 + NUL 경고
LC_ALL=en_US.UTF-8: mam_int $' '30$' ' 1 → 거부 (_positive_int 도 거부)
[VERDICT: PASS]