# BAT-022 Implementation - Status: in-delivery - Batch: BAT-022 - Profile: standard ## Changed Files - `trex_core/application/portfolio.py` — `SnapshotEnvelope` 에 `slots: list[dict]`(additive) + `to_dict()` 동봉, **canonical `slot_to_dict(s)`**(operator-facing: slot_id/trading_date/status/symbol/qty/avg_price/entry_price/allocated_capital/strategy_id/sell_strategy_id/portfolio_id/forced_close_at), `build_snapshot_envelope` 가 `slot_to_dict` 로 `slots[]`(전체 슬롯 테이블) 산출. (REQ-028 AC-4) - `trex_core/application/messaging_handlers.py` — 로컬 `_slot_to_dict` 를 제거하고 `portfolio.slot_to_dict` import(단일 source of truth → BAT-007 stub 경로와 BAT-008 envelope 경로가 **동일 슬롯 스키마** 산출). `_slot_to_dict = slot_to_dict` alias 유지. - `trex_core/runtime/cli.py` — `_cmd_slots` 가 `resp["slots"]`(실제 슬롯 상태)를 읽어 status 포함 표시, `--status` 를 슬롯 status 기준 client-side 필터(all/holding/buyable=idle/pending), watchdog-owned 를 slot.portfolio_id 로 결합; `--status` choices 에 `pending` 추가. 가격은 `entry_price`(canonical 외부명) 표시. `_cmd_universe` 는 `assets[]` 유지. (REQ-032 CHG-20260608-03) - `docs/interface-contract.md` — snapshot 응답에 `slots[]` 스키마 계약 추가(canonical shape), `assets[]` vs `slots[]` 구분 명시, CLI↔RPC 매핑 행 정정(`slots` = slot 상태). (REQ-028/032 baseline 동기화) - `tests/application/test_snapshot_selection.py`, `tests/application/test_snapshot_handler.py` — envelope key set 에 `slots` 추가, no-price 검증을 `assets[]` 로 scope. - `tests/runtime/test_cli_operator.py` — `_SNAPSHOT` 에 canonical `slots[]` 추가; `slots` 가 universe 가 아닌 슬롯 상태 표시·`--status` 슬롯 status 필터·watchdog 결합 테스트로 갱신. ## Post-verification 보정 (BAT-022 범위 내, REQ-032) - 운영 사용 중 발견: `universe --json` 이 envelope 전체(assets + 신규 `slots[]` + `watchdog[]` + meta)를 덤프해 출력이 과다. envelope 에 `slots[]` 를 추가한 부수효과 — `universe` 는 universe(assets)만 보여야 함. - 수정: `trex_core/runtime/cli.py` `_cmd_universe` 의 json 을 `{instance, view, assets, available_slots, degraded, reason}` 로 scope(`slots[]`/`watchdog[]` 제외 — `slots` 명령 소관). `slots --json` 은 이미 `{instance, status, slots}` 로 scope 되어 일관. - 검증: 신규 단위 `tests/runtime/test_cli_operator.py::test_universe_json_is_scoped_to_universe`(키셋·누출 없음) + 라이브 `trex-core universe --json` → keys={assets,available_slots,degraded,instance,reason,view}, slots/watchdog 누출 없음. cli 17 passed, ruff clean. ## 검증 중 발견 (중요) - BAT-007 stub 경로(`messaging_handlers`)는 이미 `_slot_to_dict` 로 `slots[]` 를 반환하고 있었다 — 갭은 **BAT-008 envelope 경로(snapshot_store 연결 시, 운영 배포)에만** 존재했다(`build_snapshot_envelope` 이 slots 미동봉). 두 경로의 슬롯 dict 스키마가 달라 CLI 일관성도 깨질 뻔해, canonical `slot_to_dict` 로 통일(외부명 `entry_price` 유지, dcy-012 호환). ## Execution Log 1. portfolio.py: `slots[]` envelope 필드 + `_slot_to_dict` + builder 동봉. 2. cli.py: `_cmd_slots` 가 `slots[]` 읽기·status 필터·watchdog 결합으로 정정, `universe` 분리 유지. 3. 기존 테스트 4건(OLD 동작 검증)을 새 계약으로 갱신. 4. 검증 수행(아래). ## Validation - ruff: `ruff check`(변경 파일) → All checks passed. - 타깃 단위: `pytest tests/application/test_snapshot_selection.py tests/application/test_snapshot_handler.py tests/runtime/test_cli_operator.py` → 31 passed. - 전체 회귀: `pytest -q` → **338 passed, 1 failed, 34 skipped**. 유일 실패 `tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` 는 KIS 샌드박스 env(`TREX_KIS_BROKER_BASE_URL` 이 live URL)에 의존하는 **사전 환경 이슈**로 본 변경(portfolio/cli)과 무관. ## Remaining Risks - 인프라 게이트(실 PostgreSQL/RabbitMQ)·운영 재현(`trex-core slots` 가 실제 슬롯 N개 status 표시)은 verification 단계에서 확인 필요. - KIS 샌드박스 env 테스트 실패는 본 batch 범위 밖(환경 설정) — verification 에서 분리 기록. ## 구현 단계 ### M1 — REQ-028 envelope `slots[]` - [ ] `trex_core/application/portfolio.py` `build_snapshot_envelope` 에 `slots[]` 산출 추가. - `_slot_to_dict(slot)` 헬퍼: `{slot_id, status, symbol, qty, limit_price, avg_price, portfolio_id}` 매핑(없는 값 null). - `slot_repo.list_slots(trading_date)` 순회 → `slots` 리스트, `to_dict()` payload 에 `"slots"` 동봉. 빈 배열 `[]`. - [ ] `assets[]`/meta/`watchdog[]`/degraded 불변 확인. ### M2 — REQ-032 CLI `slots` - [ ] `trex_core/runtime/cli.py` `_cmd_slots`: - `slots = resp.get("slots", [])` 로 변경(기존 `assets`). - `--status` → 슬롯 status 필터 매핑(all/holding(held)/buyable(idle)/pending). - 출력: `count=len(slots)` + 슬롯별 `slot_id status symbol qty price portfolio_id` + watchdog-owned 태그(portfolio_id 매칭). - `--json` 경로 유지. - [ ] `_cmd_universe` 무변경(assets[] 유지) 확인. ### M3 — 문서 - [ ] `docs/interface-contract.md` snapshot 응답에 `slots[]` 필드 계약 + `assets[]` vs `slots[]` 구분 명시. - [ ] 필요 시 `docs/runtime-flows.md` 보강. ### M4 — 테스트/게이트 - [ ] envelope `slots[]` 스키마 단위(빈 배열·status enum·가격 필드 포함). - [ ] `slots` CLI 가 슬롯 상태(status 포함) 표시, universe(`assets[]`)와 구분. - [ ] `--status` 필터 케이스(idle/holding/pending). - [ ] 전체 회귀 + ruff + 인프라 게이트 green. ## 변경 파일 (예상) - `trex_core/application/portfolio.py` (`build_snapshot_envelope`, `_slot_to_dict`) - `trex_core/runtime/cli.py` (`_cmd_slots`) - `docs/interface-contract.md` - tests: snapshot envelope `slots[]`, cli `slots` 슬롯 상태 표시 ## 구현 노트 - 순수 read 노출 — 슬롯 도메인/전이/워치독 판정/청산 무변경. - 비침습: 요청은 `actor="operator"`/`touch_watchdog=false` 유지(dcy-016). - 가격 노출은 `slots[]`(operator) 한정 — strategy `assets[]` no-price 규칙 불변.