Files
trex-core/docs/batches/bat-030_20260610_asset-centric-cutover/implementation.md
T
genichinandClaude Opus 4.8 c04fd276b3 feat(reset): cutover 도구 폐기 → trex-core-reset(DB 재초기화) 대체 (REQ-064 CHG-20260610-03)
실 DB 점검 중 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 데이터(같은
자산이 closed+재진입 holding 으로 portfolio_id 중복)에 적용 불가 → cutover·
데몬 부팅 모두 차단되는 결함 확인. 데이터 보존 cutover 를 폐기하고 DB wipe +
자산 중심 재초기화로 전환(사용자 결정). 빈 테이블에는 009 인덱스가 정상
생성되고 자산 중심에서 (instance_key, portfolio_id)는 설계상 유일.

- trex_core/runtime/reset.py 신규(trex-core-reset): slot-core 7테이블 wipe +
  apply_migrations 재적용 → 자산 중심 스키마 착지 검증. dry-run 기본/--yes 적용,
  파괴적·DB 전역. 보유 carry-over 없음(재초기화 후 reconcile_core_holdings 재편입).
- runtime/cutover.py + trex-core-cutover console script + test_cutover_rehearsal.py 삭제.
- pyproject: trex-core-reset 등록.
- tests/runtime/test_reset.py 신규 5건(PG-gated): wipe+착지·dry-run 무변경·멱등·
  wipe 후 admission·테이블 커버리지.
- change-req REQ-064 CHG-20260610-03(cutover→재초기화 재정의) + srs index 갱신.
- baseline 4문서(data-model/runtime-flows/project-structure/interface-contract)
  + BAT-030 design/planning/index/implementation/verification 도구 서술 reset 으로 교체.

검증: ruff clean, 단위 367 passed, PG+RabbitMQ 통합 411 passed/1 skip(KIS sandbox).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 16:08:05 +09:00

9.0 KiB
Raw Blame History

BAT-030 Implementation

  • Status: done
  • Batch: BAT-030
  • 일자: 2026-06-10

구현 요약

design.md 확정 결정(D2=전부 완성 / D3=매도 qty 브로커 정본) 구현 완료. M1~M5 수행. 단, D1(데이터 보존 cutover)은 폐기 — 실 DB 점검 중 마이그레이션 009 부분 유니크 인덱스가 legacy 풀 데이터(같은 자산이 closed+재진입 holding 으로 portfolio_id 중복)와 충돌해 cutover/데몬 부팅이 모두 막히는 결함 발견. 사용자 결정으로 데이터 보존 마이그레이션을 버리고 DB wipe + 자산 중심 재초기화(trex-core-reset)로 대체(REQ-064 CHG-20260610-03). 빈 테이블에는 009 인덱스가 정상 생성되고 자산 중심에서 (instance_key,portfolio_id)는 설계상 유일.

  • M1 — literal inactive 전면화 (REQ-057 잔여): SlotState 5-state(inactive/pending_buy/holding/pending_sell/error) — IDLE/CLOSED 별칭·normalize_state 완전 제거, 전이 12종(6 base + 4 to-error + 2 resolve). 마이그레이션 010: 기존 idle/closed 행 → inactive UPDATE + status CHECK 교체(drop-and-add 멱등). execution.py 매도 체결 PENDING_SELL→INACTIVE, 매수 롤백 PENDING_BUY→INACTIVE. cli.py buyable 필터 inactive+universe_detached 제외. ops_checks.py ledger-gap 검출을 slot_transition_log(reason=sell_filled/sell_partial_cancel) 기반으로 재작성(closed 상태 소멸 대응).
  • M2 — 집행 매도 수량 브로커 정본 (REQ-062 M2(b), D3): dispatch 매도 qty = broker.get_balance() 의 해당 symbol hldg_qty(tick 당 1회 lazy fetch). 미러 slot.qty fallback 없음. 브로커 0 → 주문 미제출 + WARN + ExecutionResult.sell_skipped_no_balance 카운터(divergence 는 reconcile/운영자 경로로 표면화). skip 매도가 큐 head 를 막지 않도록 단건 claim(find_pending_for_dispatch) → 리스트 기반(list_undispatched_pending(instance_key, limit)) dispatch 로 전환.
  • M3 — 컬럼 drop + legacy pool 제거 (REQ-056 잔여): 마이그레이션 011: daily_slot.entry_count·trading_date DROP(IF EXISTS 멱등). 글롭 재적용 정합: 001 의 (trading_date,status) 인덱스 → (status) 교체, 008(entry_count ADD)은 no-op 화(매 boot 컬럼 부활 방지). Slot 모델/EXPECTED_SLOT_FIELDS/_SLOT_COLUMNS/_row_to_slot 정합(19 컬럼). repo 계약 개편: pre_allocate_slots/find_idle_slot/find_allocatable_slot/increment_symbol_entry/get_symbol_entry_count 제거, list_slots(instance_key)/count_slots_by_status(instance_key)/reconcile_universe(instance_key, universe) 로 전환, 집행 큐(list_undispatched_pending/list_dispatched_pending)도 instance 스코프(공유 DB 의 두 데몬이 서로의 주문을 집행하지 않음 — REQ-056). preallocation_scheduler.py 삭제 + 데몬 7 스레드. admission 은 자산 중심 단일 경로(slot_id 정본/portfolio_id dual-accept; max_active_slots 미설정 = 무캡 — design §5: 운영 TOML 로 M 부여). BuyIntent.trading_date·intent/이벤트/슬롯 뷰 payload 의 trading_date 키 제거. capacity 를 SlotCapacity(inactive/holding/pending/active/available/total/scarcity) 자산 중심 재정의(available = max(0, M−active)). daily_symbol_entries 코드 경로 제거(테이블 잔존, baseline 에 폐기 명시). config 에서 total_slots/daily_preallocation_time 제거. SnapshotScheduler reconcile 은 slot_repo 주입 시 항상 on.
  • M4 — DB 재초기화 도구 (REQ-064, CHG-20260610-03): trex_core/runtime/reset.py + console script trex-core-reset(기본 dry-run, --yes 적용). 절차: 대상 DB 확인 → slot-core 7개 테이블 중 존재하는 것 행수 집계(dry-run 은 여기서 보고만) → --yes 면 TRUNCATE ... RESTART IDENTITY CASCADE → apply_migrations(빈 테이블이라 009 유니크 인덱스 정상 생성·010 inactive CHECK·011 컬럼 drop) → daily_slot 컬럼 == EXPECTED_SLOT_FIELDS 검증. 파괴적·DB 전역(모든 instance). 데이터 보존(보유 carry-over)은 하지 않음 — 재초기화 후 universe reconcile 이 자산 슬롯 생성, 보유는 reconcile_core_holdings(REQ-062)로 운영자 재편입. (구 cutover.py·trex-core-cutover·test_cutover_rehearsal.py 삭제.)
  • M5 — baseline 마감 (REQ-065): data-model/runtime-flows/project-structure/interface-contract 4문서 자산 중심 최종 갱신(daily_symbol_entries·dcy-014 preallocation·풀 admission 폐기 확정, slot_id 계약·5-state·M 캡·브로커 정본 반영). config/instance.example.toml 에 max_active_slots 안내. .claude/skills/run-trex-core-daemon(SKILL.md+smoke.py) 자산 중심으로 갱신.

변경 내역 (Changed Files)

  • trex_core/domain/slot/: state.py(5-state) · models.py(컬럼 drop+SlotCapacity 재정의) · service.py(preallocate/카운터 제거, INACTIVE 리셋=점유 초기화+자산 정체성 보존, capacity 자산 중심) · repository.py · in_memory_repository.py(+add_slot 테스트 헬퍼) · __init__.py
  • trex_core/adapters/persistence/: postgresql.py · ops_checks.py(재작성) · sql/001(인덱스 교체)·008(no-op 화)·010_literal_inactive_status.sql(신규)·011_drop_pool_model_columns.sql(신규)
  • trex_core/application/: intents.py(단일 자산 중심 admission) · execution.py(M2+inactive 전이+instance 스코프) · messaging_handlers.py · portfolio.py · safety_sell.py · killswitch.py · snapshot_scheduler.py · preallocation_scheduler.py 삭제
  • trex_core/runtime/: daemon.py(7 스레드) · config.py · cli.py · reset.py(신규) · cutover.py(삭제)
  • pyproject.toml: trex-core-reset console script (구 trex-core-cutover 제거)
  • docs: data-model.md · runtime-flows.md · project-structure.md · interface-contract.md (자산 중심 마감)
  • config/instance.example.toml · .claude/skills/run-trex-core-daemon/{SKILL.md,smoke.py}
  • tests: 도메인/애플리케이션/어댑터/런타임 전면 정합(test_state·test_service 재작성, test_slot_entry_count·test_buy_admission_reallocation·test_preallocation_scheduler 삭제, test_execution 에 M2 3건 신설, tests/runtime/test_reset.py 신규 5건(PG-gated), test_postgresql_repository 자산 중심 재작성+dispatch 인스턴스 격리, test_ops_checks 재작성)

테스트

  • 단위 전체: 367 passed (PG/RabbitMQ/KIS unset 시 40 skipped), ruff clean
  • PG-gated(trex_core_test): 26 passed — 마이그레이션 010/011 round-trip + 멱등 재적용, daily_slot 컬럼 == EXPECTED_SLOT_FIELDS 드리프트 가드(컬럼 drop 후 정합), reconcile ON CONFLICT, dispatch 인스턴스 격리, ops_checks 재작성, reset 5건(wipe+자산 중심 스키마 착지·dry-run 무변경·멱등·wipe 후 즉시 admission 가능·slot-core 테이블 커버리지)
  • RabbitMQ-gated: 21 passed (실 브로커)
  • 통합(PG+RabbitMQ enabled): 411 passed, 1 skipped(KIS sandbox — 기존 환경 이슈, 본 배치 무관)

검증 실행 (run-the-app, 자산 중심 e2e)

test DB(trex_core_test) + 실 RabbitMQ + paper broker + stub universe(2 자산) + instance_key=e2etest, max_active_slots=1 로 데몬 기동:

  1. boot 직후 universe reconcile {'created': 2} — 영구 슬롯 자동 생성
  2. snapshot RPC: slots=2(inactive), available_slots=1(M−active)
  3. buy by slot_id admitted(REQ-061) → 4. 동일 intent_id 중복 → identical 응답(전이 1회)
  4. M=1 에서 2번째 자산 buy → capacity_full(REQ-059) → 6. paper fill → holding, available=0
  5. 동일 슬롯 재매수 → already_active → 8. sell by slot_id → 체결 → inactive(qty=None, portfolio_id 보존, available=1)
  6. 동일 영구 슬롯 재활성(re-entry) admitted
  • DB 최종: ('e2etest','pf-000660','inactive',None), ('e2etest','pf-005930','holding',3.0); transition_log reason {buy_intent×2, buy_filled×2, discretionary_sell×1, sell_filled×1}; ledger_outbox completed×3; 데몬 로그 ERROR/Traceback 0, Normal shutdown.

Remaining Risks / Blockers

  • 실 DB 재초기화 미실행(의도) — 운영자가 trex-core-reset --config <instance> --yes 실행(파괴적·DB 전역). 사전 백업 필수. 재초기화라 롤백 = 백업 복원.
  • 데이터 보존 cutover 폐기로 기존 보유 포지션은 재초기화 시 소실 — 재초기화 후 브로커 보유는 universe reconcile + reconcile_core_holdings(REQ-062)에서 unmanaged 로 표면화되어 운영자가 재편입. 실거래 보유가 있는 DB 라면 재편입 절차를 release 노트에 포함.
  • 마이그레이션 009 부분 유니크 인덱스는 legacy 풀 중복 데이터에 적용 불가(이 결함이 cutover→reset 전환의 직접 원인). 자산 중심 DB(신규/재초기화 후)에서는 정상.
  • 전략 모듈(trex-orb) 계약 전환: buy slot_id 정본(snapshot slots[] round-trip), trading_date 키 소멸, 이벤트 payload 변경 — portfolio_id dual-accept 로 무중단이나 전략 측 전환 권고(REL 노트 대상).
  • max_active_slots 미설정 = 무캡 — 운영 TOML 에 반드시 설정(배포 체크리스트, design §5).