release(v4.8.1): bump framework and eight skills to 4.8.1 in lockstep; archive PR #43 reviews (two rounds)
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
This commit is contained in:
1 parent
0eec795cd1
commit
2e90c809d3
28 files changed
+922
-26
No files matched your search
@@ -0,0 +1,111 @@
|
||||
# 🔍 Review Report — PR #43 `fix/issue-26` 리뷰 (job `645adb2a`)
|
||||
|
||||
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-10-01
|
||||
- **대상**: PR #43 @ `b58edac`, base `main e1eb22b` (v4.8.0). 6커밋, 17파일 `+1590/−94`, `git diff --check` 클린.
|
||||
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20261001T131705Z-a60092/1`. **identity 확인**: `HEAD = b58edace…4893`, `HEAD^{tree} = 14b670fb…6ffa` — 브리프와 일치.
|
||||
- **안전 규칙 준수**: 검증 경로·소스 워크트리·본 체크아웃(`main e1eb22b`) 모두 수정·커밋·checkout 없음. 실제 `.mam.env` 는 **값을 출력하지 않고** 형태만 셌다(`$`·`export`·인라인 `#`·따옴표 줄 수 = 모두 0). 파서 실험은 scratchpad 의 합성 파일로만. 테스트는 브리프대로 `.mam.env` 를 export 하지 않고(`MQTT_*` 미설정 확인) 돌렸다. 실제 `.mam`·데몬·세션·herdr 조작 없음, push·merge·PR 상태 변경 없음.
|
||||
- **참고(이 잡의 liveness)**: 이 잡의 `started` 발행 때 브로커 `100.69.147.175:1883` 이 연결을 거부했다(`Connection refused`, 지금도 포트 닫힘). `publish_event` 는 rc 2 였지만 레코드는 `running`·`events.log` 에 `started`(토큰 포함)가 기록됐고 heartbeat 타이머도 떠 있다 — #18 계약상 로컬 증거로 충분하다. 브로커 쪽은 오케스트레이터 확인이 필요하다(아래 판정과 무관).
|
||||
|
||||
## 결론 요약
|
||||
|
||||
**병합을 막을 결함은 없다.** 두 로더는 같은 입력에서 같은 결과를 내고(합성 파일 15줄 실측), 래퍼의 eval 경로는 어떤 값도 셸 해석하지 않으며(명령 치환 주입 실험: 실행 0), bash `mam_int` 와 Python `_positive_int` 는 15가지 입력에서 결과가 같다. 로컬 브로커로 옮긴 시험들은 skip 을 fail 로 바꾸고 양쪽 로그·감사 기록까지 단언해 **오히려 강해졌다**. 비차단 관찰 5건(R1–R5).
|
||||
|
||||
## 1. 파서 규칙 (검토 항목 1)
|
||||
|
||||
`envfile.py:45-73` `parse`, `mqtt_common.py:98-101` 은 `envfile.apply` 에 위임 — Python 쪽 로더는 **하나**다. 래퍼는 같은 파일을 `--shell` 로 실행한다. 같은 합성 파일을 두 경로에 넣은 결과:
|
||||
|
||||
| 입력 줄 | Python 로더 | 래퍼 eval 후 |
|
||||
|---|---|---|
|
||||
| `BOMKEY=1` (UTF-8 BOM) | 건너뜀(invalid key, 1행 경고) | 미설정 |
|
||||
| `A = spaced \r` (CRLF) | `'spaced'` | `spaced` |
|
||||
| `B="q's"` | `"q's"` | 동일 |
|
||||
| `C='x''` | `"x'"`(바깥 한 쌍만) | 동일 |
|
||||
| `D=a=b=c` | `'a=b=c'` | 동일 |
|
||||
| `E=` | `''` | `''` |
|
||||
| `export F=exp` | 건너뜀(invalid key) | 미설정 |
|
||||
| `G=val # trailing` | `'val # trailing'` | 동일 |
|
||||
| `H=$(touch …/PWNED) \`id\` ${HOME}` | 글자 그대로 | 글자 그대로, **PWNED 파일 생성 안 됨** |
|
||||
| `9BAD=x` / `noequals` | 건너뜀 + 경고 | 미설정 |
|
||||
| `IFS=x` / `BASH_ENV=x` | 건너뜀(reserved) | 미설정 |
|
||||
| `I="unbalanced` | `'"unbalanced'` | 동일 |
|
||||
| `J= "inner"` | `'inner'` | 동일 |
|
||||
|
||||
**두 경로가 완전히 일치한다.** 경고에는 값이 찍히지 않는다(줄 번호·사유만). 이전 Python 로더(`strip('"').strip("'")`)는 따옴표를 여러 겹 벗기고 키 검사가 없었는데, 이제 한 쌍만 벗기고 형식 오류를 경고한다 — 문서화된 규칙과 맞다.
|
||||
|
||||
**R1 (비차단, 문서)**: `.mam.env.example` 은 `$HOME/...` 예시를 `/home/you/...` 로 바꿔 "셸 해석 없음" 과 **일관**되게 했지만, 머리말에 규칙 자체(셸 확장 없음, 인라인 `#` 은 값의 일부, `export` 접두어 불가, BOM 저장 시 첫 줄 무시, **이미 있는 환경변수가 우선**)를 적지 않았다. 특히 래퍼 사용자에게는 세 가지가 **동작 변경**이다 — 전에는 bash `source` 가 `export KEY=`·`$HOME` 확장·인라인 주석을 받아들였다. 실제 `.mam.env` 에는 해당 줄이 없음을 확인했다(형태만 셈).
|
||||
|
||||
## 2. 래퍼의 eval 경로 (검토 항목 2)
|
||||
|
||||
`multi-agent-mux-delegate-job:41-46`: `python -I envfile.py --shell` 의 출력을 `eval`. 출력은 `envfile.py:97-98` 의 `print(f"export {key}={shlex.quote(val)}")` 뿐이고, `key` 는 `_KEY.fullmatch` 를 통과한 것만, `val` 은 `splitlines` 로 개행이 없고 `shlex.quote` 로 감싸진다 — 주입 실험에서 `$()`·백틱·`${}` 모두 글자 그대로 남았다.
|
||||
- 래퍼가 그 전에 세운 비-export 변수는 `SCRIPT_DIR`·`REPO_ROOT`·`TARGET_ENV` 셋뿐이고, 셋 다 환경으로 넘겨 파일이 덮어쓸 수 없다(`:42`). env-wins 실측: `D=fromenv` 가 파일의 `D=a=b=c` 를 이긴다.
|
||||
- 실패 경로: 파일이 **없으면** rc 0·출력 없음, **읽을 수 없으면** stderr `WARNING: … cannot read (PermissionError)` 후 rc 0 — 래퍼는 설정 없이 계속한다. 래퍼의 `|| { echo "ERROR: cannot read $TARGET_ENV"; exit 1; }`(`:44`)는 **Python 실행 자체가 실패할 때**(인터프리터 없음 등)만 걸려, 메시지와 실제 원인이 어긋난다(R2, 사소).
|
||||
- `-I`: `PYTHONPATH`·사용자 site 를 무시하지만 `envfile.py` 는 표준 라이브러리만 쓰고 파일 경로로 실행되므로 문제없다. 오히려 `.mam.env` 를 읽기 전 환경의 `PYTHON*` 이 파서에 끼어들 여지를 없앤다.
|
||||
|
||||
## 3. 우선순위 변경의 영향 (검토 항목 3)
|
||||
|
||||
전에는 래퍼의 `source` 가 환경을 덮어썼고 Python 로더는 환경을 지켰다 — 같은 파일이 두 경로에서 다른 결과를 냈다. 이제 둘 다 env-wins.
|
||||
- **루프 러너**가 래퍼에 넘기는 env(`DELEGATE_JOB_PYTHON`, `--registry-dir` 대신 쓰는 `MAM_JOBS_DIR` 등): 전에는 파일 값에 덮일 수 있었다 → 이제 러너 값이 이긴다. **개선**.
|
||||
- **워크트리 creator**: create/resume 이 넘기는 절대경로 바인딩(`AGENT_SESSIONS_YAML`·`MAM_JOBS_DIR`·…)이 이제 파일 값에 덮이지 않는다 — #21 의 의도와 맞는 **개선**.
|
||||
- **원격 러너**(`remote.py`)는 원래 Python 로더라 변화 없음. **테스트**는 `conftest` 가 `MAM_ENV_FILE` 을 없는 파일로 고정해 무관.
|
||||
- 동작이 나빠지는 경로: 셸에 **오래된 `MQTT_*` 를 export 해 둔 채** 파일만 고친 사용자는 이제 파일 값이 적용되지 않는다. 이 부분이 R1 의 문서 누락과 겹친다.
|
||||
|
||||
## 4. 정수 정규화 (검토 항목 4)
|
||||
|
||||
bash `mam_int`(`lib.sh:2345-2356`)와 Python `_positive_int`(`runner.py:71-82`)를 같은 15개 입력에 돌린 결과 **전부 일치**: `' 30 '→30`, `0700→700`(8진 아님), `0900→900`, `0→거부(min 1)`, `-5`·`+5`·`1e3`·`30s`·전각 `30`·빈 값 → 거부, `999999999→수용`, `1000000000→거부`(9자리 상한), `0000000000000000001→1`(앞 0 제거 후 길이 판정).
|
||||
- 래퍼: 환경 노브는 `mam_int_knob`(잘못되면 경고 + 기본값, `:154-156`), 플래그는 `parse_opts` 끝의 루프(잘못되면 `ERROR … not a positive decimal integer` rc 1, `:196-200`). `MAM_INJECT_ACK_GRACE_S` 는 min 0(0 = 대기 없음 허용, 의미상 맞다), `ACK_TIMEOUT`·heartbeat·budget 은 min 1. lib.sh `SKS_*` 대기 노브는 min 0(대기 생략 허용), `delegate_wait_started` 도 0 허용(`:2379`).
|
||||
- 빠진 노브: `$(( ))` 안에 환경 기본값을 직접 쓰는 자리는 남아 있지 않다(B5 정적 검사가 고정). `[ -ge ]`/`[ -le ]`/`sleep` 에만 쓰이는 노브(`MAM_READY_EMPTY_GIVEUP`·`SKS_EMPTY_GIVEUP`·`SKS_IDLE_RETRIES`·…)는 `test` 의 비교가 원래 10진수라 8진 문제가 없고, PR 이 표로 분류해 "모든 정수 노브가 분류됨" 을 시험한다(`test_b5_every_integer_env_default_is_classified`). 그 노브들에 숫자가 아닌 값이 오면 `[` 가 오류(rc 2)를 내는 기존 동작은 남는다(R3, 범위 밖).
|
||||
|
||||
## 5. 로컬 브로커와 이전된 시험 (검토 항목 5)
|
||||
|
||||
`tests/_local_mqtt.py` 는 MQTT 3.1.1 의 필요한 부분을 클라이언트가 기대하는 순서로 처리한다: CONNECT(레벨 3/4만, 그 외 CONNACK rc 1)→CONNACK, SUBSCRIBE→SUBACK(QoS 최대 1 부여) 뒤 **매칭되는 retained 전달**, PUBLISH QoS1 은 **라우팅·retained 저장 뒤에 PUBACK**(b58edac — 발행자가 ack 를 받은 시점에 브로커 상태가 이미 반영됨), 빈 payload retained 는 삭제, UNSUBSCRIBE→UNSUBACK, PINGREQ→PINGRESP, `+`/`#` 와일드카드와 `$` 토픽 예외(`topic_matches`). 미지원(QoS2·will·지속 세션)은 이 저장소가 쓰지 않는 기능이다. `127.0.0.1:0` 에 바인딩해 **포트 충돌이 없고**, 스레드는 daemon 이며 `stop()` 이 리스너·연결을 닫는다.
|
||||
|
||||
이전된 시험의 단언은 **약해지지 않았고 강해졌다**:
|
||||
- `test_acceptance5`: 공개 브로커용 `_skip_if_broker_did_not_deliver`·`no CONNACK` skip 이 삭제되고, 대신 러너 로그의 `DISPATCH`·`published started/completed`·`DONE rc=0` 과 오케스트레이터 감사 기록의 `dispatch_verified`·`dispatch_fingerprint == sha256(token)` 을 양쪽에서 단언한다.
|
||||
- `test_issue19`: "브로커 도달 불가면 skip" 이 `pytest.fail` 로 바뀌었다(로컬 브로커는 항상 떠 있으므로).
|
||||
- S9/S10 의 `_live_broker()`(2·3회차 리뷰에서 내가 P2 로 지적한 공개 브로커 의존)가 `local_broker_env()` 로 대체됐다.
|
||||
- `test_b19` 는 `send_keys_safe` 본문을 앞 2500자가 아니라 **함수 끝까지** 검사하게 고쳐졌다(정확성 개선).
|
||||
- 정적 검사(`test_local_mqtt.py:215`)가 공개 브로커 주소의 재유입을 막는다. 시험 로그에 `hivemq`·실제 브로커 IP 흔적 0건.
|
||||
|
||||
## 6. 테스트 실행 (검증 방법·검토 항목 6)
|
||||
|
||||
```
|
||||
cd <검증 경로> && AGENT_PYTHON_BIN=<pr-43 .venv> PYTHONDONTWRITEBYTECODE=1 <pr-43 .venv> -m pytest -p no:cacheprovider -q -rfEs \
|
||||
--basetemp=<밖> tests/test_issue26_envfile.py tests/test_issue26_ints.py tests/test_local_mqtt.py \
|
||||
tests/test_issue18_liveness_contract.py tests/test_issue33_budget.py tests/test_issue30_stall_watch.py tests/test_tier1_unit.py
|
||||
(.mam.env 미export, MQTT_* 미설정)
|
||||
→ 228 passed, 1 skipped in 525.44s (0:08:45) failed 0 (skip = S6, /proc)
|
||||
```
|
||||
|
||||
- PR #41 리뷰 내내 공개 브로커 때문에 흔들리던 S9·S10·R2·B3·acceptance 계열이 **외부 브로커 없이 전부 통과**했다 — PR 의 주장 확인.
|
||||
- **기준 실패 주장(검토 항목 6)**: `tests/test_issue10_13_purge.py` 를 이 PR 트리와 base(`e1eb22b` 를 `git archive` 로 풀어 놓은 사본)에서 각각 단독 실행한 결과 **둘 다 23 passed**, 실패 0. 즉 이 호스트에서 이 리뷰어의 조건으로는 "12건 기준 실패" 가 **재현되지 않는다**. 양쪽 결과가 같으므로 어느 경우든 PR 회귀는 아니다. 오케스트레이터의 전체 스위트에서만 나타난다면 시험 간 순서·상태(예: 실제 홈의 대화 파일, 이전 시험이 남긴 환경) 의존을 의심할 만하다(R4).
|
||||
- 격리: 실제 `.mam/jobs` 는 이 잡 자신의 파일 외 **추가 0**. 검증 경로 tracked 파일 변화 없음(HEAD `b58edac`), 본 체크아웃 `main e1eb22b`. 실행 뒤 호스트에 남은 `stall_watch`(이 잡을 감시하는 오케스트레이터의 것)·`sleep 300`(다른 세션의 프로세스 그룹) 은 내 실행과 무관함을 부모/프로세스 그룹으로 확인했다.
|
||||
|
||||
## 7. 설계 경계 (검토 항목 7)
|
||||
|
||||
- **판정이 Python 으로 이동**: `.mam.env` 해석(키 검증·예약 변수 거부·우선순위)이 bash `source` 에서 `envfile.py` 로 옮겨졌다 — #17 항목 4 의 "`.mam.env` 파싱(#26) — bash `source` 제거" 그대로다. 래퍼에는 실행과 `eval` 만 남는다.
|
||||
- **bash 에 새로 생긴 로직**: `mam_int`/`mam_int_knob` 과 `parse_opts` 의 플래그 검증. 이는 bash 산술로 들어가는 값을 그 자리에서 정규화하는 **입력 정제**이고, Python 쌍둥이(`_positive_int`)와 한 규칙으로 고정됐다(B1 시험). 판정(무엇을 할지)이 아니라 형식 변환이라 경계 위반으로 보지 않지만, 1회차 PR #41 리뷰의 M4(`MAM_JOB_TIMEOUT_S` 검증이 bash 에 있음)와 같은 부류이므로 항목 4 에서 래퍼 인자 처리가 Python 으로 갈 때 함께 옮길 목록에 둘 것을 권한다(R5).
|
||||
|
||||
## 8. 판정
|
||||
|
||||
버전 락스텝 bump 부재는 판정에서 제외한다. 병합을 막을 결함은 없다.
|
||||
|
||||
| # | 비차단 관찰 | 근거 |
|
||||
|---|---|---|
|
||||
| R1 | `.mam.env.example` 머리말에 새 파싱 규칙과 **env-wins** 가 적혀 있지 않다 — 래퍼 사용자에게 `export` 접두어·`$` 확장·인라인 주석·우선순위 4가지가 동작 변경 | `.mam.env.example:1-20`; §1·§3 |
|
||||
| R2 | 래퍼의 `ERROR: cannot read $TARGET_ENV` 는 Python 실행 실패에만 걸리고, 실제 읽기 실패는 경고 후 설정 없이 진행 — 메시지가 원인과 어긋난다 | `multi-agent-mux-delegate-job:44`, `envfile.py:52-53` |
|
||||
| R3 | `[ -ge ]`/`sleep` 전용 정수 노브에 숫자가 아닌 값이 오면 `[` 오류(rc 2)를 내는 기존 동작은 남는다 | PR 이 분류표로 의도적으로 범위 밖 처리 |
|
||||
| R4 | "test_issue10_13_purge.py 12건 기준 실패" 가 이 리뷰어 조건(단독, PR·base 양쪽)에서는 재현되지 않음 — 전체 스위트 순서 의존 가능성 | §6 |
|
||||
| R5 | `mam_int`/플래그 검증은 bash 의 입력 정제 — 항목 4 이전 목록에 추가 권고 | `lib.sh:2345-2364`, 래퍼 `:196-200` |
|
||||
|
||||
## 9. 교차검증 가이드
|
||||
|
||||
```bash
|
||||
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261001T131705Z-a60092/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}' # b58edac… / 14b670f…
|
||||
# 합성 .env 15줄을 envfile.apply 와 `-I envfile.py --shell` + eval 양쪽에 → 결과 동일, $(…)/`…` 미실행 (§1 표)
|
||||
# mam_int vs _positive_int 15개 입력 → 전부 동일 (§4)
|
||||
# base: git -C "$RP" archive e1eb22b | tar -x -C <tmp> → tests/test_issue10_13_purge.py → 23 passed (PR 도 23 passed)
|
||||
```
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20261001T140030Z-34381f",
|
||||
"base_sha": "e1eb22bd59ce825e3a22e86433c30f7ffd833810",
|
||||
"head_sha": "0eec795cd1b46f7a28b9a1cc87ef8577619a731f",
|
||||
"tree_sha": "584eac551f880232896b898cc43156f28ae1bacb",
|
||||
"review_commit": "0eec795cd1b46f7a28b9a1cc87ef8577619a731f",
|
||||
"reviewer": "planner-reviewer-claude-01",
|
||||
"job_id": "b9600325",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-10-01T14:27:22Z"
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
# 🔍 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]
|
||||
Reference in new issue
Block a user