Merge-workspace review: round 1 (@518beda) claude agy grok PASS, codex NOT PASS (finalize wait measured on the wall clock); fixed in4c86107. Round 2 delta (@4c86107) claude agy grok PASS; codex (24b21b9c, after 817d1650 closed deadline_timeout with no report) found R1 closed and no new defect, but withheld PASS only because its sandbox could not bind the suite's loopback broker. The same two-file suite run here at4c86107: 278 passed. Full suite at518bedaon macOS / Python 3.9: 1711 passed / 1 failed / 38 skipped; the failure (test_issue45_restore, herdr 0.9.3 cwd restore) also fails on main. 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
4c86107846
commit
8fde39be33
24 files changed
+735
-26
No files matched your search
@@ -0,0 +1,124 @@
|
||||
# PR #51 — Codex review, job aa885e48
|
||||
|
||||
## 판정: NOT PASS
|
||||
|
||||
일반 입력과 안정적인 시스템 시각에서는 기존/새 판정의 비교 검사들이 일치했다. 그러나 새 _sleep이 경과 시간 측정에 wall clock을 사용해, 시스템 시각이 뒤로 이동할 때 두 finalize 재관측 간격이 달라진다. 핵심 계약인 동작 불변을 충족하려면 R1을 수정해야 한다. 수정 범위는 작은 helper와 회귀 검사이며 재계획은 필요하지 않다.
|
||||
|
||||
## Identity / 범위
|
||||
|
||||
- loop: `loop-20261007T142403Z-10d77f`
|
||||
- base: `7209750ac7e3ab9a7f6b0abc3670dbe0b1060f88`
|
||||
- head: `518bedac5d8ab832347bfc6682b146f3201021f1`
|
||||
- tree: `137b4e46c58b4f27e03dc19e6f9c6b42f574baa3`
|
||||
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261007T142403Z-10d77f/1`
|
||||
- base..head 7개 커밋의 변경 전체를 검토했다. 제품 소스 수정·커밋·브랜치 변경은 하지 않았다.
|
||||
- 검증 중 별도 탐침의 workspace 격리 실수가 있었다. 아래 “검증 과정의 격리 오류”에 별도로 기록한다.
|
||||
|
||||
## R1 — P2: wall-clock 역행이 짧은 sleep까지 늘려 완료/heartbeat 재관측을 지연시킨다
|
||||
|
||||
**근거:** `.agents/skills/lib_py/delegation/wrapper.py:145–153`은 `end = time.time() + seconds`와 `end - time.time()`으로 sleep의 남은 시간을 계산한다. 이를 broker 재관측(`:208`, 최대 3초)과 디스크 heartbeat 유예(`:236`, 1초)가 모두 사용한다.
|
||||
|
||||
기존 bash는 해당 위치에서 `sleep "$left"` 또는 `sleep 1`을 호출한 다음 디스크를 다시 읽는다. 새 구현은 그 짧은 sleep 도중 wall clock이 뒤로 이동하면 되돌아간 시간까지 기다린 후에야 레코드를 읽는다. 잡의 wall-clock deadline이 이동된 시각에 맞춰 판단되는 기존 정책과, 개별 sleep 자체가 늘어나는 것은 별개의 동작이다.
|
||||
|
||||
**재현 조건/절차:**
|
||||
|
||||
1. `tests/data/issue17_item4_old_decisions.sh`의 실제 옛 `finalize_unfinished`를 실행한다. 이 파일의 함수 9개는 `git show 7209750`의 대응 함수와 바이트 단위로 일치함을 확인했다.
|
||||
2. 실제 시스템 시각 대신 모의 시각을 사용한다. 시작 wall time=100, START_TIME=100, TIMEOUT=60, IDLE_TIMEOUT=10. 첫 sleep 중 wall clock만 10초 뒤로 이동하고, elapsed time은 정상 증가시킨다.
|
||||
3. 디스크 레코드는 elapsed 1초부터 completed를 반환하게 한다.
|
||||
4. 같은 입력으로 새 `finalize_end`를 실행한다. 두 경로 모두 실제 레지스트리/브로커/서버에 접근하지 않는다.
|
||||
|
||||
| 경로 | 옛 bash가 completed를 확인한 elapsed | 새 Python이 확인한 elapsed |
|
||||
| --- | --- | --- |
|
||||
| broker_unavailable, 3초 재관측 | 3초 | 13초 |
|
||||
| timeout, 1초 디스크 유예 재관측 | 1초 | 11초 |
|
||||
|
||||
**기대:** 기존과 같이 짧은 sleep의 경과 시간 뒤 디스크를 다시 읽고, 그 시점의 wall clock으로 잡 deadline/evidence를 판단한다.
|
||||
|
||||
**실제:** 이미 completed인 잡을 새 코드만 추가 10초 기다린다. 시각 보정 폭이 커지면 지연도 그만큼 커진다. 기존 비교 테스트는 안정적인 시각만 사용하므로 이 차이를 잡지 않는다.
|
||||
|
||||
**수정 방향:** `_sleep`의 내부 end/remaining만 `time.monotonic()`으로 계산한다. `_now()`, START_TIME+TIMEOUT, 이벤트 timestamp, evidence_until의 wall-clock 정책은 그대로 유지한다. 모의 wall clock 역행 중에도 1초/3초 간격으로 디스크를 재관측하는 비교 회귀 검사를 추가한다.
|
||||
|
||||
재현 파일: `/tmp/aa885e48-clock.py`
|
||||
결과: `/tmp/aa885e48-clock.log`
|
||||
검증 cwd에서 재실행:
|
||||
```sh
|
||||
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-51/.venv/bin/python /tmp/aa885e48-clock.py
|
||||
```
|
||||
호스트 시계는 변경하지 않았다. 표의 시간은 모의 elapsed time이다.
|
||||
|
||||
## 브리프 검토 항목
|
||||
|
||||
### 1. 옛/새 판정과 finalize
|
||||
|
||||
- fixture의 mam_int, mam_int_knob, start_stall_watch, _disk_status, _recent_disk_heartbeat, _map_sub_rc, _rc_for, finalize_unfinished, cmd_wait 총 9개 함수를 실제 base와 비교해 일치를 확인했다.
|
||||
- 일반 finalize 라벨·phase별 cancelled 이유·inject의 agent_blocked·direct만 이벤트 발행·loop/discuss의 이벤트 억제·dispatch token mismatch rc=4 시 후속 이벤트 억제는 비교 검사에서 보존됐다.
|
||||
- FAIL_CLOSED=0, completed 레코드 조기 종료, terminal error/cancelled, remote/human 분기도 확인했다.
|
||||
- 기존 스위트 외에 preflight detail의 공백/따옴표/줄바꿈/빈 값 4건과 broker 대기 중 error/cancelled/completed 도착 3건을 옛/새로 직접 비교했다. stdout, stderr, FINAL_JOB_STATUS, registry/publish 호출이 모두 일치했다.
|
||||
- 단 두 대기의 시계 역행 동작은 R1처럼 달라진다.
|
||||
|
||||
### 2. bash ↔ Python 경계
|
||||
|
||||
`multi-agent-mux-delegate-job:97–101`의 _decide는 인자를 `"$@"`로 넘긴다. 일반 값의 공백과 따옴표는 유지되며, `:805–831`의 begin 단일 행과 end fd 3 결과 분리는 비교 검사에서 close/publish stdout과 상태가 섞이지 않았다.
|
||||
|
||||
Python 경로를 존재하지 않는 경로로 바꾼 추가 탐침에서 exit-code, wait-rc, finalize는 각각 nonzero(이 호스트 bash에서 255/127/127)로 끝나 성공으로 바뀌지 않았다. begin의 command substitution 실패는 read 자체에 가려질 수 있지만, 뒤의 end 실행 실패로 정상 성공 상태를 반환하지 않았다. start_stall_watch의 판정 실패는 기존의 notify-only 감시 기동 실패와 마찬가지로 감시 없이 반환하므로 모든 보조 경로가 job을 중단하는 정책은 아니다.
|
||||
|
||||
판정은 wrapper.py로 이동했고 bash에는 실행/대기/프로세스 정리, diagnostic probe, CLI 흐름이 남았다. 새 int 모듈의 직접 실행은 -I로 격리되며 lib.sh 없이 lib_int.sh만 source한 실행도 확인했다.
|
||||
|
||||
### 3. watch-pid와 종료
|
||||
|
||||
`wrapper.py:122–153,256,267` 부근의 owner 검사는 kill(0) 뒤 ps stat의 Z 접두사를 검사하고, 대기 중과 부작용 직전에 owner가 사라지면 종료한다. 제공된 TERM/미회수 zombie 검사와 추가 KILL 비교를 확인했다.
|
||||
|
||||
추가 KILL 탐침은 broker 대기와 timeout 유예 각각에서 옛/새 wrapper만 종료했다. 양쪽 모두 deadline 이후에도 close/publish 호출이 없고 레코드가 그대로였다. 새 코드의 캡처 파이프 종료는 약 0.04초/0.18초, 옛 코드는 약 2.20초/0.39초였다. 옛 코드의 남은 sleep 때문에 최초 자체 탐침의 2초 기준은 너무 짧았으며, 기존 비교 검사의 4.5초 기준으로 바로잡아 재확인했다. 이는 제품 실패로 집계하지 않았다.
|
||||
|
||||
실제 polling 구현은 브리프의 0.2초가 아니라 **최대 0.5초 간격**이다. 이 간격 자체에서는 차단 결함을 찾지 않았다. macOS에서 실행 검증했고 Linux는 직접 실행하지 않았으므로 Linux 실측 통과를 주장하지 않는다.
|
||||
|
||||
### 4. 정수 규칙
|
||||
|
||||
`ints.positive_int`는 이전 runner의 구현과 같은 ASCII 공백/숫자, 선행 0 제거, 유효 9자리 제한, minimum 검사를 유지한다. 옛 bash와 새 adapter의 C/en_US.UTF-8, 유니코드 공백/숫자, 0 허용 여부 및 경계 입력 비교를 확인했다. lib_int.sh의 두 함수는 Python 호출로 얇아졌고, runner도 같은 모듈을 사용한다. minimum/default가 계약 범위의 값일 때 추가 차이를 찾지 못했다.
|
||||
|
||||
### 5. 정적 테스트 변경
|
||||
|
||||
- issue5: 옛 bash 문자열 대신 호출 연결, Python EXIT_CODES, 상태 매핑, subscriber 상수 일치를 검사한다.
|
||||
- tier1 G10: broker unavailable 및 디스크 completed 우선 동작을 실행 검사해 기존 문자열 검사보다 의미를 직접 고정한다.
|
||||
- issue26: Python으로 옮긴 두 환경변수를 shell-only 분류표에서 제거했으며 새 단위/비교 테이블이 해당 정책을 확인한다.
|
||||
- issue21 L15: 독립 복사 fixture에 ints.py를 같이 복사한다. 기존 diff-only 거부/로컬 허용 assertion은 유지됐고 이 테스트를 별도로 실행했다.
|
||||
- #42 인벤토리는 근거 줄 번호만 바뀌었다. 정책 분류 변경은 없다.
|
||||
|
||||
### 6. 성능
|
||||
|
||||
이 호스트에서 정수 판정 100회의 경과 시간은 옛 bash 약 0.0056초, 새 Python adapter 약 4.392초였다. `_decide exit-code completed` 20회의 프로세스 실행 중앙값은 0.0474초(0.0459–0.0493초)였다. 기존 shell-only 판단보다 시작 비용은 분명히 늘었다.
|
||||
|
||||
기본 direct 완료 경로는 초기 knob 4회 + 기본 정수 flag 검증 4회 + stall-watch/sub-status/finalize-begin/exit-code 4회로 약 12회의 새 Python 실행이 발생한다. explicit idle-timeout, 조기 subscriber 종료 판정, 미완료 finalize-end, delivery/preflight knob 경로에서 더 늘어난다. 기본 구간만 대략 0.5–0.6초 추가이며 1초 안팎의 짧은 잡에서는 비율이 크다. 반면 긴 finalize 대기에서는 기존의 반복 Python 디스크 probe/date 프로세스를 인프로세스 읽기로 바꿨으므로 비용이 단순히 추가되기만 하는 것은 아니다. 지정 예산/통합 검사 결과를 함께 보았고, 이 측정 자체를 차단 사유로 삼지는 않았다.
|
||||
|
||||
### 7. 이식성/검증
|
||||
|
||||
실제 macOS, Python 3.9, bash 3.2에서 검증했다. 변경된 Python core는 해당 인터프리터 AST parse 통과, 두 변경 셸 파일 bash -n 통과, git diff --check 통과다.
|
||||
|
||||
## 테스트 결과
|
||||
|
||||
| 검증 | 결과 |
|
||||
| --- | --- |
|
||||
| 브리프의 9개 파일, 지정 Python 3.9 환경 | **604 passed, 1 skipped**, 1005.83초 |
|
||||
| 수정된 issue21 L15 검사 추가 실행 | **1 passed**, 1.08초 |
|
||||
| 옛 함수 원본 일치 확인 | 9개 함수 바이트 일치 |
|
||||
| 추가 정상 입력/늦은 종결 비교 | 7건 옛/새 일치 |
|
||||
| KILL 시 broker/timeout 두 경로 | 옛/새 모두 close·발행 없음 |
|
||||
| Python 실행 불가 경계 | 상태/종료 판정 3경로 모두 nonzero |
|
||||
| 시계 역행 비교 | **두 경로의 재관측 지연 차이 재현** — R1 |
|
||||
| Python AST / bash -n / diff --check | 통과 |
|
||||
|
||||
검증 종료 후 HEAD/tree가 브리프와 일치했고 추적 파일의 `git status --porcelain` 출력은 없었다. 전체 저장소 스위트는 요청대로 다시 실행하지 않았다.
|
||||
|
||||
추가 자료:
|
||||
- 전체 지정 선택 검사: `/tmp/aa885e48-selected.log`
|
||||
- 변경된 L15 추가 검사: `/tmp/aa885e48-l15.log`
|
||||
- KILL/실행 실패/정수 성능 탐침: `/tmp/aa885e48-boundary.py`, `/tmp/aa885e48-boundary-v2.log`
|
||||
- 시계 역행 비교: `/tmp/aa885e48-clock.py`, `/tmp/aa885e48-clock.log`
|
||||
|
||||
## 검증 과정의 격리 오류
|
||||
|
||||
추가 7개 옛/새 비교를 pytest 밖에서 호출한 첫 탐침에서, 상속된 WORKSPACE_ROOT를 제거하지 않은 제 실수가 있었다. 그 값은 검증 저장소가 아닌 `/Users/godopu16/PuKi/lac/curriculum/00_entry`였고, wrapper source 시 lib.sh의 자동 초기화가 `/Users/godopu16/PuKi/lac/curriculum/00_entry/.mam/shim/herdr` 생성 파일을 다시 썼다. 이 경로 변경은 사용자에게 즉시 알렸다.
|
||||
|
||||
해당 탐침의 registry/publish는 stand-in으로 가로챘고 herdr 진단 함수도 stub이었다. 실제 에이전트/서버 작업은 하지 않았다. 다만 그 생성 파일의 실행 전 사본을 확보하지 않았으므로 이전 내용과 동일했는지, 원복됐는지는 주장하지 않는다. 공유 파일을 추측으로 덮어써 복구하지 않았다. 이후 탐침은 HERDR/MAM/MQTT 환경을 제거하고 임시 WORKSPACE_ROOT/HOME을 명시했다. 이 오류는 PR 제품 결함 R1과 별개의 검증 절차 문제다.
|
||||
|
||||
[VERDICT: NOT PASS]
|
||||
Reference in new issue
Block a user