feat(slot): slot entry_count + closed-slot reallocation (dcy-019, BAT-025) + REL-005
dcy-019/issue #18: slot 자원을 진입 횟수 기준으로 고르게 재사용하고 idle 소진 시 closed 슬롯을 직접 재할당한다. 거버넌스는 전부 change-req(신규 REQ 없음). - 도메인(REQ-002): Slot.entry_count(기본 0) + EXPECTED_SLOT_FIELDS, 상태 머신 closed→pending_buy 신규 전이(base 8→9, ALLOWED_TRANSITIONS 14→15), _apply_transition_fields closed→pending_buy clean-slate + →pending_buy 시 +1 - 영속화(REQ-006): 멱등 마이그레이션 008(entry_count), flat 매핑, find_allocatable_slot(idle∪closed, entry_count→updated_at→slot_id) + in-memory 동등 - admission(REQ-007): _admit_buy find_idle_slot→find_allocatable_slot + from_state=slot.status - 테스트(REQ-003): 전이 카운트/계약 가드 정합 + test_slot_entry_count.py / test_buy_admission_reallocation.py - 문서(REQ-009): data-model.md / runtime-flows.md 정합 - 기존 daily_symbol_entries(symbol-scoped) 분리·미변경 BAT-025(release-candidate) delivery + change-req REQ-002/003/006/007/009(CHG-20260609-01). REL-005(confirmed, app-service): BAT-024+BAT-025 묶음. operator-facing run-the-app 스모크로 enter --forced-close-at(BAT-024)·closed 재할당(BAT-025) 실 데몬 검증 (실 PG 마이그레이션 008 round-trip 포함). 검증: 단위 366 passed / 35 skipped(KIS sandbox 1건 deselect — live .env 환경 의존), ruff clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# BAT-025 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-025
|
||||
|
||||
## 설계 목표
|
||||
- 슬롯 자원을 진입 횟수 기준으로 고르게 재사용하고 idle 소진 시 closed 슬롯을 재할당하도록, slot-scoped `entry_count` 를 도메인 정본에 영속하고 buy admission 후보를 idle∪closed 로 확장한다. 동시성·계약·기존 동작(회귀) 안전을 보장한다.
|
||||
|
||||
## 1. 슬롯 도메인 필드 (REQ-002)
|
||||
- `Slot.entry_count: int = 0`(take_profit_price 뒤, created_at 앞). `EXPECTED_SLOT_FIELDS` 에 `"entry_count"` 추가(같은 위치).
|
||||
- slot-scoped 누적 진입 횟수. `daily_symbol_entries`(symbol-scoped, AC-3)와 무관 — 본 batch 에서 후자 미변경.
|
||||
|
||||
## 2. 상태 머신 전이 (REQ-002)
|
||||
- `_BASE_TRANSITIONS` 에 `(CLOSED, PENDING_BUY)` 추가(#9). base 7→8 tuple, `ALLOWED_TRANSITIONS` 14→15.
|
||||
- docstring "6 states + 9 base allowed transitions" 갱신(`* → error` family 는 별도 집합이므로 base 카운트는 tuple 8 + 주석 #9).
|
||||
|
||||
## 3. 전이 필드 계산 (REQ-002 / `_apply_transition_fields`)
|
||||
- **closed→pending_buy clean-slate**(limit_price lifecycle 분기 앞): `to_state is PENDING_BUY and slot.status is CLOSED` 면 점유 필드(symbol/qty/avg_price/limit_price/allocated_capital/order_id/strategy_id/broker_order_id/portfolio_id/sell_strategy_id/forced_close_at/stop_loss_price/take_profit_price)를 None 으로 리셋한 `slot` 으로 교체 → 이전 사이클 점유가 새 진입에 누수되지 않음. `entry_count` 는 리셋 대상 제외(누적 보존).
|
||||
- 이후 generic `replace` 에서 caller-supplied 값으로 새 진입 필드 적용(None 이면 keep — clean-slate 후이므로 None 유지).
|
||||
- **entry_count +1**: `new_entry_count = slot.entry_count + (1 if to_state is PENDING_BUY else 0)` → idle→ 와 closed→ 모두 +1. 그 외 전이·closed→idle 복구는 보존. `replace(..., entry_count=new_entry_count)`.
|
||||
|
||||
## 4. 후보 selector (REQ-006)
|
||||
- `SlotRepository.find_allocatable_slot(trading_date)` Protocol 추가: idle∪closed 후보를 `entry_count asc → updated_at asc → slot_id` 정렬로 1건(없으면 None). caller 가 advisory lock+재시도로 마지막 후보 경합 처리.
|
||||
- PG: `SELECT {_SLOT_COLUMNS} FROM daily_slot WHERE trading_date=%s AND status IN ('idle','closed') ORDER BY entry_count ASC, updated_at ASC, slot_id ASC LIMIT 1`. 기존 `find_idle_slot` 와 동일하게 FOR UPDATE 미사용(동시성은 transit advisory lock).
|
||||
- in-memory: idle/closed 후보 중 `min(key=(entry_count, updated_at or min, str(slot_id)))`.
|
||||
|
||||
## 5. persistence flat 매핑 (REQ-006)
|
||||
- `_SLOT_COLUMNS` 끝에 `entry_count`(인덱스 18, 총 19컬럼). `update_slot` SET 에 `entry_count=%s` + params. `_row_to_slot` 에 `entry_count=int(row[18]) if row[18] is not None else 0`.
|
||||
- 마이그레이션 `008_add_slot_entry_count.sql`: `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS entry_count INTEGER NOT NULL DEFAULT 0`(additive·멱등, 004 패턴).
|
||||
|
||||
## 6. admission (REQ-007)
|
||||
- `_admit_buy` 재시도 루프: `find_idle_slot` → `find_allocatable_slot`. transit from_state 하드코딩 `IDLE` → `slot.status`(후보가 idle 이면 idle→, closed 면 closed→ 직접 재할당). 후보 없으면 `no_idle_slot` 거부. `SlotStateMismatchError` 시 다음 후보 재시도(경합 보존). 멱등(intent_log dedup) 불변.
|
||||
|
||||
## 7. baseline 문서 (REQ-009)
|
||||
- `data-model.md`: daily_slot `entry_count` 필드, Lifecycle `closed→pending_buy`, State Rules(전이 패밀리 8 base + clean-slate). 두 카운터 구분.
|
||||
- `runtime-flows.md`: buy admission `find_allocatable_slot`(idle∪closed, entry_count 정렬)·closed 재할당, transit_slot 흐름에 `→pending_buy 시 entry_count +1` 단계.
|
||||
|
||||
## 테스트 설계
|
||||
- domain: 전이 카운트(14→15, closed→pending_buy allowed/disallowed 재배치); 계약 가드(EXPECTED_SLOT_FIELDS); entry_count 증분(idle→/non-buy 미증가)·closed→idle 복구 보존; closed→pending_buy clean-slate(미공급 필드 클리어)·+1; selector(none/idle-only/closed 포함/entry_count 오름차순).
|
||||
- application: closed 재할당 admit(idle 소진 시), idle 우선(closed 보다), 양쪽 소진 reject; 동시성 경합 회귀(test_intent_concurrency).
|
||||
- PG 게이트: 마이그레이션/드리프트(`EXPECTED_SLOT_FIELDS`↔information_schema) — entry_count 자동 반영.
|
||||
- 회귀: 전체 단위(flat 매핑 추가로 인한 slot read/write 무누락) green.
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
# BAT-025 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-025
|
||||
|
||||
## 구현 요약
|
||||
- dcy-019(slot-scoped entry_count + closed 슬롯 직접 재할당, issue #18)을 전부 change-req 로 구현(신규 REQ 없음). 기존 `daily_symbol_entries`(symbol-scoped) 미변경.
|
||||
|
||||
## 변경 내역
|
||||
|
||||
### REQ-002 — 슬롯 도메인 필드 + 상태 머신 + 전이 로직
|
||||
- `trex_core/domain/slot/models.py`: `Slot.entry_count: int = 0`(take_profit_price 뒤) + `EXPECTED_SLOT_FIELDS` 에 `"entry_count"` 추가.
|
||||
- `trex_core/domain/slot/state.py`: `_BASE_TRANSITIONS` 에 `(CLOSED, PENDING_BUY)` 추가(#9, base tuple 7→8, ALLOWED_TRANSITIONS 14→15). docstring "9 base allowed transitions" 갱신.
|
||||
- `trex_core/domain/slot/service.py` `_apply_transition_fields`: closed→pending_buy clean-slate 점유 초기화 블록(limit_price lifecycle 앞) + `new_entry_count = slot.entry_count + (1 if to_state is PENDING_BUY else 0)` + generic `replace(..., entry_count=new_entry_count)`.
|
||||
|
||||
### REQ-006 — 마이그레이션 + persistence + selector
|
||||
- `trex_core/adapters/persistence/sql/008_add_slot_entry_count.sql`(신규): `ALTER TABLE daily_slot ADD COLUMN IF NOT EXISTS entry_count INTEGER NOT NULL DEFAULT 0`(additive·멱등).
|
||||
- `trex_core/adapters/persistence/postgresql.py`: `_SLOT_COLUMNS` +`entry_count`(인덱스 18); `update_slot` SET `entry_count=%s` + params; `_row_to_slot` `entry_count=int(row[18]) if row[18] is not None else 0`; 신규 `find_allocatable_slot`(idle∪closed, `ORDER BY entry_count ASC, updated_at ASC, slot_id ASC LIMIT 1`, FOR UPDATE 미사용).
|
||||
- `trex_core/domain/slot/repository.py`: `find_allocatable_slot` Protocol 추가.
|
||||
- `trex_core/domain/slot/in_memory_repository.py`: `find_allocatable_slot`(idle/closed 후보 `min(key=(entry_count, updated_at or min, str(slot_id)))`).
|
||||
|
||||
### REQ-007 — buy admission 일반화
|
||||
- `trex_core/application/intents.py` `_admit_buy`: `find_idle_slot` → `find_allocatable_slot`, transit from_state `SlotState.IDLE` → `slot.status`(closed 후보 직접 재할당). 후보 소진 `no_idle_slot` 거부·`SlotStateMismatchError` 재시도 루프·멱등 불변.
|
||||
|
||||
### REQ-003 — 테스트 계약 정합
|
||||
- `tests/domain/slot/test_state.py`: `closed→pending_buy` allowed 이동, disallowed 에서 제거, `ALLOWED_TRANSITIONS` 크기 14→15.
|
||||
- 신규 `tests/domain/slot/test_slot_entry_count.py`: entry_count 증분(idle→/non-buy 미증가/closed→idle 보존), closed→pending_buy clean-slate+ +1, `find_allocatable_slot` 정렬(none/idle-only/closed 포함/entry_count 오름차순).
|
||||
- 신규 `tests/application/test_buy_admission_reallocation.py`: closed 재할당 admit / idle 우선 / 양쪽 소진 reject.
|
||||
- `tests/domain/slot/test_contract.py`·`tests/adapters/persistence/test_migration_and_drift.py`: `EXPECTED_SLOT_FIELDS` 갱신으로 entry_count 자동 검증.
|
||||
|
||||
### REQ-009 — baseline 문서
|
||||
- `docs/data-model.md`: daily_slot `entry_count` 필드(두 카운터 구분), Lifecycle `closed→pending_buy`, State Rules(전이 패밀리 8 base 포함 + closed→pending_buy clean-slate).
|
||||
- `docs/runtime-flows.md`: buy admission `find_allocatable_slot`(idle∪closed, entry_count 정렬)·closed 직접 재할당, transit_slot 흐름 `→pending_buy 시 entry_count +1` 단계 추가.
|
||||
|
||||
## 테스트
|
||||
- 신규 단위: `test_slot_entry_count.py`(12건), `test_buy_admission_reallocation.py`(3건).
|
||||
- 수정: `test_state.py`(전이 카운트/허용집합).
|
||||
|
||||
## 검증 실행
|
||||
- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q --deselect tests/adapters/broker/test_kis.py::test_sandbox_token_and_submit` → `366 passed / 35 skipped / 1 deselected`. (deselect 한 KIS sandbox 테스트는 LIVE `.env` 가 운영 KIS URL 을 가리켜 사전 환경 실패 — 본 batch 무관.)
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
- PG 게이트(마이그레이션 008 round-trip + EXPECTED_SLOT_FIELDS 드리프트): 라이브 runtime DSN 미사용(`pg_clean` truncate 파괴적 회피). test DSN(`TREX_POSTGRESQL_TEST_DSN`) 환경에서 자동 검증 — 마이그레이션은 004 의 검증된 additive·멱등 패턴 동일, 드리프트 가드는 `EXPECTED_SLOT_FIELDS`(entry_count 포함)↔information_schema 로 자동 강제.
|
||||
@@ -0,0 +1,37 @@
|
||||
# BAT-025: slot-scoped entry_count + closed 슬롯 직접 재할당 (dcy-019)
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-019
|
||||
- Included REQ: REQ-002 (CHG-20260609-01), REQ-003 (CHG-20260609-01), REQ-006 (CHG-20260609-01), REQ-007 (CHG-20260609-01), REQ-009 (CHG-20260609-01)
|
||||
|
||||
## Summary
|
||||
- dcy-019(slot 진입 횟수 필드 + idle∪closed 후보 중 최저 진입 우선 선택 + closed 직접 재할당, issue #18)을 단일 standard batch 로 delivery 한다. 거버넌스는 dcy-019 OQ-1/2/3 결정대로 **전부 change-req, 신규 REQ 없음**.
|
||||
- 핵심: daily_slot/`Slot` 에 slot-scoped `entry_count`(기본 0) 필드 + 멱등 마이그레이션 008(REQ-002/006), 상태 머신 `closed→pending_buy` 신규 전이(8→9 base, ALLOWED_TRANSITIONS 14→15)(REQ-002), buy admission 의 후보를 idle∪closed 로 확장하고 `entry_count→updated_at→slot_id` 결정적 정렬로 선택하는 `find_allocatable_slot` selector(REQ-006) + `_admit_buy` 일반화(REQ-007), `→pending_buy`(idle→/closed→) 시 entry_count +1 및 closed→pending_buy clean-slate 점유 초기화(REQ-002). 기존 `daily_symbol_entries`(symbol-scoped 재진입 카운터)와 분리·미변경.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `Slot.entry_count`(기본 0) + `EXPECTED_SLOT_FIELDS` + 멱등 마이그레이션 008 + persistence flat 매핑 (REQ-002/006)
|
||||
- 상태 머신 `closed→pending_buy` 신규 전이 + `_apply_transition_fields` clean-slate + entry_count +1 (REQ-002)
|
||||
- `find_allocatable_slot`(idle∪closed, entry_count→updated_at→slot_id) selector + in-memory 동등 (REQ-006)
|
||||
- `_admit_buy` find_idle_slot→find_allocatable_slot + from_state=slot.status (REQ-007)
|
||||
- 테스트 계약 정합(전이 카운트·필드셋·신규 단위) (REQ-003)
|
||||
- data-model/runtime-flows baseline 정합 (REQ-009)
|
||||
- Out of Scope:
|
||||
- `daily_symbol_entries`(symbol-scoped 재진입 카운터) 제거/변경 — 별도 REQ-002 change-req 트랙(후속)
|
||||
- 운영자 CLI/스냅샷에 slot `entry_count` 노출 (dcy-019 §5 제외)
|
||||
- 슬롯별 PnL 집계, 전략 모듈에 선택 정책 노출, preallocation 총량/스케줄 변경
|
||||
- snapshot `available_slots`(=idle_count) capacity 의미 변경 (dcy-019 범위 밖 — 보수적 under-report 유지)
|
||||
|
||||
## 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-019). OQ-1 (a) daily_slot 단독 / OQ-2 entry_count→updated_at→slot_id / OQ-3 closed→pending_buy 직접 전이·→pending_buy 시 +1 Answered.
|
||||
- additive·하위호환(NFR-3): 마이그레이션 008 기본 0, closed 후보 부재 시 기존 idle-only 동작 보존(회귀 green).
|
||||
- 동시성(NFR-1): selector find 쿼리는 기존 `find_idle_slot` 와 동일하게 FOR UPDATE 미사용 — claim 경합은 `transit_slot` advisory lock + `SlotStateMismatchError` 재시도 루프로 직렬화(중복 카운트 없음).
|
||||
- 신규 slot `entry_count` 와 `daily_symbol_entries.entry_count` 는 키·목적이 다른 별개 카운터(R-1) — data-model 에 구분 기록.
|
||||
- PG 게이트 마이그레이션/드리프트 테스트는 라이브 runtime DSN 미사용(`pg_clean` truncate 파괴적) — test DSN 환경에서 자동 검증.
|
||||
@@ -0,0 +1,49 @@
|
||||
# BAT-025 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-025
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-002 (Interface, change-req): `Slot.entry_count` 필드 + `EXPECTED_SLOT_FIELDS`, 상태 머신 `closed→pending_buy` 신규 전이, `_apply_transition_fields` clean-slate + entry_count +1.
|
||||
- REQ-003 (Testing, change-req): 전이 카운트(14→15)·필드셋 계약 가드 정합 + entry_count/reallocation 신규 단위 테스트.
|
||||
- REQ-006 (Data, change-req): 멱등 마이그레이션 008 + flat slot 매핑 + `find_allocatable_slot` selector.
|
||||
- REQ-007 (Interface, change-req): `_admit_buy` find_idle_slot→find_allocatable_slot + from_state=slot.status.
|
||||
- REQ-009 (Documentation, change-req): data-model/runtime-flows baseline 정합.
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-002 도메인: `models.py` `Slot.entry_count`(기본 0)+`EXPECTED_SLOT_FIELDS`; `state.py` `_BASE_TRANSITIONS` 에 `closed→pending_buy`; `service.py` `_apply_transition_fields` closed→pending_buy clean-slate 점유 초기화 + 모든 `→pending_buy` 시 `entry_count`+1.
|
||||
2. REQ-006 영속화: `sql/008_add_slot_entry_count.sql`(ADD COLUMN IF NOT EXISTS, 기본 0); `postgresql.py` `_SLOT_COLUMNS`/`update_slot`/`_row_to_slot` +entry_count; `find_allocatable_slot`(idle∪closed ORDER BY entry_count,updated_at,slot_id LIMIT 1); `repository.py` Protocol + `in_memory_repository.py` 동등.
|
||||
3. REQ-007 admission: `intents.py` `_admit_buy` selector 전환 + from_state=slot.status(closed 후보 직접 재할당).
|
||||
4. REQ-003 테스트: `test_state.py`(전이 14→15), `test_contract.py`(entry_count 포함은 EXPECTED 갱신으로 자동), 신규 `test_slot_entry_count.py`(증분/보존/clean-slate/selector 정렬) + `test_buy_admission_reallocation.py`(closed admit/idle 우선/양쪽 소진 reject).
|
||||
5. REQ-009 문서: `data-model.md`(entry_count 필드/lifecycle/state rules), `runtime-flows.md`(admission selector/transit_slot entry_count step).
|
||||
6. 단위 회귀 + ruff + (test DSN 가용 시) PG 게이트 마이그레이션/드리프트.
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- dcy-001/BAT-001 slot 도메인(state/models/service/repository, REQ-002/003) — 변경 대상.
|
||||
- dcy-002/BAT-002 PostgreSQL 영속화 + intent admission(REQ-006/007) — 변경 대상.
|
||||
- dcy-014/BAT-018 trading_date 경계 preallocation — entry_count 는 일자 범위 slot row 기준(불변, A-1/A-2).
|
||||
- External:
|
||||
- 없음(전부 내부 도메인/스키마).
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: (a) 상태 머신 불변식 변경(8→9 base 전이) + closed→pending_buy clean-slate 의미는 도메인 계약(REQ-001/002 NFR-2)·계약 가드·기존 closed→idle 리셋과 교차 → 설계에서 전이/리셋 경계 고정. (b) admission 후보 확장(idle∪closed)이 마지막 슬롯 경합 동시성(advisory lock+재시도)·중복 카운트 없음을 깨지 않도록 selector(FOR UPDATE 미사용, find_idle_slot 동등)·증가 시점(단일 transit) 규칙을 못박아야 안전. (c) daily_slot 컬럼 추가가 `EXPECTED_SLOT_FIELDS`·flat read/write 순서를 깨면 광범위 회귀(R-3) → 모델·계약·persistence·data-model 동일 묶음 정합. → design.md 동일 턴 scaffold.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-002 도메인(field/transition/service) + 전이·계약 가드 green
|
||||
2. M2 — REQ-006 마이그레이션 008 + persistence 매핑 + selector + in-memory 동등
|
||||
3. M3 — REQ-007 `_admit_buy` 일반화 + reallocation 단위 green
|
||||
4. M4 — REQ-003 신규 단위 테스트 + REQ-009 문서 정합
|
||||
5. M5 — 단위 전체 회귀 + ruff (+ test DSN PG 게이트)
|
||||
|
||||
## Risks
|
||||
- R-1 (Low): 신규 slot `entry_count` ↔ `daily_symbol_entries.entry_count` 이름 혼동. 대응: 테이블/키/목적/증가시점 data-model 구분, 후자 미변경(REQ-002 AC-3 회귀 보존).
|
||||
- R-2 (Medium): closed 후보 포함으로 동시성/점유 리셋 어긋남 → 잘못된 재사용·상태 오염. 대응: advisory lock + 단일 transit 내 상태/점유/카운트 갱신, closed→pending_buy 가 기존 closed→idle 리셋 의미 재사용, 경합 회귀 테스트.
|
||||
- R-3 (Medium): daily_slot 컬럼 추가가 `EXPECTED_SLOT_FIELDS`/flat 순서 깨면 광범위 회귀. 대응: 모델·계약·persistence(_SLOT_COLUMNS/row index)·data-model 동일 묶음 + 계약 가드 green.
|
||||
|
||||
## Notes
|
||||
- 1차 게이트: 단위 전체 회귀 green(entry_count 추가로 인한 flat 매핑/계약 무누락).
|
||||
- 결정론(NFR-2): in-memory selector(entry_count→updated_at→slot_id) + PG ORDER BY 동일 키.
|
||||
- PG 마이그레이션/드리프트는 라이브 runtime DSN 미사용(파괴적 truncate 회피) — test DSN 자동 검증.
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
# BAT-025 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-025
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-002 (entry_count 필드 + closed→pending_buy 전이 + 전이 로직, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| `Slot.entry_count` 가 `EXPECTED_SLOT_FIELDS` 에 포함 | tests/domain/slot/test_contract.py::test_slot_fields_match_expected_contract |
|
||||
| 6 상태 / 9 base 전이(`closed→pending_buy`) | tests/domain/slot/test_state.py::test_allowed_transition[CLOSED-PENDING_BUY] / ::test_allowed_transitions_set_size(15) |
|
||||
| `→pending_buy` 시 entry_count +1(idle→/closed→), 그 외 보존 | tests/domain/slot/test_slot_entry_count.py::TestEntryCount(증분/non-buy/closed→idle 보존) |
|
||||
| closed→pending_buy clean-slate 점유 초기화 | ::TestClosedReallocation::test_closed_to_pending_buy_clean_slate_and_bumps_count / ::test_closed_to_pending_buy_clears_prior_position_when_not_supplied |
|
||||
|
||||
### REQ-003 (테스트 계약 정합, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| 전이 집합 테스트 `closed→pending_buy` allowed + 크기 15 | tests/domain/slot/test_state.py |
|
||||
| 컬럼 계약 가드 entry_count 포함 | tests/domain/slot/test_contract.py |
|
||||
| entry_count/reallocation 신규 단위 | tests/domain/slot/test_slot_entry_count.py, tests/application/test_buy_admission_reallocation.py |
|
||||
|
||||
### REQ-006 (마이그레이션 + persistence + selector, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| daily_slot `entry_count` 컬럼(마이그레이션 008, 멱등) | sql/008_add_slot_entry_count.sql + tests/adapters/persistence/test_migration_and_drift.py(test DSN 게이트, EXPECTED_SLOT_FIELDS↔information_schema 자동) |
|
||||
| `find_allocatable_slot`(idle∪closed, entry_count→updated_at→slot_id) | tests/domain/slot/test_slot_entry_count.py::TestAllocatableSelector(none/idle-only/closed 포함/오름차순) |
|
||||
| flat slot 매핑(+entry_count) round-trip | tests/adapters/persistence/test_migration_and_drift.py(test DSN) + in-memory selector 단위 |
|
||||
|
||||
### REQ-007 (`_admit_buy` 일반화, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| idle 소진 시 closed 슬롯으로 admit | tests/application/test_buy_admission_reallocation.py::test_admits_into_closed_slot_when_no_idle_remains |
|
||||
| idle 우선(closed 보다) | ::test_prefers_idle_over_closed |
|
||||
| idle·closed 모두 소진 → no_idle_slot 거부 | ::test_rejects_when_no_idle_and_no_closed |
|
||||
| 마지막 슬롯 경합 동시성 회귀 | tests/application/test_intent_concurrency.py 전체 |
|
||||
|
||||
### REQ-009 (baseline 문서 정합, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| runtime-flows admission selector/transit entry_count step | docs/runtime-flows.md(코드-문서 diff 대조) |
|
||||
| data-model daily_slot entry_count/lifecycle/state rules | docs/data-model.md(코드-문서 diff 대조) |
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- 단위 전체 회귀: `366 passed / 35 skipped / 1 deselected`(신규 entry_count 12 + reallocation 3 포함; `::test_sandbox_token_and_submit` deselect — LIVE `.env` KIS URL 환경 의존, 본 batch 무관).
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
- Logs: `366 passed, 35 skipped, 1 deselected in 1.13s`.
|
||||
|
||||
## 성공 기준(dcy-019) 대응
|
||||
- S-1(idle 없고 closed 있을 때 closed 로 admit, 이전엔 no_idle_slot 거부): test_admits_into_closed_slot_when_no_idle_remains.
|
||||
- S-2(`entry_count` 최소 선택 + tie-break 결정적): TestAllocatableSelector::test_orders_by_entry_count_ascending + test_prefers_idle_over_closed.
|
||||
- S-3(할당 전이 시 정확히 +1, 동시 경합 중복 없음, `daily_symbol_entries` 회귀 green): TestEntryCount::test_increments_on_each_pending_buy + test_intent_concurrency.py + test_service.py::TestClosedToIdleRecovery(daily_symbol_entries 보존).
|
||||
|
||||
## Carried-Over / 비차단
|
||||
- PG 게이트(마이그레이션 008 round-trip + 드리프트)는 라이브 runtime DSN 미사용(파괴적 truncate 회피) → test DSN 환경에서 자동 검증. 마이그레이션은 004 의 검증된 additive·멱등 패턴과 동일하고, 드리프트 가드는 `EXPECTED_SLOT_FIELDS`(entry_count 포함)↔information_schema 로 강제되어 코드 단위로 동등 보장.
|
||||
- 실 데몬 + 실 PG full-stack 의 closed 재할당 e2e 는 운영 배포 검증(후속 REL)에서 확인 — 단위(in-memory repo + IntentService)로 admission/전이/카운트 커버.
|
||||
- snapshot `available_slots`(=idle_count) 는 dcy-019 범위 밖(§5 제외)이라 closed 가용분을 보수적으로 under-report — admission 자체는 closed 재할당을 수행. 필요 시 후속 change-req 후보.
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- 없음. entry_count 필드/closed→pending_buy 전이/selector/admission 일반화 전부 단위 green(366 passed 회귀, flat 매핑·계약 가드 무누락, 동시성 경합 회귀 보존). PG 게이트는 test DSN 자동 검증(라이브 DSN 비파괴 정책). LIVE full-stack 은 운영 배포 검증 비차단.
|
||||
@@ -32,3 +32,4 @@
|
||||
| BAT-022 | standard | released | REQ-028 (CHG-20260608-01), REQ-032 (CHG-20260608-03) | dcy-016 | [bat-022](./bat-022_20260608_slot-state-snapshot-visibility/) |
|
||||
| BAT-023 | standard | released | REQ-017 (CHG-20260608-02), REQ-019 (CHG-20260608-02), REQ-029 (CHG-20260608-01), REQ-030 (CHG-20260608-01, Approved→Implemented) | dcy-018 | [bat-023](./bat-023_20260608_ledger-cash-position-writeback-option-d/) |
|
||||
| BAT-024 | batch-lite | release-candidate | REQ-032 (CHG-20260609-01) | dcy-016 (issue #17) | [bat-024](./bat-024_20260609_operator-enter-forced-close-at/) |
|
||||
| BAT-025 | standard | release-candidate | REQ-002 (CHG-20260609-01), REQ-003 (CHG-20260609-01), REQ-006 (CHG-20260609-01), REQ-007 (CHG-20260609-01), REQ-009 (CHG-20260609-01) | dcy-019 (issue #18) | [bat-025](./bat-025_20260609_slot-entry-count-and-closed-reallocation/) |
|
||||
|
||||
Reference in New Issue
Block a user