From 4e92134c28006781c5651705858b44cc2b247c85 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Wed, 10 Jun 2026 10:39:33 +0900 Subject: [PATCH] =?UTF-8?q?docs(batch):=20draft-batch=20BAT-027=20(dcy-020?= =?UTF-8?q?=20BAT-=CE=B1=20=EC=9E=90=EC=82=B0=20=EC=98=81=EA=B5=AC=20?= =?UTF-8?q?=EC=8A=AC=EB=A1=AF=20=ED=86=A0=EB=8C=80)=20+=20design=20?= =?UTF-8?q?=ED=99=95=EC=A0=95(=EC=95=88=20B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../design.md | 59 +++++++++++++++++++ .../implementation.md | 16 +++++ .../index.md | 35 +++++++++++ .../planning.md | 42 +++++++++++++ .../verification.md | 13 ++++ docs/batches/index.md | 1 + 6 files changed, 166 insertions(+) create mode 100644 docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md create mode 100644 docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md create mode 100644 docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md create mode 100644 docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md create mode 100644 docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md new file mode 100644 index 0000000..0c5c51c --- /dev/null +++ b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/design.md @@ -0,0 +1,59 @@ +# 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/001` +- **`entry_count`**: `application/intents.py`, `domain/slot/*`, `adapters/persistence/postgresql.py`, `sql/001`, `sql/008` +- **`SLOT_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, 사용자 확정) +1. **접근안 = 안 B(점진/additive-first).** +2. `idle/closed` 별칭 유지 → **BAT-δ(cutover)에서 완전 제거**. (수용) +3. `UNIQUE` 는 **부분 인덱스 `UNIQUE(instance_key, portfolio_id) WHERE portfolio_id IS NOT NULL`** — 풀 모델 잔존 행(portfolio_id NULL)과 충돌 회피. cutover 후 전체 UNIQUE 승격. +4. `instance_key` 컬럼 **`NOT NULL DEFAULT 'default'`** — 단일 인스턴스/기존 행 호환. cutover 에서 실제 instance_key 태깅(BAT-δ). diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md new file mode 100644 index 0000000..170974c --- /dev/null +++ b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/implementation.md @@ -0,0 +1,16 @@ +# BAT-027 Implementation + +- Status: not-started +- Batch: BAT-027 + +## 구현 요약 +- (run-batch-delivery 진입 시 작성) design.md 접근안 확정 후 채운다. + +## 변경 내역 +- (대기) + +## 테스트 +- (대기) + +## 검증 실행 +- (대기) diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md new file mode 100644 index 0000000..c599582 --- /dev/null +++ b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/index.md @@ -0,0 +1,35 @@ +# BAT-027: 자산 영구 슬롯 토대 — 스키마 + 상태머신 재정의 (dcy-020 BAT-α) + +- Status: planning +- Profile: standard +- Source Discovery: dcy-020 +- Included REQ: REQ-056 (영구 슬롯 스키마+마이그레이션), REQ-057 (상태머신 재정의 + 인스턴스 스코프 advisory lock) + +## Summary +- dcy-020(자산 중심 영구 슬롯 모델)의 **첫 구현 배치(BAT-α)**. 익명 풀 슬롯 도메인을 자산 1:1 영구 슬롯의 **데이터 모델 + 상태머신 토대**로 재작성한다. 외부 전략 계약 변경(BAT-γ)·라이브 cutover(BAT-δ)는 포함하지 않는다. +- 핵심: `Slot` 모델/`EXPECTED_SLOT_FIELDS` 재정의(portfolio_id 영구키·`instance_key`·`retired_at`, `entry_count`/슬롯 `trading_date` 제거), 멱등 마이그레이션 009(`UNIQUE(instance_key, portfolio_id)`), 상태머신 `inactive↔pending_buy↔holding↔pending_sell`(+error) 으로 재정의(`closed→idle` 복구 제거), advisory lock 인스턴스 스코프화. + +## Scope +- In Scope: + - `Slot` 모델·`EXPECTED_SLOT_FIELDS` 영구 슬롯 재정의 (REQ-056) + - 마이그레이션 009 (instance_key·retired_at·UNIQUE 복합, entry_count/trading_date 제거) + persistence flat 매핑 (REQ-056) + - 상태머신 재정의(inactive 합침·closed→idle 제거) + holding 로컬 정본 + 인스턴스 스코프 advisory lock (REQ-057) + - `data-model.md` 슬롯 스키마/상태 부분 갱신 (REQ-065 부분) + - 상태/계약 단위테스트 재작성 +- Out of Scope: + - universe reconcile lifecycle (REQ-058 → BAT-β) + - admission/capacity·intent 계약 (REQ-059/061 → BAT-β/γ) + - 수량/불일치 해소 (REQ-062 → BAT-γ) + - 라이브 cutover (REQ-064 → BAT-δ) + - 운영자 slots 단일 뷰 (REQ-063 → 후속) + +## 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-020). 본 배치는 dcy-020 의 토대 배치로, 후속 BAT-β(reconcile·admission)/BAT-γ(intent·수량)/BAT-δ(cutover)의 선행. +- **핵심 리스크(R-cascade)**: `Slot` 모델·상태머신 변경은 intents.py·safety_sell·snapshot_handlers·execution 등 다수 소비처로 연쇄된다. design.md 에서 cascade 격리 전략(소비처 동시 수정 범위)을 확정한 뒤 구현한다 — **design 체크포인트 필수**. +- 라이브 데이터는 본 배치에서 건드리지 않는다(스키마는 신규 마이그레이션으로 정의만; 기존 행 전환은 REQ-064/BAT-δ). diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md new file mode 100644 index 0000000..668f4b0 --- /dev/null +++ b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/planning.md @@ -0,0 +1,42 @@ +# BAT-027 Planning + +- Status: confirmed +- Batch: BAT-027 +- Profile: standard + +## Included REQ +- REQ-056 (Data, Approved): 영구 슬롯 스키마 — portfolio_id 영구키·instance_key·retired_at·`UNIQUE(instance_key, portfolio_id)`, entry_count/슬롯 trading_date 제거 + 마이그레이션 009. +- REQ-057 (Interface, Approved): 상태머신 재정의(inactive↔pending_buy↔holding↔pending_sell, closed→idle 제거) + holding 로컬 정본 + 인스턴스 스코프 advisory lock. + +## Delivery Plan +1. REQ-056 도메인 모델: `models.py` `Slot` 재정의 — `instance_key`(str) 추가, `retired_at: Optional[datetime]` 추가, `entry_count` 제거, `trading_date` 의미 재정의(슬롯 키 아님; 호환 위해 last_activity 성격으로 둘지 design 에서 확정). `EXPECTED_SLOT_FIELDS` 정합. +2. REQ-056 persistence: 마이그레이션 009(`ALTER`/신규 — instance_key NOT NULL·retired_at·UNIQUE(instance_key, portfolio_id), entry_count drop), `postgresql.py` `_SLOT_COLUMNS`/`_row_to_slot`/`update_slot`/insert 매핑 정합. 기존 행 전환은 본 배치 범위 밖(REQ-064). +3. REQ-057 상태머신: `state.py` 상태 집합 `{inactive, pending_buy, holding, pending_sell, error}` + 허용 전이 재정의(`closed`/`idle` → `inactive`, `closed→idle` 제거). `SLOT_ADVISORY_LOCK_ID` → 인스턴스 스코프 키(2-int advisory lock) 헬퍼. +4. REQ-057 서비스: `service.py` holding 로컬 set/clear, transit 인스턴스 스코프 lock 사용. 점유 필드 리셋 의미 재정의(종료=inactive 복귀). +5. 소비처 연쇄 정합(R-cascade): intents.py·safety_sell·snapshot_handlers·execution·reconciliation 등 `idle/closed`·`entry_count`·전역 lock 참조를 새 모델로 정합(컴파일·테스트 green 유지 범위). +6. 테스트: test_state(전이 집합)·test_contract(필드셋)·test_service 재작성 + 인스턴스 락 단위. +7. `data-model.md` 슬롯 스키마/상태 부분 갱신(REQ-065 부분). +8. 단위 회귀 + ruff (+ test DSN 마이그레이션/드리프트). + +## Dependencies +- Internal: dcy-001/BAT-001 슬롯 도메인(재작성 대상), dcy-002/BAT-002 persistence(재작성 대상), dcy-019/BAT-025 entry_count(제거 대상). +- External: 없음(본 배치는 내부 도메인/스키마). 외부 전략 계약은 BAT-γ. + +## Design Gate +- Design Required Before Implementation: yes +- Reason: (a) `Slot` 모델·상태머신 변경이 intents/safety_sell/snapshot/execution/reconciliation 으로 **연쇄(R-cascade)** — 한 배치에서 어디까지 동시 수정해 green 을 유지할지 경계를 설계에서 확정해야 한다. (b) `idle`+`closed`→`inactive` 합침과 `closed→idle` 제거가 기존 lifecycle(재진입·점유 리셋·daily_symbol_entries)과 교차 — 전이/리셋 의미를 못박아야 안전. (c) advisory lock 전역→인스턴스 스코프 전환은 단일 writer 불변식(REQ-002 NFR-1) 재정의라 락 키 설계 필요. (d) `trading_date` 컬럼 drop vs 재의미·entry_count drop 의 스키마/계약 가드 영향. → design.md 를 채우고 **구현 전 사용자 체크포인트**. + +## Milestones +1. M1 — REQ-056 모델+마이그레이션+persistence 매핑, 계약/드리프트 green +2. M2 — REQ-057 상태머신+인스턴스 락, 전이/락 단위 green +3. M3 — 소비처 연쇄 정합(전체 단위 green 회복) +4. M4 — data-model.md 부분 갱신 + 회귀/ruff + +## Risks +- R-cascade (High): 모델/상태머신 변경의 소비처 연쇄로 광범위 회귀. 대응: design 에서 동시 수정 범위 확정, 단계별 단위 green 게이트, 기존 테스트 대량 갱신 인지. +- R-trading-date (Medium): trading_date 슬롯 키 제거가 일일 회계·kill-switch day-scope·transition_log 와 얽힘. 대응: design 에서 drop vs 재의미 결정, 회계 경로는 transition_log/ledger 로 이전. +- R-lock (Medium): 인스턴스 스코프 락 전환이 동시성 회귀. 대응: 2-int advisory lock + 경합 회귀 테스트. + +## Notes +- 라이브 데이터 불변(본 배치는 목표 스키마/모델 정의 + 신규 마이그레이션). 기존 행 cutover 는 BAT-δ(REQ-064). +- 본 배치 green 기준: 단위 전체 회귀(소비처 연쇄 정합 무누락). PG 마이그레이션/드리프트는 test DSN. diff --git a/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md new file mode 100644 index 0000000..d7991ec --- /dev/null +++ b/docs/batches/bat-027_20260610_asset-permanent-slot-foundation/verification.md @@ -0,0 +1,13 @@ +# BAT-027 Verification + +- Status: not-started +- Batch: BAT-027 + +## Acceptance Mapping +- (draft-batch-verification / confirm-batch-verification 단계에서 작성) + +## Evidence +- (대기) + +## Result +- Ready for Release: false (구현 전) diff --git a/docs/batches/index.md b/docs/batches/index.md index 88f28c2..41dc450 100644 --- a/docs/batches/index.md +++ b/docs/batches/index.md @@ -34,3 +34,4 @@ | 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/) | | BAT-026 | batch-lite | release-candidate | REQ-048 (CHG-20260609-01), REQ-010 (CHG-20260609-01) | 없음 (trex-orb-v1 정합) | [bat-026](./bat-026_20260609_config-dir-env-and-config-path/) | +| BAT-027 | standard | planning | REQ-056, REQ-057 | dcy-020 (BAT-α) | [bat-027](./bat-027_20260610_asset-permanent-slot-foundation/) |