# BAT-027 Implementation - Status: completed (M1·M2·M3 완료) - Batch: BAT-027 ## 구현 요약 - 안 B(점진/additive-first). M1(additive 모델+마이그레이션+persistence)·M2(상태머신 inactive 별칭+인스턴스 스코프 lock)·M3(소비처 cascade — runtime 무변경, idle/closed 별칭으로 흡수) 완료. 전체 375 passed + PG-gated 8 passed, ruff clean. ## 변경 내역 ### M1 — REQ-056 additive (완료) - `trex_core/domain/slot/models.py`: `Slot` 에 `instance_key: str = "default"`·`retired_at: Optional[datetime] = None` 추가, `EXPECTED_SLOT_FIELDS` +instance_key/+retired_at. (entry_count/trading_date 는 안 B로 유지 — drop 은 BAT-δ) - `trex_core/adapters/persistence/sql/009_add_slot_instance_key_and_retired_at.sql`(신규): instance_key NOT NULL DEFAULT 'default', retired_at, 부분 UNIQUE(instance_key, portfolio_id) WHERE portfolio_id IS NOT NULL. additive·멱등. - `trex_core/adapters/persistence/postgresql.py`: `_SLOT_COLUMNS` +instance_key/retired_at(row[19]/[20]), `_row_to_slot` 매핑, `update_slot` SET. pre_allocate INSERT 는 DEFAULT 의존(변경 없음). ### M2 — REQ-057 상태머신 + 인스턴스 lock (완료) - `trex_core/domain/slot/state.py`: `SlotState.INACTIVE` 추가 + `idle`/`closed` 를 별칭으로 `normalize_state()` 매핑. `_BASE_TRANSITIONS` 를 inactive 기준으로 재정의(`inactive↔pending_buy↔holding↔pending_sell`), **`closed→idle` 복구 제거**(inactive self-loop). `is_allowed_transition` 가 양쪽 정규화. `slot_advisory_lock_id(instance_key)` 헬퍼(전역 base ⊕ crc32(instance_key)). - `trex_core/domain/slot/service.py`: `SlotService(repository, instance_key="default")` + advisory lock 을 `self._lock_id`(인스턴스 스코프)로 3개소 치환. holding 은 status 로컬 정본(불변). - `trex_core/domain/slot/__init__.py`: `normalize_state`/`slot_advisory_lock_id` export. `trex_core/runtime/daemon.py`: `SlotService(..., instance_key=config.instance_key)`. ### M3 — 소비처 cascade (완료, runtime 무변경) - idle/closed 가 inactive 별칭으로 흡수되어 **execution/cli/ops_checks/postgresql 의 idle·closed 사용은 그대로 유효**(별칭 정규화). closed→idle 은 runtime 미사용이라 무파손. → 별도 소비처 코드 변경 없음. (실제 inactive 전면 전환·컬럼 drop 은 BAT-δ.) - 테스트 갱신: test_state(새 전이/멤버/락), test_service(closed→idle 제거→reallocation), test_slot_entry_count, test_postgresql_repository(PG). ## 테스트 - 단위 전체: 375 passed / 35 skipped / 1 deselected. - PG-gated(실 trex_core_test): `test_migration_and_drift`(마이그레이션 009 + `EXPECTED_SLOT_FIELDS`↔컬럼 드리프트, instance_key/retired_at 포함) + `test_postgresql_repository`(lifecycle + closed→idle 거부 + closed→pending_buy reallocation) → **8 passed**. - `ruff check trex_core tests` All passed. ## 검증 실행 - `TREX_BROKER=paper pytest -q --deselect …test_sandbox…` → 375 passed / 35 skipped / 1 deselected. - `TREX_POSTGRESQL_TEST_DSN=…(trex_core_test) pytest tests/adapters/persistence/test_migration_and_drift.py test_postgresql_repository.py` → 8 passed. - `ruff check trex_core tests` → All checks passed.