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
100 lines
8.2 KiB
Markdown
100 lines
8.2 KiB
Markdown
# 🔍 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` 클린), base `main 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 파일 변화 없음(HEAD `0eec795`), 본 체크아웃 `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. 교차검증 가이드
|
||
|
||
```bash
|
||
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]
|