diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/design.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/design.md new file mode 100644 index 0000000..fb37cb2 --- /dev/null +++ b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/design.md @@ -0,0 +1,46 @@ +# BAT-028 Design + +- Status: confirmed (D1/D2/D3 확정 — 2026-06-10, 사용자 결정) +- Batch: BAT-028 + +## 설계 목표 +- 슬롯 lifecycle 을 universe reconcile 로, admission 을 자산 중심 FCFS+M 캡으로 전환하되, **라이브 데몬 무중단**을 위해 config 게이트로 도입하고 legacy pool 경로를 보존한다(안 B). + +## 핵심 결정 — config 게이트 이중 경로 (권장) +- `DaemonInstanceConfig.max_active_slots: Optional[int]`. + - **설정됨 → 자산 중심 모드**: 슬롯 생성=universe reconcile, admission=자산 슬롯 활성화+M 캡. + - **미설정 → legacy pool 모드**: 기존 preallocation + find_allocatable_slot (현행 그대로). +- 운영 데몬은 cutover(BAT-δ) 전까지 `max_active_slots` 미설정 → **legacy 유지, 무중단**. 자산 중심은 테스트/단계 전환에서 활성. +- 대안(전면 switch)은 라이브 admission 을 한 번에 바꿔 위험 → 안 B에서 비채택. + +## REQ-058 reconcile +- repo 계약 추가: `reconcile_universe(instance_key, trading_date, universe_portfolio_ids: set[str]) -> ReconcileResult`. + - universe 에 있고 슬롯 없음(instance_key, portfolio_id) → 자산 슬롯 생성(inactive, portfolio_id NOT NULL, 멱등). + - 슬롯 있고 universe 에서 빠짐 + status==inactive(idle/closed 별칭) + retired_at IS NULL → `retired_at=now` (soft-retire). + - 빠짐 + holding/pending → **tombstone**(retired_at 미설정, 유지; 신규 활성 후보 제외는 admission/조회에서 retired_at·universe 기준). +- 호출: `SnapshotScheduler.run_once()` 의 `store.swap(items)` 직후, **자산 중심 모드일 때만**. degrade(get_portfolios 실패) → reconcile 건너뜀. **대량소멸 가드**: `len(universe) == 0` 또는 급감(예: 직전 대비 50%↓) 시 retire 보류 + 경고(생성은 진행). +- 단일 writer: reconcile 쓰기는 인스턴스 스코프 advisory lock 안에서. + +## REQ-059 admission (자산 중심 분기) +- `IntentService._admit_buy`: 모드 분기. + - 자산 중심: `repo.find_slot_by_portfolio(instance_key, trading_date, portfolio_id)` 로 그 자산의 슬롯을 잡는다. + - 슬롯 없음(universe 밖) → reject `unknown_portfolio`. + - 슬롯이 이미 활성(pending_buy/holding/pending_sell) → reject `already_active`(멱등). + - 활성 수 계산 `count_active(instance_key, trading_date)` ≥ M → reject `capacity_full`(무치환). + - else inactive→pending_buy 활성화(인스턴스 lock + 재시도). over-cap(M 축소)도 같은 경로로 신규만 차단. + - legacy: 기존 find_allocatable_slot 경로. +- **"활성(active)" 정의(M 캡 대상)**: `holding + pending_buy + pending_sell`(= inactive/error 아닌 포지션 점유 상태). pending_sell 은 청산 진행 중이나 아직 포지션 보유 → 캡에 포함. (체크포인트 D2) + +## REQ-060 config +- `max_active_slots` per-instance TOML 로드(REQ-048 계열). 자산 중심 모드에서 admission 이 M 으로 사용. legacy 모드는 무시. + +## 테스트 설계 +- reconcile: 생성 멱등 / 비활성 soft-retire / 보유·pending tombstone / degrade·대량소멸 보류 (in-memory + PG). +- admission(자산 중심): 활성화 / capacity_full / already_active / over-cap / 동시성 경합 (인스턴스 lock). +- config: max_active_slots 로드 / 모드 게이트. +- legacy 회귀: max_active_slots 미설정 시 기존 pool admission/preallocation 무파손(기존 테스트 green). + +## 체크포인트 결정 (2026-06-10, 사용자 확정) +1. **D1 = config 게이트 이중 경로.** `max_active_slots` 설정 시 자산 중심, 미설정 시 legacy pool. 운영 무중단(cutover 전 pool 유지). +2. **D2 = "활성"(M 캡) = `holding + pending_buy + pending_sell`** (inactive/error 아닌 포지션 점유 상태 전부). +3. **D3 = N→0 + 급감 경고.** universe 가 0이면 retire 보류; 직전 대비 급감(50%↓)도 retire 보류 + 경고(생성은 진행). diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/implementation.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/implementation.md new file mode 100644 index 0000000..1f5d068 --- /dev/null +++ b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/implementation.md @@ -0,0 +1,16 @@ +# BAT-028 Implementation + +- Status: not-started +- Batch: BAT-028 + +## 구현 요약 +- (run-batch-delivery 진입 시 작성) design.md 체크포인트(D1/D2/D3) 확정 후 채운다. + +## 변경 내역 +- (대기) + +## 테스트 +- (대기) + +## 검증 실행 +- (대기) diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/index.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/index.md new file mode 100644 index 0000000..f8c3e0c --- /dev/null +++ b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/index.md @@ -0,0 +1,32 @@ +# BAT-028: universe reconcile + 활성 capacity admission (dcy-020 BAT-β) + +- Status: planning +- Profile: standard +- Source Discovery: dcy-020 +- Included REQ: REQ-058 (universe reconcile lifecycle), REQ-059 (활성 capacity admission FCFS·M·over-cap), REQ-060 (max_active_slots per-instance TOML) + +## Summary +- dcy-020 의 **2번째 구현 배치(BAT-β)**. 슬롯 lifecycle 을 일일 preallocation 에서 **universe reconcile**(자산 1:1 생성/soft-retire/tombstone)로, buy admission 을 pool(find_allocatable_slot)에서 **자산 중심 FCFS + 동시 활성 M 캡**으로 전환한다. M 은 per-instance TOML(`max_active_slots`). +- **안 B 핵심(라이브 안전)**: 자산 중심 경로를 **config 게이트(`max_active_slots` 설정 시 활성)**로 도입하고, 미설정 시 기존 pool 동작 유지. 운영 데몬은 cutover(BAT-δ) 전까지 pool 모드로 무중단 — 신규 경로는 테스트/단계 전환용. + +## Scope +- In Scope: + - REQ-060: `DaemonInstanceConfig.max_active_slots`(per-instance TOML) — config 게이트 신호 + - REQ-058: `reconcile_slots(universe)` — 자산 1:1 생성(멱등)·비활성 soft-retire·보유/pending tombstone·degrade/대량소멸 보류. 스냅샷 refresh 경로에 결합 + - REQ-059: 자산 중심 admission — 자산 슬롯 활성화(inactive→pending_buy), 활성(holding+pending) M 캡·`capacity_full` 거부·무치환·over-cap·already_active, 인스턴스 스코프 lock 경합 + - runtime-flows.md 부분 갱신(REQ-065 부분) +- Out of Scope: + - 기존 pool 경로 제거(legacy 유지 — 제거는 BAT-δ cutover) + - intent 자산 키 통일·sell-by-portfolio_id (REQ-061 → BAT-γ) + - 수량/불일치(REQ-062), 운영자 뷰(REQ-063) → BAT-γ + - 라이브 cutover·컬럼 drop·literal inactive 전면화 (REQ-064 → BAT-δ) + +## 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). BAT-027(토대) 후속, BAT-γ/δ 선행. +- **핵심 리스크**: admission/슬롯 생성은 **라이브 데몬 동작**이라, config 게이트로 pool 모드 기본 유지(운영 무중단)하고 자산 중심은 신규 경로로 도입 — design 에서 게이트 경계 확정 후 구현(design 체크포인트). diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/planning.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/planning.md new file mode 100644 index 0000000..c96a1e3 --- /dev/null +++ b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/planning.md @@ -0,0 +1,41 @@ +# BAT-028 Planning + +- Status: confirmed +- Batch: BAT-028 +- Profile: standard + +## Included REQ +- REQ-060 (Configuration, Approved): `max_active_slots` per-instance TOML — 자산 중심 모드 게이트 + M 값. +- REQ-058 (Integration, Approved): universe reconcile lifecycle(생성/soft-retire/tombstone, degrade 보류). +- REQ-059 (Interface, Approved): 자산 중심 admission(FCFS·M캡·무치환·over-cap·already_active). + +## Delivery Plan +1. REQ-060: `DaemonInstanceConfig.max_active_slots: Optional[int]`(config/*.toml). 설정 시 자산 중심 모드, 미설정 시 legacy pool. `instance.example.toml`/`config/.env.example` 주석. +2. REQ-058: `SlotRepository.reconcile_universe(instance_key, trading_date, universe_portfolio_ids)` + in-memory/PG 구현 — 신규 portfolio_id → 자산 슬롯 생성(멱등, inactive, portfolio_id NOT NULL), universe 에서 빠진 비활성 → retired_at set, 보유/pending → tombstone(retired_at 보류). degrade/대량소멸(N→0) 가드는 호출측(scheduler). `SnapshotScheduler.run_once` 의 swap 직후 reconcile 훅(자산 중심 모드일 때만). +3. REQ-059: `IntentService._admit_buy` 자산 중심 분기 — 자산 중심 모드면 `find_slot_by_portfolio(instance_key, trading_date, portfolio_id)` 로 그 자산 슬롯을 잡아 활성화, 활성(holding+pending) 수 < M 이면 inactive→pending_buy, ==M 이면 `capacity_full`, 이미 활성이면 `already_active`. 무치환·over-cap(신규만 차단). legacy 모드면 기존 find_allocatable_slot 경로 유지. +4. daemon 배선: 자산 중심 모드면 preallocation 스레드 대신 reconcile(스냅샷 경로), admission 모드 플래그 전달. +5. runtime-flows.md 부분 갱신(reconcile/admission 흐름, 모드 게이트). +6. 단위(reconcile/admission/capacity/over-cap) + 회귀 + ruff (+ test DSN reconcile). + +## Dependencies +- Internal: BAT-027(영구 슬롯 스키마·instance_key·inactive 상태머신·인스턴스 lock) 선행 완료. dcy-005/BAT-008 SnapshotScheduler·universe read. dcy-014/BAT-018 preallocation(legacy 유지). +- External: portfolio-hub universe(get_portfolios) — 이미 동작. + +## Design Gate +- Design Required Before Implementation: yes +- Reason: (a) admission·슬롯 생성은 **라이브 데몬 동작** 변경이라, 자산 중심 신규 경로와 legacy pool 경로의 **게이트 경계**(config 기준)를 설계에서 확정해 운영 무중단을 보장해야 한다. (b) reconcile 의 tombstone/soft-retire 가 보유 포지션·safety-sell 와 얽히므로 repo 계약·degrade 가드를 못박아야 안전. (c) M 캡 카운팅(활성=holding+pending)·over-cap·동시성(인스턴스 lock)·already_active 규칙을 admission 분기에서 정확히. → design.md 확정 후 **구현 전 사용자 체크포인트**. + +## Milestones +1. M1 — REQ-060 config(게이트) + 단위 +2. M2 — REQ-058 reconcile(repo + scheduler 훅) + 단위/ PG +3. M3 — REQ-059 admission 자산 중심 분기 + capacity/over-cap 단위 +4. M4 — daemon 배선(모드) + runtime-flows 부분 + 회귀/ruff + +## Risks +- R-mode (High): legacy/자산 중심 이중 경로 혼선·라이브 회귀. 대응: config 게이트 단일 신호(max_active_slots), 기본 legacy, 신규 경로 테스트 격리. +- R-reconcile (Medium): tombstone/soft-retire 오판으로 보유 포지션 은퇴. 대응: 보유/pending tombstone·degrade 보류·대량소멸 가드, 회귀 테스트. +- R-capacity (Medium): M 캡 동시성 경합으로 초과 활성. 대응: 인스턴스 스코프 lock + 재시도, 경합 테스트. + +## Notes +- 라이브 데몬은 cutover(BAT-δ) 전까지 legacy pool 모드 유지(무중단). 자산 중심 default 전환은 BAT-δ. +- 본 배치 green 기준: 단위 전체 회귀(legacy 경로 무파손) + 신규 자산 중심 단위 + test DSN reconcile. diff --git a/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/verification.md b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/verification.md new file mode 100644 index 0000000..cb78cf2 --- /dev/null +++ b/docs/batches/bat-028_20260610_universe-reconcile-and-active-admission/verification.md @@ -0,0 +1,13 @@ +# BAT-028 Verification + +- Status: not-started +- Batch: BAT-028 + +## 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 41dc450..b82ff13 100644 --- a/docs/batches/index.md +++ b/docs/batches/index.md @@ -35,3 +35,4 @@ | 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/) | +| BAT-028 | standard | planning | REQ-058, REQ-059, REQ-060 | dcy-020 (BAT-β) | [bat-028](./bat-028_20260610_universe-reconcile-and-active-admission/) |