feat: add kill-switch control flow

This commit is contained in:
2026-06-07 14:00:20 +09:00
parent c912c08071
commit 7e23648489
30 changed files with 1424 additions and 51 deletions
@@ -0,0 +1,56 @@
# BAT-012 Design
- Status: confirmed
- Batch: BAT-012
## Architecture Summary
BAT-012 는 운영자 비상 정지(kill-switch)를 기존 인프라 위에 추가한다.
1. **제어 채널 일원화** (`trex.control.req`): 기존 `trex.pause.req` 단일 목적 큐를 `action` discriminator 기반 제어 채널로 확장. `make_control_rpc_handler` 가 pause/resume/list(REQ-032 위임) + kill_switch(REQ-045) + resume_kill_switch(REQ-046)를 분기. daemon 의 pause consumer 스레드를 control consumer 로 교체(스레드 수 7 불변).
2. **day-scoped 제외 store** (`killswitch_exclusion` + `PostgreSQLKillSwitchStore`): `(portfolio_id, trading_date)` 키. `snapshot_pause`(영구 pause)와 분리. `list_excluded(today)` 가 당일 항목만 반환 → day-scoped 자동 복귀.
3. **kill_switch 실행**: 범위별 holding 조회 → 순차 `SlotService.sell()` → 당일 제외 표시 → 이력 로깅. dcy-007 safety-sell 과 독립(REQ-036 idempotency 로 충돌 무해).
4. **masking 합성 + resume**: snapshot envelope masking 에 `killswitch_today` 추가. `resume_kill_switch` 가 day-scoped 제외 row 삭제(청산된 슬롯은 비가역).
## Changed Areas
- Docs:
- `docs/interface-contract.md` — 제어 채널 `trex.control.req` + action 목록 + kill_switch/resume_kill_switch payload/응답 + masking 소스 (REQ-047)
- `docs/runtime-flows.md` — kill-switch 발동/masking/자동복귀/resume 흐름 (REQ-047)
- `docs/project-structure.md` — control consumer + killswitch_exclusion 테이블/store + 005 sql (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) + kill_switch / resume_kill_switch handler
- `trex_core/adapters/persistence/sql/005_create_killswitch_exclusion.sql` (신규)
- `trex_core/adapters/portfolio/killswitch_store.py` — `PostgreSQLKillSwitchStore`
- `trex_core/application/portfolio.py`(envelope builder) — masking 소스에 `list_excluded(today)` 추가
- `trex_core/application/killswitch.py`(신규, 후보) 또는 `domain/slot/service.py` — 범위별 holding 순차 sell() 진입점
- `trex_core/runtime/daemon.py` — pause consumer → control consumer 교체 + killswitch_store 배선 + snapshot/control handler 주입
- Interfaces:
- `trex.control.req` action 계약: `pause|resume|list|kill_switch|resume_kill_switch`
- kill_switch payload `{action, scope: portfolio|global, portfolio_id?, actor?}` → `{status, scope, portfolio_id?, liquidated}`
- resume_kill_switch payload `{action, scope, portfolio_id?, actor?}` → `{status, scope, portfolio_id?}`
- `KillSwitchStore.exclude/clear/list_excluded(trading_date)`
## Key Decisions (초안 — 구현 전 확정 대상)
1. **제어 큐 전환 방식**: `trex.control.req` 단일 채널로 일원화. 기존 `trex.pause.req` 하위호환은 (a) 별칭 큐 동시 선언 유지 vs (b) control.req 단일화 + 클라이언트 갱신 중 택1 — release rollout 영향이라 확정 필요(R-1). 기본안: control.req 단일화(RPC pause 사용처는 전략/운영 한정, CLI 무영향).
2. **전역 제외 표현**: `killswitch_exclusion.portfolio_id = '*'` sentinel(기본안) vs scope 컬럼 분리. masking 소비처(REQ-046)와 계약만 일치하면 됨. 기본안: `'*'` sentinel + `list_excluded` 가 sentinel 포함 시 전체 마스킹 신호로 반환.
3. **kill_switch 실행 진입점**: 신규 `application/killswitch.py`(범위별 holding 조회 + 순차 sell() + store exclude + 로깅)로 분리(기본안). domain service 는 단일 슬롯 책임 유지, 복수 슬롯 오케스트레이션은 application 계층.
4. **trading_date 기준**: 데몬 기존 관례(UTC date) 사용 — REQ-045 표시·REQ-046 조회·자동복귀 모두 동일 키. (safety-sell `_trading_date(now)` 와 일치)
5. **dcy-007 독립성**: kill_switch 는 safety-sell worker 스레드와 상태/락 공유 없음. 동일 슬롯 동시 sell() 은 REQ-036 PENDING_SELL idempotency 로 1회만 전이.
## Edge Cases
- kill_switch scope=portfolio + portfolio_id 누락 → `{status:error, reason:portfolio_id_required}`.
- 대상 중 이미 pending_sell → sell() 건너뜀(idempotent), liquidated 카운트에서 제외 여부는 구현 결정(기본: 신규 전이만 카운트).
- resume_kill_switch 후 이미 청산된 슬롯 → 상태 불변(매도 비가역). resume 은 제외 row 만 삭제.
- 다음 거래일 → `list_excluded(D+1)` 에 전일 row 미포함 → 자동 복귀(별도 동작 불필요).
- 데몬 재시작(같은 거래일) → store 영속 → masking 유지(NFR-3).
- unknown action → `{status:error, reason:unknown_action:<a>}`.
## Architecture Impact
- runtime-flows: 제어 채널 + kill-switch 4흐름 추가 — REQ-047 에서 문서화.
- project-structure: control consumer 전환 + 신규 store/테이블/sql — REQ-047 에서 문서화.
- interface-contract: pause→control 채널 계약 갱신 — REQ-047.
## Reference Doc Update Plan
- `interface-contract.md`: 제어 채널 action 목록 + kill_switch/resume_kill_switch 계약 + masking 소스 (REQ-047 AC-1/2)
- `runtime-flows.md`: 발동/masking/자동복귀/resume 흐름 (REQ-047 AC-3)
- `project-structure.md`: control consumer + killswitch_exclusion/store + 005 sql (REQ-047 AC-4)
@@ -0,0 +1,51 @@
# BAT-012 Implementation
- Status: completed
- Batch: BAT-012
## Plan Summary
- 구현 순서(planning Delivery Plan): REQ-044 store/마이그레이션 → REQ-043 제어 채널/dispatch → REQ-045 kill_switch 실행 → REQ-046 masking/resume → REQ-047 baseline 정합. 각 단계 unit test green 후 진행.
## Changed Files
- (구현 시 기록)
### 신규 파일 (예정)
- `trex_core/adapters/persistence/sql/005_create_killswitch_exclusion.sql` (REQ-044)
- `trex_core/adapters/portfolio/killswitch_store.py` — `PostgreSQLKillSwitchStore` (REQ-044)
- `trex_core/application/killswitch.py` — 범위별 holding 순차 sell() 진입점 (REQ-045)
- `tests/adapters/portfolio/test_killswitch_store.py` (REQ-044)
- `tests/application/test_control_dispatch.py` (REQ-043)
- `tests/application/test_killswitch_execution.py` (REQ-045)
- `tests/application/test_killswitch_masking_resume.py` (REQ-046)
### 기존 파일 수정 (예정)
- `trex_core/adapters/messaging/topology.py` — CONTROL_REQ_QUEUE (REQ-043)
- `trex_core/application/messaging_handlers.py` — make_control_rpc_handler + kill_switch/resume_kill_switch (REQ-043/045/046)
- `trex_core/application/portfolio.py` — masking 소스 확장 (REQ-046)
- `trex_core/runtime/daemon.py` — control consumer 전환 + killswitch_store 배선 (REQ-043/045/046)
- `docs/interface-contract.md`, `docs/runtime-flows.md`, `docs/project-structure.md` (REQ-047)
## Changed Files (실제)
- 신규: `sql/005_create_killswitch_exclusion.sql`, `adapters/portfolio/killswitch_store.py`(PostgreSQLKillSwitchStore), `application/killswitch.py`(KillSwitchService), 테스트 4개(test_killswitch_store / test_control_dispatch / test_killswitch_execution / test_killswitch_masking_resume).
- 수정: `adapters/messaging/topology.py`(CONTROL_REQ_QUEUE), `adapters/messaging/rabbitmq.py`(control 큐 declare), `application/portfolio.py`(GLOBAL_KILLSWITCH + KillSwitchStore Protocol + InMemoryKillSwitchStore + envelope masking), `application/messaging_handlers.py`(make_control_rpc_handler + kill_switch/resume_kill_switch + snapshot handler killswitch_store), `runtime/daemon.py`(control consumer 전환 + KillSwitchService + killswitch_store 배선), `tests/conftest.py`(killswitch_exclusion truncate), baseline 3문서(REQ-047).
## Execution Log
1. REQ-044 store + 005 마이그레이션 → 단위 5 passed(PG 5 gated).
2. REQ-045 KillSwitchService → 단위 6 passed.
3. REQ-043 control dispatch(topology+handler+daemon) → 단위 9 passed, import/회귀 green.
4. REQ-046 masking + resume → 단위 6 passed.
5. REQ-047 baseline 3문서 정합.
6. 전체 회귀: 단위 262 passed / 28 skipped, ruff clean.
7. 인프라 게이트(PG+RabbitMQ): 290 passed(마이그레이션 001~005 + killswitch PG round-trip 포함).
8. paper 데몬 e2e(control RPC): buy 2 → `kill_switch global`(liquidated=2) → 양쪽 `holding→pending_sell(emergency_stop)→closed`, `killswitch_exclusion=[('*',2026-06-07)]` → `resume_kill_switch global` → exclusion 0. PASS.
## Validation
- 단위: `pytest -q --deselect ...::test_sandbox_token_and_submit` → 262 passed / 28 skipped.
- 인프라 게이트: `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) pytest -q` → 290 passed.
- lint: `ruff check trex_core tests` → All passed.
- e2e: 위 8 (paper 데몬 control RPC kill_switch/resume).
## Remaining Risks
- R-1(제어 큐 rename 하위호환): control.req 단일화 구현. 기존 `trex.pause.req` 사용 RPC 클라이언트 이전은 release rollout 결정(REQ-043 Notes). CLI 무영향.
- R-5 해소: `005` 실 PG round-trip = 인프라 게이트 290 passed(killswitch PG 테스트 green).
- (이월) 실 KIS LIVE kill_switch 발동→실매도 제출→에러 관찰은 차기 거래일 운영 실증(safety-sell LIVE 선례). paper/mock 으로 충분 검증.
@@ -0,0 +1,35 @@
# BAT-012: 비상 정지/kill-switch — 제어 채널 + 범위별 청산 + 당일 제외 masking/resume
- Status: release-candidate
- Profile: standard
- Source Discovery: dcy-008
- Included REQ: REQ-043, REQ-044, REQ-045, REQ-046, REQ-047
- Excluded REQ: REQ-030 (BAT-008 released 포함, 옵션 D 는 portfolio-hub#8 후 별도 change-req)
## Summary
- dcy-008(비상 정지/강제 청산 kill-switch) 전체를 단일 standard batch 로 delivery 한다. 운영자가 `trex.control.req` 제어 채널로 portfolio/전역 단위 holding 슬롯을 즉시 청산하고 당일 전략 실행에서 제외하며(다음 거래일 자동 복귀 + 수동 resume), 기존 `SlotService.sell()`(REQ-036)·outbox·execution 인프라를 재사용한다. 구성: 제어 채널 + action dispatch(REQ-043), day-scoped 제외 store + 마이그레이션(REQ-044), kill_switch 실행(REQ-045), 당일 제외 masking + 자동복귀 + resume_kill_switch(REQ-046), baseline 3문서 정합(REQ-047).
## Scope
- In Scope:
- `trex.pause.req` → `trex.control.req` 제어 채널 확장 + `action` dispatch(pause/resume/list 유지 + kill_switch/resume_kill_switch 추가) (REQ-043)
- 신규 `killswitch_exclusion` 테이블 + `005_*.sql` additive 마이그레이션 + 전용 store 어댑터 (REQ-044)
- kill_switch 실행: 범위별(portfolio/전역) holding 순차 `SlotService.sell()` + REQ-036 idempotency + 발동 이력 로깅 (REQ-045)
- 당일 제외 masking(snapshot envelope `mask = pending|warning|pause|killswitch_today`) + 다음 거래일 자동 복귀 + `resume_kill_switch` 수동 해제 (REQ-046)
- baseline 3문서(interface-contract/runtime-flows/project-structure) 정합 (REQ-047)
- Out of Scope:
- pending_buy 슬롯 롤백 (dcy-008 OQ-3 Deferred — 기본 구현 HOLDING 한정)
- 부분 수량 청산 (GitHub #5 별도 Discovery)
- kill-switch 발동 후 자율 안전 매도(dcy-007/REL-003) 비활성화 — 두 경로 독립
- 제어 큐 rename 의 기존 클라이언트 호환 정책 확정 — release 단계 결정(REQ-043 Notes)
## 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-008). OQ-1(제어 채널)·OQ-2(별도 테이블)·OQ-4(수동 resume) Answered, OQ-3(pending_buy) REQ Deferred.
- NFR-1(청산 지연)·NFR-2(dcy-007 독립)는 REQ-045 AC-7/8, NFR-3(영속 masking)은 REQ-046 AC-5 로 흡수(별도 NFR REQ 없음).
- 기존 자산 재사용: `SlotService.sell()`(REQ-036/BAT-010), outbox·execution worker, pause masking 패턴(`portfolio.py`). 호출 진입점·제어 채널·day-scoped store 만 신규.
- REQ-047(문서 정합)은 REQ-043~046 구현 완료 후 verification 단계 수행(REQ-042 선례).
@@ -0,0 +1,52 @@
# BAT-012 Planning
- Status: confirmed
- Batch: BAT-012
- Profile: standard
## Included REQ
- REQ-043 (Interface): `trex.pause.req` → `trex.control.req` 제어 채널 + `action` dispatch. pause/resume/list 회귀 보존 + kill_switch/resume_kill_switch 라우팅. unknown action error. 워커 스레드 수 불변(pause→control 교체).
- REQ-044 (Data): `killswitch_exclusion(portfolio_id, trading_date, actor, created_at)` 테이블 + `005_*.sql` additive 마이그레이션 + `PostgreSQLKillSwitchStore`(exclude/clear/list_excluded(day)). 전역 sentinel.
- REQ-045 (Interface): kill_switch handler — payload `{action, scope, portfolio_id?}` → 범위별 holding 순차 `SlotService.sell(actor="kill-switch", reason="emergency_stop")` + REQ-036 idempotency + 당일 제외 표시(044) + 발동 이력 로깅 + 응답 `{liquidated:N}`.
- REQ-046 (Interface): snapshot masking 소스에 `list_excluded(today)` 추가(portfolio/전역) + day-scoped 자동 복귀 + `resume_kill_switch` handler(`clear`) — 청산된 슬롯은 비가역.
- REQ-047 (Documentation): interface-contract(제어 채널+action+payload), runtime-flows(발동/masking/복귀/resume 흐름), project-structure(control consumer + killswitch_exclusion + store + 005 sql) 정합.
## Delivery Plan
1. REQ-044: 마이그레이션 `005_create_killswitch_exclusion.sql` + `PostgreSQLKillSwitchStore` + 단위/PG 게이트 테스트(day-scoped/멱등/전역 sentinel).
2. REQ-043: `topology.CONTROL_REQ_QUEUE` + `make_control_rpc_handler`(action dispatch, pause/resume/list 위임 + kill_switch/resume_kill_switch 라우팅) + `daemon.py` pause consumer→control consumer 교체. dispatch 단위 테스트(회귀 + 라우팅 + unknown).
3. REQ-045: kill_switch handler + 범위별 holding 조회·순차 sell() 진입점(application service 또는 service 헬퍼) + 044 store exclude 호출 + 이력 로깅. 단위 테스트(전역/portfolio/idempotency/portfolio_id 누락/이력/독립성/지연).
4. REQ-046: snapshot envelope masking 확장(`killswitch_today`) + resume_kill_switch handler(clear) + day-scoped 자동복귀. 단위 테스트(masking/추가매수거부/자동복귀(날짜 mock)/재시작 영속/resume).
5. REQ-047: baseline 3문서 정합(구현 완료 상태 반영).
6. 전체 회귀 + ruff + 인프라 게이트(PG: 005 round-trip, RabbitMQ: control RPC) + e2e(발동→청산→masking→resume).
## Dependencies
- Internal:
- REQ-036/BAT-010 `SlotService.sell()` — kill_switch 청산 경로(재사용).
- REQ-032/BAT-008 `PostgreSQLPauseStore` + pause masking 패턴(`portfolio.py`) — masking 합성 참조(분리 유지).
- REQ-022/BAT-007 `make_*_rpc_handler` + consume_rpc_forever — control consumer 전환 대상.
- BAT-008 snapshot envelope builder — masking 소스 확장 지점.
- External:
- 없음 (kill_switch 청산은 기존 broker 경로 사용; 신규 외부 API 없음).
## Design Gate
- Design Required Before Implementation: yes
- Reason: (a) **제어 채널 전환**(`trex.pause.req`→`trex.control.req`) 은 인터페이스·daemon consumer 구조 변경이며 기존 pause 클라이언트 하위호환(별칭 vs 단일화) 결정이 design 에서 필요하다. (b) **day-scoped store 의 전역 sentinel 표현**과 masking 합성 계약(REQ-044↔046) 을 설계에서 고정해야 구현 drift 가 없다. (c) **kill_switch 실행 진입점 배치**(domain service vs application service)와 발동 이력/순차 처리 모델, dcy-007 safety-sell 과의 독립성 보장을 설계에서 명시해야 한다. → `design.md` 동일 턴 scaffold.
## Milestones
1. M1 — REQ-044 store + 마이그레이션 + 단위/PG 테스트 green
2. M2 — REQ-043 control 채널 + dispatch + pause/resume/list 회귀 green
3. M3 — REQ-045 kill_switch 실행 + idempotency + 이력 단위 green
4. M4 — REQ-046 masking + 자동복귀 + resume 단위 green
5. M5 — REQ-047 baseline 3문서 정합
6. M6 — 전체 회귀 + ruff + 인프라 게이트 + e2e(발동→청산→masking→resume)
## Risks
- R-1 (Medium): 제어 큐 rename(`pause.req`→`control.req`) 하위호환 — 기존 RPC pause 클라이언트 영향. 대응: design 에서 별칭 유지 vs 단일화 결정, release rollout 에 명시. CLI pause/resume 는 store 직접 호출이라 무영향.
- R-2 (Medium): kill_switch 다수 슬롯 순차 sell() 큐 부하(dcy-008 R-1). 대응: sell()=PENDING_SELL 전이(DB write 1회) 빠름, N<10 가정(NFR-1).
- R-3 (Low): day-scoped 전역 sentinel 표현 불일치(044 store ↔ 046 masking). 대응: design 에서 계약 고정 + 단위 테스트.
- R-4 (Low): kill_switch ↔ 자율 안전 매도(dcy-007) 동시 sell() 중복. 대응: REQ-036 idempotency(PENDING_SELL 중복 무시).
- R-5 (Low): `005_*.sql` 실 PG round-trip(BAT-010 R-2 선례). 대응: verification 인프라 게이트에서 확인.
## Notes
- pending_buy 슬롯 처리(OQ-3)는 본 batch 범위 밖(HOLDING 한정). 필요 시 후속 REQ/change-req.
- 제어 큐 호환 최종 결정은 release(REL) 단계 rollout 에서 확정(REQ-043 Notes).
@@ -0,0 +1,80 @@
# BAT-012 Verification
- Status: verified
- Batch: BAT-012
## Acceptance Mapping
> 구현 완료. evidence: 신규 단위 26 + PG 게이트(killswitch store 5) + e2e. 파일: test_control_dispatch.py(9), test_killswitch_execution.py(6), test_killswitch_masking_resume.py(6+1), test_killswitch_store.py(in-mem 5 + PG 5).
### REQ-043 (제어 채널 + action dispatch)
| AC | Evidence (예정) |
|----|---------|
| AC-1: pause 회귀 | control dispatch 단위(pause) |
| AC-2: resume/list 회귀 | 동일 |
| AC-3: kill_switch 라우팅 | 동일 |
| AC-4: resume_kill_switch 라우팅 | 동일 |
| AC-5: unknown action error | 동일 |
| AC-6: 워커 스레드 7 불변 | daemon 구조 검사/test_daemon |
### REQ-044 (day-scoped 제외 store + 마이그레이션)
| AC | Evidence (예정) |
|----|---------|
| AC-1: 테이블 스키마 | 마이그레이션 + PG 게이트 |
| AC-2: 마이그레이션 멱등 | 005 IF NOT EXISTS + 재적용 |
| AC-3: exclude→list_excluded | store 단위 |
| AC-4: day-scoped 자동 제외 | list_excluded(D+1) |
| AC-5: 멱등 exclude | store 단위 |
| AC-6: clear | store 단위 |
| AC-7: 전역 sentinel | store 단위 |
### REQ-045 (kill_switch 실행)
| AC | Evidence (예정) |
|----|---------|
| AC-1: 전역 전체 청산 | killswitch 실행 단위 |
| AC-2: portfolio 범위 한정 | 동일 |
| AC-3: pending_sell idempotency | 동일 |
| AC-4: portfolio_id 누락 error | 동일 |
| AC-5: 당일 제외 표시 | 044 store 연동 |
| AC-6: 이력 로깅 | 로그 캡처 |
| AC-7: dcy-007 독립 | 동일 |
| AC-8: 지연 N×100ms+5s | 측정/코드 검사 |
### REQ-046 (masking + 자동복귀 + resume)
| AC | Evidence (예정) |
|----|---------|
| AC-1: portfolio masking | masking 단위 |
| AC-2: 전역 masking | 동일 |
| AC-3: 추가매수 거부 | buyable masking |
| AC-4: 다음 거래일 자동복귀 | 날짜 mock |
| AC-5: 재시작 영속 masking | 통합/날짜 mock |
| AC-6: resume_kill_switch | resume 단위 |
| AC-7: portfolio_id 누락 error | 동일 |
| AC-8: 청산 슬롯 비가역 | 상태 불변 확인 |
### REQ-047 (baseline 정합)
| AC | Evidence (예정) |
|----|---------|
| AC-1: interface-contract 제어 채널+action | 문서 diff |
| AC-2: interface-contract kill/resume payload | 문서 diff |
| AC-3: runtime-flows 4흐름 | 문서 diff |
| AC-4: project-structure control consumer + store/sql | 문서 diff |
## Evidence
- Test:
- 신규 단위 26 + PG 게이트 killswitch store 5 green. (control dispatch 9 / 실행 6 / masking·resume 7 / store in-mem 5 / store PG 5)
- 단위 전체 회귀: `262 passed / 28 skipped`(`::test_sandbox_token_and_submit` deselect).
- 인프라 게이트(PG trex_core_test + RabbitMQ): `290 passed`(마이그레이션 001~005 적용 + killswitch PG round-trip 포함).
- `ruff check trex_core tests` → All passed.
- Manual / e2e:
- paper 데몬 control RPC e2e: buy 2(pf-A/pf-B) → `{action:kill_switch, scope:global}` → `{status:ok, liquidated:2}` → 양쪽 `holding→pending_sell(emergency_stop)→closed`, `killswitch_exclusion=[('*',2026-06-07)]` → `{action:resume_kill_switch, scope:global}` → exclusion 0. PASS.
- Logs:
- `262 passed, 28 skipped` / `290 passed ... ~13s` / e2e `RESULT: PASS`.
## Carried-Over / e2e
- BAT-010 R-2 선례(실 PG 마이그레이션 round-trip): `005` 인프라 게이트 290 passed 로 확인.
- (이월, 비차단) 실 KIS LIVE kill_switch 발동 → 실매도 제출 → 상황별 에러(주말/휴장) 관찰 — safety-sell LIVE e2e 선례와 동일 접근, 차기 거래일 운영 실증. paper/mock + 290 passed 로 release gate 충족.
## Result
- Ready for Release: true
- Blocking Issues:
- 없음. 제어 큐 rename 하위호환(R-1)은 release rollout 결정 항목(REQ-043 Notes), 실 KIS 라이브 발동은 운영 실증 이월 — 둘 다 blocker 아님.
+1
View File
@@ -19,3 +19,4 @@
| BAT-009 | batch-lite | release-candidate | REQ-010 (CHG-20260606-01 / AC-5) | dcy-002 | [bat-009](./bat-009_20260606_paper-broker-env-tuning/) |
| BAT-010 | standard | release-candidate | REQ-034, REQ-035, REQ-036, REQ-041 | dcy-007 | [bat-010](./bat-010_20260607_safety-sell-foundation/) |
| BAT-011 | standard | release-candidate | REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 | dcy-007 | [bat-011](./bat-011_20260607_safety-sell-triggers/) |
| BAT-012 | standard | release-candidate | REQ-043, REQ-044, REQ-045, REQ-046, REQ-047 | dcy-008 | [bat-012](./bat-012_20260607_kill-switch-control/) |