feat(rel-007): dcy-025/024 + BAT-035/036 — slots.req 즉시 교체 + safety_sell_enabled

BAT-035 (dcy-025): trex.snapshot.req → trex.slots.req 즉시 교체
- topology: SNAPSHOT_REQ_QUEUE 제거, SLOTS_REQ_QUEUE = "trex.slots.req" 추가
- portfolio.py: View/build_snapshot_envelope/SnapshotEnvelope/_asset_to_dict 제거;
  slot_to_dict 시그니처 확장(snapshot_store/watchdog_store/pause_store/watchdog_timeout_sec)
  — asset_quantity(hub)/cash_balance/cash_estimated/warned/watchdog{}/paused 병합 (REQ-074)
- messaging_handlers.py: make_snapshot_rpc_handler 제거, make_slots_rpc_handler 추가
  — filter(all/inactive/pending/holding/error) + spy(bool) 처리; strategy 필드 제외 (REQ-073/074)
- daemon.py: _snapshot_rpc_loop → _slots_rpc_loop, make_slots_rpc_handler 연결
- cli.py: universe/slots → slots_queue + spy=True + server-side filter; buyable→inactive 매핑 (REQ-063)
- rabbitmq.py: snapshot_queue → slots_queue
- docs: interface-contract.md + runtime-flows.md slots.req 계약으로 업데이트 (REQ-075)
- REQ-028: Implemented → Deprecated (코드 삭제 완료)
- 테스트: test_snapshot_handler/selection/rpc_watchdog/messaging_snapshot_consumer 삭제;
  test_slots_handler.py(신규, 13 cases), test_killswitch_masking_resume 재작성,
  test_ledger_option_d._apply_cash_estimate → slots handler 경유, test_topology/cli_operator 갱신

BAT-036 (dcy-024): per-instance safety_sell_enabled 플래그
- config.py: DaemonInstanceConfig + DaemonConfig 에 safety_sell_enabled: bool = True 추가;
  from_toml 파싱, from_config 전파
- daemon.py: start() workers 조건부 — safety_sell_enabled=false 시 trex-safety-sell 미기동 + WARNING
- tests: test_instance_config.py 에 safety_sell_enabled 파싱 3개 케이스 추가

REQ: 073/074/075/021/034/063/048/005 Approved → Implemented

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 14:45:45 +09:00
co-authored by Claude Opus 4.8
parent fb05a73130
commit f0ad37747b
44 changed files with 1963 additions and 1189 deletions
@@ -0,0 +1,55 @@
# BAT-035 Design
- Status: confirmed
- Batch: BAT-035
## Architecture Summary
- `trex.snapshot.req` RPC 파이프라인(큐 선언·핸들러·View enum·assets[]/available_slots/watchdog[] 구조)을 제거하고 `trex.slots.req` 단일 RPC로 즉시 교체한다.
- 슬롯 행(slot_to_dict)이 자산·워치독·overlay를 흡수해 소비자 측 배열 조인을 없앤다.
- 변경은 topology → messaging_handlers → portfolio → CLI 레이어 순으로 안쪽부터 바깥으로 진행한다.
## Changed Areas
- Docs: docs/interface-contract.md (snapshot→slots 계약 교체 + 필드별 정본 출처 표), docs/runtime-flows.md (read 흐름 교체)
- Modules:
- `trex_core/adapters/messaging/topology.py` — SNAPSHOT_REQ_QUEUE → SLOTS_REQ_QUEUE, snapshot_queue() → slots_queue()
- `trex_core/application/messaging_handlers.py` — make_slots_rpc_handler 신설, make_snapshot_rpc_handler 제거, _attach_watchdog 제거
- `trex_core/application/portfolio.py` — slot_to_dict 확장, View enum 제거, build_snapshot_envelope 제거
- CLI 모듈 — slots.req 사용, spy=true, filter 매핑
- Tests:
- tests/adapters/messaging/ — topology 단언
- tests/application/ — slots 핸들러·slot_to_dict·divergence·overlay
## Key Decisions
1. **즉시 교체(lockstep)**: snapshot.req 큐·핸들러·legacy stub 경로를 같은 커밋에서 제거한다. 전략 모듈(trex-orb-v1)은 배포 전 lockstep 대응 완료 상태여야 한다(REQ-073, dcy-025 OQ-1).
2. **slot_to_dict 서명 확장**: `slot_to_dict(s, *, snapshot_store=None, watchdog_store=None, pause_store=None) → dict`. snapshot_store 없으면 asset_quantity=None, cash_balance=None, cash_estimated=False; watchdog_store 없으면 watchdog=None; pause_store 없으면 paused=False. None 필드는 응답에 그대로 포함(소비자가 구분 가능).
3. **warned 계산**: `warned = (slot.qty != asset_quantity)`. asset_quantity가 None(snapshot 미연결)이면 warned=False(불확정, 조용히 처리). slot.qty는 슬롯 미러 값.
4. **watchdog 행 흡수**: `watchdog: {expired: bool, last_seen: str|null}`. watchdog_store.get(slot_id) → WatchdogEntry(last_seen_at, timeout_sec). expired = (now - last_seen_at) > timeout_sec (또는 last_seen_at is None). WatchdogStore None 이면 watchdog 필드 전체 None.
5. **filter 분기**: `filter` → `{all: 전체, inactive: status==inactive, pending: status in (pending_buy, pending_sell), holding: status==holding, error: status==error}`. 미지정/null/"all" = 전체. 미지정 값은 all로 처리(조용히).
6. **spy 분기**: `spy=true` → holding 슬롯 워치독 미터치. `spy=false` or 미지정 → holding 슬롯 watchdog.update(). REQ-034 CHG-01 시맨틱.
7. **View enum 제거**: `portfolio.py:65`의 `class View(str, Enum)` 전체 제거. 호출 지점(build_snapshot_envelope, CLI) 정리.
8. **build_snapshot_envelope 제거**: `portfolio.py:340`의 `build_snapshot_envelope` 함수 전체 제거. messaging_handlers.py 의 snapshot 경로(221~244) 같이 제거.
9. **REQ-028 Deprecated 전환**: 이번 배치에서 snapshot.req 코드가 제거되면 REQ-028 상태를 Implemented→Deprecated로 전환한다.
## Edge Cases
- asset_quantity(hub) = 0 이고 qty(slot) = 0 → warned=False (정상 일치)
- asset_quantity(hub) = 0 이고 qty(slot) = 1 → warned=True (divergence)
- snapshot_store 미연결(degraded) → asset_quantity=None, cash_balance=None, cash_estimated=False, degraded=true
- spy=true 요청인데 holding 슬롯 없음 → watchdog 터치 없음, 정상 반환
- invalid filter 값("buyable" 등) → all 처리 (하위호환, CLI 매핑은 상위 계층에서 처리)
## Architecture Impact
- runtime-flows: yes (snapshot → slots 흐름 교체)
- project-structure: no (파일 추가 없음, 기존 파일 수정)
## Reference Doc Update Plan
- docs/interface-contract.md: snapshot 섹션 제거 → slots.req 계약 + 필드별 정본 출처 표 추가 (REQ-075 AC-1)
- docs/runtime-flows.md: read 흐름 snapshot→slots 교체 (REQ-075 AC-2)
@@ -0,0 +1,35 @@
# BAT-035 Implementation
- Status: draft
- Batch: BAT-035
## Plan Summary
- topology.py: SLOTS_REQ_QUEUE 신설, SNAPSHOT_REQ_QUEUE 제거, snapshot_queue→slots_queue
- messaging_handlers.py: make_slots_rpc_handler 신설, make_snapshot_rpc_handler + _attach_watchdog 제거
- portfolio.py: View enum 제거, build_snapshot_envelope 제거, slot_to_dict 확장
- CLI: slots.req 사용 + spy=true + filter 매핑
- docs: interface-contract.md, runtime-flows.md 교체
- tests: topology + slots 핸들러 + slot_to_dict
## Changed Files
- `trex_core/adapters/messaging/topology.py`
- `trex_core/application/messaging_handlers.py`
- `trex_core/application/portfolio.py`
- CLI 모듈 (slots.req 호출 경로)
- `docs/interface-contract.md`
- `docs/runtime-flows.md`
- `tests/adapters/messaging/test_topology.py` (또는 해당 파일)
- `tests/application/test_messaging_handlers.py` (또는 해당 파일)
- `tests/application/test_portfolio.py` (또는 해당 파일)
- `docs/srs/Interface/req-028_snapshot-read-message-contract-and-envelope.md` (Deprecated 전환)
- `docs/srs/index.md` (REQ-028 Deprecated)
## Execution Log
(구현 진행 시 기록)
## Validation
(구현 후 기록)
## Remaining Risks
- trex-orb-v1 lockstep 대응 확인 전 배포 불가
@@ -0,0 +1,25 @@
# BAT-035: 전략 read 표면 재설계 — trex.slots.req + 슬롯 단일 행 스키마 (dcy-025)
- Status: confirmed
- Profile: standard
- Source Discovery: dcy-025
- Included REQ: REQ-073, REQ-074, REQ-075, REQ-021 (CHG-20260611-01), REQ-034 (CHG-20260611-01), REQ-063 (CHG-20260611-01)
- Excluded REQ: 없음
## Summary
- `trex.snapshot.req` 파이프라인(view/assets[]/available_slots/watchdog[] 구조)을 `trex.slots.req` 단일 RPC로 즉시 교체(과도기 없음)한다. 슬롯 행 하나가 슬롯 상태·자산(hub 정본 + 슬롯 미러)·워치독·overlay 플래그를 모두 운반해 소비자 측 배열 조인을 없앤다. 필터는 status 단일 값, spy=true 시 워치독 무터치. strategy 계열 필드는 전면 제외.
## Scope
- In: topology.py 큐명 교체(SNAPSHOT_REQ_QUEUE → SLOTS_REQ_QUEUE) + snapshot.req 선언 제거, messaging_handlers.py slots 핸들러 신설 + snapshot 핸들러 제거 + `_attach_watchdog` top-level 제거, portfolio.py `View` enum 폐기 + `slot_to_dict` 확장(asset_quantity/qty/warned/cash_balance/cash_estimated/watchdog nested/paused) + `build_snapshot_envelope` 제거, CLI slots.req 사용 + spy=true + --status→filter 매핑, docs(interface-contract.md / runtime-flows.md) snapshot→slots 계약 교체, 테스트(topology/slots 핸들러/filter/spy/응답 키 집합)
- Out: strategy 계열 필드 slots 행 도입(후속 discovery), trex-orb-v1 소비 측 코드 수정(운영자 별도 lockstep 확인), 라이브 브로커 잔고 행 포함, overlay 서버측 필터링
## Documents
- Planning: [planning.md](./planning.md)
- Design: [design.md](./design.md)
- Implementation: [implementation.md](./implementation.md)
- Verification: [verification.md](./verification.md)
## Notes
- 즉시 교체(과도기 없음): 배포 시점에 trex-orb-v1 의 `trex.slots.req` 대응 준비 완료 여부를 사전 확인해야 한다(dcy-025 R-1, REQ-073 Notes).
- REQ-028(snapshot 계약 REQ): keep-current(Implemented) 유지, 이번 배치에서 코드 제거 완료 시 Deprecated 전환.
- REQ-062(hub 정본 + 슬롯 미러 + 브로커 ground truth 정합)는 이번 배치 영향 없이 유지 — slot_to_dict 가 그 계약을 행에 표현한다.
@@ -0,0 +1,61 @@
# BAT-035 Planning
- Status: done
- Batch: BAT-035
- Profile: standard
## Included REQ
- REQ-073: trex.slots.req RPC 계약 (Interface/High) — 큐 교체, filter/spy, 응답 slots+health only
- REQ-074: 슬롯 단일 행 스키마 (Data/High) — asset_quantity+qty+warned, cash_balance+cash_estimated, watchdog nested, paused
- REQ-075: baseline 문서 정합 (Documentation/Medium) — interface-contract/runtime-flows
- REQ-021 CHG-20260611-01: 토폴로지 큐명 snapshot→slots 교체
- REQ-034 CHG-20260611-01: watchdog 트리거=slots.req 기본/spy=무터치, 행 흡수
- REQ-063 CHG-20260611-01: CLI slots.req+spy=true, --status→filter 매핑
## Delivery Plan
### M1: 토폴로지 + 인프라 계약 교체
- `trex_core/adapters/messaging/topology.py`: SNAPSHOT_REQ_QUEUE → SLOTS_REQ_QUEUE (`"trex.slots.req"`), `snapshot_queue()` → `slots_queue()`
- `docs/interface-contract.md`: trex.snapshot.req 섹션 제거, trex.slots.req 계약(요청 {filter, spy}, 응답 {slots[], degraded/reason/message}, 필드별 정본 출처 표) 추가
- `docs/runtime-flows.md`: read 흐름 snapshot→slots 교체
### M2: messaging_handlers — slots 핸들러 신설 + snapshot 핸들러 제거
- `make_snapshot_rpc_handler` 전체 제거
- `make_slots_rpc_handler` 신설: filter(str) / spy(bool) 파싱, filter 분기 슬롯 조회, spy=false → holding watchdog 터치, 응답 {slots, degraded, reason, message} only
- `_attach_watchdog` top-level 함수 제거 (watchdog 처리가 핸들러 내부로 흡수)
### M3: portfolio — slot_to_dict 확장 + View 폐기 + snapshot envelope 제거
- `portfolio.py`: `View` enum 제거, `build_snapshot_envelope` 제거
- `slot_to_dict(s, snapshot_store=None, watchdog_store=None, pause_store=None)` 확장:
- 슬롯/코어 정본 필드(기존 유지)
- `asset_quantity` (snapshot_store → hub 정본) + `qty` (슬롯 미러 s.qty) + `warned` (qty≠asset_quantity)
- `cash_balance` + `cash_estimated` (snapshot_store)
- `watchdog: {expired: bool, last_seen: str|null}` nested (watchdog_store)
- `paused: bool` (pause_store)
### M4: CLI — slots.req 사용 + spy=true + filter 매핑
- `trex_core/interfaces/cli.py` (또는 해당 모듈): 슬롯 조회 시 slots.req + spy=true, `--status` 인자→filter 매핑 (all/inactive/pending/holding/error, 기존 "buyable" → "inactive")
### M5: 테스트 + 회귀
- tests/adapters/messaging/: topology 단언 (SLOTS_REQ_QUEUE 존재, SNAPSHOT_REQ_QUEUE 부재)
- tests/application/: slots 핸들러 filter·spy·응답 키 집합, watchdog 터치/무터치, degraded 신호, portfolio slot_to_dict 확장 필드 + divergence + warned + cash_estimated + paused holding 직교, strategy 필드 부재
- 전체 suite green
## Dependencies
- Internal: REQ-062(hub 정본 + 슬롯 미러 정합 — 이번 배치가 그 계약을 표면에 구현), PauseStore, WatchdogStore 기존 API 재사용
- External: trex-orb-v1 lockstep 대응(배포 전 확인)
## Design Gate
- Design Required Before Implementation: **yes**
- Reason: topology.py 큐 선언 변경(REQ-021), messaging_handlers.py 핸들러 신설/제거(REQ-073), portfolio.py slot_to_dict 서명 변경 + View 폐기(REQ-074)가 상호 연결된 인터페이스 변경. 제거 지점 전수와 새 핸들러 설계 계약을 design.md에 먼저 고정해 구현 drift를 방지.
## 위험 요소
- R-1 (lockstep): trex-orb-v1 이 snapshot.req 를 아직 쓰고 있으면 배포 즉시 read 실패 → 배포 전 trex-orb-v1 의 slots.req 대응 완료 여부 확인 필수
- R-2 (slot_to_dict 서명 변경): 호출 지점이 다수(messaging_handlers + CLI + 테스트) → grep 으로 호출 지점 전수 확인 후 한 번에 교체
- R-3 (build_snapshot_envelope 제거): snapshot_store wiring path 삭제 시 CLI / daemon wiring 코드에서 잔존 참조 compile 오류 → M5 회귀로 검출
## 검증 계획 (요약)
- topology: SLOTS_REQ_QUEUE 존재, SNAPSHOT_REQ_QUEUE 부재 assert
- slots 핸들러: filter 분기(all/inactive/pending/holding/error), spy 터치/무터치, 응답 키 집합 {slots/degraded/reason/message} only, strategy 필드 부재
- slot_to_dict: asset_quantity(hub) + qty(mirror) + warned(divergence) + cash_balance + cash_estimated + watchdog:{expired,last_seen} + paused 포함
- 회귀: 전체 suite green
@@ -0,0 +1,72 @@
# BAT-035 Verification
- Status: confirmed
- Batch: BAT-035
## Acceptance Mapping
- REQ-073 AC-1: topology 선언 — `trex.slots.req` 큐 존재, `trex.snapshot.req` 큐·핸들러 부재
- REQ-073 AC-2: filter 분기 — inactive/pending(buy∪sell)/holding/error/all/invalid 처리
- REQ-073 AC-3: 응답 키 집합 — {slots/degraded/reason/message} only, assets/available_slots/watchdog(top) 키 부재
- REQ-073 AC-4: spy=true → last_seen_at 미갱신; spy=false(default) → holding watchdog 갱신
- REQ-073 AC-5: degraded=true + reason 전파, credentials sanitized
- REQ-074 AC-1: 행 필드 — slot_id/status/avg_price/entry_price/forced_close_at/symbol/portfolio_id/cash_balance/asset_quantity/qty/watchdog:{expired,last_seen}/paused/warned 포함
- REQ-074 AC-2: qty≠asset_quantity → warned=true, 두 값 모두 노출
- REQ-074 AC-3: cash_estimated=true 구간 + 수렴 시 false
- REQ-074 AC-4: paused holding → filter=holding 반환, paused=true (직교)
- REQ-074 AC-5: strategy 계열 키 부재 (strategy_id/sell_strategy_id/strategy_params/active_strategy_id)
- REQ-075 AC-1: interface-contract.md 에 slots.req 계약 + 필드 소스 테이블 추가, snapshot.req 현행 계약 섹션 제거
- REQ-075 AC-2: runtime-flows.md read 흐름 slots.req 기준으로 업데이트
- REQ-075 AC-3: `trex.snapshot.req` 현행 계약 서술 제거됨 (change log 이력 제외)
- REQ-021 CHG-01 AC: 토폴로지에 slots.req 선언, snapshot.req 없음 (SNAPSHOT_REQ_QUEUE 상수 제거)
- REQ-034 CHG-01 AC: spy=true → watchdog 미갱신; spy=false → holding watchdog 갱신
- REQ-063 CHG-01 AC: CLI spy=true 전송, --status 인자 server-side filter 매핑 (buyable→inactive)
## Evidence
### Tests (420 passed, 45 skipped — 2026-06-11)
- **REQ-073 AC-1**: `tests/adapters/messaging/test_topology.py::test_topology_constants`
- `topology.SLOTS_REQ_QUEUE == "trex.slots.req"` ✓
- `not hasattr(topology, "SNAPSHOT_REQ_QUEUE")` ✓
- **REQ-073 AC-2**: `tests/application/test_slots_handler.py`
- `test_filter_all_returns_all_slots` ✓
- `test_filter_inactive_returns_inactive_slots` ✓
- `test_filter_holding_returns_holding_slots` ✓
- `test_filter_pending_returns_pending_buy_and_sell` ✓
- `test_invalid_filter_treated_as_all` ✓
- **REQ-073 AC-3**: `test_response_keys_only_slots_health` ✓
- **REQ-073 AC-4**: `test_spy_true_no_watchdog_update`, `test_spy_false_updates_watchdog` ✓
- **REQ-073 AC-5**: `test_degraded_signal_propagated` ✓
- **REQ-074 AC-1**: `test_slot_row_contains_merged_fields` ✓
- **REQ-074 AC-2**: `test_warned_true_when_qty_diverges` ✓
- **REQ-074 AC-3**: `tests/application/test_ledger_option_d.py::test_apply_cash_estimate_overlays_pending_delta` ✓
- **REQ-074 AC-4**: `test_paused_holding_orthogonal_to_filter` ✓
- **REQ-074 AC-5**: `test_strategy_fields_absent` ✓
- **REQ-021 CHG-01**: `test_topology_constants` + `tests/adapters/messaging/test_instance_topology.py::test_named_instance_suffix` ✓
- **REQ-034 CHG-01**: spy 워치독 억제 tests (위) ✓
- **REQ-063 CHG-01**: `tests/runtime/test_cli_operator.py`
- `test_universe_uses_slots_queue_with_spy` ✓
- `test_slots_status_sends_server_side_filter` ✓
- `test_slots_buyable_maps_to_inactive_filter` ✓
### Manual
- `tests/application/test_killswitch_masking_resume.py`: View/build_snapshot_envelope 의존 제거 후 재작성 완료
- `tests/application/test_ledger_option_d.py`: _apply_cash_estimate 직접 테스트 → slots handler 경유 재작성 완료
### Code Review
- `trex_core/adapters/messaging/topology.py`: SNAPSHOT_REQ_QUEUE 제거, SLOTS_REQ_QUEUE 추가 ✓
- `trex_core/application/portfolio.py`: View, build_snapshot_envelope, _asset_to_dict, SnapshotEnvelope 제거 ✓
- `trex_core/application/messaging_handlers.py`: make_snapshot_rpc_handler 제거, make_slots_rpc_handler 추가 ✓
- `trex_core/runtime/daemon.py`: _snapshot_rpc_loop → _slots_rpc_loop, make_slots_rpc_handler 연결 ✓
- `trex_core/runtime/cli.py`: slots_queue + spy=True + server-side filter ✓
- `docs/interface-contract.md`: slots.req 계약 추가, snapshot.req envelope 섹션 제거 ✓
- `docs/runtime-flows.md`: 슬롯 RPC flow 추가, snapshot RPC flow 교체 ✓
- `docs/srs/index.md`: REQ-028 → Deprecated ✓
## Result
- Ready for Release: true
- Blocking Issues: 없음
@@ -0,0 +1,42 @@
# BAT-036 Design
- Status: confirmed
- Batch: BAT-036
## Architecture Summary
- `DaemonInstanceConfig`에 `safety_sell_enabled: bool = True` 필드를 추가하고 per-instance TOML 파싱 경로(`from_toml`)와 `DaemonConfig.from_config()` 전파 경로를 업데이트한다.
- `Daemon.start()` 내부 workers 리스트 구성 시 `safety_sell_enabled` 값을 체크해 False이면 `("trex-safety-sell", self._safety_sell_loop)` 항목을 포함하지 않는다.
## Changed Areas
- Docs: 없음 (docs 변경 없음)
- Modules:
- `trex_core/runtime/config.py` — DaemonInstanceConfig 필드 + from_toml + from_config 전파
- `trex_core/runtime/daemon.py` — start() workers 분기
- Tests:
- config 파싱 단위 테스트
- daemon workers 구성 테스트
## Key Decisions
1. **absent=true 기본값**: `from_toml()` 내 `data.get("safety_sell_enabled", True)`. TOML 키 부재 시 True → 기존 배포 동작 보존.
2. **DaemonConfig 전파 경로**: `DaemonConfig.from_config()` → `DaemonInstanceConfig.from_toml()` → `DaemonInstanceConfig.safety_sell_enabled` → `DaemonConfig` 생성자 또는 속성으로 전달. `Daemon.__init__` 에서 `self._config.safety_sell_enabled` 로 참조.
3. **workers 조건 분기 위치**: `daemon.py:328` workers 리스트 구성 블록 내, `("trex-safety-sell", self._safety_sell_loop)` 항목을 조건부로 append. 리스트 구성 후 순회 루프(`for name, target in workers:`)는 변경 없음.
4. **로그 메시지**: `logger.warning("safety_sell disabled — trex-safety-sell worker not started. Autonomous safety-sell will NOT run.")`. RUNNING 로그(기존)로 전체 워커 시작 상태를 가시화.
5. **AC-6 보존**: `_safety_sell_loop` 제거가 아닌 workers 리스트 미포함. `Daemon._safety_sell_loop` 메서드 자체는 삭제하지 않는다(CLI exit/cancel/kill-switch 독립 경로는 이 메서드를 직접 호출하지 않으므로 무관).
## Edge Cases
- safety_sell_enabled=True (기본): workers에 포함, 기존 동작과 동일
- safety_sell_enabled=False: workers에 미포함, 경고 로그, 나머지 워커 영향 없음
- TOML 키 오타(예: "safety_sell_enable"): get() 기본값 True 처리 → 활성화(안전한 방향)
## Architecture Impact
- runtime-flows: no (daemon 기동 흐름만 경미하게 변경, 문서 갱신 불필요)
- project-structure: no
## Reference Doc Update Plan
- docs/interface-contract.md: 없음
- docs/runtime-flows.md: 없음
@@ -0,0 +1,23 @@
# BAT-036 Implementation
- Status: draft
- Batch: BAT-036
## Plan Summary
- config.py: DaemonInstanceConfig.safety_sell_enabled 필드 + from_toml 파싱 + from_config 전파
- daemon.py: start() workers 조건 분기
## Changed Files
- `trex_core/runtime/config.py`
- `trex_core/runtime/daemon.py`
- `tests/` (config 파싱 + daemon workers 테스트)
## Execution Log
(구현 진행 시 기록)
## Validation
(구현 후 기록)
## Remaining Risks
- 없음
@@ -0,0 +1,25 @@
# BAT-036: safety_sell_enabled TOML 설정 + 조건부 데몬 워커 기동 (dcy-024)
- Status: confirmed
- Profile: standard
- Source Discovery: dcy-024
- Included REQ: REQ-048 (CHG-20260611-01), REQ-005 (CHG-20260611-01)
- Excluded REQ: 없음
## Summary
- per-instance TOML 에 `safety_sell_enabled: bool = true` 필드를 추가하고, `false` 시 daemon.start() 가 `trex-safety-sell` 워커를 workers 리스트에서 제외해 스레드를 기동하지 않는다. 기동 로그로 상태를 가시화하며, 나머지 실행 경로(ExecutionService/CLI exit/cancel/kill-switch/strategy sell intent)는 이 값과 무관하게 동작한다.
## Scope
- In: `trex_core/runtime/config.py` — DaemonInstanceConfig에 `safety_sell_enabled: bool = True` 필드 추가 + from_toml/from_config 파싱 + DaemonConfig로 전파, `trex_core/runtime/daemon.py` — start() workers 리스트 조건 분기 (safety_sell_enabled=False → trex-safety-sell 제외 + RUNNING 로그 + 경고 로그), 관련 테스트
- Out: env 변수 TREX_SAFETY_SELL_ENABLED, CLI 노출, slots/snapshot 응답 노출, 다른 워커 제어
## Documents
- Planning: [planning.md](./planning.md)
- Design: [design.md](./design.md)
- Implementation: [implementation.md](./implementation.md)
- Verification: [verification.md](./verification.md)
## Notes
- absent=true 기본값: TOML에 키 없으면 True(기존 동작 보존).
- 선례: max_active_slots, default_order_timeout_sec 와 동일한 per-instance TOML 경로.
- REQ-005 AC-6: ExecutionService/CLI exit/cancel/kill-switch/strategy sell intent 는 safety_sell_enabled 값과 무관하게 동작 — 이번 배치 구현에서 그 경로를 건드리지 않는 것이 검증 기준.
@@ -0,0 +1,49 @@
# BAT-036 Planning
- Status: done
- Batch: BAT-036
- Profile: standard
## Included REQ
- REQ-048 CHG-20260611-01: DaemonInstanceConfig.safety_sell_enabled: bool=True, from_toml/from_config 파싱, DaemonConfig 전파 (AC-10)
- REQ-005 CHG-20260611-01: daemon.start() 조건부 workers — safety_sell_enabled=False 시 trex-safety-sell 제외 + RUNNING 로그 + 경고 로그 (AC-5/6)
## Delivery Plan
### M1: config.py — DaemonInstanceConfig 필드 추가
- `trex_core/runtime/config.py:DaemonInstanceConfig` 에 `safety_sell_enabled: bool = True` dataclass 필드 추가
- `from_toml()`: `data.get("safety_sell_enabled", True)` 파싱
- `DaemonConfig.from_config()`: DaemonConfig 생성 시 inst.safety_sell_enabled 전파
### M2: daemon.py — start() 조건부 workers
- `trex_core/runtime/daemon.py:328~337` workers 리스트 구성 부분에서 조건 분기:
```python
if self._config.safety_sell_enabled:
workers.append(("trex-safety-sell", self._safety_sell_loop))
else:
logger.warning("safety_sell disabled — trex-safety-sell worker not started")
```
- RUNNING 로그(기존 구조 안에서)로 비활성 상태 가시화
### M3: 테스트
- config 파싱: TOML `safety_sell_enabled=false` → False; 키 부재 → True
- daemon start: safety_sell_enabled=False → trex-safety-sell 스레드 미기동 단언
- AC-6: ExecutionService/exit/cancel/kill-switch 경로는 이번 M1~M2 변경으로 건드리지 않음(회귀 테스트로 확인)
## Dependencies
- Internal: DaemonConfig 내 DaemonInstanceConfig 전파 경로(from_config → DaemonInstanceConfig.from_toml)
- External: 없음
## Design Gate
- Design Required Before Implementation: **yes**
- Reason: DaemonConfig ↔ DaemonInstanceConfig 전파 경로와 daemon.start() workers 리스트 수정 지점을 design.md 에 먼저 확정해 구현 오류를 방지.
## 위험 요소
- R-1 (absent=true 누락): from_toml 에서 get 기본값을 False 로 잘못 설정하면 기존 인스턴스 동작 파괴 → 단위 테스트로 absent→True 확인
- R-2 (DaemonConfig 전파): DaemonConfig 에 safety_sell_enabled 를 전달하지 않으면 daemon.start() 에서 참조 실패 → M1 에서 전파 경로를 먼저 확인
## 검증 계획 (요약)
- TOML 파싱: present=false→False, present=true→True, absent→True
- daemon.start() workers: False → trex-safety-sell 제외, True → 포함
- 다른 워커(trex-execution 등) 영향 없음
- 전체 suite green
@@ -0,0 +1,44 @@
# BAT-036 Verification
- Status: confirmed
- Batch: BAT-036
## Acceptance Mapping
- REQ-048 CHG-20260611-01 AC-10: Given per-instance TOML safety_sell_enabled(absent=true), When from_toml/from_config, Then 값이 파싱되어 DaemonConfig로 전파된다
- present=false → DaemonInstanceConfig.safety_sell_enabled=False → DaemonConfig.safety_sell_enabled=False
- present=true → True
- absent → True (기본값)
- REQ-005 CHG-20260611-01 AC-5: Given safety_sell_enabled=False, When daemon.start(), Then trex-safety-sell 스레드 미기동 + WARNING 로그
- REQ-005 CHG-20260611-01 AC-6: ExecutionService/CLI exit/cancel/kill-switch/strategy sell intent — safety_sell_enabled 값과 무관 동작 (회귀: 해당 경로 테스트 green)
## Evidence
### Tests (420 passed, 45 skipped — 2026-06-11)
- **REQ-048 CHG-20260611-01 AC-10**: `tests/runtime/test_instance_config.py`
- `test_safety_sell_enabled_default_true` ✓
- `test_safety_sell_enabled_false_from_toml` ✓
- `test_safety_sell_enabled_true_explicit_from_toml` ✓
- **REQ-005 CHG-20260611-01 AC-6** (회귀): 전체 테스트 스위트 420 passed — execution/exit/cancel/kill-switch 경로 모두 green ✓
### Code Review
- `trex_core/runtime/config.py`:
- `DaemonInstanceConfig.safety_sell_enabled: bool = True` ✓
- `from_toml()`: `safety_sell_enabled=bool(data.get("safety_sell_enabled", True))` ✓
- `DaemonConfig.safety_sell_enabled: bool = True` ✓
- `from_config()` replace: `safety_sell_enabled=inst.safety_sell_enabled` ✓
- `trex_core/runtime/daemon.py`:
- `start()` workers 조건부: `if self.config.safety_sell_enabled: workers.append(...)` ✓
- `else: _log.warning("safety_sell_enabled=false — trex-safety-sell worker not started...")` ✓
### Manual
- `daemon.start()` 스레드 조건부 기동: 단위 테스트에서 실제 스레드 기동 없이 코드 경로 검증 (config 파싱 tests + code review)
- safety_sell 비기동 시 execution/exit/cancel/kill-switch 경로 무관함을 회귀 테스트로 확인
## Result
- Ready for Release: true
- Blocking Issues: 없음
+2
View File
@@ -42,3 +42,5 @@
| BAT-032 | standard | release-candidate | REQ-068, REQ-069, REQ-070, REQ-071, REQ-072, REQ-061(CHG-05), REQ-013(CHG-03) | dcy-022 | [bat-032](./bat-032_20260610_execution-policy/) |
| BAT-033 | batch-lite | release-candidate | REQ-032 (CHG-20260610-06) | REQ-032 change-req (issue #23) | [bat-033](./bat-033_20260610_enter-qty-validation/) |
| BAT-034 | standard | release-candidate | REQ-002 (CHG-20260610-02), REQ-006 (CHG-20260610-02), REQ-007 (CHG-20260610-02), REQ-028 (CHG-20260610-01), REQ-032 (CHG-20260610-07) | dcy-023 (issue #22) | [bat-034](./bat-034_20260610_drop-allocated-capital/) |
| BAT-035 | standard | confirmed | REQ-073, REQ-074, REQ-075, REQ-021 (CHG-20260611-01), REQ-034 (CHG-20260611-01), REQ-063 (CHG-20260611-01) | dcy-025 | [bat-035](./bat-035_20260611_strategy-read-surface-slots-req/) |
| BAT-036 | standard | confirmed | REQ-048 (CHG-20260611-01), REQ-005 (CHG-20260611-01) | dcy-024 | [bat-036](./bat-036_20260611_safety-sell-disable-mode/) |