# BAT-053: Planning ## Scope Summary 그날-시작 시 portfolio-hub `/v1/user_info`에서 KIS 인증정보를 fetch하고, 실패 시 fail-closed로 신규 진입을 차단하며, 성공 시 그날 캐시로 KIS API 장중 연속성을 보장한다. 4개 REQ 모두 같은 delivery 단위: - REQ-112: 트랜스포트 계층 (포트 인터페이스 + HTTP 어댑터 + stub) - REQ-113: 설정 계층 (TOML opt-in + fetch/break-glass/env-only 우선순위) - REQ-114: 그날-시작 로드 게이트 (fail-closed + 알람 + BuyIntent 차단) - REQ-115: 그날 고정 캐시 + 장중 무중단 NFR ## Task Breakdown ### T1: REQ-112 — 포트/어댑터 계층 - `application/portfolio.py`: `BrokerCredentialPayload`, `TradingContext` 데이터클래스 + `PortfolioHubReadPort.fetch_broker_credential()` 포트 메서드 추가 - `adapters/portfolio/hub_read.py`: `_fetch_user_info()` 헬퍼 추출 + `fetch_broker_credential()` 구현 - `adapters/portfolio/stub_read.py`: `fetch_broker_credential()` stub 추가 - 테스트: `tests/adapters/portfolio/test_hub_read.py` AC-1~7, `test_stub_read.py` AC-6 ### T2: REQ-113 — 설정 계층 - `runtime/config.py`: `DaemonInstanceConfig.credential_fetch_enabled: bool = False` + `from_toml()` 파싱 - `DaemonConfig.from_config()` 또는 new helper: fetch/break-glass/env-only 우선순위 로직 - 테스트: `tests/runtime/test_instance_config.py` AC-1~7(추가) ### T3: REQ-114 + REQ-115 — 그날-시작 게이트 + 그날 고정 캐시 - `application/snapshot_scheduler.py`: - `run_once()` 앞에 `credential_fetch_enabled=True` 경로에서 `fetch_broker_credential()` 호출 - `entry_allowed: bool` in-process 상태 + `_cached_credential: BrokerCredentialPayload | None` + `_credential_date: date | None` - fail-closed 3 조건(PortfolioHubUnavailableError / broker None / trading_enabled False) → ERROR 로그 + CRITICAL 알람 DAY_START_LOAD_FAILED - 그날 고정 — 장중 재fetch 방지 - `application/alarm_emitter.py`: `DAY_START_LOAD_FAILED` 알람 코드 추가(기존 패턴 재사용 — 실제로 코드 자체는 문자열이므로 사용처 추가) - `application/intents.py` (IntentService): `entry_allowed` 게이트를 BuyIntent admission에 추가 - 테스트: `tests/application/test_snapshot_scheduler.py` AC-1~9 + REQ-115 AC-1~6 - 테스트: `tests/application/test_execution.py` 또는 test_intent 계열 AC-4, AC-5 ## Design Gate **design.md 필요함(standard profile)**: 다음 판단이 필요. 1. `entry_allowed` 상태를 어디에 보관할지: SnapshotScheduler 내부 vs 별도 `DayStartState` 객체 vs IntentService에 주입. 2. IntentService에 `entry_allowed` 게이트를 어떻게 연결할지(생성자 주입 callable vs setter vs 공유 객체). 3. `credential_fetch_enabled` 플래그가 False(기본)일 때 기존 경로 byte-for-byte 보존 검증 방법. 4. 중장기 인스턴스(dcy-027)의 그날-시작 후크 위치 확인(현 코드에서 run_once() 위치 분석 필요). 5. `fetch_broker_credential()` 반환값이 `DaemonConfig.from_config()`에서 `KISConfig` 재조립으로 이어지는 흐름 설계. ## Risks - R1: IntentService에 `entry_allowed` 게이트를 연결할 때 기존 pass-through 테스트 회귀 가능성 → `credential_fetch_enabled=False` 기본 경로에서 게이트를 비활성화하면 회귀 방지 - R2: `app_secret` 로그 노출 — 마스킹 로직 누락 주의 - R3: `stub_read.py`의 stub이 포트 인터페이스와 불일치 → pytest import sweep으로 조기 발견 - R4: REQ-113의 `DaemonConfig.from_config()` 변경이 기존 `test_instance_config.py` 회귀 유발 가능 → AC-1(opt-in off → env-only, fetch 미호출) 케이스가 핵심 회귀 가드 ## Dependencies - portfolio-hub REQ-049 (dcy-010): `/v1/user_info` 확장 계약 — 머지 완료(미배포). 스텁 기반으로 실 portfolio-hub 없이 구현. - REQ-099/100 (dcy-033, BAT-049): AlarmEmitter `trex.alerts` 계약 — 이미 구현됨. - REQ-019: ledger_outbox 무손실 — 기존 구현 재확인(REQ-115 AC-2).