docs(bat-053): draft-batch-verification 완료 — AC 커버리지 + 미해결 기록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
2026-06-28 11:00:18 +09:00
co-authored by Claude Sonnet 4.6
parent e4e5c70422
commit a612e8763b
@@ -0,0 +1,89 @@
# BAT-053: Verification
- Status: draft-batch-verification
- Test run: 2026-06-28
- Test method: pytest (스텁 기반 단위/통합 테스트 — 실 portfolio-hub 없이 실행)
## 테스트 실행 결과
```
745 passed, 47 skipped in 1.61s (0 failed)
```
전체 스위트 실행: 기존 회귀 없음, 신규 케이스 전부 통과.
## REQ별 AC 커버리지
### REQ-112: portfolio-hub broker credential read 포트 메서드 + HTTP 어댑터
| AC | 테스트 | 결과 |
| --- | --- | --- |
| AC-1: 유효한 broker+trading_enabled=True → TradingContext 반환 | `test_fetch_broker_credential_returns_trading_context` | PASS |
| AC-2: broker:null → TradingContext(broker=None) | `test_fetch_broker_credential_broker_null_returns_none_broker` | PASS |
| AC-3: credential_payload:null → TradingContext(broker=None) | `test_fetch_broker_credential_credential_payload_null_returns_none_broker` | PASS |
| AC-4: 네트워크 오류/403/500 → PortfolioHubUnavailableError | `test_fetch_broker_credential_network_error_raises_unavailable`, `test_fetch_broker_credential_403_raises_unavailable`, `test_fetch_broker_credential_500_raises_unavailable` | PASS |
| AC-5: whoami() 계약 불변 (회귀 없음) | `test_fetch_broker_credential_whoami_still_works` + 기존 whoami 테스트 전부 통과 | PASS |
| AC-6: stub 결정론 반환 (네트워크 없음) | `test_stub_fetch_broker_credential_default_happy_path`, `test_stub_fetch_broker_credential_overridable_context`, `test_stub_fetch_broker_credential_raises_when_configured` | PASS |
| AC-7: app_secret 평문 로그 미노출 | `test_fetch_broker_credential_app_secret_not_in_logs` | PASS |
| AC-8: domain import sweep | `test_domain_has_no_forbidden_imports` | PASS |
### REQ-113: KISConfig 소스 우선순위 — opt-in + break-glass
| AC | 테스트 | 결과 |
| --- | --- | --- |
| AC-1: credential_fetch_enabled=false(부재 포함) → env-only, fetch 미호출 | `test_run_once_no_credential_fetch_when_disabled`, `test_credential_fetch_enabled_false_propagates`, `test_from_env_credential_fetch_enabled_always_false` | PASS |
| AC-2: credential_fetch_enabled=true + 유효한 fetch → KISConfig fetch 결과로 구성 | `test_run_once_credential_fetch_success_sets_entry_allowed` (TradingContext 캐시 검증) | PASS |
| AC-3: credential_fetch_enabled=true + BREAK_GLASS=true → fetch 미시도, env 경로 | `test_break_glass_skips_fetch` | PASS |
| AC-4: break-glass 활성화 시 INFO 로그 | `test_break_glass_skips_fetch` (caplog 미검사, 로그 코드 확인) | PARTIAL (로그 내용 assert 없음) |
| AC-5: app_secret 마스킹 | `test_app_secret_not_in_logs` | PASS |
| AC-6: fetch 성공 시 INFO 소스 로깅 | `test_app_secret_not_in_logs` (로그 출력 확인) | PASS |
| AC-7: TOML 부재 시 False 파싱 | `test_credential_fetch_enabled_default_false_from_toml`, `test_credential_fetch_enabled_true_from_toml` | PASS |
**주의 (AC-4)**: break-glass INFO 로그 내용을 assert하는 전용 테스트가 없음. 코드상 `_log.info(...)` 호출이 있음을 확인. 단순 문자열 로그이므로 동작 보증 범위 내.
### REQ-114: 그날-시작 로드 게이트 — fail-closed + 알람
| AC | 테스트 | 결과 |
| --- | --- | --- |
| AC-1: PortfolioHubUnavailableError → entry_allowed=False + ERROR + CRITICAL 알람 | `test_run_once_portfolio_hub_unavailable_sets_fail_closed` | PASS |
| AC-2: broker=None → entry_allowed=False + ERROR + CRITICAL 알람 | `test_run_once_broker_null_sets_fail_closed` | PASS |
| AC-3: trading_enabled=False → entry_allowed=False + ERROR + CRITICAL 알람 | `test_run_once_trading_disabled_sets_fail_closed` | PASS |
| AC-4: entry_allowed=False → BuyIntent admission 거부 | `test_intent_service_buy_rejected_when_entry_not_allowed` | PASS |
| AC-5: entry_allowed=False → SellIntent 통과 | `test_intent_service_sell_passes_when_entry_not_allowed` | PASS |
| AC-6: 성공 → entry_allowed=True + INFO 로그(account_ref, app_secret 제외) | `test_run_once_credential_fetch_success_sets_entry_allowed`, `test_alarm_not_emitted_on_success`, `test_app_secret_not_in_logs` | PASS |
| AC-7: credential_fetch_enabled=false → fetch 없이 기존 스냅샷 로직 | `test_run_once_no_credential_fetch_when_disabled` | PASS |
| AC-8: 알람 envelope에 DAY_START_LOAD_FAILED + instance_key 포함 | `test_run_once_portfolio_hub_unavailable_sets_fail_closed`, `test_run_once_broker_null_sets_fail_closed`, `test_run_once_trading_disabled_sets_fail_closed` | PASS |
| AC-9: fail-closed 후 entry_allowed 그날 고정 | `test_entry_allowed_fixed_for_day_no_refetch` | PASS |
### REQ-115: 그날-시작 장중 무중단 — 그날 고정 캐시
| AC | 테스트 | 결과 |
| --- | --- | --- |
| AC-1: 그날 성공 후 장중 portfolio-hub 장애 → KIS 계속(캐시 활용) | In-process 캐시 설계 + `test_successful_credential_cached_for_day` | PARTIAL (KISBroker 실제 적용은 M3 defer, 캐시 저장 자체는 검증) |
| AC-2: 장중 장애 후 체결 → ledger_outbox durable 기록 | 기존 REQ-019 테스트 전부 통과 (745 passed 포함) | PASS (회귀 재확인) |
| AC-3: 그날 성공 후 동일 날짜 재fetch 없음 | `test_successful_credential_cached_for_day` (fetch_count 검증) | PASS |
| AC-4: credential_fetch_enabled=false → 기존 회귀 0 | 전체 스위트 745 passed | PASS |
| AC-5: app_secret Redis 미기록 | 메모리 전용 저장(코드 검토), Redis 기록 코드 없음 | PASS (코드 검토 기반) |
| AC-6: 성공 후 INFO 로그에 app_secret 없음 + account_ref/캐시 날짜 포함 | `test_app_secret_not_in_logs` | PASS |
**주의 (AC-1)**: KISBroker 실제 재조립(fetch된 app_key/app_secret를 KIS API 호출에 적용)은 M3 후속 batch에서 구현. 이번 batch에서는 캐시 저장(`_cached_credential`)까지 완료, KISBroker 연결은 미구현.
## 회귀 검증
- 전체 스위트 745 passed, 47 skipped — **기존 테스트 0 실패** (회귀 없음)
- `credential_fetch_enabled=False`(기본) 경로: fetch 코드가 실행되지 않아 기존 동작 byte-for-byte 보존 확인
- domain import sweep 통과 — domain 오염 없음
- whoami() 기존 테스트 전부 통과 — REQ-112 AC-5 보존 확인
## 스텁 기반 테스트 명시
- 이 batch의 모든 테스트는 `StubPortfolioRead.fetch_broker_credential()`과 MockTransport 기반으로 작성됨.
- 실 portfolio-hub(REQ-049 dcy-010 배포 완료 이후) 통합 검증은 이 batch 범위 밖.
- Step2 운영 적용 후 실 환경에서 별도 통합검증 필요.
## 미해결/위험
1. **KISBroker 재조립 미구현**: `_cached_credential`이 메모리에 저장되나 실제 KIS API 호출에 적용되지 않음. REQ-115 AC-1의 완전한 충족은 M3 batch에서 연결 필요.
2. **REQ-113 AC-4 break-glass INFO 로그 assert 미작성**: 코드상 구현됨, 단위 테스트에서 로그 내용을 assert하지 않음. 리스크 낮음.
3. **중장기 인스턴스 후크**: 현재 `run_once()`를 단기/중장기 모두 공유하나, dcy-027 중장기 인스턴스 추가 시 별도 트리거 주기 확인 필요.
4. **실 portfolio-hub 통합 검증**: dcy-010 REQ-049 배포 완료 시점에 별도 E2E 검증 필요.