Files
trex-core/docs/batches/bat-047_20260619_event-publisher-connection-reliability/verification.md
T
genichinandClaude Opus 4.8 d182464b7a feat(messaging): OPS-005 이벤트 발행 연결 신뢰성 (BAT-047)
발행 전용 연결의 idle heartbeat 사망 후 첫 발행 유실 결함 제거.
- REQ-091 발행 재시도 + 강제 재연결(_force_reconnect, is_open 불신)
- REQ-092 idle keepalive(execution-loop 단일스레드 틱, process_data_events time_limit=0)
- REQ-093 DLQ 가시성 + emit 실패 ERROR 경보(구조화 로그, dcy-029 재사용)
- REQ-094 baseline 문서 정합(runtime-flows/operations/project-structure)

독립 opus QC PASS-WITH-NOTES → 시정 2건(ruff F401, AC-3 프로덕션 경로) 완료.
554 passed/47 skipped/회귀 0/ruff clean. NFR-1 wire 무회귀(interface-contract diff 0).
BAT-047 release-candidate, REQ-091~094 Implemented. dcy-031. M2 트랙B.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
2026-06-19 22:39:43 +09:00

9.9 KiB

BAT-047 Verification

  • Status: approved (confirm-batch-verification, 팀장 2026-06-19)
  • Batch: BAT-047
  • Verified By: trex-core-dev (draft-batch-verification → QC PASS-WITH-NOTES 시정 후 재검증)
  • Verified At (KST): 2026-06-19 (최초), 2026-06-19 (QC 시정 재검증)
  • Confirmed By: 팀장 (독립 opus qc-reviewer PASS-WITH-NOTES + 시정 2건 완료 + 팀장 직접 재확인)

Human Approval Memo (팀장 confirm-batch-verification)

  • 독립 opus qc-reviewer 판정 = PASS-WITH-NOTES. 반환 2건 모두 시정 완료:
    • ① ruff F401 3건 제거 → 팀장 직접 재실행 All checks passed! 확인.
    • ② REQ-093 AC-3 테스트가 프로덕션 Daemon._make_event_emitter()를 실제 호출하도록 재작성 → 팀장 코드 확인(L398 object.__new__(Daemon) → L404 _make_event_emitter() → L422 emit(...)).
    • 팀장 직접 전체 재실행: 554 passed / 47 skipped / 0 실패 (회귀 0).
  • 수용한 잔여 한계(blocker 아님, release 노트에 명시할 것):
    • half-open / broker 재시작 / idle 60초 생존은 mock unit으로만 검증. 실 broker 통합(TREX_RABBITMQ_TEST_URL)은 환경 미설정으로 skip → M1 운영 또는 broker 포함 CI로 통합검증 위임.
    • NFR-2 "publish 경로 블록 ≤5초"는 재연결(2.0s)+재시도(2.0s) 구간에 엄밀 성립. 1차 실패 시도가 원 socket_timeout(5.0s)에 묶이면 최악 케이스 초과 가능 → 필요 시 후속 REQ로 초기 publish timeout 강화.
  • NFR-1(M1 라이브 무회귀): wire 계약·consume/RPC·토폴로지·interface-contract.md(diff 0) 불변 확인 — qc-reviewer 코드 추적 + 팀장 확인.

Test Run Summary

정정 (QC PASS-WITH-NOTES 시정 후): 최초 verification.md에서 "ruff clean"으로 기재했으나, QC 독립 검증에서 ruff F401 3건(threading, unittest.mock.call, trex_core.runtime.daemon.Daemon)이 검출됐다. 또한 REQ-093 AC-3 테스트가 프로덕션 _make_event_emitter 경로를 호출하지 않고 emit 클로저를 테스트 본문에 직접 복제하는 구조였다. 이하는 시정 후 실제 재실행 결과다.

시정 후 재실행 결과

명령: python3 -m ruff check .
결과: All checks passed!
명령: python3 -m pytest tests/ --ignore=tests/adapters/messaging/test_rabbitmq_transport.py \
                               --ignore=tests/adapters/messaging/test_rabbitmq_intent_e2e.py -q
결과: 554 passed, 40 skipped in 1.51s
기존 baseline: 537 passed (BAT-046 기준)
신규 추가: 17 passed (tests/adapters/messaging/test_publish_reliability.py)
회귀: 0건

Acceptance Criteria Mapping

REQ-091: 이벤트 발행 재시도 + 강제 재연결

AC 내용 Evidence Result
AC-1 half-open → 재연결+재시도 → 발행 성공 TestPublishRetryOnHalfOpen::test_half_open_first_attempt_fails_retry_succeeds PASSED PASS
AC-2 재시도 소진 → TransportUnavailableError 전파 TestPublishRetryExhausted::test_retry_exhausted_raises_transport_unavailable PASSED PASS
AC-3 타임아웃 상한 확인 (connect_timeout=2.0) TestForceReconnectTimeout::test_force_reconnect_uses_2s_timeout PASSED PASS
AC-4 정상 발행(연결 살아 있음) — 재시도 경로 미호출 TestNormalPublishNoRetry::test_normal_publish_does_not_call_force_reconnect PASSED PASS
AC-5 consume/RPC 경로 무회귀 TestConsumePaths::test_rabbitmq_transport_has_consume_methods PASSED + 전체 회귀 554 passed 0 회귀 PASS
AC-6 broker 재시작 후 발행 복구 TestBrokerRestartScenario::test_broker_restart_publish_recovers PASSED PASS

REQ-092: 발행 전용 연결 idle keepalive

AC 내용 Evidence Result
AC-1 idle 후 연결 생존 (keepalive가 process_data_events 호출) TestKeepaliveTick::test_keepalive_tick_calls_process_data_events_on_live_connection PASSED PASS
AC-2 발행 소유 스레드에서만 I/O (design 결정: execution-loop 틱에 삽입) OQ-1 design 문서에 Answered 기록. daemon.py _execution_loop 내 keepalive_tick() 호출 코드 diff 확인. pika 스레드 경계 불침범 PASS
AC-3 동시 발행+keepalive 충돌 없음 TestKeepaliveAndPublishSameThread::test_keepalive_then_publish_sequential_no_error PASSED (단일 스레드 순차 호출) PASS
AC-4 keepalive 비블록 (time_limit=0) TestKeepaliveTimeLimitZero::test_keepalive_uses_time_limit_zero PASSED PASS
AC-5 consume/RPC 경로 무회귀 TestKeepaliveDoesNotAffectConsumePaths::test_keepalive_tick_not_present_on_consume_rpc_forever_signature PASSED PASS
AC-6 OQ-1 종료 조건 (design 문서에 keepalive 위치 결정) design.md "OQ-1: Answered — execution-loop 틱 삽입" 기록 완료 PASS

REQ-093: DLQ 가시성/경보 + emit 실패 경보

AC 내용 Evidence Result
AC-1 unroutable → DLQ 유입 + 관측 지표 노출 TestDlqVisibilityOnUnroutable::test_unroutable_calls_publish_dlq_and_logs_error PASSED + test_publish_dlq_logs_error_on_entry PASSED PASS
AC-2 DLQ 깊이 파악 가능 운영자 RabbitMQ 관리 UI + rabbitmqctl list_queues 조회 절차 docs/operations.md §4에 기재 PASS
AC-3 emit 최종 실패 → ERROR 로그(rk+slot_id+strategy 포함) TestEmitErrorOnFinalPublishFailure::test_emit_logs_error_on_transport_unavailable PASSED — 시정 후: 복제 클로저 제거, object.__new__(Daemon)으로 stub 생성 후 event_publisher만 주입해 실제 Daemon._make_event_emitter()가 반환한 emit 클로저 직접 호출로 재작성. 프로덕션 경로 회귀 가드 확립. PASS
AC-4 _publish_dlq 재발행 실패 → ERROR 로그 TestDlqPublishFailureLogsError::test_dlq_publish_failure_logs_error_not_warning PASSED PASS
AC-5 OQ-3 종료 조건 (design 문서에 DLQ 가시성 방식 결정) design.md "OQ-3: Answered — 구조화 로그 기반 (dcy-029 자산 재사용)" 기록 완료 PASS
AC-6 consume/RPC 경로 무회귀 TestConsumePaths PASSED + 전체 회귀 554 passed 0 회귀 PASS

REQ-094: baseline 문서 정합

AC 내용 Evidence Result
AC-1 runtime-flows.md 발행/keepalive 경로 정합 docs/runtime-flows.md — 슬롯 이벤트 발행/구독 재시도/재연결/keepalive/DLQ 관측 경로 기술 추가 확인 PASS
AC-2 OQ-1 결정(keepalive 위치) runtime-flows.md 반영 데몬 lifecycle flow에 trex-execution 틱 시작 시 keepalive_tick() 호출 기술 추가 확인 PASS
AC-3 OQ-3 결정(DLQ 가시성) operations.md DLQ 절차 신규 기재 docs/operations.md §4 "DLQ 가시성 / 이벤트 발행 경보" 섹션 신규 생성 확인 PASS
AC-4 project-structure.md 신규 컴포넌트 반영 messaging.py(keepalive_tick), rabbitmq.py(재시도+keepalive), daemon.py(keepalive/ERROR 격상), 신규 테스트 파일 기술 추가 확인 PASS
AC-5 interface-contract.md 불변 diff 확인 — 본 batch에서 docs/interface-contract.md 변경 없음 (AMQP 토폴로지·큐·exchange·라우팅 키 불변) PASS

Wire 계약 무회귀 검증 (NFR-1)

  • declare_topology 경로: 변경 없음.
  • consume_rpc_forever / consume_events_forever / call_rpc 경로: 변경 없음.
  • EVENTS_EXCHANGE, EVENTS_DLQ_EXCHANGE, EVENTS_DLQ_QUEUE 토폴로지: 변경 없음.
  • 발행 routing key 형식({event_type}.{strategy_id}.{symbol}): 변경 없음.
  • 페이로드 스키마: 변경 없음.
  • docs/interface-contract.md: 변경 없음.

baseline 문서 동기화 (Architecture Impact: runtime-flows)

  • docs/runtime-flows.md — 갱신 완료 (Last Updated: 2026-06-19 BAT-047)
  • docs/project-structure.md — 갱신 완료 (Last Updated: 2026-06-19 BAT-047)
  • docs/operations.md — DLQ 절차 신규 기재 완료

미검증 항목 (Blocking Issues 없음, 잔여 위험으로 분류)

  • 실제 broker half-open 재현: TREX_RABBITMQ_TEST_URL 환경이 없어 통합 테스트(test_rabbitmq_transport.py, test_rabbitmq_intent_e2e.py) skip됨 (기존 skip과 동일 조건). 신규 테스트는 모두 mock 기반 unit test로 로직을 검증함.
  • OQ-2 소비자 멱등성: orb/tsl 재시도 중복 이벤트 수신 시 멱등성 확인은 각 repo 별도 추적(본 batch 밖). trex-core 범위 완료.

QC PASS-WITH-NOTES 시정 내역 (2026-06-19 재검증)

QC opus 검증에서 지적된 2건을 시정했다.

시정 1 — ruff F401 3건 제거

  • tests/adapters/messaging/test_publish_reliability.py L28: import threading 제거
  • tests/adapters/messaging/test_publish_reliability.py L29: from unittest.mock import MagicMock, call, patch → call 제거
  • trex_core.runtime.daemon.Daemon import는 시정 2에서 실제 사용하도록 재작성해 F401 해소

시정 2 — REQ-093 AC-3 테스트를 프로덕션 경로로 재작성

  • 구: 테스트 본문에 emit 클로저 직접 복제 (프로덕션 _make_event_emitter 미호출)
  • 신: object.__new__(Daemon)으로 stub 인스턴스 생성 → event_publisher만 mock 주입 → daemon_stub._make_event_emitter()가 반환한 emitter를 그대로 호출
  • 효과: daemon.py L247-253의 _log.error(...) 경로를 실제로 타므로, 해당 코드가 제거/변경되면 테스트가 실패해 회귀를 가드함

재검증 실제 결과

  • python3 -m ruff check . → All checks passed!
  • python3 -m pytest tests/ --ignore=... -q → 554 passed, 40 skipped in 1.51s, 회귀 0

Ready for confirm-batch-verification

  • 신규 테스트: 17 passed
  • 전체 테스트: 554 passed, 40 skipped (기존 대비 +17, 회귀 0)
  • ruff: clean (F401 3건 시정 완료, 실제 실행 결과)
  • AC 커버: REQ-091 AC-16, REQ-092 AC-16, REQ-093 AC-16, REQ-094 AC-15 전부 PASS 또는 evidence 있음
  • wire 계약 무회귀: PASS
  • baseline 문서 정합: PASS
  • Blocking Issues: 없음
  • Ready for Release: false (멈춤 — confirm-batch-verification은 팀장 게이트)