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
+17 -2
View File
@@ -9,7 +9,9 @@ manual holds).
from __future__ import annotations
import logging
from typing import Optional
from trex_core.application.alarm_emitter import AlarmEmitter
from trex_core.application.broker_port import BrokerPort
from trex_core.application.portfolio import SnapshotStore
from trex_core.domain.slot.state import SlotState
@@ -17,8 +19,14 @@ from trex_core.domain.slot.state import SlotState
_log = logging.getLogger(__name__)
def reconcile_core_holdings(*, slot_repo, slot_service, broker: BrokerPort,
instance_key: str) -> dict[str, str]:
def reconcile_core_holdings(
*,
slot_repo,
slot_service,
broker: BrokerPort,
instance_key: str,
alarm_emitter: Optional[AlarmEmitter] = None,
) -> dict[str, str]:
"""dcy-020 / REQ-062 (BAT-029): surface core-slot holding ↔ broker divergence.
Broker balance is the reconcile ground truth. Decisions (no silent auto-fix,
@@ -48,6 +56,13 @@ def reconcile_core_holdings(*, slot_repo, slot_service, broker: BrokerPort,
"core holding but broker flat: slot=%s symbol=%s -> error",
slot.slot_id, sym,
)
# dcy-033 / REQ-100: CRITICAL alarm — position mismatch.
if alarm_emitter is not None:
alarm_emitter.publish_alarm(
"core_holding_broker_flat",
f"슬롯 보유인데 브로커 잔고 0 (slot={slot.slot_id} symbol={sym})",
fields={"slot_id": str(slot.slot_id), "symbol": sym},
)
except Exception: # noqa: BLE001 - never let reconcile crash the loop
_log.exception("failed to error-quarantine slot %s", slot.slot_id)
elif slot.qty is not None and int(slot.qty) != bq: # C — qty mismatch