발행 전용 연결의 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
9.9 KiB
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()를 실제 호출하도록 재작성 → 팀장 코드 확인(L398object.__new__(Daemon)→ L404_make_event_emitter()→ L422emit(...)). - 팀장 직접 전체 재실행: 554 passed / 47 skipped / 0 실패 (회귀 0).
- ① ruff F401 3건 제거 → 팀장 직접 재실행
- 수용한 잔여 한계(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 강화.
- half-open / broker 재시작 / idle 60초 생존은 mock unit으로만 검증. 실 broker 통합(
- 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.pyL28:import threading제거tests/adapters/messaging/test_publish_reliability.pyL29:from unittest.mock import MagicMock, call, patch→call제거trex_core.runtime.daemon.Daemonimport는 시정 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.pyL247-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-16, REQ-093 AC-16, REQ-094 AC-15 전부 PASS 또는 evidence 있음 - wire 계약 무회귀: PASS
- baseline 문서 정합: PASS
- Blocking Issues: 없음
- Ready for Release: false (멈춤 — confirm-batch-verification은 팀장 게이트)