# BAT-047 Verification - Status: release-candidate - 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-1~6, REQ-092 AC-1~6, REQ-093 AC-1~6, REQ-094 AC-1~5 전부 PASS 또는 evidence 있음 - wire 계약 무회귀: PASS - baseline 문서 정합: PASS - Blocking Issues: 없음 - Ready for Release: true (팀장 confirm-release 승인 2026-06-25 — 이미 검증·푸시 완료, REL-009 포함 확정)