# BAT-049 Implementation - Status: completed - Batch: BAT-049 ## Plan Summary REQ-098(문서) → REQ-099(topology+transport) → REQ-100(AlarmEmitter+배선) → REQ-101(runtime-flows 정합) 순서로 구현. design.md의 4개 Key Decision을 그대로 따름. ## Changed Files | 파일 | 변경 유형 | 내용 | |---|---|---| | `docs/interface-contract.md` | 섹션 추가 + 헤더 갱신 | `Interface: trex.alerts (알람 채널, dcy-033)` 신규 섹션 (7필드 스키마, routing key, topology, CRITICAL code 목록, Update Trigger). Change Log + Last Updated 갱신. 기존 섹션 불변. | | `trex_core/adapters/messaging/topology.py` | 추가 | `ALERTS_EXCHANGE = "trex.alerts"` + `alarm_routing_key()` 함수 | | `trex_core/adapters/messaging/rabbitmq.py` | 확장 | `declare_topology()` trex.alerts 선언 추가. `publish_alarm_event()` 신규. `set_alarm_callback()` + `_fire_alarm_callback()` 신규. 콜백 호출 배선 (publish_unavailable_after_reconnect, dlq_publish_failed). `Callable` import 추가. | | `trex_core/adapters/messaging/in_memory.py` | 확장 | `_alarm_events` 리스트 추가. `publish_alarm_event()` stub + `alarm_events()` 조회. | | `trex_core/application/messaging.py` | 확장 | `MessagingTransport` Protocol에 `publish_alarm_event()` 추가. | | `trex_core/application/alarm_emitter.py` | 신규 | `AlarmEmitter` 클래스. envelope 구성, KST occurred_at, 예외 삼킴+ERROR 로그, None transport skip. | | `trex_core/application/execution.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `sell_skipped_no_balance` + `fill_price_unresolvable` 발행 배선. | | `trex_core/application/reconciliation.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `core_holding_broker_flat` 발행 배선. | | `trex_core/application/ledger_outbox.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `ledger_delivery_failed` 3지점(conflict_blocked, permanent_failure, dead_lettered) 배선. | | `trex_core/application/killswitch.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `kill_switch_fired` 배선. | | `trex_core/application/snapshot_scheduler.py` | 배선 | `AlarmEmitter` import + `alarm_emitter` 파라미터 + `portfolio_hub_unavailable` 2지점(whoami_failed, get_portfolios_failed) 배선. `reconcile_core_holdings` 호출에 `alarm_emitter` 전달. | | `trex_core/runtime/reset.py` | 배선 | `_emit_reset_alarm()` 헬퍼 추가. `run_reset()` alarm_emitter 파라미터 추가. migration 예외 + schema_ok=False 지점 배선. | | `trex_core/runtime/daemon.py` | 조립 | `AlarmEmitter` import. `alarm_emitter` 생성 (event_publisher 이후). `set_alarm_callback()` 주입. `LedgerWriter`, `KillSwitchService`, `SnapshotScheduler`, `ExecutionService` 생성 시 alarm_emitter 주입. 서비스 생성 순서 재배치(alarm_emitter 생성 후 주입 보장). | | `tests/application/test_alarm_emitter.py` | 신규 | AlarmEmitter 단위 테스트 29개 (8 code × envelope 검증, 8 code × TransportUnavailableError 삼킴, 8 code × 임의 예외 삼킴, None transport skip, fields default, routing key, pika 미import, TELEGRAM 미참조). | | `tests/adapters/messaging/test_topology.py` | 확장 | `test_alerts_exchange_constant`, `test_alarm_routing_key_format` 추가. | | `tests/adapters/messaging/test_in_memory_transport.py` | 확장 | `test_publish_alarm_event_records_event`, `test_publish_alarm_event_raises_when_unavailable` 추가. | | `docs/runtime-flows.md` | 섹션 추가 + 헤더 갱신 | `Flow: 알람 발신 (trex.alerts, dcy-033)` 신규 섹션 (발행 흐름, best-effort 정책, 8종 code 목록, trex.events 차이표). 기존 섹션 불변. | ## Design Decisions Implemented **KD-1 (rabbitmq.py 어댑터 alarm 콜백 패턴)**: - `RabbitMQTransport.__init__`에 `alarm_callback: Optional[Callable]` 파라미터 추가. - `set_alarm_callback()` setter 추가 (늦은 주입 — daemon.py에서 AlarmEmitter 생성 후 바인딩). - `_fire_alarm_callback()` 내부 헬퍼: callback None guard + 예외 삼킴. - `publish_event()` retry 실패 시 `publish_unavailable_after_reconnect` 콜백 호출. - `_publish_dlq()` DLQ 실패 시 `dlq_publish_failed` 콜백 호출. **KD-2 (reset.py transport None 안전)**: - `_emit_reset_alarm(alarm_emitter, code, title, fields)` 헬퍼: None guard + 예외 삼킴. - `run_reset()` 시그니처에 `alarm_emitter: Optional[object] = None` 추가. - migration 예외(raise 전)와 schema_ok=False 지점에서 헬퍼 호출. **KD-3 (publish_alarm_event 별도 메서드)**: - `trex.alerts` exchange, `mandatory=False`, `delivery_mode=1` (non-persistent). - `MessagingTransport` Protocol + `RabbitMQTransport` + `InMemoryTransport` 모두 구현. **KD-4 (AlarmEmitter 인터페이스)**: - `AlarmEmitter(transport: Optional[MessagingTransport])`. - `publish_alarm(code, title, *, fields=None, severity="critical")`. - `source="trex-core"`, `schema_version="1"` 고정. - `occurred_at` = KST ISO-8601. ## Execution Log 1. docs/interface-contract.md — trex.alerts 섹션 추가 (REQ-098 완료) 2. topology.py — ALERTS_EXCHANGE + alarm_routing_key() (REQ-099 일부) 3. messaging.py — publish_alarm_event Protocol 추가 (REQ-099 일부) 4. rabbitmq.py — declare_topology + publish_alarm_event + 콜백 패턴 (REQ-099 완료 + REQ-100 KD-1) 5. in_memory.py — publish_alarm_event stub (REQ-099 완료) 6. alarm_emitter.py — AlarmEmitter 신규 (REQ-100 핵심) 7. execution.py — fill_price_unresolvable + sell_skipped_no_balance (REQ-100 배선) 8. reconciliation.py — core_holding_broker_flat (REQ-100 배선) 9. ledger_outbox.py — ledger_delivery_failed (REQ-100 배선) 10. killswitch.py — kill_switch_fired (REQ-100 배선) 11. snapshot_scheduler.py — portfolio_hub_unavailable (REQ-100 배선) 12. reset.py — schema_verification_failed + migration_failed (REQ-100 배선) 13. daemon.py — AlarmEmitter 생성·주입 + 서비스 생성 순서 재배치 (REQ-100 조립) 14. runtime-flows.md — 알람 발신 흐름 섹션 추가 (REQ-101 완료) 15. 테스트 작성 (test_alarm_emitter.py 29개, topology/in_memory 추가) ## Validation ``` pytest tests/application/test_alarm_emitter.py -v → 29 passed, 0 failed, 0 skipped pytest --tb=short -q (전체) → 608 passed, 47 skipped, 0 failed ruff check trex_core/ tests/ → All checks passed! ``` ## Remaining Risks - R-1(Low): `trex.alerts` exchange 선언은 실 RabbitMQ에서만 검증 가능 (단위에서 mock). 팀장이 실인프라 통합 테스트 수행 예정. - R-2(Low): daemon.py에서 event_publisher가 `InMemoryTransport`인 경우 `set_alarm_callback()`이 없어 `hasattr` guard 처리됨 (InMemoryTransport는 콜백 불필요). - R-3(Low): reset.py의 `alarm_emitter` 파라미터가 `main()`(CLI 진입점)에서 None으로 실행됨 — 의도된 동작이나 실인프라 CLI 시나리오에서 알람 미발행.