feat(alarm): trex-core 알람 emitter + trex.alerts exchange (dcy-033 BAT-049, Implemented)

AlarmEmitter(application, best-effort 비차단)+trex.alerts topic exchange 가산·격리+8 CRITICAL code 배선. 608 passed, opus QC PASS, 팀장 e2e(텔레그램 HTTP200) 통과. REQ-098~101 Implemented.

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-21 19:25:55 +09:00
co-authored by Claude Opus 4.8
parent b2165eac25
commit d4c5fcbba1
29 changed files with 1195 additions and 47 deletions
+61 -1
View File
@@ -4,7 +4,7 @@
- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml
- Status: draft
- Owner: project maintainers
- Last Updated (KST): 2026-06-21 (BAT-048 — dcy-032 체결가 0/None 백필 가드 + WARN 로깅)
- Last Updated (KST): 2026-06-21 (BAT-049 — dcy-033 알람 발신 흐름 추가)
- Source Discovery / Batch: bootstrap-baseline (pre-Discovery); 데몬/HTTP 흐름 = dcy-002 / BAT-002
- Primary Runtime: daemon (HTTP API) + library (slot 도메인)
@@ -382,3 +382,63 @@ timeout 미지정·취소 실패로 잔여 고착된 pending 슬롯을 처분:
- 2026-06-10 :: BAT-031 (dcy-021) — 포지션 사이징(증액·부분 감액) 흐름 추가, transit 13 전이.
- 2026-06-12 :: BAT-038 (dcy-026/issue #24) — 매도 증권거래세 누락 정정: `체결 → ledger outbox` flow 의 `tax=0` → `calculate_tax(side, amount)`(매도 0.2%/매수 0) 정책 계산. fee/tax 단일 정책 지점 + env(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`), paper·KIS 동일 공유, `tax=0.0` 하드코딩 제거 (REQ-018 CHG-20260612-01).
- 2026-06-12 :: BAT-041 (dcy-027/issue #20 이월) — 중장기 인스턴스 병행 운영 flow 신설: 전용 instance_key 프로파일(`config/longterm.example.toml`), `watchdog_timeout_sec` per-instance TOML(REQ-076, env 폴백), 보호 규칙 경계(전략 모듈 전적 책임 + CLI exit 백스톱, 코어 손절매 미사용), forced_close_at 중장기 미사용 (REQ-076~078).
- 2026-06-21 :: BAT-049 (dcy-033) — 알람 발신 흐름 신설: `AlarmEmitter` → `MessagingTransport.publish_alarm_event()` → `trex.alerts` exchange, best-effort 비차단 정책, transport None 안전 skip, CRITICAL code 8종 발생 지점 목록.
## Flow: 알람 발신 (trex.alerts, dcy-033)
trex-core가 CRITICAL 조건 발생 시 `trex.alerts` exchange로 표준 alarm envelope을 발행한다.
### 발행 흐름
```
발생 지점 (execution.py / killswitch.py / reconciliation.py 등)
│ alarm_emitter.publish_alarm(code, title, fields=...)
▼
AlarmEmitter.publish_alarm() [application 레이어]
│ envelope 구성: source="trex-core", severity="critical",
│ schema_version="1", occurred_at=KST ISO-8601
│ routing_key = "alarm.critical.trex-core"
│ MessagingTransport.publish_alarm_event(routing_key, payload)
▼
RabbitMQTransport.publish_alarm_event() [adapters 레이어]
│ basic_publish(exchange=trex.alerts, mandatory=False, delivery_mode=1)
▼
trex.alerts exchange (topic, durable=True)
│ notifier가 alarm.# 패턴으로 바인딩한 큐
▼
trex-notifier (단일 소비자) → 텔레그램 등 전달
```
### best-effort 비차단 정책
- 발행 실패(`TransportUnavailableError`, 임의 예외) 시: ERROR 로그 후 예외 삼킴(re-raise 금지).
- 슬롯 전이·매매 집행·원장 기록 경로는 알람 발행 실패에 영향받지 않는다.
- transport 미연결(None) 시: INFO 로그 후 silent skip. `reset.py` 등 CLI 진입점에서 안전.
- notifier 미연결(unroutable) 시: `mandatory=False` → silent drop. `TransportUnavailableError` 없음.
### trex-core CRITICAL code 8종 발생 지점
| code | 발생 모듈 | 조건 |
|---|---|---|
| `fill_price_unresolvable` | `application/execution.py` | 체결가·지정가 모두 0/None → 슬롯 PENDING 고착 위험 |
| `sell_skipped_no_balance` | `application/execution.py` | 매도 dispatch skip: 브로커 잔고 0 |
| `core_holding_broker_flat` | `application/reconciliation.py` | 슬롯 HOLDING인데 브로커 잔고 0 (정산 불일치) |
| `ledger_delivery_failed` | `application/ledger_outbox.py` | 원장 영구 실패 / dead-letter / 409 충돌 |
| `kill_switch_fired` | `application/killswitch.py` | 긴급 청산 발동 |
| `publish_unavailable_after_reconnect` | `adapters/messaging/rabbitmq.py` | 재연결 후에도 이벤트 발행 불가 (콜백 패턴) |
| `dlq_publish_failed` | `adapters/messaging/rabbitmq.py` | DLQ 발행 실패 (콜백 패턴) |
| `portfolio_hub_unavailable` | `application/snapshot_scheduler.py` | whoami / get_portfolios 실패 → 스냅샷 부실화 |
| `schema_verification_failed` | `runtime/reset.py` | 리셋 후 daily_slot 컬럼 불일치 |
| `migration_failed` | `runtime/reset.py` | apply_migrations 예외 |
### trex.events와의 차이점
| 항목 | `trex.events` | `trex.alerts` |
|---|---|---|
| 용도 | 체결/슬롯 상태 이벤트 | CRITICAL 알람 envelope |
| exchange 타입 | topic, durable | topic, durable |
| mandatory | True (DLQ 라우팅) | **False** (silent drop) |
| DLQ | 있음 (`trex.events.dlq`) | **없음** |
| delivery_mode | 2 (persistent) | 1 (non-persistent) |
| 소비자 | 전략 모듈(strategy_id 필터) | trex-notifier 단독 |
| 발행 재시도 | 재연결 후 1회 retry | **없음** (best-effort) |