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
@@ -0,0 +1,116 @@
# req-044: kill-switch day-scoped 제외 store + 테이블 + 마이그레이션
- Status: Approved
- Type: Data
- Priority: High
- Owner: project maintainers
## Intent
kill-switch 발동 시 대상 범위(portfolio/전역)를 **당일(day-scoped) 전략 실행에서 제외**하는 상태를 데몬 재시작 후에도 유지하기 위해 영속화한다. 기존 `snapshot_pause`(portfolio_id PK, resume 전까지 영구, 날짜 없음)와 의미가 다르므로(당일만, `trading_date` 키), **별도 테이블 + 전용 store 어댑터를 신설**한다(dcy-008 OQ-2 Answered). 다음 거래일 부팅 시 자동 무효된다.
## Requirement
- 신규 테이블(예: `killswitch_exclusion`)을 추가하는 additive 마이그레이션을 작성한다. 최소 컬럼: `portfolio_id`(전역 제외 표현 포함), `trading_date`, `actor`, `created_at`.
- 전역(global) 제외 표현 방식(예: `portfolio_id = '*'` sentinel 또는 별도 scope 컬럼)은 구현 시 결정한다(Notes).
- PK 또는 unique 제약은 `(portfolio_id, trading_date)` 로 동일 거래일 중복 제외를 멱등 처리한다.
- 전용 store 어댑터(예: `PostgreSQLKillSwitchStore`)가 아래를 제공한다: `exclude(portfolio_id|global, trading_date, actor)`, `clear(portfolio_id|global, trading_date)`(수동 해제, REQ-046), `list_excluded(trading_date) -> set`.
- `list_excluded(trading_date)` 는 **해당 거래일** 의 제외 항목만 반환한다. 다른 날짜 row 는 자동으로 결과에서 제외된다(day-scoped). 과거 row 정리(삭제)는 선택이며 조회가 trading_date 로 필터링되므로 정합에 영향 없다.
- 기존 `snapshot_pause` / `PostgreSQLPauseStore` 는 변경하지 않는다(관심사 분리).
## Acceptance Criteria
1. Given 마이그레이션 적용, When 스키마를 확인하면, Then `killswitch_exclusion` 테이블이 `(portfolio_id, trading_date, actor, created_at)` 컬럼과 `(portfolio_id, trading_date)` 멱등 제약으로 존재한다.
2. Given 마이그레이션을 2회 적용, When 재적용하면, Then `IF NOT EXISTS` 패턴으로 오류 없이 멱등하다(기존 데이터 보존).
3. Given `exclude("pf-A", D)`, When `list_excluded(D)`, Then `{"pf-A"}` 를 포함한다.
4. Given `exclude("pf-A", D)`, When `list_excluded(D+1)`(다음 거래일), Then `pf-A` 가 포함되지 않는다(day-scoped 자동 무효).
5. Given 같은 `(pf-A, D)` 로 `exclude` 2회, When 호출하면, Then 멱등(중복 row 없음, 오류 없음).
6. Given `exclude("pf-A", D)` 후 `clear("pf-A", D)`, When `list_excluded(D)`, Then `pf-A` 가 더 이상 포함되지 않는다(수동 해제, REQ-046).
7. Given 전역 제외, When `exclude` 후 `list_excluded(D)`, Then 전역 sentinel 이 결과에 포함되어 호출자가 전체 제외로 해석할 수 있다.
## Impacted Area
- Docs:
- `docs/project-structure.md` — 신규 테이블/store 모듈 반영 (REQ-047 처리)
- Modules:
- `trex_core/adapters/persistence/sql/005_create_killswitch_exclusion.sql` (신규, additive)
- `trex_core/adapters/portfolio/killswitch_store.py` 또는 동등 위치 — `PostgreSQLKillSwitchStore`
- Tests:
- store CRUD + day-scoped 조회 + 멱등 + 전역 sentinel 단위/통합 테스트(PG 게이트)
## Notes
- dcy-008 OQ-2 Answered: 별도 테이블/store 신설(snapshot_pause 와 분리).
- 전역 제외 표현(`portfolio_id='*'` vs scope enum 컬럼)은 구현 결정 — masking 소비처(REQ-046)와 계약 일치만 보장하면 됨.
- `trading_date` 기준: dcy-008 의 day-scoped 의미 = "발동 거래일". 거래일 경계(UTC vs KST)는 데몬의 기존 trading_date 관례(UTC date)를 따르되 REQ-045/046 와 일치시킨다.
- BAT-010 R-2(마이그레이션 실 PG round-trip) 선례에 따라 verification 에서 실 PG 적용 확인.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-008(confirmed) → REQ-044 초안 생성.
#### Intent
- 당일 제외 영속(OQ-2 Answered: 별도 테이블)을 Data REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~7 전체
#### Impacted Area Delta
- Docs: project-structure.md (REQ-047)
- Modules: sql/005_create_killswitch_exclusion.sql, killswitch_store.py
- Tests: store CRUD + day-scoped + 멱등
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인.
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-008(confirmed) OQ-2 결정(별도 테이블) 반영. 전역 sentinel 표현은 구현 결정으로 승인 blocker 아님.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성.
@@ -0,0 +1,113 @@
# req-047: kill-switch baseline 문서 정합
- Status: Approved
- Type: Documentation
- Priority: Medium
- Owner: project maintainers
## Intent
dcy-008(비상 정지/kill-switch) 기능(REQ-043~046)의 구현 완료 후 `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` 세 baseline 문서를 최신 아키텍처와 정합시킨다.
## Requirement
- `docs/interface-contract.md` 에 아래를 반영한다:
- `trex.pause.req` → `trex.control.req` 제어 채널 확장 + action 목록(`pause`/`resume`/`list`/`kill_switch`/`resume_kill_switch`) (REQ-043)
- `kill_switch` payload(`scope`, `portfolio_id?`)/응답(`liquidated`) 계약 (REQ-045)
- `resume_kill_switch` payload/응답 계약 (REQ-046)
- snapshot masking 소스에 "kill-switch 당일 제외" 추가 명시 (REQ-046)
- `docs/runtime-flows.md` 에 아래 흐름을 추가한다:
- kill-switch 발동 흐름(control RPC → 범위별 holding 순차 sell() → 당일 제외 표시) (REQ-045)
- 당일 제외 masking / 다음 거래일 자동 복귀 / 수동 resume 흐름 (REQ-046)
- `docs/project-structure.md` 에 아래를 반영한다:
- control consumer(`trex.control.req`) 로의 전환 (REQ-043)
- 신규 `killswitch_exclusion` 테이블 + `005_create_killswitch_exclusion.sql` + kill-switch store 모듈 (REQ-044)
## Acceptance Criteria
1. Given `docs/interface-contract.md` 를 읽을 때, When 제어 채널을 찾으면, Then `trex.control.req` + action 목록(kill_switch/resume_kill_switch 포함)이 명시되어 있다.
2. Given `docs/interface-contract.md` 를 읽을 때, When kill_switch/resume_kill_switch 를 찾으면, Then payload(scope/portfolio_id)와 응답 구조가 명시되어 있다.
3. Given `docs/runtime-flows.md` 를 읽을 때, When kill-switch 관련 흐름을 찾으면, Then 발동(순차 sell()+당일 제외)·masking·자동 복귀·수동 resume 흐름이 서술되어 있다.
4. Given `docs/project-structure.md` 를 읽을 때, When 제어 채널/저장소를 찾으면, Then control consumer 전환 + `killswitch_exclusion` 테이블/store + `005_*.sql` 이 반영되어 있다.
## Impacted Area
- Docs:
- `docs/interface-contract.md`
- `docs/runtime-flows.md`
- `docs/project-structure.md`
- Modules: 없음 (문서 전용)
- Tests: 없음
## Notes
- 이 REQ 는 REQ-043~046 구현 완료 후 batch verification 단계에서 최신 구현 상태를 반영해 수행한다(REQ-042 선례).
- baseline 문서가 없는 갭이 아니라 기존 baseline 갱신이므로 `bootstrap-baseline` 선행 불필요.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-008(confirmed) → REQ-047 초안 생성.
#### Intent
- kill-switch 구현 후 세 baseline 문서 정합을 Documentation REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~4 전체
#### Impacted Area Delta
- Docs: interface-contract.md, runtime-flows.md, project-structure.md
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인 (REQ-043~046 구현 batch 이후 연계).
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC(1~4)·Impacted Area·Change Log 완비. baseline 정합 문서 REQ로 REQ-043~046 구현 후 verification 단계 수행 예정. 범위와 모순 없음.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성(REQ-043~046 이후 연계).
@@ -0,0 +1,115 @@
# req-043: trex.control.req 제어 채널 + action dispatch
- Status: Approved
- Type: Interface
- Priority: High
- Owner: project maintainers
## Intent
기존 `trex.pause.req` 단일 목적 큐를 `trex.control.req` 제어 채널로 개념 확장하고, payload `action` 필드 기반 dispatch로 운영자 제어 명령을 단일 채널에서 처리한다. 기능마다 큐/consumer 스레드를 늘리지 않기 위한 단일 제어 채널 + action discriminator 패턴(dcy-008 OQ-1 Answered). 기존 pause/resume/list 동작을 보존하면서 kill-switch(REQ-045)와 resume_kill_switch(REQ-046) 라우팅 진입점을 추가한다.
## Requirement
- 데몬은 단일 제어 큐 `trex.control.req` 를 소비하고 메시지 payload 의 `action` 필드로 핸들러를 분기한다.
- 기존 제어 action(`pause`/`resume`/`list`, REQ-032)의 동작·응답 계약을 그대로 유지한다.
- 신규 action 라우팅을 추가한다: `kill_switch`(REQ-045), `resume_kill_switch`(REQ-046). 본 REQ는 라우팅·메시지 계약 골격만 정의하고 실제 청산/해제 동작은 각 REQ에서 정의한다.
- 알 수 없는 `action` 은 `{status: "error", reason: "unknown_action:<action>"}` 으로 응답한다.
- 기존 `trex.pause.req` consumer 스레드를 control consumer(`trex.control.req`)로 교체한다. 데몬 워커 스레드 총수는 불변(7개 유지).
- 하위호환: `trex.pause.req` 를 사용하던 기존 클라이언트 호환 방식(별칭 큐 유지 vs `trex.control.req` 단일화)은 배포 영향이 있으므로 Notes 에 결정 필요로 남긴다.
## Acceptance Criteria
1. Given `trex.control.req` 에 `{action: "pause", portfolio_id}` 발행, When 처리되면, Then 기존 REQ-032 pause 동작과 동일하게 `snapshot_pause` 에 반영되고 `{status: "ok", paused[], action}` 응답을 반환한다.
2. Given `{action: "resume", portfolio_id}` / `{action: "list"}`, When 처리되면, Then 각각 기존 resume/list 동작과 동일한 응답을 반환한다.
3. Given `{action: "kill_switch", ...}`, When 처리되면, Then kill-switch 핸들러(REQ-045)로 라우팅된다.
4. Given `{action: "resume_kill_switch", ...}`, When 처리되면, Then resume 핸들러(REQ-046)로 라우팅된다.
5. Given `{action: "bogus"}`, When 처리되면, Then `{status: "error", reason: "unknown_action:bogus"}` 를 반환한다.
6. Given 데몬 기동, When 워커 스레드를 확인하면, Then pause consumer 가 control consumer 로 교체되어 총 워커 스레드 수가 7로 불변이다.
## Impacted Area
- Docs:
- `docs/interface-contract.md` — 제어 채널 계약(`trex.control.req` + action 목록) (REQ-047 처리)
- Modules:
- `trex_core/adapters/messaging/topology.py` — `CONTROL_REQ_QUEUE = "trex.control.req"` 추가(또는 `PAUSE_REQ_QUEUE` 대체)
- `trex_core/application/messaging_handlers.py` — `make_control_rpc_handler`(action dispatch) 신설 또는 `make_pause_rpc_handler` 확장
- `trex_core/runtime/daemon.py` — pause consumer → control consumer 교체
- Tests:
- control dispatch 단위 테스트(pause/resume/list 회귀 + kill_switch/resume_kill_switch 라우팅 + unknown action)
## Notes
- dcy-008 OQ-1 Answered: 단일 제어 채널 + action discriminator. 새 큐 추가 없음.
- 하위호환 결정 필요(배포): 기존 `trex.pause.req` 토픽 이름을 유지(별칭)할지, `trex.control.req` 로 단일화하고 전략/운영 클라이언트를 함께 갱신할지 — batch planning/release 단계에서 확정. 현재 CLI pause/resume 는 store 직접 호출이라 영향 없음(RPC 경로만 대상).
- kill_switch/resume_kill_switch 의 payload 상세(scope/portfolio_id)는 REQ-045/046 에서 정의.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-008(confirmed) → REQ-043 초안 생성.
#### Intent
- kill-switch 트리거 표면(OQ-1 Answered)을 단일 제어 채널 Interface REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~6 전체
#### Impacted Area Delta
- Docs: interface-contract.md (REQ-047)
- Modules: topology.py, messaging_handlers.py, daemon.py
- Tests: control dispatch 단위
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인.
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC·Impacted Area·Change Log 완비. dcy-008(confirmed) 범위·결정(OQ-1/2/4 Answered, OQ-3 Deferred)과 모순 없음. 하위호환(큐 rename)은 batch/release 단계 비핵심 운영 결정으로 승인 blocker 아님.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성.
@@ -0,0 +1,122 @@
# req-045: kill-switch 실행 — 범위별 holding 즉시 청산 + 이력 로깅
- Status: Approved
- Type: Interface
- Priority: High
- Owner: project maintainers
## Intent
운영자가 `trex.control.req` 에 `action: "kill_switch"` 를 발행하면 대상 범위(portfolio 단위 또는 전역)의 모든 `holding` 슬롯을 즉시 청산한다. 기존 `SlotService.sell()`(REQ-036)을 재사용해 순차 매도를 제출하고, 같은 발동에서 대상 범위를 당일 제외(REQ-044/046)로 표시한다. 자율 안전 매도(dcy-007)와 독립적으로 동작하며, 발동 이력을 로깅한다(dcy-008 FR-1/4/5/6, NFR-1/2).
## Requirement
- `action: "kill_switch"` payload: `{action, scope: "portfolio"|"global", portfolio_id?(scope=portfolio 시 필수), actor?}`.
- 발동 시 대상 범위의 모든 `holding` 슬롯에 `SlotService.sell(actor="kill-switch", reason="emergency_stop")` 을 순차 호출한다.
- scope=portfolio: 해당 `portfolio_id` 의 holding 슬롯만.
- scope=global: 당일 전체 holding 슬롯.
- 이미 `pending_sell` 인 슬롯은 중복 sell 하지 않는다(REQ-036 idempotency).
- 발동과 동시에 대상 범위를 당일 제외로 표시한다(REQ-044 store 의 `exclude(...)` 호출). masking 동작 자체는 REQ-046.
- 발동 이력(actor, 발동 시각, scope, portfolio_id, 처리 슬롯 수)을 데몬 로그에 1건 기록한다.
- 응답: `{status: "ok", scope, portfolio_id?, liquidated: <처리 슬롯 수>}`. scope=portfolio 인데 `portfolio_id` 누락 시 `{status: "error", reason: "portfolio_id_required"}`.
- kill-switch 실행은 자율 안전 매도 루프(dcy-007)와 상태/스레드를 공유하지 않으며 서로 영향을 주지 않는다.
## Acceptance Criteria
1. Given holding 슬롯 N개(전역), When `{action:"kill_switch", scope:"global"}` 처리, Then N개 모두 `holding→pending_sell`(actor=kill-switch, reason=emergency_stop) 전이되고 응답 `liquidated=N`.
2. Given portfolio `pf-A` 에 holding 2개 + `pf-B` 에 holding 1개, When `{action:"kill_switch", scope:"portfolio", portfolio_id:"pf-A"}`, Then `pf-A` 2개만 청산되고 `pf-B` 는 holding 유지.
3. Given 대상 중 이미 `pending_sell` 인 슬롯, When kill_switch 처리, Then 그 슬롯에 중복 sell 호출이 없다(REQ-036 idempotency, transition_log 중복 없음).
4. Given scope=portfolio 인데 `portfolio_id` 누락, When 처리, Then `{status:"error", reason:"portfolio_id_required"}`.
5. Given kill_switch 발동, When 처리 완료, Then 대상 범위가 REQ-044 store 에 당일 제외로 기록된다(`list_excluded(today)` 에 포함).
6. Given kill_switch 발동, When 로그를 확인, Then actor/발동 시각/scope/portfolio_id/처리 슬롯 수를 담은 이력 1건이 기록된다.
7. Given 자율 안전 매도 루프(dcy-007)가 동작 중, When kill_switch 가 발동, Then 두 경로가 독립적으로 동작하며(REQ-036 idempotency 로 충돌 없음) safety-sell 루프가 중단/오류 없이 유지된다.
8. Given holding N개, When kill_switch 처리, Then 전체 sell() 제출 완료까지 지연이 N×100ms + 5s 이내이다(순차, outbox 전송 제외; A-4 N<10 가정).
## Impacted Area
- Docs:
- `docs/runtime-flows.md` — kill-switch 발동 흐름(트리거→순차 sell()→당일 제외 표시) (REQ-047 처리)
- `docs/interface-contract.md` — kill_switch payload/응답 계약 (REQ-047 처리)
- Modules:
- `trex_core/application/messaging_handlers.py` — kill_switch handler(REQ-043 dispatch 하위)
- `trex_core/domain/slot/service.py` 또는 신규 application 서비스 — 범위별 holding 조회 + 순차 sell() 진입점
- REQ-044 store 연동(당일 제외 표시)
- Tests:
- kill_switch handler 단위 테스트(전역/portfolio/idempotency/portfolio_id 누락/이력 로깅/독립성)
## Notes
- NFR-1(지연)·NFR-2(dcy-007 독립)는 본 REQ AC-8/AC-7 로 흡수(별도 REQ 미승격 — 독립 backlog 가치 낮음, dcy-008 정합).
- pending_buy 슬롯 처리는 dcy-008 OQ-3 Deferred → 기본 구현은 HOLDING 만 대상. pending_buy 롤백은 후속(REQ 추가 또는 change-req).
- R-1(큐 부하): sell()은 PENDING_SELL 전이(DB write 1회)로 빠름. execution worker 순차 처리 유지.
- R-3(브로커 오류): sell()→PENDING_SELL 성공 시 기존 outbox retry 가 eventual consistency 보장.
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-008(confirmed) → REQ-045 초안 생성.
#### Intent
- kill-switch 즉시 청산(FR-1/4/5/6)과 NFR-1/2 흡수를 Interface REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~8 전체
#### Impacted Area Delta
- Docs: runtime-flows.md, interface-contract.md (REQ-047)
- Modules: messaging_handlers.py, service.py(또는 신규 서비스), REQ-044 store 연동
- Tests: kill_switch handler 단위
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인.
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC(1~8)·Impacted Area·Change Log 완비. dcy-008(confirmed) 범위와 모순 없음. NFR-1/2 흡수, OQ-3(pending_buy) REQ Deferred 는 비핵심 후속으로 승인 blocker 아님(기본 구현 HOLDING 한정).
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성.
@@ -0,0 +1,121 @@
# req-046: kill-switch 당일 제외 masking + 자동 해제 + 수동 resume
- Status: Approved
- Type: Interface
- Priority: High
- Owner: project maintainers
## Intent
kill-switch 발동(REQ-045)으로 표시된 당일 제외 상태를 snapshot RPC 응답에 반영해 대상 자산을 당일 전략 실행에서 제외한다(추가 매수 차단). 제외는 day-scoped 라 다음 거래일 자동 복귀하며(REQ-044 `trading_date` 키), 운영자는 같은 거래일 내 `action: "resume_kill_switch"` 로 수동 해제할 수 있다(dcy-008 FR-2/3, NFR-3, OQ-4 Answered).
## Requirement
- snapshot RPC 응답 산출 시 masking 소스에 "kill-switch 당일 제외"(REQ-044 `list_excluded(today)`)를 추가한다: `mask = pending | warning | pause | killswitch_today`.
- scope=portfolio 제외는 해당 `portfolio_id` 자산만, 전역 제외는 당일 전체 자산을 마스킹한다.
- 마스킹된 자산은 buyable/held 뷰에 노출되지 않아 당일 추가 매수 의도가 거부된다.
- 당일 제외는 `trading_date` 기준 day-scoped 다. 다음 거래일 첫 조회/부팅 시 `list_excluded(다음 날)` 에 포함되지 않아 자동 복귀한다(별도 해제 동작 불필요).
- 영속성: 데몬 재시작 후에도 같은 거래일이면 masking 이 유지된다(REQ-044 영속, NFR-3).
- 수동 해제: `action: "resume_kill_switch"` payload `{action, scope, portfolio_id?, actor?}` 처리 시 대상 범위의 당일 제외를 해제(REQ-044 `clear(...)`)해 다음 조회부터 정상 매매 대상으로 복귀시킨다.
- resume 은 **당일 제외(매매 차단)만 해제**하며 이미 청산(매도)된 슬롯을 되돌리지 않는다(매도 비가역).
- 응답: `{status: "ok", scope, portfolio_id?}`. scope=portfolio 인데 `portfolio_id` 누락 시 `{status:"error", reason:"portfolio_id_required"}`.
## Acceptance Criteria
1. Given `pf-A` 가 당일 제외(REQ-044)로 표시됨, When snapshot RPC(view=buyable/all) 처리, Then `pf-A` 자산이 응답 assets 에서 마스킹되어 노출되지 않는다.
2. Given 전역 당일 제외 표시, When snapshot RPC 처리, Then 당일 전체 자산이 마스킹된다.
3. Given 당일 제외 표시 후, When 당일 추가 매수 intent 가 해당 자산에 들어오면, Then buyable 마스킹으로 거부/비노출된다.
4. Given `pf-A` 당일(D) 제외, When 거래일 D+1 로 snapshot RPC 처리, Then `pf-A` 가 다시 노출된다(자동 복귀, 별도 resume 불필요).
5. Given `pf-A` 제외 후 데몬 재시작(같은 거래일 D), When snapshot RPC 처리, Then 여전히 마스킹된다(영속, NFR-3).
6. Given `pf-A` 당일 제외 상태, When `{action:"resume_kill_switch", scope:"portfolio", portfolio_id:"pf-A"}` 처리, Then 다음 snapshot RPC 부터 `pf-A` 가 정상 노출되고 응답 `{status:"ok"}`.
7. Given resume_kill_switch scope=portfolio 인데 `portfolio_id` 누락, When 처리, Then `{status:"error", reason:"portfolio_id_required"}`.
8. Given resume_kill_switch 처리, When 이미 청산된 슬롯을 확인, Then 슬롯 상태(pending_sell/closed)는 되돌려지지 않는다(매도 비가역).
## Impacted Area
- Docs:
- `docs/interface-contract.md` — masking 소스에 kill-switch 당일 제외 + resume_kill_switch 계약 (REQ-047 처리)
- `docs/runtime-flows.md` — 당일 제외 masking / 자동 복귀 / 수동 resume 흐름 (REQ-047 처리)
- Modules:
- `trex_core/application/portfolio.py`(또는 snapshot envelope builder) — masking 소스에 `list_excluded(today)` 추가
- `trex_core/application/messaging_handlers.py` — `resume_kill_switch` handler(REQ-043 dispatch 하위) + snapshot 핸들러에 killswitch store 주입
- REQ-044 store 연동(`list_excluded`/`clear`)
- Tests:
- masking 단위 테스트(portfolio/전역/추가매수 거부) + day-scoped 자동 복귀(날짜 mock) + 재시작 영속 + resume 단위
## Notes
- dcy-008 OQ-4 Answered: 수동 해제 지원(`resume_kill_switch`). 자동 복귀(day-scoped)와 병행.
- NFR-3(영속 후 재시작 masking 유지)는 본 REQ AC-5 로 흡수.
- 전역 제외 sentinel 해석은 REQ-044 store 계약을 따른다.
- masking 합성은 기존 pause masking 패턴(`portfolio.py`)을 재사용(A-2).
## Change Log
### CHG-20260607-01
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- dcy-008(confirmed) → REQ-046 초안 생성.
#### Intent
- 당일 제외 masking/자동 복귀/수동 resume(FR-2/3, OQ-4, NFR-3 흡수)을 Interface REQ 로 정규화.
#### Acceptance Criteria Delta
- Added: AC-1~8 전체
#### Impacted Area Delta
- Docs: interface-contract.md, runtime-flows.md (REQ-047)
- Modules: portfolio.py, messaging_handlers.py, REQ-044 store 연동
- Tests: masking + day-scoped + 영속 + resume 단위
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 로 승인.
### CHG-20260607-02
- Date: 2026-06-07
- Author: Justin Ko
#### Change Summary
- Proposed → Approved (confirm-req).
#### Intent
- 승인 게이트 통과: 필수 필드·구체 AC(1~8)·Impacted Area·Change Log 완비. dcy-008(confirmed) OQ-4 결정(resume_kill_switch) 반영, NFR-3 흡수. 범위·결정과 모순 없음.
#### Acceptance Criteria Delta
- (변경 없음)
#### Impacted Area Delta
- (변경 없음)
#### Delivery Trace Delta
- Batches: none (미할당)
- Releases: none
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: suggest-batch-reqs / draft-batch 로 delivery 편성.
+8 -3
View File
@@ -74,7 +74,7 @@
## Next Requirement ID
- Current: `REQ-043`
- Current: `REQ-048`
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
## Requirement Register
@@ -123,11 +123,16 @@
| REQ-040 | update_thresholds intent RPC 액션 | Interface | Implemented | High | project maintainers | [req-040](./Interface/req-040_update-thresholds-intent-rpc.md) |
| REQ-041 | safety-sell 환경 설정 — 5종 env var | Configuration | Implemented | High | project maintainers | [req-041](./Configuration/req-041_safety-sell-env-config.md) |
| REQ-042 | safety-sell baseline 문서 정합 | Documentation | Implemented | Medium | project maintainers | [req-042](./Documentation/req-042_safety-sell-baseline-alignment.md) |
| REQ-043 | trex.control.req 제어 채널 + action dispatch | Interface | Approved | High | project maintainers | [req-043](./Interface/req-043_control-channel-action-dispatch.md) |
| REQ-044 | kill-switch day-scoped 제외 store + 마이그레이션 | Data | Approved | High | project maintainers | [req-044](./Data/req-044_killswitch-exclusion-store-and-migration.md) |
| REQ-045 | kill-switch 실행 — 범위별 holding 즉시 청산 + 이력 | Interface | Approved | High | project maintainers | [req-045](./Interface/req-045_kill-switch-execution.md) |
| REQ-046 | kill-switch 당일 제외 masking + 자동해제 + resume | Interface | Approved | High | project maintainers | [req-046](./Interface/req-046_killswitch-day-exclusion-masking-and-resume.md) |
| REQ-047 | kill-switch baseline 문서 정합 | Documentation | Approved | Medium | project maintainers | [req-047](./Documentation/req-047_killswitch-baseline-alignment.md) |
## Recent Change Log Summary
- 2026-06-07 :: REQ-043~047 Proposed → Approved (confirm-req): kill-switch 5개 전원 승인 게이트 통과. dcy-008(confirmed) 범위·결정(OQ-1/2/4 Answered, OQ-3 Deferred)과 모순 없음, 필수 필드·구체 AC·Impacted Area·Change Log 완비. 큐 rename 호환·전역 sentinel·pending_buy 는 비핵심 후속으로 blocker 아님. (confirm-req)
- 2026-06-07 :: REQ-043~047 생성 (draft-req): dcy-008(비상 정지/kill-switch, confirmed) → 5개 REQ 초안. 제어 채널 `trex.control.req`+action dispatch(043), day-scoped 제외 store+마이그레이션(044), kill-switch 실행=범위별 holding 순차 sell()+이력(045), 당일 제외 masking+자동복귀+resume_kill_switch(046), baseline 정합(047). NFR-1/2/3 은 045/046 AC 로 흡수, OQ-3(pending_buy) REQ Deferred. (draft-req)
- 2026-06-07 :: REQ-038 CHG-20260607-05 Approved → Implemented (confirm-req-implemented): tr_id 정정(`FHKST01010100`) 구현·재검증 — kis.py 수정, AC-7 테스트 정정, 단위 236 passed/ruff clean, 실 KIS LIVE `get_current_price=329000`(rt_cd=0) 확인.
- 2026-06-07 :: REQ-038 CHG-20260607-04 Implemented → Approved (change-req): 현재가 시세 tr_id 정정 `TTTC8397R`/`VTTC8397R` → `FHKST01010100`. 배포 전 실 KIS LIVE 진단에서 구 tr_id 가 "없는 서비스 코드"(rt_cd=1)로 거부 → LIVE 에서 손절/익절 silent 미발동 확인. 표준 시세 tr 정상(stck_prpr=329000). (change-req)
- 2026-06-07 :: REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 Approved → Implemented (confirm-req-implemented): BAT-011 verify 완료(신규 38 테스트 green, 단위 236 passed, 인프라 게이트 259 passed, ruff clean). SafetySellService(forced_close/SL·TP KIS polling/장 마감), `BrokerPort.get_current_price`, `SlotService.update_thresholds` + intent RPC 액션, `trex-safety-sell` 7번째 스레드, baseline 3문서 정합 확인. (※ REQ-038 AC-7 tr_id 는 CHG-04 로 후속 정정)
- 2026-06-07 :: REQ-034, REQ-035, REQ-036, REQ-041 Approved → Implemented (confirm-req-implemented): BAT-010 verify 완료(32 신규 테스트 green, 95 회귀 passed). WatchdogStore, stop_loss/take_profit 도메인 필드, SQL 마이그레이션, SlotService.sell(), SafetySellConfig env vars 5종 구현 확인.
- 2026-06-07 :: REQ-034~042 Proposed → Approved (confirm-req): 9개 전원 승인 게이트 통과. dcy-007(confirmed) 범위·결정과 모순 없음, 필수 필드·AC·Impacted Area·Change Log 완비 확인. (confirm-req)