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
@@ -37,6 +37,47 @@
## Change Log
### CHG-20260615-02
- Date: 2026-06-15
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented, BAT-042). 신규 AC-6(strategy_state 맵+occupancy_seq 행 필드) 구현·검증 완료 — `slot_to_dict` occupancy_seq + 핸들러 strategy_state 조립, `tests/application/test_slots_handler_strategy_state.py`(row 단언). 기존 AC-1~5 불변. 479 passed, ruff clean. Implemented 복귀.
### CHG-20260615-01
- Date: 2026-06-15
- Author: Justin Ko
#### Change Summary
- (change-req, dcy-028/issue #26 C-1) 슬롯 행 스키마에 두 additive 필드를 추가한다: **`strategy_state`**(`{<strategy_id>: {version,data,updated_at}}` opaque 맵 — 별도 `slot_strategy_state` 테이블(REQ-079)에서 조립, 미보유 시 omit/`{}`)과 **`occupancy_seq`**(int — 슬롯 행 컬럼, REQ-081 점유 가드). 기존 필드(슬롯/자산/워치독/overlay 정본)·divergence·overlay 동작은 불변. AC-5 의 "strategy 계열 필드 없음"은 strategy_* **reasoning** 필드(strategy_id/sell_strategy_id/params/active)를 가리키며, `strategy_state`(opaque 상태 맵)는 그와 구분되는 신규 additive 필드다.
#### Intent
- 전략 opaque 상태와 점유 식별자를 슬롯 행에 노출해 read piggyback(C-1)과 write CAS 점유 가드(REQ-080)를 지원.
#### Acceptance Criteria Delta
- Added:
- AC-6(신규): 행에 `strategy_state` 맵(보유 시)과 `occupancy_seq`(int)가 포함된다. `strategy_state` 는 별도 테이블 조립, `occupancy_seq` 는 슬롯 행 컬럼. strategy_* reasoning 필드 부재 유지(AC-5 정합).
- Updated:
- AC-5: 제외되는 "strategy 계열"을 strategy_* reasoning 필드로 한정하고 `strategy_state`/`occupancy_seq` additive 필드는 허용함을 명확화.
- Removed: 없음
#### Impacted Area Delta
- Docs: docs/interface-contract.md(strategy_state·occupancy_seq 행 필드 + 정본 라벨 — REQ-082), docs/data-model.md
- Modules: trex_core/application/portfolio.py(slot_to_dict — strategy_state 맵·occupancy_seq 병합)
- Tests: tests/application/(행 strategy_state 맵·occupancy_seq 단언)
#### Delivery Trace Delta
- Batches: BAT-035(dcy-025) 불변 — 신규 AC-6 은 dcy-028 batch 에서 구현·검증.
- Releases: REL-006(REQ-074 released) — 기존 계약 영향 없음(additive). 신규 필드는 dcy-028 batch.
#### Revalidation Impact
- Reverification Needed: yes (신규 AC-6 만)
- Existing Implementation Invalidated: no (기존 행 필드/divergence/overlay 보존)
- Status Recommendation: revert-to-approved (Implemented → Approved — 신규 additive AC 구현·검증 대기, 선례 REQ-002/006 CHG-20260612)
- Follow-up Action: confirm-req → dcy-028 batch 에서 REQ-073/079/081 과 함께 구현·검증 → confirm-req-implemented
#### Validation Plan
- slot_to_dict 테스트: 행에 strategy_state 맵(보유/미보유)·occupancy_seq 포함, 기존 필드/divergence/overlay 회귀.
### CHG-20260611-01
- Date: 2026-06-11
- Author: Justin Ko
@@ -0,0 +1,86 @@
# req-079: Slot strategy-state 저장소 — `slot_strategy_state` 테이블 + StrategyStateStore 포트 + occupancy_seq·version CAS
- Status: Implemented
- Type: Data
- Priority: High
- Owner: project maintainers
## Intent
- 전략 데몬(trex-tsl 등)이 슬롯 점유 동안 누적하는 opaque 상태(예: TSL peak)를 슬롯 수명에 묶어 server-side 영속화할 저장소를 신설한다. trex-core 는 값을 해석하지 않고(opaque) 저장/조회/CAS 만 책임진다. 슬롯이 source of truth 이자 GC 가 되어 데몬은 cleanup 책임이 없다. (dcy-028 FR-2/OQ-1/OQ-4, issue #26 C-2)
## Requirement
- 별도 테이블 `slot_strategy_state` 를 신설한다(슬롯 행 JSONB 미사용 — dcy-028 OQ-4): 복합 PK `(slot_id, strategy_id)`, 컬럼 `version int`, `data text`, `updated_at timestamptz`, 그리고 점유 식별 `occupancy_seq int`(REQ-081 가드 참조). strategy 당 1행.
- `StrategyStateStore` 포트(Protocol)를 정의하고 PG 구현과 in-memory 구현(복합키 dict)을 함께 제공한다(paper/테스트 무중단).
- CAS upsert 의미:
1. occupancy_seq 일치 + version 일치 → commit, version+1 반환.
2. occupancy 불일치 → `occupancy_changed` (저장 안 함). version 불일치 → `version_conflict` (저장 안 함). 슬롯 inactive/미존재 → `slot_inactive` (저장 안 함).
3. 최초(엔트리 없음 + expected_version=0 + occupancy 일치) → version=1 생성.
4. 동일 입력(같은 expected_version) 재적용은 부작용 없음(멱등).
- `data` 는 opaque UTF-8 문자열로 저장한다. core 검증은 (1) 유효 UTF-8 + (2) byte 크기 상한뿐 — JSON/base64 를 강제하지 않는다. 상한은 2단(기본 1~2KB / hard ceiling 4KB byte). 초과 시 `state_too_large` 로 저장 거부(REQ-080 RPC 가 reason 운반).
- 조회 API 는 slot_id 집합으로 batched 조회를 지원해 C-1 read(REQ-073/074) 가 추가 왕복 없이 행별 맵으로 조립할 수 있어야 한다.
## Acceptance Criteria
1. Given 비어 있는 `(slot_id, strategy_id)`, When expected_version=0 + 일치 occupancy_seq 로 set, Then version=1 로 생성되고 같은 키 조회 시 data·version=1 이 반환된다.
2. Given version=N 인 엔트리, When expected_version=N + 일치 occupancy_seq 로 set, Then commit + version=N+1. When expected_version≠N 으로 set, Then `version_conflict` 로 저장되지 않고 기존 값이 유지된다.
3. Given 슬롯의 현재 occupancy_seq=k, When expected_occupancy_seq≠k 로 set, Then `occupancy_changed` 로 저장되지 않는다. Given inactive/미존재 슬롯, When set, Then `slot_inactive` 로 저장되지 않는다.
4. Given 크기 상한 초과 `data`, When set, Then `state_too_large` 로 저장 거부된다. Given 유효 UTF-8 + 상한 이내, Then core 가 내용 해석 없이 그대로 저장한다.
5. Given 여러 slot_id, When batched 조회, Then 각 slot_id 별 `{strategy_id: {version,data,updated_at}}` 맵으로 그룹되어 반환된다(C-1 조립 입력).
6. Given PG 구현과 in-memory 구현, When 동일 시나리오 테스트, Then 두 구현이 동일한 CAS 의미를 보인다.
## Impacted Area
- Docs: docs/data-model.md(`slot_strategy_state` 스키마), docs/project-structure.md(저장소 구조 — REQ-082)
- Modules: trex_core/adapters/persistence/postgresql.py(PG StrategyStateStore + 마이그레이션 SQL), trex_core/domain/slot/in_memory_repository.py 또는 신규 모듈(in-memory), 신규 포트 정의(예: trex_core/application/ports.py 또는 strategy_state 모듈)
- Tests: tests/adapters/persistence/(PG CAS 게이트), tests/(in-memory CAS 6 케이스 — commit/version_conflict/occupancy_changed/slot_inactive/state_too_large/first-create + 멱등)
## Notes
- occupancy_seq 컬럼은 REQ-081(슬롯 점유 가드)과 짝을 이룬다 — 본 REQ 는 저장·CAS, REQ-081 은 슬롯 측 occupancy_seq 부여·bump·close 삭제를 소유한다.
- write RPC 계약(요청/응답 shape, reject reason 운반)은 REQ-080 이 소유한다. 본 REQ 는 저장 의미.
- 단일 writer 전제(해당 slot·strategy 데몬 1개). CAS 는 재시도/중복·점유전환 안전용이지 다중 writer 동시성 제어가 아니다.
- 출처: dcy-028 FR-2, OQ-1(인코딩/상한), OQ-4(별도 테이블+맵+slot_id 키), R-2/R-3.
## Change Log
### CHG-20260615-03
- Date: 2026-06-15
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-042(release-candidate) verification — `slot_strategy_state` 테이블(마이그 015) + `StrategyStateStore`/`InMemoryStrategyStateStore`/`PgStrategyStateStore` + occupancy_seq·version CAS + opaque UTF-8·size cap. AC-1~6 evidence: `tests/application/test_strategy_state_store.py`(CAS 6 케이스+ABA+batched+delete) + `test_postgresql_repository.py::test_strategy_state_store_pg`(PG 게이트). 479 passed, ruff clean. Blocking Issues 없음.
### CHG-20260615-02
- Date: 2026-06-15
- Author: Justin Ko
- Type: status
- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC(6개) 충족, source dcy-028(confirmed, Open OQ 0건, OQ-1/OQ-4 정합) 일치. blocker 없음. REQ-080/081/082 + REQ-073/074 CHG 와 동일 batch 편성 예정.
### CHG-20260615-01
- Date: 2026-06-15
- Author: Justin Ko
#### Change Summary
- dcy-028 FR-2/OQ-1/OQ-4 로부터 최초 생성(Proposed). slot_strategy_state 저장소 + StrategyStateStore 포트 + occupancy_seq·version CAS + opaque UTF-8 data + 2단 size cap.
#### Intent
- 전략 opaque 상태를 슬롯 수명에 묶어 server-side 영속화 — 데몬 재시작/장애에도 상태 보존, orphan-cleanup 제거.
#### Acceptance Criteria Delta
- Added: first-create/version CAS/occupancy CAS/size cap/batched 조회/PG·in-memory 동등 6개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: data-model.md, project-structure.md(REQ-082)
- Modules: postgresql.py, in-memory store, 신규 포트
- Tests: PG CAS, in-memory CAS 6 케이스
#### 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 후 batch 편성(REQ-080/081/082 + REQ-073/074 CHG 와 동일 batch)
#### Validation Plan
- in-memory CAS 6 케이스 + PG 게이트 테스트로 저장 의미 검증. batched 조회는 C-1 조립 테스트(REQ-073/074)와 연계.
@@ -0,0 +1,81 @@
# req-081: 슬롯 occupancy_seq 정합성 가드 + close 시 strategy_state 삭제 불변식
- Status: Implemented
- Type: Data
- Priority: High
- Owner: project maintainers
## Intent
- 영구 슬롯이 재사용될 때(점유 전환) 옛 점유가 계산한 strategy_state 가 새 점유에 바인딩되는 ABA 누수를 구조적으로 차단하고, 슬롯 close 시 strategy_state 를 자동 삭제해 데몬 cleanup 책임을 없앤다. occupancy_seq 경량 가드(컬럼 1개)로 full 3계층 분할 없이 정합성 임계부만 선취한다. (dcy-028 FR-2b/FR-3, R-1, issue #26 occupancy race #4705990067)
## Requirement
- `Slot` 도메인에 monotonic `occupancy_seq:int` 필드를 추가하고 `EXPECTED_SLOT_FIELDS` 계약과 PG 스키마(daily_slot, 레거시 명 — 실제 영구 자산 슬롯)에 마이그레이션으로 반영한다. 기본값 0(또는 점유 없음 표시).
- 새 점유 시작 전이(`inactive → pending_buy`)에서 `occupancy_seq` 를 ++ 한다. 이 값은 한 점유 구간 동안 안정적이고 직전 점유와 달라야 한다(monotonic, 실패한 매수 롤백으로 번호가 건너뛰어도 무방).
- `occupancy_seq` 를 슬롯 행에 노출해 C-1 read(REQ-073/074)·write CAS(REQ-079/080)가 점유 식별에 사용하게 한다.
- 슬롯이 어떤 경로로든 close(`→INACTIVE`: 전략 매도 체결/safety-sell/forced-close/operator)되면 단일 전이 지점(`service.py` INACTIVE 분기, position 필드 clear 와 동일 위치)에서 해당 slot_id 의 모든 strategy_state 를 삭제한다(REQ-079 store). 행 삭제가 아니라 영구 슬롯 `→INACTIVE` 전이 훅이다(dcy-020 정합).
- 재사용 슬롯은 새 occupancy_seq + 빈 strategy_state(version 없음)로 시작한다.
## Acceptance Criteria
1. Given `Slot` 도메인, When 필드 계약 검사, Then `occupancy_seq` 가 `EXPECTED_SLOT_FIELDS` 에 포함되고 PG daily_slot 에 컬럼이 존재한다.
2. Given inactive 슬롯, When `inactive→pending_buy` 전이, Then `occupancy_seq` 가 직전보다 증가하고 해당 점유 구간(pending_buy→holding→pending_sell) 동안 동일 값으로 유지된다.
3. Given holding 슬롯의 strategy_state 존재, When 슬롯이 임의 close 경로로 `→INACTIVE`, Then 해당 slot_id 의 모든 strategy_state 가 삭제된다(전략 매도/safety-sell/forced-close 모두 커버).
4. Given close 후 재매수된 슬롯, When 조회, Then 새 occupancy_seq + 빈 strategy_state(version 없음)로 시작한다.
5. Given 점유 전환 ABA 시나리오(옛 점유 기준 occupancy_seq 로 write), When set, Then REQ-080 가 `occupancy_changed` 로 reject 해 stale 상태가 새 점유에 바인딩되지 않는다(R-1 차단).
## Impacted Area
- Docs: docs/data-model.md(occupancy_seq 필드), docs/runtime-flows.md(점유 가드·close 삭제 흐름 — REQ-082)
- Modules: trex_core/domain/slot/models.py(`occupancy_seq` 필드 + EXPECTED_SLOT_FIELDS), trex_core/domain/slot/service.py(inactive→pending_buy bump + →INACTIVE strategy_state 삭제 훅), trex_core/adapters/persistence/postgresql.py(컬럼 + 마이그레이션 SQL), trex_core/application/portfolio.py(slot_to_dict occupancy_seq 노출)
- Tests: tests/domain/slot/(occupancy_seq bump·계약 가드), tests/(각 close 경로 후 strategy_state 부재 + ABA reject S-4)
## Notes
- 저장·CAS 의미는 REQ-079, write RPC 는 REQ-080 이 소유한다 — 본 REQ 는 슬롯 측 occupancy_seq 부여·bump 와 close 삭제 훅(lifecycle)을 소유한다.
- full 3계층(별도 occupancy 테이블 + strategy_state 를 occupancy_id 키로 전환)은 dcy-028 Out of Scope — 후속 "영구 슬롯 구조 정합" Discovery. 본 REQ 는 컬럼 1개 경량 가드.
- 중장기 영구 슬롯은 점유 회전이 드물어 full 분할이 과하고, intraday(trex-tsl Mode A) 정합성은 본 가드로 충족(dcy-028 OQ-4).
- 출처: dcy-028 FR-2b(C-2b)/FR-3(C-3), R-1.
## Change Log
### CHG-20260615-03
- Date: 2026-06-15
- Author: Justin Ko
- Type: status
- Summary: Approved → Implemented (confirm-req-implemented). 근거: BAT-042 verification — `Slot.occupancy_seq`(+EXPECTED_SLOT_FIELDS, 마이그 014) + `inactive→pending_buy` bump + `→INACTIVE` strategy_state delete 훅(service). AC-1~5 evidence: `tests/domain/slot/test_occupancy_seq_and_close_delete.py`(bump/안정성/모든 close 경로 삭제/재사용 빈 시작) + `test_strategy_state_store.py::TestAbaGuard`(S-4 ABA reject) + `test_contract`(계약 가드). 479 passed, ruff clean. Blocking Issues 없음.
### CHG-20260615-02
- Date: 2026-06-15
- Author: Justin Ko
- Type: status
- Summary: Proposed → Approved (confirm-req). 필수 필드·구체 AC(5개) 충족, source dcy-028(FR-2b/FR-3, R-1 완화) 정합. occupancy_seq 경량 가드로 점유 ABA 구조적 차단. blocker 없음.
### CHG-20260615-01
- Date: 2026-06-15
- Author: Justin Ko
#### Change Summary
- dcy-028 FR-2b/FR-3 로부터 최초 생성(Proposed). occupancy_seq 슬롯 필드+bump+노출 + close 시 strategy_state 삭제 불변식.
#### Intent
- 점유 전환 ABA 누수(R-1) 구조적 차단 + close cleanup 자동화.
#### Acceptance Criteria Delta
- Added: 필드 계약/bump 안정성/close 삭제 모든 경로/재사용 빈 시작/ABA reject 5개 기준
- Updated: 없음
- Removed: 없음
#### Impacted Area Delta
- Docs: data-model.md, runtime-flows.md(REQ-082)
- Modules: models.py, service.py, postgresql.py, portfolio.py
- Tests: occupancy_seq bump·계약, close 경로 삭제, ABA reject
#### 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 후 batch 편성(REQ-079/080/082 + REQ-073/074 CHG 동일 batch)
#### Validation Plan
- occupancy_seq bump·계약 가드 테스트 + 각 close 경로 후 strategy_state 부재 테스트 + ABA reject 테스트(S-4)로 검증.