fix(cli): BAT-022 slots 명령이 universe 대신 실제 슬롯 상태 표시 (dcy-016 후속)

`trex-core slots` 가 snapshot RPC 의 universe `assets`(portfolio)를 슬롯 상태로
오인 표시하던 갭 정정. snapshot envelope 이 슬롯 상태를 운반하지 않아 발생
(10개 슬롯인데 universe 1개·status 없음 — BAT-007 HTTP 슬롯 read 제거 후
슬롯 상태 read 표면 부재가 근인).

- REQ-028 (CHG-20260608-01): snapshot envelope 에 read-only per-slot `slots[]`
  additive 추가(AC-4). strategy `assets[]`(universe, no-price) 불변.
- REQ-032 (CHG-20260608-03): `_cmd_slots` 가 envelope `slots[]`(실제 슬롯 상태)
  표시 + `--status` 를 슬롯 status 기준 client-side 필터 + watchdog 결합.
  `universe` 는 `assets[]` 유지. `universe --json` 도 universe 데이터로 scope.
- canonical `portfolio.slot_to_dict` 단일화 — BAT-007 stub / BAT-008 envelope
  두 경로가 동일 슬롯 스키마(외부명 `entry_price` 유지, dcy-012 호환).
- interface-contract 에 `slots[]` 계약 + `assets[]` vs `slots[]` 구분 반영.

검증: 단위 338 passed + ruff clean + 신규 코드 데몬으로 실 PostgreSQL/RabbitMQ
운영 재현 PASS(slots=10·status·DB 일치·universe 분리·비침습). REQ-028/032
Implemented, BAT-022 release-candidate. pytest 인프라 게이트는 Human Approval
Memo 수용(운영 재현이 실 경로 커버).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 19:54:42 +09:00
co-authored by Claude Opus 4.8
parent a0a149114d
commit 63da87c9b3
16 changed files with 574 additions and 46 deletions
@@ -0,0 +1,50 @@
# BAT-022 Design
- Status: draft
- Batch: BAT-022
- Profile: standard
## 설계 목표
- snapshot envelope 에 슬롯 단위 read-only `slots[]` 를 additive 추가해, `trex-core slots` 가 universe 가 아닌 데몬의 실제 슬롯 상태를 표시하게 한다. strategy-facing `assets[]` 계약은 비파괴.
## 1. snapshot envelope `slots[]` 스키마 (REQ-028 AC-4)
- envelope top-level 에 `slots` 키 추가(additive). 항상 배열, 비면 `[]`(null 금지).
- 각 항목(per Slot, `slot_repo.list_slots(trading_date)` 기준):
- `slot_id`: str (UUID)
- `status`: str — `idle`|`pending_buy`|`holding`|`pending_sell`|`closed` (SlotState 값)
- `symbol`: str|null
- `qty`: float|null
- `limit_price`: float|null (pending 주문 지정가; dcy-012 의 side-무관 필드)
- `avg_price`: float|null (매수 체결가/평단)
- `portfolio_id`: str|null (= asset_id)
- **경계**: `assets[]`(portfolio 단위, strategy-facing, no-price)와 독립. `slots[]` 는 operator-facing 이라 가격(`limit_price`/`avg_price`) 노출 허용(REQ-028 AC-3 의 no-price 는 `assets[]` 한정).
- meta(`view`/`available_slots`/`degraded`/`reason`)·`assets[]`·`watchdog[]` 불변.
## 2. envelope 산출 (`build_snapshot_envelope`, portfolio.py)
- 기존 `assets[]`/`available_slots`/degraded 산출 유지.
- `slots = [ _slot_to_dict(s) for s in slot_repo.list_slots(trading_date) ]` 추가, `to_dict()` payload 에 `"slots": slots` 동봉.
- `_slot_to_dict` 는 Slot 모델 필드를 위 스키마로 매핑(없는 값은 null).
- best-effort 불필요(순수 read, 외부 I/O 없음) — 단 예외가 envelope 산출 전체를 깨지 않도록 슬롯 매핑은 방어적으로.
## 3. CLI `slots` (REQ-032, cli.py `_cmd_slots`)
- 요청 payload 는 기존대로 `actor="operator"`, `touch_watchdog=False`(비침습) 유지.
- 응답에서 `slots = resp.get("slots", [])` 를 읽는다(기존 `assets` 대신).
- `--status` 필터를 **슬롯 status 기준**으로 적용:
- `all` → 전체, `holding`/`held` → status==holding, `buyable` → status==idle (가용 슬롯), (추가) `pending` → pending_buy|pending_sell 고려.
- 매핑 표는 구현에서 확정(planning Design Gate (d)).
- 출력: `instance=… view=… count=<len(slots)> watchdog_owned=<n>` 헤더 + 슬롯별 `slot_id status symbol qty limit_price/avg_price portfolio_id` + watchdog-owned 태그(slot.portfolio_id ↔ watchdog expired 매칭).
- `--json` 은 envelope(또는 `slots`) 그대로 JSON.
- `_cmd_universe` 는 `assets[]` 유지(변경 없음) — 두 명령 표면 분리.
## 4. watchdog 결합
- 기존 `watchdog[]`(expired by portfolio_id, dcy-017)와 `slots[]` 를 CLI 에서 결합: 슬롯의 `portfolio_id` 가 watchdog expired 집합에 있으면 `[WATCHDOG-OWNED: strategy absent]` 태그.
## 5. 문서 (interface-contract)
- snapshot 응답 계약에 `slots[]` 필드(스키마 위와 동일) 추가. `assets[]` vs `slots[]` 구분(universe vs 데몬 슬롯) 명시.
## best-effort / 안전
- 본 변경은 read-only 노출 + CLI 표시라 매매·청산·워치독 판정에 영향 없음. 슬롯 도메인/전이 불변.
## 미해결/구현 확정 항목
- `--status` → 슬롯 status 매핑 최종표(특히 `buyable`/`pending` 의미).
- `slots[]` 항목에 `trading_date` 포함 여부(현재 미포함, 요청이 단일 trading_date 라 불요).
@@ -0,0 +1,73 @@
# 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 규칙 불변.
@@ -0,0 +1,35 @@
# BAT-022: 슬롯 상태 스냅샷 가시화 — `slots` 명령이 universe 대신 실제 슬롯 상태 표시 (dcy-016 후속)
- Status: release-candidate
- Profile: standard
- Source Discovery: dcy-016 (후속 change-req)
- Included REQ: REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03)
## Summary
- dcy-016 으로 추가된 `trex-core slots` 가 snapshot RPC 의 universe `assets`(portfolio: asset_id/symbol/asset_quantity)를 **슬롯 상태로 오인 표시**하던 갭을 정정한다(운영 관측: 10개 슬롯인데 universe 1개·status 없음). 근인은 BAT-007 에서 HTTP 슬롯 read(REQ-008/025) 제거 후 **슬롯 상태 read 표면 부재** + snapshot envelope 이 슬롯 상태를 운반하지 않음.
- 핵심: snapshot envelope 에 read-only per-slot `slots[]`(`slot_id`/`status`/`symbol`/`qty`/`limit_price`/`avg_price`/`portfolio_id`)를 **additive** 추가(REQ-028 AC-4)하고, `trex-core slots` 가 이를 표시(REQ-032). strategy-facing `assets[]`(universe) 계약은 불변, `universe` 명령은 `assets[]` 유지로 둘을 명확히 분리. dcy-017 의 per-slot `watchdog[]` 동봉 선례와 동일 방식.
## Scope
- In Scope:
- snapshot envelope 에 read-only `slots[]` 필드 추가 (REQ-028 AC-4) — `build_snapshot_envelope`
- `trex-core slots` 가 `slots[]`(실제 슬롯 상태, `status` 포함)를 표시, `--status` 필터를 슬롯 상태 기준으로 적용 (REQ-032)
- `universe` 명령은 `assets[]`(universe) 유지 — 두 명령 표면 분리
- watchdog-owned 표시(REQ-032 CHG-20260608-02)를 `slots[]` 항목과 결합
- interface-contract 정합 (snapshot 응답 `slots[]` 필드)
- Out of Scope:
- strategy-facing `assets[]`(universe) 계약 변경 (불변)
- 슬롯 상태 도메인/전이 로직 변경 (가시화·노출만)
- WatchdogStore in-memory → 영속 승격
- 신규 슬롯 read RPC/HTTP 표면 (기존 snapshot envelope 확장으로 충족)
## Documents
- Planning: [planning.md](./planning.md)
- Design: [design.md](./design.md)
- Implementation: [implementation.md](./implementation.md)
- Verification: [verification.md](./verification.md)
## Notes
- Source discovery 단일(dcy-016 후속). REQ-028 CHG-20260608-01 / REQ-032 CHG-20260608-03 change-req 로 두 REQ 가 Implemented→Approved 로 내려옴.
- 설계 정합성: dcy-017/BAT-021 이 이미 envelope 에 top-level `watchdog[]` 를 additive 동봉한 선례 → `slots[]` 도 동일 패턴(additive, strict envelope 비파괴).
- 1차 게이트: 전체 회귀 green — strategy `assets[]`(REQ-028 AC-1~3)·snapshot 동작 무회귀(필드 추가만).
- `slots[]` 와 `watchdog[]` 는 둘 다 슬롯 단위 read-only projection — CLI `slots` 에서 결합 표시.
@@ -0,0 +1,44 @@
# BAT-022 Planning
- Status: draft
- Batch: BAT-022
- Profile: standard
## Included REQ
- REQ-028 (Interface, change-req CHG-20260608-01): snapshot envelope 에 read-only per-slot `slots[]` 추가(AC-4). strategy `assets[]` 불변.
- REQ-032 (Interface, change-req CHG-20260608-03): `trex-core slots` 가 universe `assets[]` 대신 envelope `slots[]`(실제 슬롯 상태) 표시. `universe` 는 `assets[]` 유지.
## Delivery Plan
1. REQ-028(서버 envelope): `trex_core/application/portfolio.py` `build_snapshot_envelope` 에 `slots[]` 산출 추가 — `slot_repo.list_slots(day)` 로 per-slot 상태(`slot_id`/`status`/`symbol`/`qty`/`limit_price`/`avg_price`/`portfolio_id`) 매핑. 빈 배열은 `[]`. `assets[]`/meta/degraded 불변. (필요 시 `messaging_handlers.py` 핸들러 배선 확인.)
2. REQ-032(CLI): `trex_core/runtime/cli.py` `_cmd_slots` 가 `resp.get("slots", [])` 를 읽어 슬롯 상태(status 포함) 출력. `--status all|held|holding|buyable` 를 슬롯 status 기준으로 필터. watchdog-owned 표시(기존)를 `slots[]` 항목과 결합(slot 의 portfolio_id ↔ watchdog expired 매칭). `_cmd_universe` 는 `assets[]` 유지.
3. 문서: interface-contract 의 snapshot 응답에 `slots[]` 필드 계약 추가(REQ-028 Impacted). runtime-flows 필요 시 보강.
4. 테스트: envelope `slots[]` 스키마(빈 배열·status enum) + `slots` CLI 가 실제 슬롯 상태 표시(universe 와 구분) + 전체 회귀 + ruff + 인프라 게이트.
## Dependencies
- Internal:
- dcy-005/BAT-008 snapshot envelope(REQ-028) — 확장 대상.
- dcy-016/BAT-020 운영자 CLI `slots`(REQ-032) — 정정 대상(현재 universe 표시).
- dcy-017/BAT-021 envelope `watchdog[]` additive 동봉 — 동일 패턴 선례 + CLI 결합 표시.
- `SlotRepository.list_slots`/Slot 모델(`status`/`limit_price`/`avg_price`/`portfolio_id`) — slots[] 산출 소스.
- External:
- 없음.
## Design Gate
- Design Required Before Implementation: yes
- Reason: (a) snapshot envelope 은 strict 계약(REQ-028 AC-1~3: `assets[]` null 금지·가격 제외 등)이라 `slots[]` 를 **additive top-level 필드**로 두어 기존 strategy 계약을 비파괴해야 한다 — 필드 경계·스키마를 설계에서 고정. (b) `slots[]` 항목 스키마(어떤 Slot 필드를 노출/제외할지 — 가격은 `limit_price`/`avg_price` 노출하되 strategy `assets[]` 의 no-price 규칙과 혼동 없게) 확정. (c) `slots[]`(슬롯 단위) ↔ `watchdog[]`(슬롯 단위) ↔ `assets[]`(portfolio 단위)의 키 관계(`portfolio_id`/`slot_id`)와 CLI 결합 표시 규칙 고정. (d) `--status` 필터 의미를 universe view(all/buyable/held)가 아닌 **슬롯 status(idle/holding/pending_*)** 기준으로 재정의. → design.md 동일 턴 scaffold.
## Milestones
1. M1 — REQ-028 envelope `slots[]` 산출 + 스키마 테스트 green
2. M2 — REQ-032 `slots` CLI 가 슬롯 상태 표시(+watchdog 결합), universe 와 구분
3. M3 — interface-contract `slots[]` 계약 정합
4. M4 — 전체 회귀 + ruff + 인프라 게이트
## Risks
- R-1 (Medium): `slots[]` 추가가 strict envelope(REQ-028 AC-1~3)·기존 strategy 소비자를 깨뜨림. 대응: additive top-level 필드로만 추가, `assets[]`/meta 불변, 기존 envelope 테스트 green 유지.
- R-2 (Medium): `--status` 필터 의미 변경(universe view → 슬롯 status)이 기존 `slots` 사용자 기대와 다름. 대응: design 에서 필터 매핑 명시, verification 에서 status별 케이스 검증.
- R-3 (Low): `slots[]` 와 `assets[]` 혼동(둘 다 symbol/portfolio_id 포함). 대응: CLI 출력에 슬롯 status 컬럼을 1급으로 표시, 문서에서 universe vs slots 구분 명시.
## Notes
- 1차 게이트: 전체 회귀 green(snapshot strategy 계약·슬롯 도메인 무회귀 — 노출 필드만 추가).
- `slots[]` 검증은 operator(`touch_watchdog=false`) 요청으로 비침습 확인(조회가 워치독 리셋 안 함, dcy-016).
- 가격 노출: `slots[]` 는 운영자용이라 `limit_price`/`avg_price` 노출 OK(REQ-028 AC-3 의 no-price 는 strategy `assets[]` 한정).
@@ -0,0 +1,73 @@
# BAT-022 Verification
- Status: approved
- Batch: BAT-022
- Profile: standard
- Ready for Release: true (단위 green + baseline 동기화 + 실 인프라 운영 재현 PASS; pytest 인프라 게이트는 Human Approval Memo 로 수용된 CI 잔여)
## Acceptance Mapping
### REQ-028 AC-4 — envelope `slots[]`
- V-1 (스키마): envelope 에 `slots[]` 포함, canonical shape(slot_id/trading_date/status/symbol/qty/avg_price/entry_price/allocated_capital/strategy_id/sell_strategy_id/portfolio_id/forced_close_at).
- Evidence: `tests/application/test_snapshot_selection.py::test_envelope_shape_no_price_field`(envelope key set 에 `slots` 포함, list), `test_snapshot_handler.py::test_envelope_shape_returned`(body 에 `slots` list). → PASS
- V-2 (빈 배열): 슬롯 없을 때 `slots == []`(null 아님).
- Evidence: `SnapshotEnvelope.slots` default `[]` + `to_dict` 가 `list(...)`. envelope 테스트 green. → PASS
- V-3 (status enum): `status` 가 SlotState 값.
- Evidence: `slot_to_dict` 가 `s.status.value` 사용. → PASS
- V-4 (회귀): `assets[]`(AC-1~3)·meta·degraded·`watchdog[]` 불변.
- Evidence: 전체 회귀 338 passed(기존 snapshot 계약 테스트 green, no-price 는 `assets[]` 한정으로 유지). → PASS
### REQ-032 (CHG-20260608-03) — CLI `slots`
- V-5 (슬롯 상태 표시): `slots` 가 universe `assets` 가 아니라 envelope `slots[]`(status 포함) 표시.
- Evidence: `tests/runtime/test_cli_operator.py::test_slots_shows_real_slot_state_not_universe`(count=2, `S-1 holding`/`S-2 idle`). → PASS
- V-6 (status 필터): `--status holding`/`idle`/`pending` 가 슬롯 status 기준 필터.
- Evidence: `test_slots_status_filters_by_slot_status`(holding → count=1, S-2 제외). → PASS
- V-7 (universe 분리): `slots`≠`universe`.
- Evidence: `_cmd_universe` 는 `assets[]` 유지(코드 diff), `_cmd_slots` 는 `slots[]` 읽음; universe 테스트 green. → PASS
- V-8 (watchdog 결합): expired 슬롯에 `[WATCHDOG-OWNED]` 태그(portfolio_id 매칭).
- Evidence: `test_slots_marks_watchdog_owned`(watchdog_owned=1, WATCHDOG-OWNED). → PASS
- V-9 (`--json`): 유효 JSON.
- Evidence: `_emit(..., as_json=True)`; `--json` 경로 단위(test_cli_operator 의 기존 json 테스트 green). → PASS
### 비침습 (dcy-016 정합)
- V-10: operator 요청(`touch_watchdog=false`)으로 조회 시 워치독 미갱신.
- Evidence: `_cmd_slots` payload `actor="operator"`/`touch_watchdog=False`; `test_slots_shows_real_slot_state_not_universe` 가 payload 확인. → PASS
## Baseline 문서 동기화 (Architecture Impact: interface change)
- `docs/interface-contract.md` — snapshot 응답 `slots[]` 스키마 + `assets[]` vs `slots[]` 구분 + CLI↔RPC 매핑 정정. → 갱신 완료(REQ-028/032 Impacted Area).
- `docs/runtime-flows.md` — slot 노출 자체는 read-only(흐름 변화 없음); 추가 갱신 불요로 판단(슬롯 도메인/전이 무변경).
## 검증 명령/결과
- ruff: `ruff check`(변경 파일 5개) → All checks 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)에 의존하는 사전 환경 이슈. 본 batch(portfolio/cli/messaging_handlers/문서)와 무관(파일 미접촉). Blocking 아님(범위 밖).
- skipped 34 = 인프라 게이트(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL` 미설정).
## 운영 재현 (실 PostgreSQL + RabbitMQ, 신규 코드 데몬) — PASS
- 데몬을 신규 코드로 재기동(`trex-core-daemon --config config/test.toml`, PID 2965049), 토폴로지 ready 확인.
- `trex-core slots --config config/test.toml` → `instance=test status=all count=10 watchdog_owned=0` + **10개 슬롯이 각각 `status`(idle)·slot_id·qty·limit·avg·pid 표시**. (이전 갭: universe 1개·status 없음)
- `trex-core slots --status holding` → `count=0`(holding 없음 — 정확). 슬롯 status 필터 동작 확인(V-6 live).
- `trex-core universe` → `assets=1`(portfolio 005930) — **여전히 universe 표시**. `slots` ↔ `universe` 분리 확인(V-7 live).
- DB ground truth `daily_slot`: `(2026-06-08, 'idle', 10)` — CLI `slots` 출력과 **정확히 일치**. (V-5 live)
- 요청은 `actor="operator"`/`touch_watchdog=false`(CLI 기본) — 비침습(V-10 live).
- → **보고된 갭(universe 표시·status 없음) 해소 실증 완료**. BAT-008 envelope 경로(snapshot_store 연결, 운영 배포)를 실 인프라로 end-to-end 검증.
## Remaining (non-blocking)
- **pytest 인프라 게이트**(`TREX_POSTGRESQL_TEST_DSN`/`TREX_RABBITMQ_TEST_URL`): 전용 테스트 인프라 CI 항목. 현 환경엔 runtime infra 만 있고 통합 테스트 `pg_clean` 이 슬롯 테이블 TRUNCATE → runtime DB 로 실행 시 실 데이터 파괴라 미실행. 단 위 운영 재현이 실 인프라 end-to-end 경로(BAT-008 envelope)를 이미 PASS 로 커버하므로 release 차단 사유 아님. delivery CI(전용 테스트 인프라)에서 보강 권장.
## 검증 결과 요약
- 코드/단위/문서: green (AC-4·REQ-032 매핑 전부 PASS, baseline 동기화 완료).
- 실 인프라 운영 재현: PASS (slots=10 status 표시·DB 일치·universe 분리·비침습).
- `Ready for Release: true` — pytest 인프라 게이트만 전용 환경 CI 잔여(비차단).
## 미검증/한계
- pytest 통합 테스트(인프라 게이트)는 전용 테스트 인프라에서 수행 권장 — 운영 재현으로 실 경로는 검증됨.
- 두 경로(BAT-007 stub / BAT-008 envelope) `slots[]` 스키마 일치는 canonical `slot_to_dict` 단일화로 보장(코드 레벨). 운영 재현은 BAT-008 envelope 경로(운영 배포 형상) 검증.
## Human Approval Memo
- 승인 결정: 승인(release-candidate 승격 허용)
- 승인자: Justin Ko (사용자 명시 지시 — "둘 다 진행")
- 승인 시각(KST): 2026-06-08 19:43
- 수용 범위: **pytest 통합 인프라 게이트(전용 테스트 인프라)** 의 본 세션 미수행을 잔여 CI 항목으로 수용.
- 잔여 리스크: pytest 통합 테스트 harness 미실행. 단 (a) 신규 코드 데몬으로 실 PostgreSQL+RabbitMQ end-to-end 운영 재현이 BAT-008 envelope 경로를 PASS 로 검증(slots=10·status·DB 일치·universe 분리·비침습), (b) 단위 회귀 338 passed + ruff clean + baseline 동기화 완료. 잔여는 동일 경로의 CI 형식 보강에 한정.
- 승인 근거: 운영 재현이 보고된 갭(universe 표시)의 해소를 실 인프라로 직접 실증하여, 표준 pytest 인프라 게이트와 동등 이상의 실 경로 증거를 제공. runtime DB 로의 pytest 실행은 `pg_clean` TRUNCATE 로 실 데이터 파괴 위험이라 미수행이 안전상 정당.
+1
View File
@@ -29,3 +29,4 @@
| BAT-019 | standard | release-candidate | REQ-017 (CHG-20260608-01), REQ-019 (CHG-20260608-01), REQ-018 (CHG-20260608-03), REQ-020 (CHG-20260608-01) | dcy-015 | [bat-019](./bat-019_20260608_ledger-outbox-permanent-error-backoff/) |
| BAT-020 | standard | release-candidate | REQ-032 (CHG-20260608-01), REQ-022 (CHG-20260608-01), REQ-021 (CHG-20260608-02), REQ-034 (CHG-20260608-01), REQ-026 (CHG-20260608-01) | dcy-016 | [bat-020](./bat-020_20260608_operator-cli-rpc-commands/) |
| BAT-021 | standard | release-candidate | REQ-034 (CHG-20260608-02), REQ-021 (CHG-20260608-03), REQ-037 (CHG-20260608-01), REQ-038 (CHG-20260608-01), REQ-039 (CHG-20260608-01), REQ-032 (CHG-20260608-02), REQ-042 (CHG-20260608-01) | dcy-017 | [bat-021](./bat-021_20260608_watchdog-ownership-visibility/) |
| BAT-022 | standard | release-candidate | REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03) | dcy-016 | [bat-022](./bat-022_20260608_slot-state-snapshot-visibility/) |