docs(req): dcy-008 kill-switch confirm-discovery + REQ-043~047 (Approved)

dcy-008(비상 정지/kill-switch) OQ-2(별도 테이블/store)·OQ-4(수동 해제
resume_kill_switch) 결정 → confirm-discovery(confirmed) → draft-req →
confirm-req(5개 Approved).

- REQ-043 (Interface): trex.pause.req → trex.control.req 제어 채널 + action dispatch (OQ-1)
- REQ-044 (Data): day-scoped killswitch_exclusion 테이블 + store + 005 마이그레이션 (OQ-2)
- REQ-045 (Interface): kill_switch 실행 — 범위별 holding 순차 sell() + idempotency + 이력 (NFR-1/2 흡수)
- REQ-046 (Interface): 당일 제외 masking + 다음 거래일 자동복귀 + resume_kill_switch (OQ-4, NFR-3 흡수)
- REQ-047 (Documentation): baseline 3문서 정합

OQ-3(pending_buy 롤백)은 REQ Deferred. srs index Next ID REQ-048, Register +5,
요약 갱신. dcy-008 생성된 REQ 참조 기록.

dcy-009(멀티 인스턴스 RabbitMQ, draft)는 별도 주제로 제외.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 11:29:31 +09:00
co-authored by Claude Opus 4.8
parent 6fda381f62
commit c912c08071
8 changed files with 638 additions and 45 deletions
@@ -1,13 +1,13 @@
# 0. 문서 상태
- 상태: draft
- 상태: confirmed
- 문서 ID: dcy-008_20260607_emergency-stop-kill-switch
- 이슈명: 비상 정지/강제 청산(kill-switch) — portfolio/전역 슬롯 청산 + 당일 전략 제외
- 작성 시각(KST): 2026-06-07
- 마지막 갱신 시각(KST): 2026-06-07
- 마지막 갱신 시각(KST): 2026-06-07 11:11 (draft-req: REQ-043~047 생성)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: 없음
- 생성된 REQ 참조: REQ-043, REQ-044, REQ-045, REQ-046, REQ-047
# 1. 계획 상태 요약
@@ -52,17 +52,18 @@
# 4. 이번에 정의할 변경
- **kill-switch 트리거 진입점 추가**: 운영자가 RPC 메시지(`trex.pause.req` 확장 또는 신규 채널) 또는 CLI로 kill-switch를 발동하면, 대상 범위(portfolio/전역)의 모든 holding 슬롯에 `SlotService.sell()`을 순차 호출한다.
- **kill-switch 트리거 진입점 추가**: 운영자가 RPC 메시지(`trex.control.req`, `action: "kill_switch"`) 또는 CLI로 kill-switch를 발동하면, 대상 범위(portfolio/전역)의 모든 holding 슬롯에 `SlotService.sell()`을 순차 호출한다. 기존 `trex.pause.req`를 `trex.control.req`로 개념 확장하고 action 필드 기반 dispatch로 구현한다(새 큐 불필요).
- **day-scoped 전략 제외 기능 추가**: kill-switch 발동 후 대상 범위 자산을 당일 전략 실행에서 제외한다. snapshot RPC 응답의 masking 소스에 "kill-switch 당일 제외"를 추가한다. 다음 거래일 데몬 부팅 시 자동 복귀한다.
- **당일 제외 상태 영속화**: kill-switch 당일 제외 상태를 데몬 재시작 후에도 유지하기 위해 DB에 영속한다(pause_store 확장 또는 별도 테이블 — OQ-2).
- **당일 제외 상태 영속화**: kill-switch 당일 제외 상태를 데몬 재시작 후에도 유지하기 위해 DB에 영속한다. **별도 테이블/store 어댑터를 신설**한다(OQ-2 Answered) — `snapshot_pause`(영구 pause)와 분리하고 `trading_date` 키로 day-scoped 표현. 다음 거래일 부팅 시 자동 무효.
- **수동 해제(resume) 지원**: 운영자가 같은 거래일 내에 kill-switch 당일 제외를 해제할 수 있도록 `trex.control.req` 에 `action: "resume_kill_switch"`(범위: portfolio/전역)를 추가한다(OQ-4 Answered). 해당 범위의 day-scoped 제외 row 를 삭제해 정상 매매를 재개한다. 단, 이미 청산된 슬롯은 되돌리지 않는다(매도 비가역).
## 영향 범위
- 코드 영향:
- `trex_core/domain/slot/service.py`: kill-switch 발동 시 복수 슬롯에 sell()을 순차 호출하는 진입점(메서드 또는 헬퍼)
- `trex_core/application/messaging_handlers.py`: kill-switch 명령 처리 handler 추가(RPC 채널 결정 이후)
- `trex_core/application/messaging_handlers.py`: `trex.control.req` action dispatch — kill-switch handler 추가
- `trex_core/adapters/persistence/postgresql.py` 또는 신규 어댑터: day-scoped 제외 상태 영속 read/write
- `trex_core/runtime/daemon.py`: kill-switch RPC consumer 스레드 추가 또는 기존 pause RPC 확장
- `trex_core/runtime/daemon.py`: pause consumer를 control consumer(`trex.control.req`)로 확장 — action 기반 dispatch
- `trex_core/application/portfolio.py` 또는 snapshot handler: masking 소스에 "kill-switch 당일 제외" 추가
- 문서 영향:
- `docs/interface-contract.md`: kill-switch RPC 메시지 계약(payload 구조, 대상 범위, 응답 구조)
@@ -70,7 +71,7 @@
- `docs/project-structure.md`: kill-switch 관련 신규 모듈/handler 반영
- 운영 영향:
- DB 스키마: day-scoped 제외 상태 영속 컬럼 또는 테이블 추가(additive)
- 기존 pause RPC(trex.pause.req) 채널 확장 또는 신규 채널 추가(OQ-1)
- `trex.pause.req` → `trex.control.req` 개념 확장. kill-switch는 `action: "kill_switch"` 필드로 dispatch(새 큐 불필요). daemon.py의 pause consumer 스레드를 control consumer로 교체.
# 5. 요구사항 목록
@@ -94,7 +95,7 @@
- In Scope
- holding 슬롯 즉시 청산 (portfolio/전역 단위, `SlotService.sell()` 재사용)
- 당일 전략 실행 제외 + 다음 거래일 자동 복귀 (day-scoped, 영속 필요)
- kill-switch 트리거 표면: RPC 메시지 (CLI는 REQ 단계에서 결정)
- kill-switch 트리거 표면: `trex.control.req` RPC, `action: "kill_switch"` 메시지 (CLI는 REQ 단계에서 결정)
- kill-switch 발동 후 당일 추가 매수 거부 (masking 소스 확장)
- kill-switch 발동 이력 로깅
- Out of Scope
@@ -110,8 +111,7 @@
- R-1 (Medium): kill-switch 발동 시 다수 슬롯에 순차 sell()을 호출하면 execution worker FIFO 큐에 부하가 집중된다. 슬롯 수가 많을 경우 처리 지연이 발생할 수 있다.
- 완화 방안: sell() 호출은 PENDING_SELL 전이만 수행하므로 빠르다(DB write 1회). execution worker는 기존 순차 처리 유지. 슬롯 수 10 미만 운영 환경 기준으로 NFR-1 허용 지연 설정.
- R-2 (Medium): 당일 제외 상태 영속 방식 미결정(pause_store 확장 vs 별도 테이블). pause와 의미 충돌 시 마스킹 로직 복잡도 증가.
- 완화 방안: OQ-2에서 방식을 결정한다. pause는 "resume 전까지 유지(persistent)"이고 kill-switch 제외는 "당일만(day-scoped)"이라 별도 컬럼 또는 테이블로 분리를 권장한다.
- R-2 (해소): 당일 제외 상태 영속 방식 — **별도 테이블/store 신설로 결정**(OQ-2 Answered). pause는 "resume 전까지 유지(persistent)", kill-switch 제외는 "당일만(day-scoped, `trading_date` 키)"으로 테이블 분리 → 의미 충돌 없음, 기존 pause 마스킹 코드 무영향.
- R-3 (Low): kill-switch 발동 중 브로커 오류로 일부 슬롯의 sell()이 실패하거나 지연될 수 있다.
- 완화 방안: sell() → PENDING_SELL 전이 성공 시 기존 outbox retry 경로가 eventual consistency를 보장한다. 발동 이력(FR-6)으로 미처리 슬롯 추적 가능.
- R-4 (Low): kill-switch와 자율 안전 매도(dcy-007)가 동시에 같은 슬롯에 sell()을 호출하면 중복 전이가 발생할 수 있다.
@@ -127,21 +127,23 @@
## 오픈 질문
- OQ-1: kill-switch 트리거 표면 — 기존 `trex.pause.req` 채널에 `action: "kill_switch"` 메시지를 추가할지, 신규 채널(`trex.killswitch.req`)을 별도로 열지?
- 상태: Open
- 처리 방안: confirm-discovery 단계에서 Justin Ko가 결정. 채널 재사용 시 RabbitMQ 큐 추가 불필요하나 pause 핸들러 확장 필요; 신규 채널 시 명확한 관심사 분리.
- 종료 조건: confirm-discovery 완료 전 Justin Ko 결정 또는 REQ 초안 단계에서 Deferred 처리.
- 상태: Answered
- 결정: `trex.pause.req` → `trex.control.req` 개념 확장. kill-switch는 `action: "kill_switch"` 필드로 라우팅. 새 큐 불필요. 기능마다 채널을 추가하면 consumer 스레드/커넥션이 기능 수에 비례해 증가하므로 단일 제어 채널 + action discriminator 패턴을 채택한다.
- 반영 위치: # 4 변경 정의, # 4 영향 범위 운영 영향, # 5 범위 경계 In Scope
- OQ-2: 당일 제외 상태 영속 방식 — `PostgreSQLPauseStore`(pause_store)를 확장해 day-scoped 컬럼을 추가할지, 별도 테이블/어댑터를 신설할지?
- 상태: Open
- 처리 방안: confirm-discovery 단계에서 결정. pause_store 확장 시 DB 마이그레이션 1건; 별도 테이블 시 명확한 분리.
- 종료 조건: confirm-discovery 완료 전 결정 또는 REQ 단계 Deferred.
- 상태: Answered
- 결정: **별도 테이블/어댑터를 신설**한다. kill-switch 당일 제외는 `trading_date` 키로 day-scoped 표현이 자연스럽고, 기존 `snapshot_pause`(portfolio_id PK, resume까지 영구·날짜 없음)와 의미가 달라 같은 테이블에 섞으면 마스킹/만료 로직 복잡도가 올라간다(R-2). 신규 테이블(예: `killswitch_exclusion(portfolio_id, trading_date, actor, ...)`) + 전용 store 어댑터로 관심사를 분리하고, snapshot 마스킹은 `mask = pending | warning | pause | killswitch_today`로 합성한다. 기존 pause 코드는 무영향.
- 반영 위치: # 4 변경 정의(당일 제외 상태 영속화), # 4 영향 범위(신규 테이블/마이그레이션 + store 어댑터), # 6 R-2 완화, # 5 범위 경계
- 종료 조건: 충족(REQ 단계에서 정확한 테이블 컬럼/만료 기준 = trading_date 경과 자동 무효 상세화).
- OQ-3: kill-switch 발동 시 `pending_buy` 상태 슬롯 처리 — 현재 진행 중인 매수를 무시하고 HOLDING이 된 이후에 청산하도록 기다릴지, 즉시 롤백(PENDING_BUY→IDLE) 처리를 별도로 추가할지?
- 상태: Deferred → REQ 단계
- 처리 방안: PENDING_BUY 롤백은 execution 루프와 상호작용이 복잡하므로 REQ 단계에서 상세 설계. 기본 구현은 HOLDING 슬롯만 대상.
- 종료 조건: REQ 초안 작성 시 pending_buy 범위 명시.
- OQ-4: 전역 halt 수동 해제 지원 여부 — kill-switch 발동 후 같은 거래일 내에 운영자가 당일 제외를 해제하고 정상 매매를 재개할 수 있어야 하는가?
- 상태: Open
- 처리 방안: confirm-discovery 단계에서 Justin Ko 결정. 해제 지원 시 `resume_kill_switch` action 추가 필요.
- 종료 조건: confirm-discovery 완료 전 Justin Ko 결정.
- 상태: Answered
- 결정: **수동 해제를 지원한다.** 운영자가 같은 거래일 내에 kill-switch 당일 제외를 해제하고 정상 매매를 재개할 수 있어야 한다. `trex.control.req` 에 `action: "resume_kill_switch"`(범위: portfolio/전역) 를 추가하고, 해당 범위의 day-scoped 제외 row(OQ-2 신규 테이블)를 삭제(또는 무효화)한다. 해제는 "당일 제외"만 풀며 이미 청산된 슬롯을 되돌리지 않는다(매도는 비가역).
- 반영 위치: # 4 변경 정의(수동 해제 action), # 4 영향 범위(control RPC handler), # 5 범위 경계 In Scope, # 10 DECIDE
- 종료 조건: 충족(REQ 단계에서 `resume_kill_switch` payload/응답 + 권한/감사 상세화).
### 오픈 질문 처리 기준
@@ -164,16 +166,16 @@
# 8. REQ로 넘기기 전 확인 체크
- [ ] 범위(In Scope/Out of Scope) 확정
- 기준: OQ-1(트리거 표면), OQ-2(영속 방식), OQ-4(수동 해제 여부) 결정 또는 Deferred 처리 완료
- [ ] 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1~S-4 각각 단위 테스트 또는 e2e 근거 확정
- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(큐 부하), R-2(영속 방식) 완화 방안 REQ 단계 인계 여부 확인
- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ-1, OQ-2, OQ-4 결정 완료 또는 REQ Deferred 처리 완료 (OQ-3은 이미 Deferred)
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: Justin Ko 확인
- [x] 범위(In Scope/Out of Scope) 확정
- OQ-1/2/4 Answered, OQ-3 REQ Deferred. scope = portfolio+전역 둘 다(FR-4). 확정.
- [x] 성공 지표의 측정 방식/데이터 출처 확정
- S-1~S-4 각각 단위 테스트/e2e 근거 명시(# 7 측정 방식). 확정.
- [x] High 영향 리스크 우선순위 및 해소 책임자 지정
- R-1(큐 부하) 완화 = 순차 sell()·N<10 가정으로 NFR-1 인계, R-2(영속 방식) 해소(별도 테이블). REQ 단계 인계.
- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- OQ-1/2/4 Answered, OQ-3 Deferred → `Open` 없음.
- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정
- Confirmed By = Justin.
# 9. 파일 처리 결과
@@ -189,18 +191,17 @@
## DECIDE
- {{DECIDE_TRIGGER_SURFACE}}: kill-switch 트리거 표면 — `trex.pause.req` 채널 확장(`action: "kill_switch"`) vs 신규 채널(`trex.killswitch.req`) (OQ-1)
- {{DECIDE_PERSIST_METHOD}}: 당일 제외 상태 영속 방식 — pause_store 확장(컬럼 추가) vs 별도 테이블 신설 (OQ-2)
- {{DECIDE_MANUAL_RESUME}}: 같은 거래일 내 kill-switch 당일 제외 수동 해제 지원 여부 (OQ-4)
- ✅ DECIDE_PERSIST_METHOD (OQ-2, Answered): 당일 제외 상태 영속 방식 = **별도 테이블/store 신설**(`snapshot_pause`와 분리, `trading_date` day-scoped).
- ✅ DECIDE_MANUAL_RESUME (OQ-4, Answered): 수동 해제 **지원** — `trex.control.req action: "resume_kill_switch"` 추가, day-scoped 제외 해제(청산된 슬롯은 비가역).
## CONFIRM
- {{CONFIRM_SCOPE_PORTFOLIO_AND_GLOBAL}}: portfolio 단위와 전역 단위 둘 다 이번 Discovery 범위에 포함하는지, 혹은 전역만 우선 구현할지 확인
- {{CONFIRM_PENDING_BUY_DEFERRED}}: pending_buy 슬롯 롤백은 REQ 단계로 Deferred 처리해도 무방한지 확인 (OQ-3)
- ✅ CONFIRM_SCOPE_PORTFOLIO_AND_GLOBAL: **portfolio 단위 + 전역 단위 둘 다 포함**. 본문 FR-4("portfolio 단위와 전역 단위 둘 다 구분해 지원") + In Scope("portfolio/전역 단위") + 이슈명에서 이미 확정 — 동일하게 유지.
- ✅ CONFIRM_PENDING_BUY_DEFERRED: **REQ 단계 Deferred로 처리(동의)**. OQ-3(Deferred → REQ 단계) + Out of Scope("pending_buy 슬롯의 롤백 처리 — OQ-3 Deferred")에 비차단으로 명시됨. 기본 구현은 HOLDING 슬롯만 대상.
## DATA
- {{DATA_SLOT_COUNT_UPPER_BOUND}}: 실 운영 환경의 최대 동시 holding 슬롯 수 (NFR-1 지연 허용치 산정 기준)
- (비차단, REQ 단계 계측) DATA_SLOT_COUNT_UPPER_BOUND: 실 운영 환경의 최대 동시 holding 슬롯 수. A-4 가정(통상 10 미만)으로 NFR-1(N×100ms+5s) 산정 충분. 실측 상한은 REQ acceptance/verification 단계에서 확정 — Discovery confirm 차단 아님.
# 11. Discovery Freeze
@@ -214,8 +215,8 @@
## Freeze 플래그
- Handoff Decision: 보류
- Handoff Rationale: 초기 Discovery 초안 생성 완료. OQ-1(트리거 표면), OQ-2(영속 방식), OQ-4(수동 해제 여부) 미결 — confirm-discovery에서 Justin Ko 결정 후 REQ 초안 작성 가능.
- Ready for REQ Drafting: false
- Confirmed By: TBD
- Confirmed At (KST): TBD
- Handoff Decision: 승인
- Handoff Rationale: OQ-1(trex.control.req+action), OQ-2(별도 테이블/store 신설), OQ-4(수동 해제=resume_kill_switch) Answered, OQ-3(pending_buy) REQ Deferred. CONFIRM(scope=portfolio+전역=FR-4, pending_buy=REQ Deferred) 본문 근거로 확정, DATA(slot 수 상한)는 REQ 단계 계측(비차단). 코드 사실관계(SlotService.sell REQ-036, trex.pause.req) 일치, baseline 모순 없음. REQ 초안 작성 가능.
- Ready for REQ Drafting: true
- Confirmed By: Justin
- Confirmed At (KST): 2026-06-07 11:11
+1 -1
View File
@@ -13,4 +13,4 @@
| dcy-005_20260604_portfolio-hub-asset-snapshot-api | 20260604 | portfolio-hub 종목 스냅샷 read API — 전략 모듈에 전략 실행 대상 자산 리스트 제공 | [Discovery](./dcy-005_20260604_portfolio-hub-asset-snapshot-api.md) | confirmed |
| dcy-006_20260605_strategy-module-rabbitmq-transport | 20260605 | trex-core ↔ 전략 모듈 통신 transport — RabbitMQ 도입 (스냅샷 read + intent 제출 + 이벤트) | [Discovery](./dcy-006_20260605_strategy-module-rabbitmq-transport.md) | confirmed |
| dcy-007_20260606_core-autonomous-safety-sell | 20260606 | core 자율 안전 매도 — stop-loss·시간 만기 자동 트리거 | [Discovery](./dcy-007_20260606_core-autonomous-safety-sell.md) | confirmed |
| dcy-008_20260607_emergency-stop-kill-switch | 20260607 | 비상 정지/강제 청산(kill-switch) — portfolio/전역 슬롯 청산 + 당일 전략 제외 | [Discovery](./dcy-008_20260607_emergency-stop-kill-switch.md) | draft |
| dcy-008_20260607_emergency-stop-kill-switch | 20260607 | 비상 정지/강제 청산(kill-switch) — portfolio/전역 슬롯 청산 + 당일 전략 제외 | [Discovery](./dcy-008_20260607_emergency-stop-kill-switch.md) | confirmed |