# req-114: 그날-시작 로드 게이트 — fail-closed 신규 진입 차단 + 알람 - Status: Implemented - Type: Integration - Priority: High - Owner: trex-core-dev ## Intent - dcy-036 C2/FR-2에 따라, 그날-시작 체크포인트(단기: 07:30 KST 스냅샷, 중장기: dcy-027 운영 프로파일 주기)에서 portfolio-hub가 불가하거나 broker 인증정보 부재(`broker: null`) 또는 `trading_enabled: false`이면 그날 **신규 진입을 개시하지 않는다(fail-closed)**. 사유를 로깅하고 운영자 알람을 발신한다. - "그날 고정" 원칙: 로드 성공 후 그날 인증정보는 캐시로 유지되며, 장중 portfolio-hub 복구 시에도 그날 재fetch하지 않는다(결정론 — REQ-115 담당). ## Requirement - **그날-시작 체크포인트 후크**: `SnapshotScheduler.run_once()`(단기 07:30 KST 스냅샷 트리거)의 `credential_fetch_enabled = true` 경로에서, 스냅샷 전 `fetch_broker_credential()` (REQ-112)를 호출해 `TradingContext`를 획득한다. 중장기 인스턴스는 dcy-027 운영 프로파일 리밸런싱 주기의 동등한 후크 지점에서 동일 로직을 수행한다. - **fail-closed 조건**: 다음 중 하나라도 해당하면 그날 `entry_allowed = False` 상태를 설정한다: 1. `PortfolioHubUnavailableError` (네트워크 오류 / 비-2xx / 403) 2. `TradingContext.broker is None` (인증정보 부재) 3. `TradingContext.trading_enabled is False` - **진입 차단**: `entry_allowed = False` 상태에서 BuyIntent(신규 진입 의도)는 admission 단계에서 거부된다. 이미 진행 중인 주문(DISPATCHED 슬롯)과 청산(SellIntent) 경로는 차단하지 않는다. - **사유 로깅**: fail-closed 진입 시 ERROR 레벨로 사유(portfolio-hub 불가 / broker null / trading_enabled false)와 인스턴스 정보를 로깅한다. app_secret은 로그에 포함하지 않는다. - **알람 발신**: fail-closed 진입 시 CRITICAL 알람을 `AlarmEmitter`를 통해 발신한다(`trex.alerts` exchange, REQ-099/100). 알람 코드: `DAY_START_LOAD_FAILED`. 알람 페이로드에는 사유 코드와 인스턴스 키를 포함한다. - **성공 로깅**: 로드 성공 시 INFO 레벨로 fetch 소스, account_ref(app_secret 제외), trading_enabled를 기록한다. ## Acceptance Criteria 1. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `PortfolioHubUnavailableError` 발생, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람(`DAY_START_LOAD_FAILED`) 발신. 2. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `TradingContext.broker is None`, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람 발신. 3. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트에서 `TradingContext.trading_enabled = False`, When 체크포인트 실행, Then `entry_allowed = False` 설정 + ERROR 로그 + CRITICAL 알람 발신. 4. Given `entry_allowed = False` 상태, When BuyIntent 수신, Then BuyIntent가 admission 단계에서 거부된다 (신규 진입 차단). 5. Given `entry_allowed = False` 상태, When SellIntent 수신, Then SellIntent는 차단되지 않고 정상 처리된다 (청산 경로 영향 없음). 6. Given `credential_fetch_enabled = true`이고 그날-시작 체크포인트 성공(`broker` 있음 + `trading_enabled = True`), When 체크포인트 실행, Then `entry_allowed = True` 설정 + INFO 로그(account_ref 포함, app_secret 제외). 7. Given `credential_fetch_enabled = false`, When 그날-시작 체크포인트 실행, Then `fetch_broker_credential()` 호출 없이 기존 스냅샷 로직만 수행된다 (회귀 없음, opt-in 보존). 8. Given 알람 발신 성공, When `trex.alerts` exchange 메시지 확인, Then `DAY_START_LOAD_FAILED` 코드와 instance_key를 포함한 알람 envelope가 존재한다. 9. Given fail-closed 후 `entry_allowed = False` 상태에서 portfolio-hub가 복구됨, When 그날 중 재fetch 없이 운행, Then `entry_allowed` 상태는 그날 고정(재fetch로 자동 해제 안 됨) — 다음 그날-시작 체크포인트까지 유지. ## Impacted Area - Docs: 없음 (baseline 문서 갱신은 별도 scope) - Modules: - `trex_core/application/snapshot_scheduler.py` — `run_once()` 또는 그날-시작 후크 지점에 `fetch_broker_credential()` 호출 + `entry_allowed` 상태 설정 + 알람 발신 추가 - `trex_core/application/execution.py` (또는 IntentService) — BuyIntent admission에 `entry_allowed` 게이트 추가 - `trex_core/application/alarm_emitter.py` — `DAY_START_LOAD_FAILED` 알람 코드 추가(REQ-100 패턴 재사용) - Tests: - `tests/application/test_snapshot_scheduler.py` — AC-1~3, AC-6~9 케이스 추가 (stub fetch_broker_credential + AlarmEmitter mock) - `tests/application/test_execution.py` (또는 IntentService 테스트) — AC-4~5 케이스 추가 ## Notes - OQ-4(그날-시작 로드 실패 알람 trex-notifier 연동): **Deferred** — 이 REQ는 `trex.alerts` exchange 발신(REQ-099/100 계약)까지만 정의한다. trex-notifier(dcy-033) 연동은 별도 REQ/Discovery로 추적한다. 알람 등급은 CRITICAL(거래 개시 불가). - `entry_allowed` 상태 저장소: in-process 상태 변수(process-local)로 충분하다. 재시작 시 그날-시작 체크포인트가 재실행되므로 내구성 저장소 불필요. - 중장기 인스턴스의 "그날-시작 체크포인트" 시점은 dcy-027 운영 프로파일 리밸런싱 주기의 구체적 후크 위치에 의존한다. 구현 단계에서 확인 후 조정. - AC-9(그날 고정 상태 불변성): 장중 재fetch 없음이 REQ-115의 보완 요건이기도 하다. 이 REQ는 "진입 차단 상태의 그날 고정"만 명시하고, "장중 장애 후 인증정보 캐시 지속 운행"은 REQ-115가 담당. - Source: dcy-036 C2, FR-2, OQ-1 Answered (단기 07:30 / 중장기 프로파일 주기), OQ-2 Answered (그날 고정). ## Change Log ### CHG-20260628-01 - Date: 2026-06-28 - Author: trex-core-dev #### Change Summary - 신규 생성 (Proposed) — dcy-036 C2/FR-2 산출물. #### Intent - dcy-036 FR-2(그날-시작 로드 시점 fail-closed 진입 차단 + 알람)를 Integration REQ로 정규화한다. #### Acceptance Criteria Delta - Added: - AC-1~9 전체 (신규 초안) - Updated: 없음 - Removed: 없음 #### Impacted Area Delta - Docs: 없음 - Modules: - `snapshot_scheduler.py` 그날-시작 후크 + entry_allowed 상태 + 알람 - `execution.py` / IntentService BuyIntent admission 게이트 - `alarm_emitter.py` 코드 추가 - Tests: - `test_snapshot_scheduler.py`, `test_execution.py` #### Delivery Trace Delta - Batches: 미정 (suggest-batch-reqs 대기) - Releases: 미정 #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: confirm-req 후 REQ-112~115 일괄 batch 배정 검토. #### Validation Plan - pytest로 AC-1~9 각 케이스 확인. stub `fetch_broker_credential`과 AlarmEmitter mock으로 I/O 격리. AC-4/5(차단 범위 경계: 매수 차단 / 매도 통과)가 가장 중요한 케이스. ### CHG-20260628-02 - Date: 2026-06-28 - Author: 팀장(Claude) - confirm-req: `Proposed` → `Approved`. dcy-036 FR-2/OQ-1/OQ-2 추적. **진입 fail-closed(BuyIntent 차단) + 청산·DISPATCHED 통과(AC-4/5)** 비대칭이 사장님 fail-safe 결정 정합 — 자본 갇힘 방지. CRITICAL 알람 기존 trex.alerts(REQ-099/100) 재사용·그날 고정(AC-9) 적절. OQ-4(trex-notifier 연동) Deferred 정당. blocker 없음. ### CHG-20260628-03 - Date: 2026-06-28 - Author: 팀장(Claude) - confirm-req-implemented: `Approved` → `Implemented`. 근거: BAT-053 PR #34 머지(merge commit 01655ab). fail-closed 게이트 AC-1~9 pytest 검증 완료 — BuyIntent 차단/SellIntent 통과 비대칭, CRITICAL 알람(`DAY_START_LOAD_FAILED`), opt-in off 비활성(AC-7), 그날 고정(AC-9) 확인. QC PASS-WITH-NOTES(opus, blocker 0). 745 passed/0 회귀.