Files
trex-core/docs/batches/bat-021_20260608_watchdog-ownership-visibility/design.md
T
genichinandClaude Opus 4.8 6d7bca1460 feat(safety-sell): dcy-017 워치독 소유권 가시화 — snapshot 상태 + 발동 로깅 (BAT-021)
전략 이탈로 슬롯이 내부 청산 루틴(워치독) 소유로 넘어간 상태가 무성이라
운영자가 인지할 수 없던 문제(issue #12)를 가시화한다.

거버넌스: 전부 change-req (신규 REQ 없음, dcy-017 OQ-3; Redis 미채택).
- REQ-034/021: WatchdogStore.last_seen + snapshot RPC 응답에 holding 슬롯
  워치독 상태(expired/last_seen) read-only 동봉(best-effort)
- REQ-032: trex-core slots 가 expired 슬롯을 WATCHDOG-OWNED(전략 이탈) 표시
- REQ-037/038/039: safety-sell 워치독 소유 진입(WARN)/이탈(INFO) + 발동
  (forced_close·stop_loss=WARN, take_profit·market_close=INFO) 로그 +
  in-process 카운터(SafetySellService.counters), 구조화 필드(slot_id/
  portfolio_id/reason/phase/instance_key). per-tick 스킵은 카운터만
- REQ-042: runtime-flows/interface-contract 정합

best-effort 격리(NFR-1): 모든 가시화는 예외 격리 — 워치독 판정·청산 불변.
dcy-016 억제 읽기로 관측이 워치독을 리셋하지 않음(비침습).

검증: 단위 335 passed/34 skipped, 인프라 게이트(PG+RabbitMQ) 369 passed, ruff clean.
신규 test_safety_sell_visibility(6) + snapshot watchdog(1) + CLI(1). BAT-021 rc.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 17:10:26 +09:00

3.2 KiB

BAT-021 Design

  • Status: confirmed
  • Batch: BAT-021

설계 목표

  • 전략 이탈로 슬롯이 워치독(안전망) 소유로 넘어간 상태와 safety-sell 발동을 무성에서 가시화한다(issue #12). 가시화는 best-effort — 판정·청산을 절대 막지 않는다(NFR-1).

1. 워치독 상태 노출 (REQ-034/021)

  • WatchdogStore.last_seen(slot_id) -> Optional[datetime] read-only getter 추가(판정 로직 불변).
  • make_snapshot_rpc_handler(..., watchdog_timeout_sec=120) + _attach_watchdog(payload, slot_repo, day, watchdog_store, timeout):
    • holding 슬롯마다 {slot_id, portfolio_id, symbol, expired(is_expired), last_seen} 산출 → payload["watchdog"].
    • try/except 로 감싸 실패 시 응답 불변(best-effort). envelope·stub 두 경로 모두 attach.
  • additive top-level 필드라 REQ-028 strict asset shape(no-price) 불변.

2. safety-sell 가시화 (REQ-037/038/039, OQ-2)

  • SafetySellService(instance_key="default") + self._owned: set + self.counters: dict.
  • _log_fire(slot, reason): _WARN_REASONS={forced_close_at, stop_loss} → WARN, 그 외(take_profit, market_close_liquidation) → INFO. 구조화 필드(phase/slot_id/portfolio_id/reason/instance_key) + counters["fire:{reason}"]++. check_forced_close/check_prices/check_market_close 의 발동 지점에서 호출.
  • _update_ownership(now): grace 중이면 skip. current = {holding ∧ watchdog_expired}; current - _owned → WARN(owned:enter), _owned - current → INFO(owned:exit); _owned = current. run_once 매 tick 호출. 예외 격리.
  • per-tick 스킵/no-fire 평가는 로그 X, 카운터만(30초 로그 스팸 방지). 영속 거부 반복 경고는 REQ-052 별도.

3. CLI 표시 (REQ-032)

  • _cmd_slots: snapshot 응답의 watchdog 필드에서 expired=true 인 portfolio_id 집합 → 자산 출력에 [WATCHDOG-OWNED: strategy absent] 태그 + watchdog_owned=N 요약. --json 은 원응답 그대로.

4. 배선

  • daemon: snapshot handler 에 watchdog_timeout_sec=config.safety_sell.watchdog_timeout_sec; SafetySellService 에 instance_key=config.instance_key.

5. baseline 문서 (REQ-042)

  • runtime-flows: 워치독 갱신 flow 에 상태 노출(5) + safety-sell 가시화 flow 신규(소유 진입/이탈·발동 차등·카운터·best-effort).
  • interface-contract: snapshot 응답 watchdog[] 필드.

best-effort 격리 (NFR-1)

  • _attach_watchdog·_update_ownership 예외 무시(no-op), _log_fire 는 순수 로그/카운터. 워치독 판정(is_expired)·청산(SlotService.sell) source of truth 불변.

재시작 (R-3)

  • WatchdogStore·_owned·counters in-memory → 재시작 시 소유 상태 소실(폴링 재개 시 재구성). 발동 로그는 외부 로그 싱크에 이력 유지. 영속 승격은 Out of Scope.

테스트 설계

  • tests/application/test_safety_sell_visibility.py: 소유 진입 WARN+카운터, 이탈 INFO+카운터, 안정 시 churn 없음, forced_close WARN, take_profit INFO, grace 억제(6건). instance_key 로그 포함 단언.
  • tests/application/test_messaging_snapshot_consumer.py: 워치독 만료 holding → watchdog 필드 expired/last_seen 노출.
  • tests/runtime/test_cli_operator.py: slots 가 expired 슬롯을 WATCHDOG-OWNED 로 표시.