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
This commit is contained in:
2026-06-19 22:39:43 +09:00
co-authored by Claude Opus 4.8
parent bc79f60274
commit d182464b7a
19 changed files with 1067 additions and 43 deletions
+13 -1
View File
@@ -244,12 +244,24 @@ class Daemon:
try:
self.event_publisher.publish_event(rk, payload)
except Exception: # noqa: BLE001 - publish failure must not roll back
_log.warning("event publish failed (rk=%s)", rk, exc_info=True)
# REQ-093: escalate to ERROR so operators can observe via log
# alert. Include routing key + slot identity for correlation.
_log.error(
"event publish failed (rk=%s slot_id=%s strategy=%s)",
rk,
getattr(slot, "slot_id", None) or getattr(slot, "id", None),
getattr(slot, "strategy_id", None),
exc_info=True,
)
return emit
# ------------------------------------------------------------ workers
def _execution_loop(self) -> None:
while not self.stop.wait(self.config.execution_interval_sec):
# REQ-092: keepalive tick — process pending I/O frames on the
# publish connection before each execution run. Called in the
# same thread that owns event_publisher (pika thread-safety rule).
self.event_publisher.keepalive_tick()
try:
self.execution.run_once()
except Exception: # noqa: BLE001 - loop must survive transient errors