Files
trex-core/docs/batches/bat-053_20260628_daystart-credential-fetch-failsafe/implementation.md
T
genichinandClaude Sonnet 4.6 e4e5c70422 feat(bat-053): 그날-시작 KIS 인증정보 fetch + fail-safe 로드 (REQ-112~115)
REQ-112: BrokerCredentialPayload/TradingContext 데이터클래스 + PortfolioHubReadPort.fetch_broker_credential() 포트 메서드 + hub_read 어댑터(_fetch_user_info 헬퍼 추출) + stub_read 결정론 stub
REQ-113: DaemonInstanceConfig/DaemonConfig.credential_fetch_enabled opt-in(기본 False) + TOML 파싱 + break-glass(TREX_KIS_BROKER_BREAK_GLASS) 감지
REQ-114: SnapshotScheduler._maybe_fetch_credential() — fail-closed 3조건(unavailable/broker-null/trading_disabled) + CRITICAL 알람 DAY_START_LOAD_FAILED + IntentService entry_allowed_fn 게이트(BuyIntent 차단, SellIntent 통과)
REQ-115: 그날 고정 캐시(_cached_credential/_credential_date) — 동일 날짜 재fetch 방지 + app_secret 메모리 한정(Redis 미기록)

스텁 기반 구현: 실 portfolio-hub/네트워크 없이 AC 전부 단위 테스트.
회귀: credential_fetch_enabled=False(기본) 경로 byte-for-byte 보존.
745 passed, 47 skipped (0 failed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
2026-06-28 11:00:18 +09:00

5.5 KiB

BAT-053: Implementation

변경 파일 목록

신규/확장 모듈

파일 변경 유형 관련 REQ
trex_core/application/portfolio.py 확장 REQ-112
trex_core/adapters/portfolio/hub_read.py 확장 REQ-112
trex_core/adapters/portfolio/stub_read.py 확장 REQ-112
trex_core/runtime/config.py 확장 REQ-113
trex_core/application/snapshot_scheduler.py 확장 REQ-113/114/115
trex_core/application/intents.py 확장 REQ-114
trex_core/runtime/daemon.py 확장 (조립) REQ-113/114

신규 테스트

파일 관련 REQ
tests/adapters/portfolio/test_hub_read.py (확장) REQ-112 AC-1~7
tests/adapters/portfolio/test_stub_read.py (확장) REQ-112 AC-6
tests/runtime/test_instance_config.py (확장) REQ-113 AC-1,7
tests/application/test_snapshot_scheduler.py (확장) REQ-114 AC-1~9, REQ-115 AC-3/6

REQ별 구현 요약

REQ-112: BrokerCredentialPayload + TradingContext + 포트 메서드 + 어댑터

  • application/portfolio.py:
    • BrokerCredentialPayload(account_ref, app_key, app_secret, credential_version) frozen dataclass 추가
    • TradingContext(broker, trading_enabled) frozen dataclass 추가
    • PortfolioHubReadPort.fetch_broker_credential() -> TradingContext 포트 메서드 추가
  • adapters/portfolio/hub_read.py:
    • whoami() 내부 로직을 _fetch_user_info() -> dict 헬퍼로 추출 — 외부 동작 불변(AC-5 회귀 보존)
    • fetch_broker_credential() 구현: broker null → TradingContext(broker=None), credential_payload null → TradingContext(broker=None), 성공 → BrokerCredentialPayload 반환
    • NFR-2: app_secret 평문 로그 없음 — 어댑터 내 로깅 코드에 app_secret 미포함
  • adapters/portfolio/stub_read.py:
    • trading_context: Optional[TradingContext], fetch_broker_raises: bool 인자 추가
    • fetch_broker_credential() stub 구현 — 네트워크 없이 결정론적 반환/예외

REQ-113: credential_fetch_enabled opt-in + break-glass

  • runtime/config.py:
    • DaemonInstanceConfig.credential_fetch_enabled: bool = False 추가 (TOML opt-in, 기본 False)
    • DaemonInstanceConfig.from_toml(): credential_fetch_enabled 파싱 (부재 시 False)
    • DaemonConfig.credential_fetch_enabled: bool = False 추가
    • DaemonConfig.from_config(): inst.credential_fetch_enabled → DaemonConfig 전파
    • DaemonConfig.from_env(): 변경 없음 → 항상 False (AC-1 회귀 0)
  • application/snapshot_scheduler.py:
    • 생성자 credential_fetch_enabled: bool = False 인자 추가
    • 초기화 시 TREX_KIS_BROKER_BREAK_GLASS env 감지(break-glass 플래그)
    • break-glass: INFO 로그 후 env-only 경로(fetch 없이 entry_allowed=True)

REQ-114: 그날-시작 로드 게이트 + fail-closed + 알람

  • application/snapshot_scheduler.py:
    • _entry_allowed: bool = True 상태 추가
    • _maybe_fetch_credential(): fail-closed 3조건 처리 + CRITICAL 알람 DAY_START_LOAD_FAILED 발신
    • run_once() 선두: credential_fetch_enabled=True이면 _maybe_fetch_credential() 호출
    • get_entry_allowed() -> bool 노출
  • application/intents.py:
    • IntentService 생성자 entry_allowed_fn: Optional[Callable[[], bool]] = None 추가
    • _admit_buy() 선두: entry_allowed_fn is not None and not entry_allowed_fn() → reject day_start_load_failed
    • SellIntent(_admit_sell) 변경 없음 — 청산 경로 영향 없음 (AC-5)
  • runtime/daemon.py:
    • SnapshotScheduler 생성 후 entry_allowed_fn = scheduler.get_entry_allowed를 IntentService에 주입
    • credential_fetch_enabled=False(기본) → entry_allowed_fn=None → 게이트 비활성

REQ-115: 그날 고정 캐시 + 장중 무중단

  • application/snapshot_scheduler.py:
    • _cached_credential: Optional[BrokerCredentialPayload], _credential_date: Optional[date] 추가
    • _maybe_fetch_credential(): _credential_date == today 시 재fetch 생략 (그날 고정 캐시, AC-3)
    • 성공 시 캐시 저장 + INFO 로그(account_ref/trading_enabled/credential_version; app_secret 미포함)
    • Redis 등 외부 저장소 기록 없음 — 메모리 한정 (AC-5)

설계 결정 반영 확인

  • D1(entry_allowed 위치): SnapshotScheduler 내 + IntentService callable 주입 — 구현됨
  • D2(credential_fetch_enabled 전파): DaemonInstanceConfig → DaemonConfig → SnapshotScheduler — 구현됨
  • D3(우선순위 로직 위치): SnapshotScheduler._maybe_fetch_credential() — 구현됨
  • D4(그날 고정 캐시): _credential_date 비교로 동일 날짜 재fetch 방지 — 구현됨
  • D5(중장기 후크): BAT-053에서는 단기 07:30 경로(run_once())만 구현. 중장기는 동일 run_once() 경유로 처리됨 — 확인
  • D6(AlarmEmitter): publish_alarm("DAY_START_LOAD_FAILED", ...) 직접 호출 패턴 — 구현됨

주요 설계 메모

  • credential_fetch_enabled=False(기본)일 때: _maybe_fetch_credential() 미호출 → _entry_allowed=True 고정 → IntentService entry_allowed_fn=None → 게이트 완전 비활성. 기존 동작 byte-for-byte 보존(회귀 0).
  • KISBroker 재조립(fetch 결과를 실제 KIS API에 적용)은 BAT-053 범위 외 — M3 별도 batch. 현재 _cached_credential이 메모리에 저장되어 있으며 다음 batch에서 KISBroker 재초기화에 활용.
  • REQ-115 AC-2(ledger_outbox 무손실): 기존 REQ-019 구현 속성 재확인. 별도 구현 불필요, 기존 테스트 그린(745 passed).