Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-a665d729.md
T

7.1 KiB

Issue #5 Phase 2b — Round 4 교차 리뷰 (Codex Round 3 R4 후속 결함 수정)

  • Job: a665d729 / Reviewer: planner-reviewer-claude-01
  • 리뷰 대상: 커밋 8d7e010 "fix(issue-5): record an accepted intent, not a refused one" (기준 9a651cc)
  • 범위 고정: brief에 내장된 diff와 git diff 9a651cc..HEAD -- . ':(exclude).agents/reports'를 바이트 비교 → 동일(15,916 B). 변경 파일 4개: lib_py/delegation/job_subscriber.py, docs/contracts/issue-5/phase2b-autocomplete.md, docs/contracts/issue-5/task.md, tests/test_issue5_phase2b_autocomplete.py.
  • 동결 스냅샷: mam-loop-freeze.03Uqb9의 job_subscriber.py md5 = 9a651cc 시점과 동일(수정 전). engine/notifier/autocomplete/lib.sh는 HEAD와 동일. 즉 이 루프 자신의 구독자 데몬(pid 10863)은 수정 전 코드를 실행 중이며, 수정은 다음 루프 기동(새 동결)부터 효력을 갖는다. B-13 설계상 당연한 결과이고 diff의 결함이 아니다.

1. 지적 사항 대조 (Codex Round 3)

지적: _Watcher.on_message에서 seen_intents[jid].add(identity)가 단조 seq 가드보다 앞서 실행되어, 늦게 도착한 과거 패킷이 자기 intent를 "본 것"으로 태우고 뒤따르는 정당한 재시도가 드롭된다.

수정 후 순서(job_subscriber.py:144-179, 직접 확인):

  1. data가 dict가 아니면 intent 없음으로 취급(이전 코드의 (data or {}).get 경로 제거)
  2. intent가 있으나 dict가 아니면 드롭+경고
  3. identity 튜플 생성 → in seen_intents 대조를 try/except TypeError로 감싸 해시 불가면 드롭+경고
  4. 이미 본 identity면 드롭(seq 바닥을 올리지 않음)
  5. seq 단조 가드
  6. 가드 통과 후에만 seen_intents[jid].add(identity)
  7. 감사 로그 received 기록 → 큐 투입

brief의 요구 2)·3) 모두 충족. 요구 3)의 시나리오(last_seq=10, seq=9 거부 → 동일 intent seq=11 수신 → qsize()==1)가 test_r4b_a_stale_packet_does_not_burn_the_intent_for_its_own_retry에 그대로 있다.

2. 실측 재현

2-1. 신규 테스트 6종 — HEAD vs 수정 전 트리

9a651cc 워크트리에 HEAD의 테스트 파일을 복사해 실행:

결과 테스트
수정 전 FAIL stale_packet_does_not_burn_the_intent (seen_intents == {(7,'completed','T')} 잔존), unhashable_intent_is_refused (TypeError: unhashable type: 'list' 콜백 탈출), non_object_intent_is_refused
수정 전 PASS(보존 가드) duplicate_intent_does_not_advance_the_sequence_floor, absent_or_null_intent, real_retry_is_deduplicated_by_the_real_consumer

task.md P2B-07의 "3종 역검증 실패 / 3종 보존" 주장과 정확히 일치. HEAD에서는 파일 전체 65 passed.

2-2. 테스트 밖 엣지 — 실제 콜백에 직접 투입 (HEAD / 수정 전)

입력 수정 전 HEAD
data가 비어있지 않은 list / str AttributeError 콜백 탈출 정상 수신(intent 없음 취급)
intent의 event 필드가 dict(중첩 해시 불가) TypeError 콜백 탈출 드롭+경고, last_seq 불변
같은 wire seq QoS1 재전달(+intent) 1건 1건
거부된 replay(seq 9) 뒤 바닥(1)보다 크고 9보다 작은 정상 이벤트(seq 4) 수신 수신
필드가 전부 없는 intent {} 두 번 1건 수신, (None,None,None) 기록 동일 (O-1 참조)

data가 비객체인 경우는 task.md가 주장하지 않은 추가 탈출 경로인데 이번 수정으로 함께 닫혔다(paho 네트워크 스레드에서 콜백 예외는 해당 구독자의 모든 job 수신을 끊으므로 실제 가치가 있다).

2-3. 종단 테스트의 전제 확인

test_r4b_a_real_retry_...는 도달 불가 브로커(127.0.0.1:65432)로 notifier.flush 2회 후 감사 로그의 published 항목 2건을 실제 콜백에 투입한다. publish_event.record_outcome이 발행 성공 여부와 무관하게 published 감사 항목을 기록(G-1/G-2/G-4)함을 코드로 확인했으므로 전제가 성립하며, 두 항목의 wire seq가 다르고 notification_intent가 같음 → 1건만 큐에 남는다.

2-4. 린트·정합성

  • py_compile OK. pyflakes/ruff는 venv에 없고 저장소에 line-length 설정도 없음(pyproject/setup.cfg/ruff.toml 부재). 신규 줄 중 job_subscriber.py:161(102자)이 100자를 넘지만 기존 줄(98·170·227·228)도 이미 넘으므로 관례 위반 아님.
  • 테스트 실행 후 git status clean(샌드박스에 격리됨). HEAD 수집 총계 732 = 726 + 6.

3. 판단

결함 없음(blocking). 지적된 순서 역전은 "대조는 가드 앞, 기록은 가드 뒤"로 정확히 고쳐졌고, 그 논리(중복은 시퀀스를 소비할 자격이 없고, 전달 안 된 이벤트는 본 것이 아니다)가 코드 주석·계약 문서·테스트에 일관되게 적혀 있다. 방어적 파싱 강화는 콜백 예외 탈출 경로 3가지(비객체 intent, 해시 불가 필드, 비객체 data)를 닫았다.

4. 비차단 관찰 (다음 라운드/Phase 3 참고)

  • O-1. 식별 필드가 전부 없는 intent는 "안정된 신원"이 아니다. {}나 {"foo":1}은 identity (None,None,None)을 얻어 첫 번째만 수신되고 이후 같은 꼴은 모두 replay로 드롭된다. 정상 발행자(notifier.intent_for)는 세 필드를 항상 채우므로 실제 경로에서는 발생하지 않고, 발생하더라도 방향이 fail-closed(드롭)라 이중 적용은 없다. 다만 phase2b-autocomplete.md의 새 문장 "안정된 신원을 만들 수 없는 intent는 거부한다"와 코드가 정확히 맞으려면 identity 생성 직후 if any(f is None for f in identity): 드롭+경고 한 줄이면 된다. 검증: HEAD 워크트리에 이 4줄을 적용하고 phase2b·phase1 테스트 131 passed, {}/{"foo":1}는 드롭되고 정상 intent는 1회 수신됨을 확인.
  • O-2. task.md P2B-07 문구의 개수. "신규 회귀 6종(3종 실패 역검증, 3종 보존) + 종단 테스트"로 읽히지만 종단 테스트는 6종에 포함된다(test_r4b_ 함수는 정확히 6개, 보존 3종 중 하나가 종단 테스트). "6종(그중 1종은 종단)"으로 고치면 된다.
  • O-3. 운영 노트. 동결 스냅샷 원리상 현재 실행 중인 루프의 구독자는 수정 전 코드다. 이 라운드의 알림 재시도가 stale-first 상황을 만나면 옛 결함이 재현될 수 있으나, 재시작 후 디스크 폴백 + 멱등 ACK가 종단을 보증한다(기존 계약). 조치 불필요, 인지만.
  • 이전 라운드 인계 사항(O-7 direct 잡의 claim 토큰 발급, _delegate_py_bin 앵커링, redispatch 시 shadow dispatch 갱신, preexec_fn)은 이번 diff 범위 밖이며 그대로 유효하다.

5. 전체 스위트

.venv/bin/python -m pytest tests -q -p no:cacheprovider (백그라운드 1회, 정상 종료): 732 passed in 802.02s (0:13:22), FAILED/ERROR 0건. 이전 라운드 726 → 732 (+6, 신규 test_r4b_* 6종). 이번에는 이전 라운드(19ac4c53)에서 있었던 비정상 종료가 재현되지 않았다.

[VERDICT: PASS]