feat(slot,intent): slot strategy-state 저장소 (dcy-028) + sell retryable 플래그 (issue #27)

BAT-042 (dcy-028 / issue #26 C-1/C-2/C-2b/C-3, REQ-079~082 + REQ-073/074 CHG):
- slot_strategy_state 별도 테이블 + StrategyStateStore(in-memory+PG) + occupancy_seq·version CAS + opaque UTF-8 data + 2단 size cap (REQ-079)
- trex.slots.strategy_state.set write RPC — reject reason 집합, 멱등 (REQ-080)
- Slot.occupancy_seq 필드 + inactive→pending_buy bump + →INACTIVE close 삭제 훅 → 점유 전환 ABA 구조적 차단 (REQ-081)
- slots.req 응답 행에 strategy_state 맵(batched 조립)·occupancy_seq additive (REQ-073 AC-7/REQ-074 AC-6)
- 마이그레이션 014(occupancy_seq)/015(slot_strategy_state), baseline 문서 정합 (REQ-082)

BAT-043 (issue #27 / REQ-007 CHG):
- rejected SellIntent 응답에 additive retryable: bool (core 소유 reason→retryable 매핑, 미인식 기본 true, bad_request* false) — 손절매 재시도 안전 경로 decoupling

검증: 487 passed, 47 skipped (신규 41: store CAS 6+ABA, occupancy bump/close 4경로, set RPC 9, slots read 5, retryable 8, PG 게이트), ruff clean.
REQ-079·080·081·082·073·074·007 → Implemented. issue #27 close. PG 경로 실 DB 검증은 release 위임.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 19:27:57 +09:00
co-authored by Claude Opus 4.8
parent bfadd0d5c8
commit e0d4e71304
40 changed files with 1941 additions and 14 deletions
@@ -0,0 +1,49 @@
# BAT-042 Design
- Status: confirmed
- Batch: BAT-042
## Architecture Summary
- strategy-state 는 슬롯 본체와 분리된 저장 관심사다. 별도 `slot_strategy_state` 테이블 + `StrategyStateStore` 포트로 격리하고, 슬롯 행에는 정합성 가드용 `occupancy_seq` 컬럼 1개만 추가한다(full 3계층 분할은 후속). read 는 기존 `trex.slots.req` 에 piggyback(추가 왕복 0), write 는 신규 RPC `trex.slots.strategy_state.set` 로 분리(evaluate 순수성·재시도 격리). 점유 전환 ABA 는 write CAS 의 occupancy_seq 검증으로 구조적으로 차단한다. trex-core 는 `data` 를 해석하지 않는다(opaque UTF-8 + size cap 만 검증).
## Changed Areas
- Docs: docs/runtime-flows.md, docs/project-structure.md, docs/interface-contract.md, docs/data-model.md (REQ-082)
- Modules:
- `trex_core/domain/slot/models.py` — `Slot.occupancy_seq: int = 0` + EXPECTED_SLOT_FIELDS
- `trex_core/domain/slot/service.py` — `inactive→pending_buy` 시 occupancy_seq++ / `→INACTIVE` 시 StrategyStateStore.delete(slot_id) 훅
- 신규 `trex_core/application/strategy_state.py`(또는 ports.py 확장) — `StrategyStateStore` Protocol + `InMemoryStrategyStateStore`
- `trex_core/adapters/persistence/postgresql.py` — `PgStrategyStateStore` + 마이그레이션 SQL(occupancy_seq 컬럼, slot_strategy_state 테이블)
- `trex_core/adapters/messaging/topology.py` — `STRATEGY_STATE_SET_QUEUE` + per-instance queue helper
- `trex_core/application/messaging_handlers.py` — set 핸들러(store 위임) + slots 핸들러 strategy_state batched 조립
- `trex_core/application/portfolio.py` — slot_to_dict 에 occupancy_seq + strategy_state 맵
- `trex_core/runtime/daemon.py` — store 생성/와이어링 + set 핸들러 consumer
- Interfaces: `trex.slots.strategy_state.set` RPC(신규), `trex.slots.req` 응답 행 additive 확장
## Key Decisions
1. **저장 = 별도 테이블 + slot_id 키 + 맵**(dcy-028 OQ-4): `slot_strategy_state(slot_id, strategy_id, occupancy_seq, version, data, updated_at)` 복합 PK `(slot_id, strategy_id)`. JSONB-on-row 기각. occupancy_seq 는 행에 함께 저장(write 시 슬롯 현재값과 비교용 스냅샷이 아니라, store CAS 가 슬롯 현재 occupancy_seq 를 인자로 받아 비교).
2. **CAS 의미**(REQ-079/080): `set(slot_id, strategy_id, expected_occupancy_seq, expected_version, data, *, current_occupancy_seq, slot_active)` →
- slot_active=False → `slot_inactive`
- current_occupancy_seq ≠ expected_occupancy_seq → `occupancy_changed`
- size 초과 → `state_too_large`
- stored version ≠ expected_version → `version_conflict`
- 일치(없으면 expected_version=0) → commit, version+1.
3. **occupancy_seq bump 지점**(REQ-081): 슬롯이 `inactive→pending_buy` 로 전이할 때 ++. 한 점유 구간(pending_buy→holding→pending_sell) 동안 불변. 실패 매수 롤백(pending_buy→inactive)으로 번호가 건너뛰어도 monotonic 보장이면 무방.
4. **close 삭제 훅**(REQ-081/C-3): `service.py` 의 `→INACTIVE` 단일 분기(position 필드 clear 위치)에서 store.delete(slot_id). 모든 close 경로(전략 매도/safety-sell/forced-close/operator) 자동 커버. 행 삭제 아님.
5. **read 비대칭**(OQ-2): read 는 slots 핸들러가 반환 slot_id 들로 store.get_many(slot_ids) batched 조회 → group by → 행별 맵 조립. write 만 별도 RPC. occupancy_seq 는 슬롯 행 컬럼이라 slot_to_dict 에서 직접 노출.
6. **opaque**(OQ-1): data 는 UTF-8 str, core 무해석. size cap byte 기준 — 기본 상한 상수(예: `_STATE_CAP_BYTES`), 환경변수 override 여지(REQ 단계 확정). 기본 2KB, ceiling 4KB → 단일 cap 4KB 로 구현하고 1~2KB 권장은 문서 가이드(운영). 구현 cap = 4KB(byte) hard.
## Edge Cases
- first-create 시 expected_version=0 + occupancy_seq 일치 → version=1. occupancy 불일치면 first-create 도 `occupancy_changed`.
- 동일 correlation_id 재전송 → 동일 expected_version 이라 commit 후 재요청은 version 이 이미 +1 → `version_conflict`(멱등적으로 무부작용). 또는 store가 동일 (occ,ver,data) 재적용을 무시.
- close 직후 inactive 슬롯에 write → `slot_inactive`(저장 안 함, 부활 차단).
- occupancy_seq 오버플로우: int(8 byte PG bigint 권장) — 실질 무한.
- 빈 strategy_state 슬롯 read → 행에 strategy_state omit/`{}`.
- in-memory store: dict[(slot_id, strategy_id)] = (occupancy_seq, version, data). 슬롯 current occupancy_seq 는 slot repo 에서 조회.
## Architecture Impact
- both (project-structure + runtime-flows)
## Reference Doc Update Plan
- project-structure.md: `slot_strategy_state` 테이블·`StrategyStateStore`(포트+PG/in-memory) 위치, slot `occupancy_seq` 컬럼 추가 반영 (REQ-082).
- runtime-flows.md: strategy-state read piggyback / write RPC / occupancy_seq·version CAS / 점유 가드(occupancy_changed) / close 삭제 흐름 신설 (REQ-082).
- interface-contract.md: `trex.slots.strategy_state.set` 계약(요청/응답/reject reason) + slots.req 행의 strategy_state·occupancy_seq 필드 (REQ-082).
@@ -0,0 +1,39 @@
# BAT-042 Implementation
- Status: completed
- Batch: BAT-042
## Plan Summary
- M1~M6 순서로 구현. in-memory store 우선 + PG 구현 병행, 각 단계 테스트 동반. paper/in-memory 로 무중단(live DB 비대상).
## Changed Files
- `trex_core/domain/slot/models.py` — `Slot.occupancy_seq: int = 0` + `EXPECTED_SLOT_FIELDS`에 `occupancy_seq` (M1)
- `trex_core/domain/slot/service.py` — `inactive→pending_buy` 시 occupancy_seq++ / `→INACTIVE` 시 `strategy_state_store.delete_all(slot_id)` 훅 + 생성자 `strategy_state_store` 주입 (M1/M3)
- `trex_core/application/strategy_state.py` (신규) — `StrategyStateStore` Protocol, `StrategyStateEntry`/`SetResult`, `_cas` 공용 의사결정, `InMemoryStrategyStateStore`, reject reason 상수, size cap (M2)
- `trex_core/adapters/persistence/postgresql.py` — `_SLOT_COLUMNS`/update/`_row_to_slot`에 occupancy_seq + `PgStrategyStateStore`(SELECT FOR UPDATE CAS, batched get, delete) (M1/M2)
- `trex_core/adapters/persistence/sql/014_add_slot_occupancy_seq.sql`, `015_create_slot_strategy_state.sql` (신규 마이그레이션) (M1/M2)
- `trex_core/application/portfolio.py` — `slot_to_dict`에 `occupancy_seq` 노출 (M5)
- `trex_core/application/messaging_handlers.py` — slots 핸들러 strategy_state batched 조립(`strategy_state_store` 파라미터) + `make_strategy_state_set_handler`(write RPC, reject reason) (M4/M5)
- `trex_core/adapters/messaging/topology.py` — `STRATEGY_STATE_SET_QUEUE` + `strategy_state_set_queue()` (M4)
- `trex_core/adapters/messaging/rabbitmq.py` — declare_topology 에 strategy_state_set 큐 선언/바인딩 (M4)
- `trex_core/runtime/daemon.py` — `PgStrategyStateStore` 생성, SlotService 주입, slots 핸들러 wiring, `_strategy_state_set_loop` consumer 스레드 (M3/M4/M5)
- `trex_core/runtime/reset.py` — `slot_strategy_state` truncate 목록 추가
- 테스트: `tests/application/test_strategy_state_store.py`, `tests/application/test_strategy_state_set_handler.py`, `tests/application/test_slots_handler_strategy_state.py`, `tests/domain/slot/test_occupancy_seq_and_close_delete.py`, `tests/adapters/persistence/test_postgresql_repository.py`(PG 게이트 추가)
## Execution Log
1. M1: Slot.occupancy_seq 필드 + EXPECTED_SLOT_FIELDS + service bump(inactive→pending_buy) + PG 컬럼/마이그레이션 014.
2. M2: strategy_state.py(포트+in-memory+_cas+cap) + PgStrategyStateStore + 마이그레이션 015.
3. M3: service `→INACTIVE` delete_all 훅 + 생성자 주입.
4. M4: topology 큐 + set 핸들러(store 위임) + rabbitmq declare + daemon consumer 스레드.
5. M5: slot_to_dict occupancy_seq + slots 핸들러 strategy_state batched 조립(occupancy 필터).
6. M6: baseline 문서(REQ-082)는 verification 직후 정합 — 코드 확정 후 별도 반영(별도 커밋).
## Validation
- `TREX_BROKER=paper uv run pytest -q` → **479 passed, 47 skipped**(PG 게이트는 live DSN 없어 skip). 신규 33 테스트 포함(store CAS 6 케이스+ABA, occupancy_seq bump/close 삭제 모든 경로, set RPC reason 집합, slots read 조립+occupancy_seq+stale 필터).
- `uv run ruff check trex_core/ tests/` → All checks passed.
- 회귀: 기존 slots 핸들러/슬롯 서비스/계약(EXPECTED_SLOT_FIELDS) 테스트 전부 green.
## Remaining Risks
- M6 baseline 문서(REQ-082)는 본 구현 직후 반영 예정 — 현재 미반영(verification 에서 추적).
- PG 경로(PgStrategyStateStore, 마이그레이션 014/015)는 live DSN 테스트가 CI 에서 skip — 게이트 테스트는 작성됨, 실 DB 검증은 release 운영 단계.
- 단일 writer 전제(dcy-028) — 다중 writer 동시성은 범위 밖.
@@ -0,0 +1,35 @@
# BAT-042: Slot strategy-state 저장소 — opaque state store + occupancy_seq 가드 (dcy-028)
- Status: release-candidate
- Profile: standard
- Source Discovery: dcy-028 (issue #26 C-1/C-2/C-2b/C-3)
- Included REQ: REQ-079, REQ-080, REQ-081, REQ-082, REQ-073 (CHG-20260615-01), REQ-074 (CHG-20260615-01)
- Excluded REQ: 없음
## Summary
- trex-tsl 등 전략 데몬이 슬롯 점유 동안 누적하는 opaque 상태(예: TSL peak)를 슬롯 수명에 묶어 server-side 영속화하는 저장소를 완성한다. 별도 `slot_strategy_state` 테이블 + `StrategyStateStore`(REQ-079), write RPC `trex.slots.strategy_state.set`(REQ-080), 점유 전환 ABA를 막는 occupancy_seq 가드 + close 삭제 불변식(REQ-081), read piggyback(REQ-073/074 CHG — slots.req 행에 strategy_state 맵·occupancy_seq additive), baseline 문서 정합(REQ-082).
## Scope
- In Scope:
- REQ-079: `slot_strategy_state` 테이블(복합 PK (slot_id, strategy_id)) + `StrategyStateStore` 포트 + PG/in-memory 구현 + occupancy_seq·version CAS + opaque UTF-8 data + 2단 size cap.
- REQ-080: `trex.slots.strategy_state.set` write RPC — 요청/응답 shape, CAS, reject reason 집합(occupancy_changed/version_conflict/slot_inactive/state_too_large/bad_request), 멱등.
- REQ-081: `Slot.occupancy_seq` 필드 + EXPECTED_SLOT_FIELDS + 마이그레이션 + `inactive→pending_buy` bump + `→INACTIVE` strategy_state 삭제 훅.
- REQ-073/074 CHG: slots.req 응답 행에 strategy_state 맵(batched 조립)·occupancy_seq additive 노출.
- REQ-082: runtime-flows/project-structure/interface-contract baseline 정합.
- Out of Scope:
- C-4(SellIntent/watchdog) 신규 구현 — 기존 계약 확인(issue #26 답변 완료).
- full 3계층 슬롯 구조 분할(occupancy_id 키 전환) + daily_slot 네이밍 + daily_symbol_entries 잔재 제거 — 후속 "영구 슬롯 구조 정합" Discovery.
- 다중 writer 동시성 제어(단일 writer 전제), strategy_state 장기 보존/감사.
- optional batch set RPC 변형.
## 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-028). 신규 테이블·도메인 필드·RPC·마이그레이션 + 기존 read 표면 확장(REQ-073/074 CHG) + baseline 정합 복합 → standard profile.
- REQ-073/074 는 CHG 로 Implemented→Approved 되돌림(additive AC, 기존 동작 보존). 본 batch 에서 신규 AC(073-AC7/074-AC6) 구현·검증 후 confirm-req-implemented 로 Implemented 복귀.
- NFR-1(opaque)은 FR 흡수, NFR-2(size cap)는 REQ-079, NFR-3(수명)은 REQ-081 로 흡수(dcy-028).
- 보안: 테스트는 TREX_BROKER=paper + in-memory store 로 무중단. live DB 비대상.
@@ -0,0 +1,48 @@
# BAT-042 Planning
- Status: confirmed
- Batch: BAT-042
- Profile: standard
## Included REQ
- REQ-079 (Data) — slot_strategy_state 저장소 + StrategyStateStore + CAS
- REQ-080 (Interface) — trex.slots.strategy_state.set write RPC
- REQ-081 (Data) — occupancy_seq 가드 + close 삭제 불변식
- REQ-082 (Documentation) — baseline 정합
- REQ-073 (Interface, CHG-20260615-01) — slots.req 응답 strategy_state·occupancy_seq additive
- REQ-074 (Data, CHG-20260615-01) — 슬롯 행 스키마 strategy_state·occupancy_seq additive
## Delivery Plan
- 구현 순서(의존 기준):
1. **M1 — 슬롯 occupancy_seq (REQ-081 일부)**: `Slot.occupancy_seq` 필드 + EXPECTED_SLOT_FIELDS + PG 마이그레이션 SQL + `inactive→pending_buy` bump(service.py). 가장 하부 — 이후 CAS·노출의 선행.
2. **M2 — strategy-state 저장소 (REQ-079)**: `StrategyStateStore` 포트 + in-memory 구현 + PG 구현 + 마이그레이션(`slot_strategy_state` 테이블) + occupancy_seq·version CAS + opaque UTF-8 + size cap.
3. **M3 — close 삭제 훅 (REQ-081 나머지)**: `→INACTIVE` 전이 시 store.delete(slot_id) 훅(service.py 단일 지점) + 데몬 와이어링.
4. **M4 — write RPC (REQ-080)**: topology 큐 + set 핸들러(store 위임) + daemon 와이어링 + reject reason.
5. **M5 — read piggyback (REQ-073/074 CHG)**: slot_to_dict 에 occupancy_seq + slots 핸들러 strategy_state batched 조립.
6. **M6 — baseline 문서 (REQ-082)**: runtime-flows/project-structure/interface-contract 정합(코드 확정 후).
- 각 M 마다 테스트 추가(TDD 우선 권장). in-memory store 로 paper/테스트 무중단.
## Dependencies
- Internal:
- 슬롯 도메인(`models.py`/`service.py`/`state.py`), 슬롯 영속화(`postgresql.py`/in_memory_repository), 메시징(`topology.py`/`messaging_handlers.py`/`daemon.py`), `portfolio.py`(slot_to_dict).
- dcy-020 영구 슬롯 모델(→INACTIVE 전이), REQ-073/074 기존 read 표면.
- External:
- 없음(전략 데몬 trex-tsl 은 별도 저장소 소비자 — 본 batch 는 core 계약만).
## Design Gate
- Design Required Before Implementation: yes
- Reason: 신규 테이블 스키마 + 신규 도메인 필드(occupancy_seq) + 신규 RPC + 기존 read 표면(REQ-073/074) 확장 + CAS 동시성 의미가 얽혀 있어, 구현 전 인터페이스·스키마·전이 훅 경계를 design.md 로 고정해야 drift/재작업을 줄인다. runtime-flows baseline 영향도 있음.
## Milestones
1. M1 occupancy_seq 필드+bump+마이그레이션
2. M2 StrategyStateStore + CAS (in-memory+PG)
3. M3 close 삭제 훅
4. M4 write RPC
5. M5 read piggyback(073/074)
6. M6 baseline 문서
## Risks
- 점유 전환 ABA(R-1, dcy-028): occupancy_seq CAS 로 차단 — M1+M2 에서 ABA 테스트(S-4) 필수.
- 기존 read 표면 회귀(REQ-073/074): additive 만 — 기존 키 집합/filter/spy/divergence 회귀 테스트로 보강.
- PG 마이그레이션 2건(occupancy_seq 컬럼 + slot_strategy_state 테이블): idempotent(IF NOT EXISTS) + in-memory 병행으로 paper 무중단. live DB 비대상(테스트는 trex_core_test DSN).
- EXPECTED_SLOT_FIELDS 계약 가드(test_contract): occupancy_seq 추가 시 계약 테스트 동반 갱신 필요.
@@ -0,0 +1,32 @@
# BAT-042 Verification
- Status: confirmed
- Batch: BAT-042
## Acceptance Mapping
- REQ-079 (저장소+CAS):
- AC-1 first-create / AC-2 version commit·conflict / AC-3 occupancy·inactive reject / AC-4 size cap / AC-5 batched 조회 그룹화 / AC-6 PG·in-memory 동등 → `tests/application/test_strategy_state_store.py`(TestCas, TestBatchedRead, TestDelete) + `test_postgresql_repository.py::test_strategy_state_store_pg`(PG 게이트).
- REQ-080 (write RPC):
- AC-1 큐(declare_topology, rabbitmq) / AC-2 성공 version+1 / AC-3 occupancy·version reject / AC-4 inactive·size·bad_request / AC-5 멱등 / AC-6 reason 집합 → `tests/application/test_strategy_state_set_handler.py`(8 케이스 + reason 집합).
- REQ-081 (occupancy_seq + close 삭제):
- AC-1 계약 가드(EXPECTED_SLOT_FIELDS, `test_contract` green) / AC-2 bump·안정성 / AC-3 모든 close 경로 삭제 / AC-4 재사용 빈 시작 / AC-5 ABA reject → `tests/domain/slot/test_occupancy_seq_and_close_delete.py` + store ABA(`test_strategy_state_store.py::TestAbaGuard`).
- REQ-082 (baseline):
- AC-1 runtime-flows / AC-2 project-structure / AC-3 interface-contract / AC-4 경계 명시 → `docs/runtime-flows.md`(slot strategy-state 흐름 + Change Log), `docs/project-structure.md`(strategy_state.py 책임), `docs/interface-contract.md`(set RPC 계약 + slots.req additive + Change Log).
- REQ-073 (AC-7, CHG):
- 행에 strategy_state 맵(보유/미보유)·occupancy_seq, batched 조립, strategy_* reasoning 부재 → `tests/application/test_slots_handler_strategy_state.py` + 기존 `test_slots_handler.py` 회귀 green.
- REQ-074 (AC-6, CHG):
- 행 strategy_state 맵·occupancy_seq 필드 노출 → `tests/application/test_slots_handler_strategy_state.py`(row 단언).
## Evidence
- Test:
- `TREX_BROKER=paper uv run pytest -q` → **479 passed, 47 skipped**(PG 게이트 live DSN 없어 skip). 신규 33 테스트(store CAS 6+ABA, occupancy_seq bump/close 삭제 4경로, set RPC 9, slots read 조립 5).
- 성공 기준 매핑: S-1=slots read 조립 테스트, S-2=set 핸들러/스토어 CAS 케이스, S-3=close 삭제 모든 경로, S-4=ABA reject(store+handler).
- Manual:
- 없음(전부 자동 테스트로 커버). 실 PG 경로는 게이트 테스트 작성 + release 운영 검증 위임.
- Logs:
- ruff: `uv run ruff check trex_core/ tests/` → All checks passed.
## Result
- Ready for Release: true
- Blocking Issues:
- 없음. (PG 마이그레이션 014/015·PgStrategyStateStore 의 실 DB 검증은 release 운영 단계 — 게이트 테스트는 작성됨.)
@@ -0,0 +1,26 @@
# BAT-043 Implementation
- Status: completed
- Batch: BAT-043
## Plan Summary
- rejected SellIntent 응답에 additive `retryable: bool` 추가 (core 소유 매핑). 완전 additive·하위호환.
## Changed Files
- `trex_core/application/messaging_handlers.py` — `_SELL_REJECT_RETRYABLE` dict + `_sell_reject_retryable(reason)` helper(미인식 true, `bad_request*` false) + intent 응답 빌더에 `intent_type=="sell"` & `status=="rejected"` 시 `retryable` 부여(정상 거부 경로 + sell bad_request 조기반환 경로 모두).
- `docs/interface-contract.md` — intent RPC 응답 표에 rejected sell `retryable` 명시 + Change Log.
- `tests/application/test_sell_retryable_flag.py` (신규) — 매핑 7 reason + 미인식 + None + bad_request + 응답 필드(inactive/unknown_slot/bad_request → false, admitted/buy → 부재).
## Execution Log
1. `_SELL_REJECT_RETRYABLE` 매핑 + helper 추가(messaging_handlers).
2. 응답 빌더 2지점(정상 결정 / sell bad_request)에 additive 부여.
3. interface-contract 정합 + 테스트 작성.
## Validation
- `TREX_BROKER=paper uv run pytest -q` → **487 passed, 47 skipped**(BAT-042 479 + 본 batch 8). 신규 8: 매핑 3 + 응답 5.
- `uv run ruff check trex_core/ tests/` → All checks passed.
- 회귀: 기존 intent consumer/handler 테스트 green(admitted/buy 응답 retryable 부재 확인 포함).
## Remaining Risks
- reason 어휘 drift: 새 reason 추가 시 매핑 갱신 필요(미인식 기본 true 안전망 + 매핑 테스트가 현행 reason 전수 커버).
- 없음(완전 additive, 기존 계약 불변).
@@ -0,0 +1,31 @@
# BAT-043: rejected SellIntent `retryable` 플래그 (issue #27)
- Status: release-candidate
- Profile: batch-lite
- Source Discovery: 없음 (REQ-007 change-req, issue #27)
- Included REQ: REQ-007 (CHG-20260615-01)
- Excluded REQ: 없음
## Summary
- rejected SellIntent 응답(`trex.intent.req`, `intent_type:"sell"`, `status:"rejected"`)에 additive `retryable: bool` 을 추가한다. core 가 소유한 reason→retryable 매핑으로 채우고 `reason` 은 유지한다. trex-tsl 데몬이 손절매 재시도 판단을 reason 문자열 하드코딩 대신 권위 플래그로 하게 해 안전 경로 결합을 제거한다(issue #27, #26 sell-idempotency 후속).
## Scope
- In Scope:
- `trex_core/application/messaging_handlers.py` — intent 응답 빌더에 sell+rejected 시 `retryable` 부여 + core 소유 `_SELL_REJECT_RETRYABLE` 매핑(미인식 기본 true).
- `docs/interface-contract.md` — rejected sell 응답 `retryable` 필드 명시.
- 테스트: reason→retryable 매핑 케이스, 미인식 기본 true, admitted/processing/buy 부재, 기존 reason 유지.
- Out of Scope:
- reason 어휘 자체 변경(매핑만 추가).
- buy intent / admitted / processing 응답 변경.
- dcy-028(strategy-state) — 독립.
## Documents
- Planning: [planning.md](./planning.md)
- Design: none (batch-lite, additive 단일 필드 — 구조/흐름 영향 없음)
- Implementation: [implementation.md](./implementation.md)
- Verification: [verification.md](./verification.md)
## Notes
- 완전 additive·하위호환: 필드 부재(older core) → 데몬 reason-string 폴백. 기존 응답/admit/buy 불변이라 회귀 리스크 낮음.
- 안전 기본값: 미인식 reason → `retryable: true`(멈춘 손절매가 hammering 보다 위험, issue #27 답변에서 확정).
- `bad_request:*`(malformed) → false(클라이언트 결함, 같은 바이트 재시도 무의미).
@@ -0,0 +1,29 @@
# BAT-043 Planning
- Status: confirmed
- Batch: BAT-043
- Profile: batch-lite
## Included REQ
- REQ-007 (CHG-20260615-01) — rejected SellIntent 응답 additive `retryable: bool`
## Delivery Plan
1. `messaging_handlers.py` 에 core 소유 `_SELL_REJECT_RETRYABLE` 매핑 + helper(`_sell_retryable(reason)` — 미인식 기본 true, `bad_request:*`/terminal → false).
2. intent 응답 빌더: `intent_type=="sell"` 且 `status=="rejected"` 일 때만 `retryable` 부여. bad_request 조기반환 경로(sell 식별 가능 시)도 동일 적용.
3. 테스트 추가 + interface-contract 문서 정합.
## Dependencies
- Internal: `messaging_handlers.make_intent_rpc_handler`(응답 빌더), `intents.py`(reason 어휘 — _admit_sell/_dedup).
- External: trex-tsl 데몬(소비자, 별도 저장소) — 폴백 보유로 lockstep 불요.
## Design Gate
- Design Required Before Implementation: no
- Reason: 단일 additive 응답 필드 + 순수 매핑(dict). 구조/런타임 흐름/스키마 영향 없음. 계약 변화는 interface-contract 한 줄. design.md 불요(batch-lite).
## Milestones
1. 매핑 + helper + 응답 빌더
2. 테스트 + 문서
## Risks
- reason 어휘 drift: 매핑이 core 에 있어 새 reason 추가 시 함께 갱신(미인식 기본 true 가 안전망). — 매핑 테스트로 현행 reason 전수 커버.
- bad_request 경로: sell 여부 판별 가능 지점에서만 retryable=false 부여(불명확하면 미부여 — 데몬 폴백).
@@ -0,0 +1,23 @@
# BAT-043 Verification
- Status: confirmed
- Batch: BAT-043
## Acceptance Mapping
- REQ-007 (AC-7, CHG-20260615-01):
- rejected sell 응답에 additive `retryable` + core 소유 매핑 + `reason` 유지 → `tests/application/test_sell_retryable_flag.py::TestResponseField`(inactive→false, unknown_slot→false, bad_request→false).
- core 소유 매핑(7 reason) + 미인식 기본 true + bad_request false → `TestRetryableMapping`.
- 완전 additive(admitted/processing/buy·기존 필드 불변) → `test_admitted_sell_has_no_retryable`, `test_buy_rejection_has_no_retryable` + 기존 intent consumer 회귀.
## Evidence
- Test:
- `TREX_BROKER=paper uv run pytest -q` → **487 passed, 47 skipped**. 신규 8(매핑 3 + 응답 5).
- Manual:
- 없음(자동 테스트 커버).
- Logs:
- ruff: All checks passed.
## Result
- Ready for Release: true
- Blocking Issues:
- 없음.
+2
View File
@@ -49,3 +49,5 @@
| BAT-039 | standard | release-candidate | REQ-011 (CHG-20260612-01) | 없음 (issue #4) | [bat-039](./bat-039_20260612_paper-seq-redis-persistence/) |
| BAT-040 | standard | release-candidate | REQ-002 (CHG-20260612-01), REQ-006 (CHG-20260612-01), REQ-073 (CHG-20260612-02) | 없음 (issue #25) | [bat-040](./bat-040_20260612_slots-rpc-db-filter/) |
| BAT-041 | standard | release-candidate | REQ-076, REQ-077, REQ-078 | dcy-027 (issue #20 이월) | [bat-041](./bat-041_20260612_long-term-instance-profile/) |
| BAT-042 | standard | release-candidate | REQ-079, REQ-080, REQ-081, REQ-082, REQ-073 (CHG-20260615-01), REQ-074 (CHG-20260615-01) | dcy-028 (issue #26) | [bat-042](./bat-042_20260615_slot-strategy-state-store/) |
| BAT-043 | batch-lite | release-candidate | REQ-007 (CHG-20260615-01) | 없음 (issue #27) | [bat-043](./bat-043_20260615_sell-intent-retryable-flag/) |