REQ-056(영구 슬롯 스키마)+REQ-057(상태머신 재정의+인스턴스 lock) 토대 배치. - planning: delivery plan/Design Gate(R-cascade)/milestones/risks - design: R-cascade 실측(idle·closed/entry_count/전역 lock/trading_date 소비처) + 접근안 A/B/C → 안 B(점진/additive-first) 확정: * additive 스키마(instance_key·retired_at, entry_count/trading_date 유지→BAT-δ drop) * inactive 표준 + idle/closed 별칭(closed→idle 제거) * advisory lock 인스턴스 스코프화 * 부분 UNIQUE(instance_key, portfolio_id) WHERE portfolio_id NOT NULL * instance_key NOT NULL DEFAULT 'default' - batches/index BAT-027(standard, planning) 등록 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
5.7 KiB
5.7 KiB
BAT-027 Design
- Status: confirmed (안 B 확정 — 2026-06-10, 사용자 결정)
- Batch: BAT-027
설계 목표
- 익명 풀 슬롯 도메인을 자산 1:1 영구 슬롯의 데이터 모델 + 상태머신 토대로 재작성하되, 소비처 연쇄(R-cascade)를 통제해 각 단계에서 단위 green 을 유지한다. 라이브 데이터는 건드리지 않는다.
R-cascade — 실측 영향 범위 (근거)
저장소 grep 기준, 본 배치가 바꾸는 3개 축의 소비처:
idle/closed상태 참조:application/execution.py,adapters/persistence/ops_checks.py,runtime/cli.py,adapters/persistence/postgresql.py,sql/001entry_count:application/intents.py,domain/slot/*,adapters/persistence/postgresql.py,sql/001,sql/008SLOT_ADVISORY_LOCK_ID(전역 락):application/execution.py,application/intents.py,domain/slot/*,adapters/persistence/postgresql.py- 슬롯
trading_date:preallocation_scheduler,safety_sell,messaging_handlers,intents,killswitch,daemon,portfolio,config,cli(광범위 — 일일 회계·키 양쪽)
→ "스키마+상태머신만" 깨끗이 격리되지 않는다. 특히 idle/closed→inactive 합침과 전역→인스턴스 락은 execution/intents 를 동시 수정해야 컴파일·테스트가 선다.
접근안 (택1 — 체크포인트 결정 필요)
안 A — Big-bang 토대 (모델+상태+persistence+소비처 동시)
- BAT-027 에서 모델/상태머신/persistence + 모든 idle·closed·entry_count·락 소비처를 한 번에 새 모델로 전환.
- 장점: 깨끗한 최종 상태, 임시 이중 모델 없음. 단점: 단일 배치가 매우 큼, 회귀 위험 집중, intents/execution(=admission·집행)까지 본 배치로 끌려와 BAT-β 경계 흐려짐.
안 B — 점진(additive-first) + 단계 green ★권장
- BAT-027(본 배치): (1) 스키마 additive(instance_key·retired_at 추가,
UNIQUE(instance_key, portfolio_id)도입) —entry_count·trading_date컬럼은 이번엔 유지(drop 은 cutover BAT-δ로 미뤄 cascade 축소). (2) 상태머신:inactive를 신규 표준 상태로 추가하고idle/closed를inactive의 별칭/매핑으로 흡수(전이표는 inactive 기준 재정의,closed→idle제거). (3) advisory lock 을 인스턴스 스코프 2-int 로 전환(전역 락 호출부 일괄 치환). (4) holding 로컬 정본 헬퍼. (5) 소비처는 상태 별칭·락 시그니처만 정합(idle/closed 리터럴 → inactive 매핑 유지로 execution/cli/ops_checks 최소 수정). - 후속 배치에서
entry_count/trading_date컬럼 drop·idle/closed 별칭 완전 제거를 cutover(BAT-δ)와 함께 마무리. - 장점: 각 단계 단위 green 유지, cascade 분할, BAT-β/γ 경계 보존. 단점: 한시적 별칭/잔존 컬럼(임시 복잡도) — 단, drop 시점(BAT-δ)이 문서로 고정됨.
안 C — 최소(스키마 additive만)
- instance_key·retired_at 만 additive 추가, 상태머신은 손대지 않음. 단점: REQ-057(상태머신) 미충족이라 BAT-α 의도 미달 → 부적합.
권장 = 안 B
점진(additive-first). 이유: R-cascade·R-trading-date 를 분할해 단위 green 을 유지하고, 라이브 영향(컬럼 drop)을 cutover(BAT-δ)로 모아 안전. REQ-056/057 의 "목표 스키마·상태머신"은 충족(별칭·잔존 컬럼은 후속 drop 으로 수렴).
상태머신 (안 B 기준)
- 표준 상태:
inactive, pending_buy, holding, pending_sell, error.idle/closed는 입력 호환 별칭 →inactive로 매핑(전이 검증·저장은 inactive). - 허용 전이:
inactive→pending_buy, pending_buy→holding, pending_buy→inactive, holding→pending_sell, pending_sell→inactive, pending_sell→holding, *→error, error→{inactive,holding}. (closed→idle제거.) - holding = 로컬 정본: pending_buy→holding 에서 set, pending_sell→inactive(매도 체결)에서 clear.
advisory lock (REQ-057 / NFR-1)
SLOT_ADVISORY_LOCK_ID전역 상수 → 인스턴스 스코프 키:pg_advisory_xact_lock(SLOT_LOCK_ID, hash32(instance_key)). in-memory repo 는 (lock_id, instance_key) 별 락. 호출부(execution/intents/service/postgresql) 시그니처에 instance_key 전달.
스키마 (안 B / 마이그레이션 009)
ALTER TABLE daily_slot ADD COLUMN instance_key TEXT NOT NULL DEFAULT 'default'(cutover 전 단일 인스턴스 호환),ADD COLUMN retired_at TIMESTAMPTZ.UNIQUE(instance_key, portfolio_id)— 단, portfolio_id 가 nullable·풀 모델 잔존 행과 충돌 가능 → 본 배치에선 부분 UNIQUE(WHERE portfolio_id IS NOT NULL) 또는 cutover 후 전체 UNIQUE. (구현 시 확정.)entry_count/trading_date컬럼 유지(후속 drop).EXPECTED_SLOT_FIELDS는 +instance_key·+retired_at(−entry_count 는 후속).
테스트 설계
- test_state: 전이 집합(inactive 기준, closed→idle 부재), idle/closed 별칭 매핑.
- test_contract: 필드셋(+instance_key·retired_at).
- test_service: holding set/clear, 인스턴스 락 직렬화/비교차.
- PG 게이트(test DSN): 마이그레이션 009 round-trip + 드리프트.
- 회귀: 소비처 연쇄 정합 후 전체 단위 green.
체크포인트 결정 (2026-06-10, 사용자 확정)
- 접근안 = 안 B(점진/additive-first).
idle/closed별칭 유지 → BAT-δ(cutover)에서 완전 제거. (수용)UNIQUE는 부분 인덱스UNIQUE(instance_key, portfolio_id) WHERE portfolio_id IS NOT NULL— 풀 모델 잔존 행(portfolio_id NULL)과 충돌 회피. cutover 후 전체 UNIQUE 승격.instance_key컬럼NOT NULL DEFAULT 'default'— 단일 인스턴스/기존 행 호환. cutover 에서 실제 instance_key 태깅(BAT-δ).