fix(#26): .mam.env 를 Python 파서 하나로 읽고, 정수 환경변수를 10진수로, 테스트를 로컬 MQTT 브로커로 #43

Merged
Godopu merged 8 commits from fix/issue-26 into main 2026-10-01 15:23:26 +00:00
Owner

Closes #26

.mam.env 를 bash 와 Python 이 서로 다른 규칙으로 읽어서, 한쪽에서 정상인 줄이 다른 쪽에서 치명적이던 문제를 고친다. 같은 PR 에 두 가지를 함께 담았다. 정수 환경변수를 10진수로 정규화하는 것과, 공개 MQTT 브로커에 기대던 테스트를 없애는 것이다.

A. .mam.env 를 셸 source 대신 Python 파서로 읽는다

  • 파서는 하나다. 새 lib_py/delegation/envfile.py(표준 라이브러리만 사용)가 규칙을 한 곳에 담는다. 주석과 빈 줄은 건너뛰고, 키와 값의 앞뒤 공백을 떼고, 감싸는 따옴표 한 쌍을 벗기고, 셸 해석은 하지 않는다. Python 쪽 mqtt_common._parse_env_file 도 이 모듈에 위임한다.
  • 형식은 파서가 강제한다. 키는 ^[A-Za-z_][A-Za-z0-9_]*$ 여야 하고, 어긋나거나 = 가 없는 줄은 줄 번호와 함께 경고하고 건너뛴다. 경고에 값은 찍지 않는다.
  • bash 자체 변수는 받지 않는다. 따옴표로 감싸 내보내도 RANDOM=a[$(cmd)] 같은 bash 특수 변수 대입은 산술 평가되면서 명령을 실행한다(1회차 codex 리뷰 지적). bash 가 정의하는 변수와 BASH_·COMP_·READLINE_ 접두어는 두 로더 모두 경고하고 건너뛰며, 깨끗한 bash 가 정의하는 모든 변수를 덮는지 테스트로 고정했다. 래퍼가 이미 설정한 SCRIPT_DIR·REPO_ROOT·TARGET_ENV 도 파일이 바꿀 수 없다.
  • 래퍼는 파서를 호출한다. 파서가 shlex 로 감싼 export 문을 만들고 래퍼가 그것을 평가한다. 값이 명령줄 인자에 오르지 않는다.
  • 명령줄 환경변수가 파일 값보다 우선한다. 전에는 Python 로더는 이미 있는 값을 지켰는데 래퍼의 set -a; source 는 덮어써서, 같은 실행에서 두 쪽이 다른 값을 쓸 수 있었다. 이슈에 없던 차이로, 이번에 함께 맞췄다.
  • 이슈 작업 지시에는 "세 곳의 source"로 적혀 있었으나, 저장소 전체를 조사한 결과 셸이 env 파일을 해석하는 곳은 delegate-job 래퍼 한 곳(:38)뿐이었다.

B. 정수 환경변수를 10진수로 읽는다

bash 는 0 으로 시작하는 수를 8진수로 읽는다. MAM_JOB_TIMEOUT_S=0700 은 래퍼에서 조용히 448초가 됐고 0900 은 산술 오류로 죽었다. 루프 러너는 같은 값을 700 으로 읽어 두 쪽이 어긋났다.

  • 규칙을 하나로 맞췄다. bash 쪽 lib.sh 의 mam_int·mam_int_knob 와 러너의 _positive_int 가 같은 규칙으로 읽는다. 앞뒤 공백 허용, 숫자만, 10진수, 유효 자릿수 9 자리 이하, 값마다 정한 최솟값 이상.
  • 최솟값 1: MAM_JOB_TIMEOUT_S, MAM_HEARTBEAT_SEC, MAM_HEARTBEAT_MISSES, MAM_ACK_TIMEOUT_S, 그리고 래퍼의 해당 플래그들. 잘못된 환경변수는 경고 후 기본값, 잘못된 플래그는 종료 코드 1.
  • 최솟값 0(0 은 "한 번만 확인"): MAM_INJECT_ACK_GRACE_S, SKS_DIALOG_TIMEOUT, SKS_BUSY_TIMEOUT, SKS_DELIVERY_GRACE_S, SKS_QUIESCENT_TRIES 등.
  • tests/test_issue26_ints.py 의 표가 각 값의 최솟값을 정의하고, 정적 검사가 셸 코드에서 정규화되지 않은 정수 환경변수가 산술에 들어가는 것을 막는다.

C. 공개 브로커에 의존하는 테스트를 없앤다

  • 작업 지시는 "5종"이었으나 조사해 보니 공개 브로커(broker.hivemq.com) 왕복에 기대는 테스트는 22개였다.
  • 단순히 로그 단언으로 바꾸는 대신, 테스트 안에서 도는 로컬 MQTT 3.1.1 브로커(tests/_local_mqtt.py, 127.0.0.1)를 만들어 22개 모두가 그것을 쓰게 했다. 실제 MQTT 왕복은 그대로 검증하면서 외부 의존만 없앤다. "브로커에 닿지 않음"으로 건너뛰던 경로는 사라졌다. 로컬 브로커가 배달하지 못하면 실패다.
  • 그중 일부(#18 acceptance1·5, #19)는 실제 생산자인 구독자·러너·감사 로그에 남은 기록으로도 단언한다.
  • tests/test_local_mqtt.py 가 실제 클라이언트와 실제 publish_event → job_subscriber 경로로 브로커의 적합성을 확인하고, 테스트 코드에 공개 브로커 주소가 다시 들어오지 않도록 정적 검사를 둔다.

검증

항목 결과
전체 스위트 (34241d7) 1273 통과 / 13 실패 / 19 건너뜀
리뷰 아래 표
.mam.env 명령 실행 재현 수정 전 명령 2개 실행, 수정 후 실행 없음

실패 13건 중 12건은 이 호스트의 기준 실패(tests/test_issue10_13_purge.py)다. 나머지 1건(test_bug4_send_keys_safe_gating_order)은 함수 앞 2500자만 잘라 검사하던 테스트가 정규화 줄 추가로 대상을 놓친 것이며, 검사 순서 자체는 그대로였다. fb00637 이 함수 본문 전체를 보도록 고쳤다. 이후 커밋은 테스트 파일만 바꿨고, 해당 테스트를 각각 따로 확인했다. 전체 로그에 브로커 때문에 건너뛴 테스트는 없다.

설정 파일로 명령이 실행되는지 실제 래퍼로 재현했다.

설정 파일 수정 전 e1eb22b 수정 후 34241d7
첫 줄이 KEY= value 그 줄에서 즉시 종료 (rc 127) 정상 (rc 0)
$(touch …), RANDOM=a[$(touch …)] 등 명령 2개 실행 실행 없음, 줄 번호 경고 후 건너뜀

리뷰.

리뷰어 브랜치 전체 fb00637 델타 b58edac (테스트만)
claude NOT PASS (아래) PASS
agy PASS PASS
grok PASS PASS
opencode PASS 사용량 한도로 판정 불가
codex 판정 불가 제외
  • codex 는 1회차에 NOT PASS 로 세 가지를 지적했다. bash 특수 변수 대입을 통한 명령 실행, SKS_QUIESCENT_TRIES 의 산술 노출, 매우 긴 10진수 문자열이다. 34241d7 이 모두 반영했다. 그런데 이후 codex 는 OpenAI 의 보안 관련 콘텐츠 제한("Some cybersecurity requests may still be limited")에 막혀 판정을 내지 못했다. 이 PR 이 설정 파일을 통한 명령 실행을 막는 작업이라 그렇게 분류된 것으로 보인다. 가장 심각한 첫 지적은 위 표처럼 실제 래퍼로 수정 전후를 재현해 확인했다.
  • claude 는 브랜치 전체 리뷰에서 이 PR 이 새로 넣은 test_c0_make_client_speaks_3_1_1_wildcards_qos1_and_retained 의 경쟁 조건을 찾았다. paho 의 connect() 는 CONNACK 을 기다리지 않는데, 테스트가 연결 직후 브로커 상태를 단언했다. 브로커의 연결 처리를 늦춰 결정적으로 재현했다. b58edac 가 테스트 클라이언트가 CONNACK·UNSUBACK 을 기다리고, 클라이언트별로 단언하고, 로컬 브로커가 PUBLISH 를 배달한 뒤 PUBACK 을 보내도록 고쳤다. 브로커를 늦춘 상태의 회귀 테스트도 넣었다.
  • opencode 는 브랜치 전체에 PASS 를 냈고, 테스트만 바뀐 마지막 델타는 Ollama 세션 사용량 한도로 판정하지 못했다.

🤖 Generated with Claude Code

Closes #26 `.mam.env` 를 bash 와 Python 이 서로 다른 규칙으로 읽어서, 한쪽에서 정상인 줄이 다른 쪽에서 치명적이던 문제를 고친다. 같은 PR 에 두 가지를 함께 담았다. 정수 환경변수를 10진수로 정규화하는 것과, 공개 MQTT 브로커에 기대던 테스트를 없애는 것이다. ## A. `.mam.env` 를 셸 `source` 대신 Python 파서로 읽는다 - **파서는 하나다.** 새 `lib_py/delegation/envfile.py`(표준 라이브러리만 사용)가 규칙을 한 곳에 담는다. 주석과 빈 줄은 건너뛰고, 키와 값의 앞뒤 공백을 떼고, 감싸는 따옴표 한 쌍을 벗기고, 셸 해석은 하지 않는다. Python 쪽 `mqtt_common._parse_env_file` 도 이 모듈에 위임한다. - **형식은 파서가 강제한다.** 키는 `^[A-Za-z_][A-Za-z0-9_]*$` 여야 하고, 어긋나거나 `=` 가 없는 줄은 줄 번호와 함께 경고하고 건너뛴다. 경고에 값은 찍지 않는다. - **bash 자체 변수는 받지 않는다.** 따옴표로 감싸 내보내도 `RANDOM=a[$(cmd)]` 같은 bash 특수 변수 대입은 산술 평가되면서 명령을 실행한다(1회차 codex 리뷰 지적). bash 가 정의하는 변수와 `BASH_`·`COMP_`·`READLINE_` 접두어는 두 로더 모두 경고하고 건너뛰며, 깨끗한 bash 가 정의하는 모든 변수를 덮는지 테스트로 고정했다. 래퍼가 이미 설정한 `SCRIPT_DIR`·`REPO_ROOT`·`TARGET_ENV` 도 파일이 바꿀 수 없다. - **래퍼는 파서를 호출한다.** 파서가 `shlex` 로 감싼 export 문을 만들고 래퍼가 그것을 평가한다. 값이 명령줄 인자에 오르지 않는다. - **명령줄 환경변수가 파일 값보다 우선한다.** 전에는 Python 로더는 이미 있는 값을 지켰는데 래퍼의 `set -a; source` 는 덮어써서, 같은 실행에서 두 쪽이 다른 값을 쓸 수 있었다. 이슈에 없던 차이로, 이번에 함께 맞췄다. - 이슈 작업 지시에는 "세 곳의 `source`"로 적혀 있었으나, 저장소 전체를 조사한 결과 셸이 env 파일을 해석하는 곳은 delegate-job 래퍼 한 곳(`:38`)뿐이었다. ## B. 정수 환경변수를 10진수로 읽는다 bash 는 0 으로 시작하는 수를 8진수로 읽는다. `MAM_JOB_TIMEOUT_S=0700` 은 래퍼에서 **조용히 448초**가 됐고 `0900` 은 산술 오류로 죽었다. 루프 러너는 같은 값을 700 으로 읽어 두 쪽이 어긋났다. - 규칙을 하나로 맞췄다. bash 쪽 `lib.sh` 의 `mam_int`·`mam_int_knob` 와 러너의 `_positive_int` 가 같은 규칙으로 읽는다. 앞뒤 공백 허용, 숫자만, 10진수, 유효 자릿수 9 자리 이하, 값마다 정한 최솟값 이상. - 최솟값 1: `MAM_JOB_TIMEOUT_S`, `MAM_HEARTBEAT_SEC`, `MAM_HEARTBEAT_MISSES`, `MAM_ACK_TIMEOUT_S`, 그리고 래퍼의 해당 플래그들. 잘못된 환경변수는 경고 후 기본값, 잘못된 플래그는 종료 코드 1. - 최솟값 0(0 은 "한 번만 확인"): `MAM_INJECT_ACK_GRACE_S`, `SKS_DIALOG_TIMEOUT`, `SKS_BUSY_TIMEOUT`, `SKS_DELIVERY_GRACE_S`, `SKS_QUIESCENT_TRIES` 등. - `tests/test_issue26_ints.py` 의 표가 각 값의 최솟값을 정의하고, 정적 검사가 셸 코드에서 정규화되지 않은 정수 환경변수가 산술에 들어가는 것을 막는다. ## C. 공개 브로커에 의존하는 테스트를 없앤다 - 작업 지시는 "5종"이었으나 조사해 보니 공개 브로커(`broker.hivemq.com`) 왕복에 기대는 테스트는 **22개**였다. - 단순히 로그 단언으로 바꾸는 대신, 테스트 안에서 도는 **로컬 MQTT 3.1.1 브로커**(`tests/_local_mqtt.py`, 127.0.0.1)를 만들어 22개 모두가 그것을 쓰게 했다. 실제 MQTT 왕복은 그대로 검증하면서 외부 의존만 없앤다. "브로커에 닿지 않음"으로 건너뛰던 경로는 사라졌다. 로컬 브로커가 배달하지 못하면 실패다. - 그중 일부(#18 acceptance1·5, #19)는 실제 생산자인 구독자·러너·감사 로그에 남은 기록으로도 단언한다. - `tests/test_local_mqtt.py` 가 실제 클라이언트와 실제 `publish_event` → `job_subscriber` 경로로 브로커의 적합성을 확인하고, 테스트 코드에 공개 브로커 주소가 다시 들어오지 않도록 정적 검사를 둔다. ## 검증 | 항목 | 결과 | |---|---| | 전체 스위트 (`34241d7`) | 1273 통과 / 13 실패 / 19 건너뜀 | | 리뷰 | 아래 표 | | `.mam.env` 명령 실행 재현 | 수정 전 명령 2개 실행, 수정 후 실행 없음 | 실패 13건 중 12건은 이 호스트의 기준 실패(`tests/test_issue10_13_purge.py`)다. 나머지 1건(`test_bug4_send_keys_safe_gating_order`)은 함수 앞 2500자만 잘라 검사하던 테스트가 정규화 줄 추가로 대상을 놓친 것이며, 검사 순서 자체는 그대로였다. `fb00637` 이 함수 본문 전체를 보도록 고쳤다. 이후 커밋은 테스트 파일만 바꿨고, 해당 테스트를 각각 따로 확인했다. 전체 로그에 브로커 때문에 건너뛴 테스트는 없다. **설정 파일로 명령이 실행되는지 실제 래퍼로 재현했다.** | 설정 파일 | 수정 전 `e1eb22b` | 수정 후 `34241d7` | |---|---|---| | 첫 줄이 `KEY= value` | 그 줄에서 즉시 종료 (rc 127) | 정상 (rc 0) | | `$(touch …)`, `RANDOM=a[$(touch …)]` 등 | **명령 2개 실행** | 실행 없음, 줄 번호 경고 후 건너뜀 | **리뷰.** | 리뷰어 | 브랜치 전체 `fb00637` | 델타 `b58edac` (테스트만) | |---|---|---| | claude | NOT PASS (아래) | PASS | | agy | PASS | PASS | | grok | PASS | PASS | | opencode | PASS | 사용량 한도로 판정 불가 | | codex | 판정 불가 | 제외 | - **codex** 는 1회차에 NOT PASS 로 세 가지를 지적했다. bash 특수 변수 대입을 통한 명령 실행, `SKS_QUIESCENT_TRIES` 의 산술 노출, 매우 긴 10진수 문자열이다. `34241d7` 이 모두 반영했다. 그런데 이후 codex 는 OpenAI 의 보안 관련 콘텐츠 제한("Some cybersecurity requests may still be limited")에 막혀 판정을 내지 못했다. 이 PR 이 설정 파일을 통한 명령 실행을 막는 작업이라 그렇게 분류된 것으로 보인다. 가장 심각한 첫 지적은 위 표처럼 실제 래퍼로 수정 전후를 재현해 확인했다. - **claude** 는 브랜치 전체 리뷰에서 이 PR 이 새로 넣은 `test_c0_make_client_speaks_3_1_1_wildcards_qos1_and_retained` 의 경쟁 조건을 찾았다. paho 의 `connect()` 는 CONNACK 을 기다리지 않는데, 테스트가 연결 직후 브로커 상태를 단언했다. 브로커의 연결 처리를 늦춰 결정적으로 재현했다. `b58edac` 가 테스트 클라이언트가 CONNACK·UNSUBACK 을 기다리고, 클라이언트별로 단언하고, 로컬 브로커가 PUBLISH 를 배달한 뒤 PUBACK 을 보내도록 고쳤다. 브로커를 늦춘 상태의 회귀 테스트도 넣었다. - **opencode** 는 브랜치 전체에 PASS 를 냈고, 테스트만 바뀐 마지막 델타는 Ollama 세션 사용량 한도로 판정하지 못했다. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Godopu added 6 commits 2026-10-01 02:01:14 +00:00
- lib_py/delegation/envfile.py (stdlib only) holds the one set of rules:
  comments and blank lines skipped, key and value stripped, one matching
  quote pair removed, no shell interpretation, keys must match
  ^[A-Za-z_][A-Za-z0-9_]*$ (bad or `=`-less lines warn with the line
  number, never the value).
- mqtt_common._parse_env_file delegates to envfile.apply.
- The delegate-job wrapper runs envfile.py --shell by path and evals the
  shlex-quoted exports; values never reach a command line. The environment
  now wins over the file in both loaders.
- .mam.env.example: commented assignments no longer use $HOME, which the
  parser keeps literally.

The shell parsed the env file in one place only (wrapper :38), not three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bash read MAM_JOB_TIMEOUT_S=0700 as octal 448 (and failed on 0900) while
the loop runner read 700. One rule now, with a per-knob minimum:
lib.sh mam_int / mam_int_knob and runner._positive_int (blanks allowed,
digits only, decimal, min <= n <= 999999999).

- minimum 1: MAM_JOB_TIMEOUT_S, MAM_HEARTBEAT_SEC, MAM_HEARTBEAT_MISSES,
  MAM_ACK_TIMEOUT_S (invalid env: warning + default) and the wrapper's
  --timeout/--idle-timeout/--heartbeat/--heartbeat-misses/--ack-timeout
  (invalid: rc 1); the runner's job_budget, --job-timeout, contract_args.
- minimum 0 (0 = check once): MAM_INJECT_ACK_GRACE_S,
  delegate_wait_started's ack_s, SKS_DIALOG_TIMEOUT, SKS_BUSY_TIMEOUT,
  SKS_DELIVERY_GRACE_S.
- tests/test_issue26_ints.py: INT_KNOBS is the one table of minimums; a
  static guard keeps env defaults out of arithmetic and checks each
  normalised knob against it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- tests/_local_mqtt.py: in-process MQTT 3.1.1 broker on 127.0.0.1 (what
  the repo uses: protocol level 4, clean session, QoS 0/1, retained).
- conftest: one session broker named by MQTT_BROKER/MQTT_PORT/MQTT_TLS;
  other redirecting/auth MQTT_* dropped and restored afterwards.
- Harnesses of the 22 broker-dependent tests (#18 acceptance1/2/3/5,
  #6 t1_1/2/7/8/14/15/16/20, #19 MQTT loop x2, #33 b3/r3/b7, #30
  s9/s10/r2) use it; the "broker unreachable" skips are gone (a local
  broker that does not deliver is a failure). acceptance1/5 and #19 also
  assert on the subscriber, runner and audit logs the real producers write.
- tests/test_local_mqtt.py: conformance with the real clients and the real
  publish_event -> job_subscriber path (C0), an AST guard against public
  broker literals and MQTT_* deleted without a replacement (C2), and the
  session's broker config (C3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
R1 (P1): a quoted export still ran code when the key was a bash special
variable (RANDOM=a[$(cmd)] is evaluated arithmetically on assignment).
envfile.RESERVED (bash's own variables, plus BASH_/COMP_/READLINE_
prefixes) is skipped with a line-numbered warning by both loaders; a test
keeps it covering every variable a clean bash defines. The wrapper also
hands SCRIPT_DIR/REPO_ROOT/TARGET_ENV to the parser as environment, so the
file cannot replace the variables it has already set.

R2 (P2): SKS_QUIESCENT_TRIES reached `for ((i < tries))` through a function
argument; it is normalised at the call site (minimum 0). B5 now requires
every integer-looking env default in the shell code to be normalised or
listed in NOT_ARITHMETIC with where it goes, and exercises _pane_quiescent.

R3 (P2): _positive_int strips leading zeros and rejects more than nine
significant digits before int(), like mam_int; 5000-digit inputs no longer
raise in the runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
34241d7 added a line near the top of send_keys_safe (SKS_QUIESCENT_TRIES
normalisation), which pushed `agent prompt` from inside the 2500-character
window to offset 2519. The order the test guards is unchanged
(_pane_quiescent < _pane_dialog_open < agent prompt); it now slices up to
the function's closing brace.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (fa45b0e6): paho's connect() does not wait for the CONNACK, so C0
could read protocol_levels before the broker had handled the second
CONNECT, and the `before` slice could take in a previous test's connection.

- _Client waits for CONNACK (and UNSUBACK, instead of sleeping 0.3 s);
  C0 checks the levels of its own two client ids (stats() levels_by_client).
- The local broker now routes and stores a PUBLISH before its PUBACK, so a
  publisher holding the ack may rely on the retained state it asked for
  (late subscriber replay, retained delete).
- test_c0_holds_when_the_broker_is_slow runs C0 with CONNECT/UNSUBSCRIBE
  delayed and PUBLISH routed late; the old C0 fails it ([] == [4, 4]), as do
  an ack-before-route broker and a fixed-sleep unsubscribe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Godopu added 1 commit 2026-10-01 13:59:58 +00:00
Merge-workspace review of PR #43 (codex R1-R4, claude R1/R2).

- R1: envfile.parse raises EnvFileError when the file exists but cannot be
  read or decoded as a whole; `--shell` prints the cause and exits 1, so the
  wrapper stops (rc 1, as before #26) instead of running on defaults, and
  mqtt_common's entry points stop on the same error. A bad single line is
  still a warning. The wrapper's message no longer claims a read failure
  when the cause is elsewhere ("cannot load ... see the line above").
- R2: lib.sh mam_int spells out the ASCII blanks and digits instead of
  [[:space:]]/[0-9], which follow the locale: U+2003 30 U+2003 was 30 in the
  wrapper and invalid in the runner under en_US.UTF-8. The test oracle
  _expected uses the same ASCII rule.
- R3: an integer flag given explicitly with an empty value is refused like
  any other invalid value; only an absent flag keeps its default.
- R4: a line holding a NUL byte is skipped with a warning by both loaders;
  before, the wrapper read "leftright" and the Python loader died.
- .mam.env.example states the format: no expansion, `export` and bash's own
  variables skipped, a trailing `# ...` is part of the value, the environment
  wins, an unreadable file is an error.

Tests: unreadable (0000) and invalid-UTF-8 files stop the wrapper and the
Python loader; a NUL line is skipped by both; Unicode blanks/digits are
invalid for mam_int, _positive_int, the wrapper and the runner under C and
en_US.UTF-8; explicit empty values for the five integer flags are refused.
All of them fail on b58edac.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
Godopu merged commit 0051fd6c3b into main 2026-10-01 15:23:26 +00:00
Author
Owner

병합 요약 (v4.8.1)

병합 커밋 0051fd6, 릴리스 2e90c80. Closes #26.

리뷰 (병합 워크스페이스, 네 리뷰어)

회차 head claude agy grok codex
1 b58edac PASS PASS 판정 없음 NOT PASS (R1–R4)
2 0eec795 PASS PASS PASS (PR 전체) PASS
  • grok 1회차: 컨텍스트 압축 중 180 초 안에 started 를 보내지 못해 잡이 agent_blocked 로 닫혔다. 압축 뒤 늦게 낸 리포트(report-c9ed5b14.md, 판정에는 미포함)도 codex R1 과 같은 결함을 독립적으로 찾았다.
  • codex: 작성 워크스페이스에서는 보안 콘텐츠 제한으로 판정을 못 냈지만, 이번에는 "설정 파일 파싱 규칙의 정확성" 브리프로 정상 리뷰했다.

1회차 지적과 수정 (0eec795)

  • R1 존재하지만 읽을 수 없는 .mam.env 에서 래퍼가 rc 0 으로 기본값 진행 → EnvFileError. 래퍼 rc 1(#26 이전과 같음), Python 진입점도 멈춤. 개별 잘못된 줄은 여전히 경고 후 건너뜀. 네 리뷰어 모두 "Python 진입점도 멈춘다"에 동의(경고 후 계속하면 계정·서명 없이 공개 기본 브로커로 발행).
  • R2 UTF-8 로케일에서 U+2003 30 U+2003 을 bash 는 30, 러너는 무효 → mam_int 가 ASCII 공백·숫자를 명시.
  • R3 빈 값으로 명시한 정수 플래그가 검증 우회 → 플래그 존재를 기록해 거부.
  • R4 NUL 이 든 줄을 래퍼는 leftright, Python 은 종료 → 두 로더 모두 줄 번호 경고 후 건너뜀.
  • claude 비차단 R1·R2: .mam.env.example 형식 규칙, 래퍼 메시지.
  • 회귀 테스트 8 건, 모두 b58edac 에서 실패.

전체 스위트 (MQTT 환경변수 없이)

  • 0eec795: 1315 passed / 1 failed / 3 skipped (60 분). b58edac: 1302 passed / 같은 1 failed.
  • 실패 1건 test_issue21_acceptance.py::test_a8_second_loop_is_refused_before_a_scheduler_exists 는 이 PR 과 무관하다(main 에서도 실패). 테스트가 마커에 쓰는 ps -o lstart 값이 19 일에는 Oct 1 처럼 공백 두 칸인데, 제품 코드(loop_lock.sh)는 공백을 한 칸으로 줄여 비교한다. 매달 19 일에만 실패하고 제품 동작은 정상.
  • 공개 브로커 때문에 실패하거나 건너뛴 테스트 없음. PR 본문의 "purge 12건 기준 실패"는 이 호스트에서 재현되지 않음(claude·grok 도 단독 실행 23 passed).

후속

  • test_a8 마커의 공백 정리(테스트 한 줄).
  • Python 진입점의 EnvFileError 를 트레이스백 대신 ERROR: 한 줄로(claude S1).
  • 파일 첫 줄 BOM 이 "잘못된 키"로 건너뛰어짐; 한 파일 안 중복 키는 첫 값이 이김 — 문서화; MESSAGING.md 의 "없는 MAM_ENV_FILE 은 오류" 설명이 실제(건너뜀)와 다름(grok).
  • MAM_WAIT_GRACE_S·MAM_CHILD_WAIT_S·MAM_JOB_TREE_MAX 를 _positive_int 로(grok); [ -ge ] 전용 노브의 비숫자 값(claude R3); mam_int·플래그 검증을 #17 항목 4 이전 목록에(claude R5).

🤖 Generated with Claude Code

## 병합 요약 (v4.8.1) 병합 커밋 0051fd6, 릴리스 2e90c80. Closes #26. ### 리뷰 (병합 워크스페이스, 네 리뷰어) | 회차 | head | claude | agy | grok | codex | |---|---|---|---|---|---| | 1 | b58edac | PASS | PASS | 판정 없음 | NOT PASS (R1–R4) | | 2 | 0eec795 | PASS | PASS | PASS (PR 전체) | PASS | - **grok 1회차**: 컨텍스트 압축 중 180 초 안에 `started` 를 보내지 못해 잡이 `agent_blocked` 로 닫혔다. 압축 뒤 늦게 낸 리포트(`report-c9ed5b14.md`, 판정에는 미포함)도 codex R1 과 같은 결함을 독립적으로 찾았다. - **codex**: 작성 워크스페이스에서는 보안 콘텐츠 제한으로 판정을 못 냈지만, 이번에는 "설정 파일 파싱 규칙의 정확성" 브리프로 정상 리뷰했다. ### 1회차 지적과 수정 (0eec795) - **R1** 존재하지만 읽을 수 없는 `.mam.env` 에서 래퍼가 rc 0 으로 기본값 진행 → `EnvFileError`. 래퍼 rc 1(#26 이전과 같음), Python 진입점도 멈춤. 개별 잘못된 줄은 여전히 경고 후 건너뜀. 네 리뷰어 모두 "Python 진입점도 멈춘다"에 동의(경고 후 계속하면 계정·서명 없이 공개 기본 브로커로 발행). - **R2** UTF-8 로케일에서 `U+2003 30 U+2003` 을 bash 는 30, 러너는 무효 → `mam_int` 가 ASCII 공백·숫자를 명시. - **R3** 빈 값으로 명시한 정수 플래그가 검증 우회 → 플래그 존재를 기록해 거부. - **R4** NUL 이 든 줄을 래퍼는 `leftright`, Python 은 종료 → 두 로더 모두 줄 번호 경고 후 건너뜀. - claude 비차단 R1·R2: `.mam.env.example` 형식 규칙, 래퍼 메시지. - 회귀 테스트 8 건, 모두 b58edac 에서 실패. ### 전체 스위트 (MQTT 환경변수 없이) - 0eec795: 1315 passed / 1 failed / 3 skipped (60 분). b58edac: 1302 passed / 같은 1 failed. - 실패 1건 `test_issue21_acceptance.py::test_a8_second_loop_is_refused_before_a_scheduler_exists` 는 이 PR 과 무관하다(main 에서도 실패). 테스트가 마커에 쓰는 `ps -o lstart` 값이 1~9 일에는 `Oct 1` 처럼 공백 두 칸인데, 제품 코드(`loop_lock.sh`)는 공백을 한 칸으로 줄여 비교한다. 매달 1~9 일에만 실패하고 제품 동작은 정상. - 공개 브로커 때문에 실패하거나 건너뛴 테스트 없음. PR 본문의 "purge 12건 기준 실패"는 이 호스트에서 재현되지 않음(claude·grok 도 단독 실행 23 passed). ### 후속 - `test_a8` 마커의 공백 정리(테스트 한 줄). - Python 진입점의 `EnvFileError` 를 트레이스백 대신 `ERROR:` 한 줄로(claude S1). - 파일 첫 줄 BOM 이 "잘못된 키"로 건너뛰어짐; 한 파일 안 중복 키는 첫 값이 이김 — 문서화; `MESSAGING.md` 의 "없는 `MAM_ENV_FILE` 은 오류" 설명이 실제(건너뜀)와 다름(grok). - `MAM_WAIT_GRACE_S`·`MAM_CHILD_WAIT_S`·`MAM_JOB_TREE_MAX` 를 `_positive_int` 로(grok); `[ -ge ]` 전용 노브의 비숫자 값(claude R3); `mam_int`·플래그 검증을 #17 항목 4 이전 목록에(claude R5). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tmpl/multi-agent-mux#43